## Purpose

Guest-facing animated invitation landing pages: the personalised token route and the open/QR route that present event details and lead the guest to the RSVP call-to-action.

## ADDED Requirements

### Requirement: Personalised invitation route (FR-1.1, P0)
The system SHALL serve `/i/{token}` rendering the invitation for the specific guest and event identified by the token, pre-filling the guest's known name.

#### Scenario: Valid token
- **WHEN** a guest opens `/i/{token}` with a valid token
- **THEN** the invitation page for that guest's event renders with the guest's name personalised in the hero ("Dear Rahim Uddin, you are invited…") (FR-1.5)
- **AND** the invitation's `opened_at` is recorded and `open_count` incremented, moving status `pending → opened` on first open

#### Scenario: Invalid or unknown token
- **WHEN** a visitor opens `/i/{invalid}`
- **THEN** the system SHALL show a generic "invitation not found" page that does not reveal whether the token exists (§7.6 enumeration defence)

### Requirement: Open/QR invitation route (FR-1.2, P0)
The system SHALL serve `/e/{eventSlug}` as a tokenless open link where the guest is unknown until they register. Unpublished (draft) events MUST NOT be publicly reachable.

#### Scenario: Open link visit
- **WHEN** anyone opens `/e/walima` for a published event
- **THEN** the same animated invitation renders with a generic greeting instead of a personalised name

#### Scenario: Draft event
- **WHEN** a visitor opens `/e/{slug}` for an event with `is_published = false`
- **THEN** the system SHALL return a not-found response

### Requirement: Animated card presentation (FR-1.3, FR-1.4, P0)
The page SHALL render a full-screen animated card — an intro reveal (e.g. envelope-open or curtain) followed by scrolling sections: Hero (names + date), countdown timer, event details (time, venue, map), optional story/photo gallery, Accept/Decline call-to-action, and contact & directions footer. Each event type (Mehedi, Marriage, Walima) uses its own theme (§9.1).

#### Scenario: Page structure
- **WHEN** the invitation page loads
- **THEN** the intro animation plays for at most 3 seconds with a visible skip control (§9.2)
- **AND** all listed sections are present and reachable by scrolling

#### Scenario: Animation failure
- **WHEN** JavaScript fails or animations cannot run
- **THEN** all textual content (names, date, venue, CTA) MUST still be readable — text is never gated behind JavaScript (§9.2)

#### Scenario: Reduced motion
- **WHEN** the visitor's device sets `prefers-reduced-motion: reduce`
- **THEN** animations SHALL be reduced to simple fades (§7.5, §9.2)

### Requirement: Background music opt-in (FR-1.6, P0)
Optional background music SHALL never autoplay with sound. Default is muted with a clearly visible mute/unmute toggle, and audio is loaded only on user interaction (§7.5).

#### Scenario: First load
- **WHEN** the invitation page loads with a music track configured
- **THEN** no audio plays and a visible toggle offers tap-to-unmute

### Requirement: Language toggle (FR-1.7, P1)
The page SHALL offer a Bangla ⇄ English toggle. The initial language comes from the guest's `preferred_locale`; the project default is English.

#### Scenario: Guest with Bangla preference
- **WHEN** a guest whose `preferred_locale` is `bn` opens their token link
- **THEN** the page renders in Bangla with a toggle to switch to English

#### Scenario: Open link
- **WHEN** a visitor opens the open link with no known preference
- **THEN** the page renders in English (project default) with a toggle to Bangla

### Requirement: Calendar and directions actions (FR-1.8 P1, FR-1.9 P0)
The page SHALL provide a "Get Directions" button opening the event's Google Maps link, and an "Add to Calendar" button producing an `.ics` file and a Google Calendar link.

#### Scenario: Directions
- **WHEN** the guest taps "Get Directions"
- **THEN** the event's `map_url` opens

#### Scenario: Add to calendar
- **WHEN** the guest taps "Add to Calendar"
- **THEN** a valid `.ics` download for the event's start/end time in Asia/Dhaka is offered

### Requirement: Multi-event switcher (FR-1.10, P1) — SUPERSEDED

> **Removed by `add-event-invitation-card` (design D11, task 3.7). Do not build this.**
>
> The switcher assumed one guest record holding invitations to several events. Guests are now
> scoped to a single event, so "the same guest at another event" is a different record that
> this one cannot be linked to without matching on phone or email — an identity guess the
> invitation route must not make on the strength of a bearer token.
>
> `invitation-card/specs/event-management` states the replacement requirement: **No
> cross-event linking for guests.**

### Requirement: RSVP deadline state (FR-1.11, P0)
After an event's `rsvp_deadline` passes, the page SHALL hide the RSVP form and show a polite closed message with a host contact number.

#### Scenario: Deadline passed, no response
- **WHEN** a guest opens their link after the deadline without having responded
- **THEN** the form is not rendered and the closed message with contact number is shown

### Requirement: Already-responded state (FR-1.12, P0)
If the invitation is already accepted, the page SHALL show the guest's current answer with an "Update my response" option instead of a blank form.

#### Scenario: Returning accepted guest
- **WHEN** a guest who accepted with party size 2 revisits their link
- **THEN** the page shows their confirmed status and party size, with an option to update or cancel

### Requirement: Mobile usability and accessibility (FR-1.13 P0, §9.4)
The page MUST be fully usable one-handed on a 360px-wide screen, meet WCAG 2.1 AA contrast for text over images, keep all interactive elements keyboard-reachable with visible focus, provide alt text on images and an `aria-live` announcement on RSVP success, use ≥44×44px tap targets, and base font size ≥16px.

#### Scenario: Narrow viewport
- **WHEN** the page is viewed at 360px width
- **THEN** all content and actions are reachable and operable without horizontal scrolling

### Requirement: Performance budget (§7.5, P0)
The invitation page SHALL meet: LCP < 2.0s on 4G and < 2.5s on 3G mobile, initial page weight < 800KB including hero image, 60fps animation on mid-range Android (animating only transform/opacity), responsive WebP/AVIF images lazy-loaded below the fold.

#### Scenario: Performance test
- **WHEN** the page is profiled on an emulated 3G mid-range mobile device
- **THEN** LCP is under 2.5 seconds and initial transfer is under 800KB

### Requirement: Share action (FR-1.14, P2)
The page SHALL offer a share button using the Web Share API so guests can forward the open link to family.

#### Scenario: Share tapped
- **WHEN** a guest taps the share button on a supporting device
- **THEN** the native share sheet opens pre-filled with the event's open link
