# Authoring an invitation card

**Audience:** the designer building the card. **Deliverable:** one `.html` file plus every
image it references, handed over together.

The card is the artwork a guest sees when they open their invitation. It is uploaded through
the admin, rendered inside the invitation page, and fixed for the event — the names, date and
venue are typed into it as ordinary text by you, not filled in by the system.

---

## The file

One HTML file with **one root element**. Everything the card is lives inside it.

```html
<div class="card">
  <style>
    .card { … }
  </style>

  <h1>Nazifa &amp; Abdullah</h1>
  <p>Thursday, 24 September 2026</p>
  <p>Grand Ballroom, Dhaka</p>
  <img src="flourish.svg" alt="">
</div>
```

No `<!doctype>`, no `<html>`, no `<head>`, no `<body>` — they are stripped, because the file
is injected into a page that already has them.

## The rules

**Styles live in the file.** A `<style>` block inside your root element, or inline `style`
attributes. Both work. The card is rendered inside a *shadow root*, which means your CSS
cannot leak out onto the invitation page and the page's CSS cannot reach in and break your
design. You are working on a clean sheet — but that also means nothing is set up for you.

**No scripting.** `<script>` and `onclick`-style attributes are removed on upload. Motion is
CSS keyframes, and that is not a limitation worth fighting: keyframes run on the compositor,
which is what makes them smooth on the low-end Android half the guest list is holding.

**No external hosts.** No Google Fonts link, no CDN image, no `https://…` anywhere. An upload
containing one is **rejected by name** — deliberately, so you find out rather than the couple
discovering a blank rectangle a week later. Everything must be uploaded with the card.

**Reference companion files by bare filename.** Write `src="flourish.svg"`, not
`src="./assets/flourish.svg"`. Upload the file in the same step and the system rewrites the
reference to its stored address. A reference with no matching upload blocks the upload and
names the missing file.

**Give motion a reduced-motion branch.** Guests who have asked their phone to stop animating
things must get the card in its finished state, not its starting one:

```css
.card { animation: glow 3s ease-in-out infinite alternate; }

@media (prefers-reduced-motion: reduce) {
  .card { animation: none; }
}
```

**Size relative to the container, not the screen.** The card is rendered at whatever width the
invitation gives it — roughly 280px on a small phone, 408px at desktop. Use `%`, `em`, `rem`,
`clamp()` and container-relative units. Avoid `vw`/`vh`: they measure the whole viewport, so a
card sized in them is correct at exactly one screen size and wrong everywhere else.

**No guest data.** The guest's name is printed by the invitation page in the header directly
above your card. Do not leave a space for it, and do not write "Dear \_\_\_\_\_" — the same
card file is served to every guest.

## Fonts

Bangla is already handled: the page self-hosts a subset of Noto Sans Bengali and it inherits
through the shadow boundary. Write `font-family: inherit` (or leave it alone) and Bangla
renders correctly with no work from you.

For a display face of your own, embed it as a `data:` URI in your stylesheet. Remember the
budget below — a full weight is often larger than the rest of the card put together.

## Size budget

The whole design — the HTML plus every file with it — must come to **under 600 KB**, and the
admin warns above 500 KB. The invitation page has an 800 KB budget in total, and the card is
the largest thing on it.

Practical guidance: SVG for flourishes, borders and monograms; WebP for photographs; nothing
larger than twice the size it renders at.

## Accepted file types

`.html` for the card. `.svg`, `.webp`, `.avif`, `.png`, `.jpg` for everything with it.

## The share image

Hand over **one more file** with the card: the picture that appears when someone pastes the
invitation link into WhatsApp, Messenger, iMessage or Slack. It is uploaded in its own field,
not as a companion file, so its filename does not matter.

| | |
|---|---|
| Size | **1200 × 630** |
| Format | **PNG or JPEG only** |
| Weight | **under 300 KB** |

Those three are enforced on upload, with the reason named if a file is refused.

**Why not a screenshot of the card.** The card is portrait and a share image is landscape, so
a faithful crop of it loses the couple's names off the top and bottom — at the size that
actually matters, a 200px-wide row in a chat list, the names are the only legible thing. This
is a separate composition, not an export of the same artwork.

So: **compose it for the crop.** Couple's names large, date if it fits, everything important
inside the middle two-thirds — several applications crop the edges to their own ratio. Check
it at thumbnail size before sending it; if you cannot read the names at 200px wide, it is not
finished.

A square or otherwise off-ratio image is **accepted with a warning** rather than refused, so
that a usable picture is never held up over a shape preference. The warning tells the admin it
will be cropped.

Sending no share image is allowed. The link then previews as a title, a date and a venue with
no picture — correct, just plainer.

## What happens on upload

1. Each companion file is stored under a content-addressed name.
2. Your document is cleaned — scripts, event handlers and comments come out.
3. Every relative reference is rewritten to its stored address.
4. Anything missing or pointing at another server stops the upload with a message naming it.
5. The result is saved as a **draft**. Nothing a guest sees changes until someone publishes it.

## Before you hand it over

- Open it at 320px wide. That is the narrowest phone the invitation supports.
- Turn on "reduce motion" in your OS and reload. The card should look finished, not paused.
- Turn JavaScript off and reload. The card must still render — nothing in it may depend on JS.
- Check the total size of the folder you are about to upload.
- Shrink the share image to 200px wide and check the names are still readable.
