## Purpose

The designed digital invitation card attached to each event — the on-screen counterpart of the printed card — covering how an uploaded design is stored, versioned, published, isolated and rendered to guests, and how the design team ships one without an engineer or a deploy.

## ADDED Requirements

### Requirement: Per-event card design record (P0)
The system SHALL store at most one **published** card design per event, alongside any number of unpublished versions. A design SHALL declare a `renderer` from a fixed set, a renderer-specific configuration, and its uploaded assets. Publishing a version SHALL make it the design served to every invitation under that event and SHALL leave prior versions retrievable for rollback.

#### Scenario: Publishing a design
- **WHEN** an admin publishes version 2 of an event's card design
- **THEN** every guest holding an invitation to that event sees version 2
- **AND** version 1 remains stored and can be republished

#### Scenario: No design published
- **WHEN** an event has no published card design
- **THEN** the invitation page renders without a card and all other sections behave exactly as before

#### Scenario: Unsupported renderer
- **WHEN** a design is saved naming a renderer this deployment does not implement
- **THEN** the system SHALL reject the save with an error naming the supported renderers

### Requirement: Uploaded HTML card (P0)
The `html` renderer SHALL accept a self-contained HTML document supplied by the design team, carrying its own styles, and SHALL render it as the card. The card's content is **fixed for the event**: the system SHALL NOT substitute, interpolate, or template any value into the uploaded markup.

#### Scenario: Upload and publish without a deploy
- **WHEN** a design team member uploads a card file and publishes it
- **THEN** guests see that card with no application code change and no release

#### Scenario: What was previewed is what ships
- **WHEN** an uploaded file is rendered to a guest
- **THEN** its markup and styling are those of the uploaded document, unchanged apart from the sanitisation and asset rewriting required below

#### Scenario: No per-guest content
- **WHEN** two different guests invited to the same event open their invitations
- **THEN** each receives a byte-identical card

### Requirement: Card isolation (P0)
An uploaded card SHALL be rendered so that its styles cannot affect the surrounding page and the page's styles cannot affect it, while its text remains present in the initial server-rendered HTML.

#### Scenario: Styles do not leak outward
- **WHEN** an uploaded card defines rules for common selectors such as `p`, `h2` or `.card`
- **THEN** the header, footer and detail sections of the invitation are visually unaffected

#### Scenario: Styles do not leak inward
- **WHEN** the surrounding page's stylesheet defines rules that would otherwise match elements inside the card
- **THEN** the card renders as its own stylesheet specifies

#### Scenario: Text without JavaScript
- **WHEN** a guest loads an invitation with a published `html` card and JavaScript never executes
- **THEN** the card's text is present in the initial HTML and its CSS animations still run

### Requirement: Upload sanitisation (P0)
Uploaded HTML and SVG SHALL have scripting, event-handler attributes and references to external hosts removed before storage. The stored file SHALL be the sanitised one. Sanitisation SHALL preserve visual content — it removes capability, not appearance.

#### Scenario: Script in an uploaded card
- **WHEN** an uploaded HTML file contains a `script` element or an `on*` attribute
- **THEN** the stored file has them removed and the rest of the design renders unchanged

#### Scenario: External reference
- **WHEN** an uploaded file references a font, image or stylesheet on another host
- **THEN** that reference is removed or rejected, and the admin is told which reference failed and why

#### Scenario: SVG containing script
- **WHEN** an uploaded SVG contains a `script` element or an `on*` event handler attribute
- **THEN** the stored file has them removed while its visual nodes are preserved

### Requirement: Asset reference rewriting (P0)
Relative references from an uploaded card to files uploaded alongside it SHALL be rewritten to the stored location of those files, so the card renders correctly when injected into a page served from an unrelated path.

#### Scenario: Relative image reference
- **WHEN** a card is uploaded containing `src="border.svg"` together with a file named `border.svg`
- **THEN** the stored card references the stored location of that file and the image renders on the invitation

#### Scenario: Missing companion file
- **WHEN** a card references a relative file that was not uploaded with it
- **THEN** publication is refused, naming the missing file

### Requirement: Image renderer (P0)
The `image` renderer SHALL render a card as a single artwork file. Because its text is not machine-readable, a design using this renderer SHALL require alternative text per supported locale before it can be published.

#### Scenario: Publishing without alt text
- **WHEN** an admin attempts to publish an `image` design with an empty alt text for a supported locale
- **THEN** the system SHALL refuse to publish and state which locale is missing

#### Scenario: Guest view
- **WHEN** a guest opens an invitation with a published `image` card
- **THEN** the artwork renders at its natural aspect ratio without stretching or cropping
- **AND** the event's date, venue and other details remain readable as text elsewhere on the page

### Requirement: Media storage and ingestion limits (P0)
Uploaded files SHALL be stored outside the database and served under immutable, content-addressed URLs. The system SHALL enforce an allowlist of content types and a maximum file size, and SHALL reject anything outside them with a message naming the actual value and the limit.

#### Scenario: Oversized upload
- **WHEN** an admin uploads a file exceeding the configured size limit
- **THEN** the upload is rejected with a message stating the actual size and the limit

#### Scenario: Disallowed type
- **WHEN** an admin uploads a file whose content type is not in the allowlist
- **THEN** the upload is rejected and nothing is written to storage

#### Scenario: Re-uploading changed artwork
- **WHEN** an admin uploads a modified file with the same name
- **THEN** it is served from a different URL, so no cache anywhere returns the previous version

### Requirement: Card performance budget (P0)
A published card SHALL NOT push the invitation page beyond its existing budget: under 800KB of initial transfer including the card, and Largest Contentful Paint under 2.5 seconds on emulated 3G mid-range mobile. The system SHALL surface the combined weight of a design's files to the admin before publication.

#### Scenario: Weight shown before publish
- **WHEN** an admin views a card design whose files total 1.1MB
- **THEN** the total weight is displayed with a clear warning that it exceeds the page budget

#### Scenario: Budget verification
- **WHEN** an invitation carrying a published card is profiled on emulated 3G
- **THEN** LCP is under 2.5 seconds and initial transfer is under 800KB

### Requirement: Stale card after an event change (P0)
Because a card's date and venue are typed into the design rather than read from the event, changing a published event's date or venue SHALL warn the admin that the live card now contradicts the event.

#### Scenario: Date changed with a card published
- **WHEN** an admin changes the date of an event that has a published card
- **THEN** the interface warns that the published card shows the previous date and must be redesigned and re-uploaded

### Requirement: Full-screen card view (P1)
Guests SHALL be able to open the card full-screen to inspect its detail, and dismiss it without leaving the invitation.

#### Scenario: Opening and closing
- **WHEN** a guest activates the card
- **THEN** it opens full-screen with the page behind it prevented from scrolling
- **AND** a visible close control, the Escape key, and the browser back gesture each return the guest to the invitation with their scroll position intact

#### Scenario: Keyboard and screen reader
- **WHEN** a guest opens the full-screen view using a keyboard
- **THEN** focus moves into the view, is trapped while it is open, and returns to the trigger on close

### Requirement: Reduced motion (P0)
All card motion SHALL be suppressed for visitors whose system requests reduced motion, resolving to the card's final state rather than a faster animation.

#### Scenario: Reduced motion requested
- **WHEN** a guest with `prefers-reduced-motion: reduce` opens an invitation with an animated card
- **THEN** the card renders in its settled state with no entry animation, no looping effects and no parallax

### Requirement: Card design authoring (P0)
Admins holding the content-editing permission SHALL be able to create a design, upload its files, preview it in a phone-sized frame, and publish or roll back a version. Admins without that permission SHALL NOT reach any of these operations.

#### Scenario: Preview before publish
- **WHEN** an admin uploads a design and previews it
- **THEN** the preview renders the card exactly as a guest would see it, without affecting what guests currently see

#### Scenario: Insufficient permission
- **WHEN** an admin whose role lacks content editing calls a card design endpoint directly
- **THEN** the API returns 403 regardless of what the interface displayed

### Requirement: Authoring contract (P0)
The project SHALL document the contract an uploaded card must satisfy: a single root element, all styles contained in the file and scoped beneath that root, no scripting, no requests to external hosts, companion assets referenced by relative filename and uploaded together, motion expressed as CSS keyframes with a reduced-motion branch, sizing in container-relative units, and the event's own names, date and venue typed in as fixed text.

#### Scenario: Conforming card is published
- **WHEN** a designer delivers a card meeting the contract together with its assets
- **THEN** it uploads, previews and publishes with no code change and no manual editing of the file
