# Thank You (Moonarc)

A thanks page for after a signup: a headline, the reader's place in line rolling up, a referral link with a copy button, and one burst of confetti. The values come from the server or from the record the form lib leaves for this page, never from the URL. Without any values, the page still reads as complete.

- Pro block. Install: `npx shadcn@latest add @moonarc-pro/thank-you` (license key required)
- Tier B · category section · trigger load, click
- Readout: `<ThankYou headline="…" referralUrl="…?ref={ref}">`
- Browser support: newly (Chrome 125 · Firefox 121 · Safari 15.4); elsewhere: the place in line as plain text; the copy button says it failed where the clipboard is refused; without :has() a page with neither block keeps one empty gap where the list was
- Measured cost: 981 B raw JS · 608 B gzip · + CopyButton + runtime + Confetti + canvas (with dependencies 7.6 kB raw; CSS 16.7 kB raw)
- Page: https://moonarc.dev/components/thank-you/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `headline` | `string` | none | Headline; an h1 unless `level` says 2. Required unless the headline slot is filled (the slot wins). The build fails without either. |
| `eyebrow` | `string` | none | Short line above the headline. Slot "eyebrow" wins. |
| `copy` | `string` | none | Supporting copy. Slot "copy" wins. |
| `level` | `1 | 2` | `1` | Heading level: 1 on the thanks page itself, 2 under another h1 (a demo). |
| `position` | `number` | none | Place in line from the server. A fresh record for this page overrides it (1–15 digits, as a number or as a string, because the form lib carries a service's "12" as it came); without either the block stays hidden. |
| `referral` | `string` | none | Referral code from the server. A fresh record for this page overrides it; without either the block stays hidden. |
| `referralUrl` | `string` | none | The link to share, absolute (http or https, with its origin), with {ref} where the code goes: 'https://example.com/?ref={ref}', or in Astro new URL('/?ref=', Astro.site) + '{ref}'. The build fails on a relative URL or one without {ref}: the link is copied and sent to people who are not on this page. Its text is the URL and CopyButton copies it. Without it there is no referral block. |
| `positionLabel` | `string` | `'Your place in line'` | Label of the position block. |
| `referralLabel` | `string` | `'Your referral link'` | Label of the referral block. |
| `copyLabels` | `{ label?, doneLabel?, errorLabel? }` | `'Copy link' · 'Copied' · 'Copy failed'` | The copy button's three faces, passed to CopyButton. |
| `digits` | `number` | `5` | Columns NumberFlow reserves for the position, so it rolls without the row changing width; a longer number from the record is written as plain text. |
| `locale` | `string` | `'en'` | Locale of the position's thousands separator. |
| `celebrate` | `'submit' | 'always' | 'none'` | `'submit'` | submit: one burst when a fresh record arrives, none on reload · always: every page view · none: no Confetti rendered, no confetti JavaScript shipped. |
| `back` | `{ href, label } | false` | `{ href: '/', label: 'Back home' }` | The link back, used when the "actions" slot is empty; false for none. |
| `tone` | `'surface' | 'muted' | 'ink' | 'brand'` | `unset (paints nothing)` | The ground: the page surface, a muted band, an always-dark ink panel, or a deep brand panel. Each paints --background, --ma-tone-muted (over --muted, with a --border hairline), --ma-tone-ink or --ma-tone-brand (over --primary) with the matching text. With theme.css, a painted tone also re-declares --card, --border, --input and --ring inside the section, so cards and fields follow the ground. The two blocks take the same ground with a hairline of the text colour. |
| `density` | `'compact' | 'cozy' | 'roomy'` | `'cozy'` | Block padding and gaps ×0.8 / ×1 / ×1.25 through --ma-density. |
| `align` | `'start' | 'center'` | `'center'` | Text alignment. |

## Usage

```astro
---
import ThankYou from '@/components/moonarc/pro/ThankYou.astro';
---
<!-- after a signup: the page the form's data-success points at; the record the form lib wrote fills it in -->
<ThankYou
  eyebrow="You're in"
  headline="Thanks, you're on the list."
  copy="We'll write once, the day it opens."
  referralUrl="https://example.com/?ref={ref}"
  back={{ href: '/', label: 'Back home' }}
/>

<!-- or from the server: values you already know, both blocks rendered, no burst -->
<ThankYou
  headline="Thanks, you're on the list."
  position={1284}
  referral="ada-2418"
  referralUrl="https://example.com/?ref={ref}"
  celebrate="none"
>
  <a slot="actions" href="/changelog/">Read the changelog</a>
</ThankYou>
```

## Reduced motion

No confetti: a fresh record asks for none (no attribute, no event) and is still marked celebrated, and celebrate="always" has its request dropped by Confetti, not kept; the place in line is shown without rolling; the back link does not fade.

## With ClientRouter

Bound through the shared runtime: the record is read again on every page view, and a burst asked for before Confetti binds waits in its attribute. The form lib leaves with a full navigation, so the thanks page always starts clean.

## Craft

- The record is sessionStorage['ma:form'], written by the form lib just before it navigates. Nothing deletes it: the storage is per tab already, and a reload shows the same place and link. It is used only when its path is this page (trailing slash ignored), it is under ten minutes old and every value passes: a position is a whole number of 1–15 digits (a number, or the string a service answered with), a referral 1–32 of A–Z a–z 0–9 _ -. Anything else and the whole record is ignored; the server's values stay.
- No URL parameter is ever read: /thanks/?ref=someone-else cannot put a stranger's code in front of the reader, and neither the place nor the code ends up in analytics or the history. Values are written with textContent, never innerHTML.
- celebrate="submit" fires once per record and writes celebrated: true back, so a reload is quiet. The burst is Confetti's event trigger: the attribute and the ma:confetti event in one task, one burst whichever script binds first.
- Under a tone the cards point CopyButton's tokens at the tone's concrete text and plate, and its "Copied" and "Copy failed" faces at that same text (--ma-copy-done, --ma-copy-error): the green and red mixes fell under 4.5 : 1 on brand, and the words say the state. browser.mjs measures both faces and their ring on every tone.
- Confetti's colours are inline on its element (--primary and --chart-1…4, each falling back to the element's own colour), so no CSS chunk order decides them.
- Without any value both blocks stay hidden and the page still reads whole: Formspree and Netlify answer no position, and that is the normal case.

## Replaces

- Hosted waitlist thank-you pages
- Hand-rolled success screens (confetti + copy link)

## Source

Pro block. The source is served by the license-gated registry: `npx shadcn@latest add @moonarc-pro/thank-you` with a key in components.json (https://moonarc.dev/account/). Everything it composes is free: number-flow, copy-button, confetti.
