/* ===== ChitthiBhejo — base styles ===== */

:root {
    --ink: #2c2420;
    --ink-soft: #6b5d56;
    --ink-faint: #9a8b83;
    --paper: #fbf7f2;
    --paper-2: #f5eee5;
    --line: #e6dcd0;
    --accent: #b4384f;
    --accent-dark: #8d2439;
    --accent-wash: #f7e7ea;

    --font-heading: 'Cormorant Garamond', Georgia, serif;
    --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;

    --r-sm: 8px;
    --r-md: 14px;
    --r-lg: 22px;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
    font-family: var(--font-body);
    background: var(--paper);
    color: var(--ink);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

.container { max-width: 1120px; margin: 0 auto; padding: 0 22px; }
.container-narrow { max-width: 560px; }

h1, h2, h3 { font-family: var(--font-heading); font-weight: 500; line-height: 1.22; }

.eyebrow {
    font-size: .72rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--accent);
    opacity: .8;
    margin-bottom: 10px;
}

.lede { color: var(--ink-soft); font-size: 1.02rem; }
.micro { font-size: .8rem; color: var(--ink-faint); margin-top: 14px; }
.hidden { display: none !important; }

/* ===== Buttons ===== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    padding: 14px 30px;
    font-family: var(--font-body);
    font-size: .95rem;
    font-weight: 500;
    text-decoration: none;
    border: none;
    border-radius: 100px;
    cursor: pointer;
    transition: transform .2s ease, box-shadow .2s ease, background .2s ease;
}

.btn-primary {
    background: var(--accent);
    color: #fff;
    box-shadow: 0 8px 22px -8px rgba(140,35,55,.5);
}

.btn-primary:hover:not(:disabled) {
    background: var(--accent-dark);
    transform: translateY(-2px);
    box-shadow: 0 12px 28px -8px rgba(140,35,55,.55);
}

.btn-primary:disabled { opacity: .6; cursor: default; }

.btn-lg { padding: 16px 36px; font-size: 1rem; }
.btn-block { width: 100%; }
.btn-small { padding: 9px 18px; font-size: .85rem; background: var(--accent); color: #fff; }

.btn-whatsapp { background: #25D366; color: #fff; box-shadow: 0 8px 22px -8px rgba(37,211,102,.55); }
.btn-whatsapp:hover { background: #1eb959; transform: translateY(-2px); }

.btn-ghost-dark {
    display: inline-block;
    padding: 11px 26px;
    border-radius: 100px;
    border: 1px solid var(--line);
    color: var(--ink-soft);
    text-decoration: none;
    font-size: .88rem;
    transition: border-color .2s ease, color .2s ease;
}

.btn-ghost-dark:hover { border-color: var(--accent); color: var(--accent); }

.link-btn {
    background: none;
    border: none;
    color: var(--accent);
    font-family: var(--font-body);
    font-size: .82rem;
    cursor: pointer;
    text-decoration: none;
    padding: 0;
}

.link-btn:hover { text-decoration: underline; }

/* ===== Header ===== */

.site-header {
    padding: 18px 0;
    border-bottom: 1px solid var(--line);
    background: var(--paper);
}

.site-header-plain { border-bottom: none; }

.site-header .container {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
}

.logo {
    font-family: var(--font-heading);
    font-size: 1.3rem;
    font-weight: 600;
    color: var(--ink);
    text-decoration: none;
    letter-spacing: -.01em;
}

.header-cta { color: var(--accent); text-decoration: none; font-size: .88rem; font-weight: 500; }
.header-cta:hover { text-decoration: underline; }

.step-pill {
    font-size: .74rem;
    color: var(--ink-faint);
    background: var(--paper-2);
    border: 1px solid var(--line);
    padding: 5px 13px;
    border-radius: 100px;
    white-space: nowrap;
}

/* ===== Hero ===== */

.hero { padding: 60px 0 70px; }

.hero-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 50px;
    align-items: center;
}

.hero-copy h1 {
    font-size: clamp(2.1rem, 5vw, 3.15rem);
    margin-bottom: 18px;
    letter-spacing: -.015em;
}

.hero-copy .lede { max-width: 400px; margin-bottom: 28px; }

/* Live demo panel — reuses the real envelope component */
.hero-demo {
    position: relative;
    border-radius: var(--r-lg);
    padding: 30px 20px 70px;
    /* Tall, because the card LEAVES the envelope here. The panel has to hold the
       whole flight, not the envelope: the card rises until its bottom edge clears the
       envelope's mouth, so from the top of that arc to the bottom of the settled card
       is roughly twice the card's height. At 400px the card's top was cut off by 120px
       at the peak — the panel is overflow: hidden, and this is the first thing a
       visitor sees the product do.

       560px, and it was 500px when the demo card was text only. The demo now carries a
       voice note and a photo (see DEMO_CARD in js/main.js), and each of those bought
       itself a share of this 60px:

       - the voice player makes the card 50px taller, and --card-top subtracts
         --voice-extra so it grows UPWARDS — the card's bottom edge holds still and the
         whole extra 50px lands at the top of the arc. Peak clipping went 16px -> 39px
         on its own.
       - the photo made .env-flip appear at all, and that button hung below the settled
         card, so the panel acquired a new lowest element it had never had to hold.

       The panel centres the envelope, so height added here splits evenly above and
       below it: 60px buys 30px of headroom at the peak and 30px of clearance under the
       button, which is the same arithmetic .mini-stage's height note describes. Measured
       at 900/1000/1200 wide: peak clipping 6..9px, button bottom 20..37px inside the
       panel.

       The button's half of that is now unspent: this panel emits no flip button since the
       demo turns itself over on a timer (see wireDemo() in js/main.js), so the lowest
       element is the settled card again and there is roughly the button's height plus its
       47px of separation, halved by the centring, of slack at the bottom. The value was
       KEPT rather than wound back to 500px, for two reasons. The peak of the card's flight
       is still clipped by only 6..9px at the wide end and that margin was measured, not
       guessed — buying it back is a change to the first thing a visitor sees the product
       do. And the demo now flips in place, so the panel wants to look settled rather than
       tight while it plays. If anyone does reclaim it, the number to re-measure is the
       card's peak clipping at 900px and at 375px, not the card's bottom edge. */
    min-height: 560px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    border: 1px solid var(--line);
    background:
        radial-gradient(ellipse 80% 55% at 50% 0%, var(--bg-1), transparent 70%),
        linear-gradient(170deg, var(--bg-1), var(--bg-2));
}

/* Up from 64%, which was sized for the old side-by-side row: the card and photo used
   to settle in a row up to 146% of the envelope's width, so the envelope had to be
   shrunk until the ROW fit this panel. They now settle as a stack no wider than the
   card, so the envelope can fill the panel — and it wants to, because a BIGGER
   envelope makes a SHORTER card. The card's type and padding are absolute pixels, so
   the narrower the envelope the more lines the same message needs; at 64% the card
   came out 274px tall in a 130px envelope and ran off the bottom of the panel. */
.hero-demo .envelope-shell { width: 82%; }

/* A percentage of the envelope's own height, like .mini-stage's, not the old 78px:
   this panel's width changes with the hero grid, so a pixel drop was correct for one
   envelope size only. The envelope moves DOWN to make room above it for the card,
   which travels up — 45% is the largest drop that still keeps the envelope's bottom
   fold inside the panel, which is what makes the top of the arc fit. */
.hero-demo .envelope-shell.opened { transform: translateY(45%); }

/* The card floor, lowered for this panel only. envelope.css's 340px is the height of
   a full-size card's chrome — its padding and type at their real sizes. This demo
   overrides all of that (see the .letter rules below), so its card is genuinely
   smaller: ~213px against a 250px floor. That matters beyond the cap, because --rise
   is derived from it: at 340px the card was hauled 288px up out of a 258px envelope,
   nearly all of it overshoot, and the overshoot is exactly what the panel's top edge
   was slicing off. */
.hero-demo .envelope { --card-floor-base: 250px; }

/* No --slide: the settled stack in envelope.css is defined as a fraction of the
   envelope, so it needs no per-context travel distance. Nothing reads --slide any
   more, and leaving it here would look like a working dial. */
.hero-demo .letter { padding: 18px 16px 16px; }
.hero-demo .letter-greeting { font-size: 1.02rem; margin-bottom: 8px; }
.hero-demo .letter-message { font-size: .86rem; line-height: 1.58; margin-bottom: 12px; }
.hero-demo .letter-sign { font-size: .74rem; }
.hero-demo .letter-sign strong { font-size: .95rem; }
.hero-demo .letter-mark { width: 20px; height: 20px; margin-bottom: 9px; }

/* No `.hero-demo .env-flip` rule any more, and there is nothing to put back. This panel
   used to need one — envelope.css positions the button 50px under the ENVELOPE's bottom
   fold, which in a mock-up panel whose envelope is a percentage of its width and whose
   card is set in absolute type was wrong at both ends at once (25px outside an
   overflow: hidden panel at 1200px wide, 13px from the card's edge at 375px), so it was
   re-anchored to the card at `calc(-22% + 297px)`.

   The button itself is gone from this context: the demo turns the stack over on a timer
   now and emits no control (no `flip` id for the demo context in js/envelope.js, and
   wireDemo() in js/main.js drives it). A position override for an element that is never
   rendered is worse than no rule, because it reads as a live dial — which is the same
   reason --slide was taken out three rules above. .mini-stage's equivalent override in
   this file is untouched: preview.html still shows a real button. */

/* Sits at the bottom so the emerging letter never collides with it */
.demo-label {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    font-size: .7rem;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: #8a7a74;
    background: rgba(255,255,255,.6);
    padding: 5px 14px;
    border-radius: 100px;
    backdrop-filter: blur(4px);
    white-space: nowrap;
    z-index: 12;
    transition: opacity .35s ease, visibility .35s ease;
}

/* ---- The instruction retires once it has been followed ----
   It is an instruction for a thing that has already happened. The envelope is open; being
   told to tap it is at best noise and at worst a second invitation on a panel that is
   mid-demonstration.

   It used to have a sharper reason as well, and that reason has gone: .env-flip landed in
   the same place at the bottom of the panel and the two overlapped almost exactly at
   375px (label ~532..560, button 524..560), so one of them had to disappear and it was
   obviously the instruction. There is no button in this panel any more — the demo turns
   itself over (see wireDemo() in js/main.js) — so the collision is gone and this rule
   stands on the first reason alone. It is still the right rule, which is why it is still
   here and not deleted along with the button's position override above.

   Keyed off .opened rather than .settled so the label is gone before the card arrives
   over it, and it comes back when the demo closes itself again, which is when the
   invitation means something once more.

   :has() and not a class toggled from JS, matching the two rules in envelope.css that
   read the photo's and the voice note's presence the same way — the state is already in
   the DOM and a second copy of it in a script is a second thing to keep in step. The
   descendant combinator is needed because .env-host is display: contents, which changes
   layout but not the selector tree: .envelope is a child of #demoHost, not of .hero-demo.

   No prefers-reduced-motion guard on this one specifically: the global rule at the end
   of this file already collapses every transition-duration to .01ms, so under that
   setting the label simply disappears rather than fading.

   All three properties, and NOT opacity alone — this is the same lesson .env-flip
   records in envelope.css, learned again here the hard way. An element at opacity: 0
   still hit-tests, and this label carries z-index: 12, which put it above the flip
   button it overlapped: with only the opacity dropped, the invisible label swallowed
   every tap on "See photo" and the button was dead at 375px. Measured with
   elementFromPoint over the button's own centre, which reported .demo-label.

   That button is gone and the three properties STAY. The strip is still an invisible
   lid over the bottom of an open panel, and what is under it now is the envelope
   itself — which is a button, the one the whole panel is about — plus, at the narrow
   end, the lower edge of the settled card and anything the card carries. Reverting to
   opacity alone would make a band across the bottom of the demo dead to touch, which
   is the same bug with a different victim. */
.hero-demo:has(.envelope.opened) .demo-label {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* ===== Occasions ===== */

.occasions { padding: 66px 0; border-top: 1px solid var(--line); background: var(--paper-2); }

.occasions h2 { font-size: clamp(1.6rem, 4vw, 2.2rem); text-align: center; margin-bottom: 8px; }

.section-sub {
    text-align: center;
    color: var(--ink-soft);
    font-size: .95rem;
    margin-bottom: 40px;
}

.occasion-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 16px;
    max-width: 900px;
    margin: 0 auto;
}

.occasion {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    padding: 26px 22px;
    text-decoration: none;
    color: inherit;
    transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease;
}

.occasion:hover {
    transform: translateY(-3px);
    border-color: var(--accent);
    box-shadow: 0 14px 30px -14px rgba(60,30,40,.28);
}

.occasion-icon { display: block; width: 26px; height: 26px; margin-bottom: 14px; color: var(--accent); }
.occasion-icon svg { width: 100%; height: 100%; fill: currentColor; }

.occasion h3 { font-size: 1.2rem; margin-bottom: 5px; }
.occasion p { font-size: .86rem; color: var(--ink-soft); line-height: 1.55; }

/* Per-occasion icon tint. Each matches that occasion's --seal in
   envelope.css, so the tile previews the card it opens. */
.occ-sorry .occasion-icon { color: #3d5a80; }
.occ-miss .occasion-icon { color: #4a3b6b; }
.occ-thanks .occasion-icon { color: #35624a; }
.occ-anniversary .occasion-icon { color: #8a5a2b; }
.occ-birthday .occasion-icon { color: #b5502a; }
.occ-congrats .occasion-icon { color: #96661a; }
.occ-goodluck .occasion-icon { color: #1f6058; }
.occ-getwell .occasion-icon { color: #4a6636; }
.occ-newbaby .occasion-icon { color: #8a6248; }
.occ-farewell .occasion-icon { color: #45536b; }
.occ-rakhi .occasion-icon { color: #a83f3f; }
.occ-diwali .occasion-icon { color: #8c3d84; }
.occ-eid .occasion-icon { color: #175c4c; }
.occ-christmas .occasion-icon { color: #8d2b2b; }
.occ-newyear .occasion-icon { color: #3a3d70; }
.occ-friendship .occasion-icon { color: #b5613a; }

/* ----- Grouped shelves -----
   Seventeen tiles in one grid is a wall. Shelf headings give the eye somewhere
   to land and let a visitor skip straight to "Festivals". */

.occ-group + .occ-group { margin-top: 46px; }

.occ-group-head {
    max-width: 900px;
    margin: 0 auto 18px;
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
}

.occ-group-head h3 {
    font-family: var(--font-display);
    font-size: 1.35rem;
    font-weight: 500;
}

.occ-group-head p {
    font-size: .84rem;
    color: var(--ink-soft);
}

/* ===== How ===== */

.how { padding: 66px 0; }

.how-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 34px;
    max-width: 880px;
    margin: 0 auto;
}

.how-step { text-align: center; }

.how-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid var(--accent);
    color: var(--accent);
    font-size: .85rem;
    font-weight: 600;
    margin-bottom: 14px;
}

.how-step h3 { font-size: 1.2rem; margin-bottom: 6px; }
.how-step p { font-size: .88rem; color: var(--ink-soft); }

/* ===== Footer ===== */

.site-footer { padding: 40px 0; border-top: 1px solid var(--line); text-align: center; }
.foot-brand { font-family: var(--font-heading); font-size: 1.15rem; color: var(--ink); }
.foot-small { font-size: .8rem; color: var(--ink-faint); margin-top: 4px; }
.foot-small a { color: var(--ink-faint); }

/* The five policy pages Razorpay requires. A row that WRAPS rather than a fixed
   set of columns: on a 320px phone these five labels need three lines, and the
   separator has to be the gap rather than a "·" character — a mid-air bullet at
   the end of a wrapped line is the tell that a footer was designed at one width
   only. The dividing rules are drawn as left borders on each link instead, and
   :first-child drops the leading one, so the separators land correctly however
   the row breaks. */
.foot-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px 0;
    margin-top: 12px;
}

.foot-links a {
    padding: 2px 12px;
    border-left: 1px solid var(--line);
    font-size: .78rem;
    color: var(--ink-soft);
    text-decoration: none;
    /* line-height 1 keeps the border a tidy tick mark rather than a tall bar
       reaching into the row's leading. */
    line-height: 1.25;
}

.foot-links a:first-child { border-left: none; }
.foot-links a:hover { color: var(--accent); text-decoration: underline; }

/* Once the row wraps, the first item on each new line still carries a border,
   which reads as a stray mark hanging off the left edge. There is no CSS
   selector for "first on a wrapped line", so on the narrow widths where the wrap
   actually happens the borders are dropped entirely and the gap does the
   separating. Chosen over shrinking the labels: "Cancellation & Refund" is what
   Razorpay's checklist looks for, and abbreviating it to fit is a worse trade
   than losing four hairlines. */
@media (max-width: 640px) {
    .foot-links { gap: 2px 6px; }
    .foot-links a { border-left: none; padding: 2px 6px; }
}

/* The current page's own link, marked with aria-current in the markup. Not a
   removed link: a legal page still needs to advertise the other four, and a
   footer whose contents change per page is a footer that drifts per page. */
.foot-links a[aria-current="page"] { color: var(--ink); font-weight: 500; }

/* ===== Write page ===== */

.write-section { padding: 44px 0 70px; }
.write-head { margin-bottom: 30px; }
.write-head h1 { font-size: clamp(1.8rem, 5vw, 2.4rem); margin-bottom: 8px; }
.write-head .lede { font-size: .95rem; }

.write-form { display: flex; flex-direction: column; gap: 22px; }

.field { display: flex; flex-direction: column; gap: 8px; }

.field-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }

.field label { font-size: .88rem; font-weight: 500; color: var(--ink); }

.field input, .field textarea {
    width: 100%;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: #fff;
    font-family: var(--font-body);
    font-size: 1rem;
    color: var(--ink);
    transition: border-color .2s ease, box-shadow .2s ease;
}

/* Message gets the serif face — it's the emotional content */
.field textarea {
    font-family: var(--font-heading);
    font-size: 1.1rem;
    line-height: 1.65;
    resize: vertical;
    min-height: 130px;
}

.field input:focus, .field textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-wash);
}

.field input.invalid, .field textarea.invalid { border-color: #c0392b; }

.field-error { font-size: .8rem; color: #c0392b; margin-top: -2px; }

.field-foot { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.hint { font-size: .76rem; color: var(--ink-faint); }
.counter { font-size: .76rem; color: var(--ink-faint); white-space: nowrap; }

/* Stacked, not inline: these are full notes now, so a pill row would
   wrap into an unreadable mess. Reads as a menu of options instead. */
.chips { display: flex; flex-direction: column; gap: 6px; margin-bottom: 2px; }

/* Clamped to two lines, because a suggestion is now a whole 2–3 sentence note.
   Left unclamped, three of them stack into a wall of prose roughly the height
   of the textarea itself and push the message box off the first screen — the
   sender ends up reading a page of other people's words before they can see
   where their own would go. Two lines is enough to judge a note's tone and
   pick it; the full text arrives in the textarea on tap, which is where it is
   meant to be read anyway. */
.chip {
    background: var(--paper-2);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    padding: 10px 13px;
    font-family: var(--font-heading);
    font-size: .92rem;
    line-height: 1.45;
    color: var(--ink-soft);
    cursor: pointer;
    text-align: left;
    display: block;
    transition: border-color .18s ease, color .18s ease, background .18s ease;
}

/* The clamp lives on the inner span, not on .chip: a <button>'s text sits in an
   anonymous block that CSS cannot address, so -webkit-line-clamp on the button
   itself is ignored and the third line bleeds out under the border.

   THREE lines, not two. The suggestions are 110–160 characters now, and two lines
   cut the longer half of them off mid-sentence — a sender cannot decide to send a
   note whose ending they cannot read. Three lines shows all of them on a phone-width
   column and costs about 70px above the textarea, which is the better trade. */
.chip-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}

.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.on { background: var(--accent); color: #fff; border-color: var(--accent); }

.reassure { text-align: center; font-size: .8rem; color: var(--ink-faint); }

/* ===== Make it yours (crafting) =====
   Collapsed by default via <details>, so the sender who just wants to write and
   send never has to scroll past it. Native <details> gives us keyboard support
   and open/close state for free — worth more here than a custom accordion. */

.craft {
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--paper-2);
    overflow: hidden;
}

.craft-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 16px;
    cursor: pointer;
    list-style: none;              /* Firefox */
    user-select: none;
}

/* The default disclosure triangle would sit beside our own chevron. */
.craft-summary::-webkit-details-marker { display: none; }
.craft-summary::marker { content: ''; }

.craft-summary-main { display: flex; flex-direction: column; gap: 2px; }
.craft-summary-main b { font-size: .95rem; font-weight: 600; }
.craft-summary-main i { font-style: normal; font-size: .78rem; color: var(--ink-faint); }

.craft-summary:hover b { color: var(--accent); }

.craft-chevron {
    width: 9px;
    height: 9px;
    border-right: 2px solid var(--ink-faint);
    border-bottom: 2px solid var(--ink-faint);
    transform: rotate(45deg);
    transition: transform .2s ease;
    flex: none;
    margin-right: 4px;
}

.craft[open] .craft-chevron { transform: rotate(-135deg); }

.craft-body {
    padding: 4px 16px 18px;
    border-top: 1px solid var(--line);
    display: grid;
    gap: 20px;
}

/* ONE column at every width. This used to go to `220px 1fr` on desktop — card on
   the left, controls on the right — and the intent was good: see the effect of a tap
   without scrolling. But a 220px column is a hard ceiling on the preview, and the
   preview's whole job is to be the delivered card. The settled row is wider than the
   envelope (two fixed-width items side by side), so at 220px the sender was being
   shown a cramped version of a layout that is not cramped on a real card.

   Full width instead, and the same layout on a phone as on a desktop: one thing the
   sender scrolls through, and a preview with room to be honest.

   align-items is gone with it — `start` only ever mattered for keeping two columns
   top-aligned, and in one column it stops the stage stretching. */
@media (min-width: 720px) {
    .craft-body { gap: 26px; }
}

/* The card sits partly outside its own box (the letter is translated up out of
   the envelope), so the stage reserves room above and below rather than
   clipping. It used to clip: overflow:hidden plus a fixed 92px of top padding
   cut ~38px off the BOTTOM of the envelope while leaving slack at the top, so
   the sender could not see the whole thing they were decorating.

   Sized from the content instead of guessed: the letter is scaled to the
   envelope below, which bounds how far it can travel, and the stage reserves
   that travel as padding. */
.craft-stage {
    padding-top: 74px;
    padding-bottom: 30px;
    display: flex;
    justify-content: center;
    /* Visible, not hidden. Nothing should reach outside now that the letter is
       scaled to its envelope, and if anything ever does, the honest failure is
       seeing it overlap rather than silently losing the bottom of the card. */
    overflow: visible;
}

/* The envelope is sized in %, so the shell needs an explicit width, and max-width
   caps the ENVELOPE. Nothing inside it is wider: the card is 84% of the envelope and
   the photo sits behind it, so 290px of envelope is 290px of everything. (It used to
   be ~313px, because the card and photo sat side by side in a row that overhung both
   edges.) The enclosing .craft is overflow: hidden and .craft-body is a single
   full-width column, so there is room either way.

   Up from 210px, which was sized for the old two-column 220px slot and left the
   sender decorating a card whose edges ran outside the frame. */
.craft-phone { width: 100%; max-width: 290px; }

.craft-phone .envelope-shell { width: 100%; }

/* ---------- Fitting a full-size letter into a 210px envelope ----------
   The envelope scales with its container because every dimension in it is a
   percentage. The LETTER did not: its padding and type are absolute, so at this
   width the paper came out 259px tall inside a 140px envelope — half again the
   height of the thing meant to contain it — and hung out of both ends.

   Everything here is the same component the recipient gets, only smaller. It is
   not a separate design, which is what makes this a preview rather than an
   approximation. */
.craft-phone .letter {
    padding: 11px 11px 9px;
    /* No max-height. envelope.css now caps the card at 88cqh — a fraction of the
       ENVELOPE, which is what this hand-computed calc() was approximating for one
       fixed 210px width. The container query does it for every width, so this
       override only had to exist while the base cap was measured in vh. */
    border-radius: 3px;
}

.craft-phone .letter-mark { width: 15px; height: 15px; margin-bottom: 5px; }
.craft-phone .letter-greeting { font-size: .8rem; margin-bottom: 4px; }

.craft-phone .letter-message {
    /* Overrides --letter-size from the chosen handwriting, which is tuned for a
       full-size card. The relative differences between the hands still show,
       because the FACE is what the sender is choosing here, not the size. */
    font-size: .62rem !important;
    line-height: 1.5 !important;
    margin-bottom: 5px;
}

.craft-phone .letter-rule { margin: 5px auto 5px; width: 30px; }
.craft-phone .letter-sign { font-size: .56rem; }
.craft-phone .letter-sign strong { font-size: .68rem; margin-top: 1px; }

/* The letter animates out of the envelope in the real card. Here it is simply
   shown in its settled position: an animation replaying on every keystroke would
   be unusable.

   The position comes from the SAME --card-* variables the recipient's animation
   ends on, so this is the delivered card's final frame rather than an
   approximation of it. It used to be a hand-tuned translateY(-46%), which is how
   the preview and the card drift apart. Every one of those variables is a ratio,
   so they land correctly in this 210px envelope too. */
.craft-phone .envelope.opened .letter {
    animation: none;
    opacity: 1;
    left: var(--card-in);
    right: var(--card-in);
    top: var(--card-top);
    max-height: var(--card-maxh);
    /* The same transform the settled card holds. It has to be restated: without a
       transform the card would sit at its top/left with no translateZ, which in a
       preserve-3d deck puts it BEHIND the photo. */
    transform: translate(0px, 0px) rotate(0deg) translateZ(2px);
    box-shadow: 0 14px 30px -12px rgba(60,30,40,.32);
}

/* The photo is shown in its settled position — peeking out from behind the card,
   tilted — with no animation, same reason as the letter, and from the same
   --photo-* variables, so the sender sees exactly the stack the recipient ends up
   looking at.

   The peek transform and the rotateY(180deg) are both restated, for the same reason
   the letter's transform is: the photo is mounted face-away so that the deck's flip
   cancels it, and dropping that rotation here would show the sender a mirrored
   photo. Only its top-right corner protrudes, so only a mirrored sliver shows —
   see the mounting comment in envelope.css.

   The stack is never wider than the envelope now that both items sit on top of each
   other, so nothing here has to be narrowed or pulled in. The old
   --photo-shift/--photo-slide overrides existed to keep the side-by-side ROW from
   being sliced by an overflow: hidden ancestor; there is no row to slice.

   The transition goes too, not just the animation: without that the photo would
   dissolve in and out on every keystroke. */
.craft-phone .envelope.opened .env-photo {
    animation: none;
    transition: none;
    opacity: 1;
    left: var(--photo-in);
    right: var(--photo-in);
    top: var(--photo-top);
    transform: translate(var(--photo-peek-x), var(--photo-peek-y))
               rotate(var(--photo-peek-tilt)) translateZ(-2px) rotateY(180deg);
    box-shadow:
        0 0 0 1px rgba(140,100,110,.2),
        0 10px 22px -10px rgba(60,30,40,.36);
}

/* The interior is lifted in front of the envelope's front panels outright, rather
   than by the --inside-lift animation the recipient's card uses. This envelope is
   permanently `opened`, so that animation's 1.5s delay would leave the settled row
   drawn behind the envelope's own paper for the first second and a half of the
   write page — and would re-run every time the markup is repainted. */
.craft-phone .envelope.opened .env-inside {
    animation: none;
    z-index: 20;
    /* And the clip released outright, for the third time for the same reason. The
       recipient's card releases the bottom edge inside @keyframes inside-out, at the
       one frame where the stack has cleared the envelope; here the stack starts
       settled and low, so the clip that hides the enclosed strip would slice its
       bottom off from the first paint. */
    clip-path: inset(-300% -120% -300% -120%);
    /* Visible outright, for the same reason as the z-index above. The reveal's fade
       moved from the two children onto this layer (see .env-inside in envelope.css:
       fading them separately let the photo show through the translucent card), so
       killing the animation here without this leaves the write page's reference card
       blank. The children's own `opacity: 1` overrides below are no longer what makes
       them visible — this is. */
    opacity: 1;
}

.craft-phone .envelope-shell.opened { transform: translateY(30px); }
.craft-phone .envelope { cursor: default; }
.craft-phone .envelope:hover { transform: none; }

/* No wax on this envelope: it is permanently open, and the recipient only ever
   sees wax on a shut one. The .wax-preview swatch beside the colour row is where
   the sender's chosen wax is shown. */

/* ---------- Control rows ---------- */

.craft-controls { display: grid; gap: 14px; }

.craft-row { display: grid; gap: 7px; }

.craft-label {
    font-size: .74rem;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

.swatches { display: flex; flex-wrap: wrap; gap: 8px; }

.swatch {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 2px solid transparent;
    /* The ring sits in the gap between border and colour, so a selected swatch
       grows a ring rather than shifting its neighbours. */
    box-shadow: 0 0 0 1px var(--line);
    cursor: pointer;
    padding: 0;
    transition: transform .16s ease, box-shadow .16s ease;
}

.swatch:hover { transform: scale(1.08); }

.swatch.on {
    border-color: var(--paper-2);
    box-shadow: 0 0 0 2px var(--accent);
}

.swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Paper shades are all near-white, so the 1px --line ring is the only thing
   separating one from the next — and against the panel's own warm background
   it disappears. A darker outline is what makes four cream circles legible as
   four distinct choices rather than one smudge. */
.swatch-pale { box-shadow: 0 0 0 1px rgba(90,70,60,.28); }
.swatch-pale.on { box-shadow: 0 0 0 2px var(--accent); }

.pills { display: flex; flex-wrap: wrap; gap: 7px; }

.pill {
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 100px;
    padding: 7px 14px;
    font-family: var(--font-body);
    font-size: .84rem;
    color: var(--ink-soft);
    cursor: pointer;
    transition: border-color .16s ease, color .16s ease, background .16s ease;
}

.pill:hover { border-color: var(--accent); color: var(--accent); }
.pill.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Wax: colour + stamp, with a live seal beside them ----------
   The two controls share one row because they describe one object. Seeing the
   wax you picked with the stamp you picked pressed into it is the whole point;
   split across two rows they read as unrelated settings. */
/* Centred now that the stamp pills are gone and only one row of colours is left
   beside the seal. */
.craft-row-wax .wax-control {
    display: flex;
    align-items: center;
    gap: 14px;
}

/* Fixed box so the row cannot reflow. The seal inside is absolutely positioned
   (it hangs off an envelope flap normally), so this needs to establish the
   containing block itself. */
.wax-preview {
    position: relative;
    flex: 0 0 auto;
    width: 58px;
    height: 58px;
}

/* A seal with nothing pressed into it. The layered wax — gradients, scalloped
   edge, inner shine — is the whole point of the swatch; only the glyph is absent,
   because which glyph is no longer the sender's decision. */
.seal-blank svg { display: none; }

/* Undo the envelope-flap positioning: here the seal is the whole subject, sat
   square in its box rather than hanging off an edge. */
.wax-preview .seal {
    position: absolute;
    inset: 0;
    left: 0;
    bottom: auto;
    width: 58px;
    height: 58px;
    margin: 0;
}

.btn-mini {
    display: inline-block;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 100px;
    padding: 7px 14px;
    font-size: .84rem;
    color: var(--ink-soft);
    cursor: pointer;
    transition: border-color .16s ease, color .16s ease;
}

.btn-mini:hover { border-color: var(--accent); color: var(--accent); }

/* Keyboard focus lands on the hidden <input type="file">, not on the label, so
   the ring has to be driven from the input's state. */
#photoInput:focus-visible + .btn-mini { outline: 2px solid var(--accent); outline-offset: 2px; }

.craft-note { font-size: .76rem; color: var(--ink-faint); flex-basis: 100%; }

/* ===== Add something of yours (photo + voice) =====
   Out in the open, not inside the collapsed craft panel. A photo and a voice
   note are the whole argument for the ₹99 card, so burying them behind a
   summary line meant the upgrade was priced but never pitched.

   Deliberately styled as two offers rather than two form rows: an icon, a
   promise about what the recipient will experience, and the price. The
   "Envelope / Paper / Wax" rows inside the panel are settings; these are
   additions to the gift, and they should not look the same. */

.extras {
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--paper-2);
    padding: 14px 16px 16px;
    display: grid;
    gap: 12px;
}

.extras-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.extras-title { font-size: .88rem; font-weight: 500; color: var(--ink); }

/* Says "skip me freely" without a sentence of reassurance. Someone in a hurry
   to apologise should be able to read this row and move on. */
.extras-opt {
    font-size: .72rem;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

/* Side by side once there is room, stacked on a phone. Two tiles read as a
   choice between offers; two full-width bands read as a checklist. */
.extras-grid { display: grid; gap: 10px; }

/* Equal height, not align-items:start — two offers of visibly different height
   read as one primary and one afterthought, and the shorter tile's note floats
   away from its own bottom edge. */
@media (min-width: 620px) {
    .extras-grid { grid-template-columns: 1fr 1fr; align-items: stretch; }
}

.extra {
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    padding: 12px 13px;
    display: grid;
    gap: 9px;
    /* Both tiles keep the note at the bottom edge, so the two stay aligned
       even when one wraps its description to an extra line. */
    grid-template-rows: auto 1fr auto;
}

.extra-top { display: flex; align-items: flex-start; gap: 9px; }

.extra-icon {
    flex: none;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--accent-wash);
    color: var(--accent);
    display: grid;
    place-items: center;
}

.extra-icon svg { width: 15px; height: 15px; fill: currentColor; }

.extra-title { display: grid; gap: 1px; min-width: 0; }
.extra-title b { font-size: .88rem; font-weight: 600; color: var(--ink); }

/* The promise is about what the RECIPIENT gets, not what the control does —
   "a play button on the card" is a reason to record; "record audio" is not. */
.extra-title i { font-style: normal; font-size: .76rem; line-height: 1.45; color: var(--ink-soft); }

.extra .photo-control,
.extra .voice-control { align-self: start; }

.photo-control { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Doubles as the error channel for both tiles (a rejected photo, a refused
   microphone), which is why it is a live region's worth of space rather than a
   one-line price tag. */
.extra-note { font-size: .74rem; line-height: 1.45; color: var(--ink-faint); }

/* ---------- Voice recorder ---------- */

.voice-control { display: grid; gap: 8px; }

.voice-idle, .voice-live, .voice-done-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* A red dot on the record button, the one universally-read affordance for this. */
.voice-dot {
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: #c8322f;
    margin-right: 6px;
    vertical-align: baseline;
}

/* Pulses only while recording, so the page state is legible at a glance and
   without reading the timer. */
.voice-pulse {
    width: 10px; height: 10px;
    border-radius: 50%;
    background: #c8322f;
    animation: voice-pulse 1.1s ease-in-out infinite;
    flex-shrink: 0;
}

@keyframes voice-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: .45; transform: scale(.82); }
}

.voice-time {
    font-variant-numeric: tabular-nums;   /* stops the row twitching per second */
    font-size: .9rem;
    color: var(--ink);
    min-width: 38px;
}

/* Fills toward the 60s ceiling. The cut-off is hard and unannounced, so this bar
   is the only thing that makes it predictable rather than abrupt. */
.voice-bar {
    flex: 1;
    min-width: 60px;
    height: 4px;
    border-radius: 100px;
    background: var(--line);
    overflow: hidden;
}

.voice-bar i {
    display: block;
    height: 100%;
    width: 0;
    background: var(--accent);
    border-radius: 100px;
    transition: width .2s linear;
}

/* No max-width: inside a half-width tile a 320px player overflowed its column,
   which pushed "Remove" onto a line of its own. The tile is the constraint. */
.voice-done audio { width: 100%; height: 36px; display: block; }
.voice-done-row { margin-top: 7px; }
.voice-len { font-size: .8rem; color: var(--ink-soft); }

/* Length, Re-record and Remove are three items in a tile that is only ~208px
   wide on desktop — 86 + 62 + 50 + gaps overflows it, and "Remove" dropped to a
   line of its own. The length label carries the least weight of the three once
   the player is visible right above it (which already shows the duration), so
   it is the one that gives way: it moves onto its own line ABOVE the two
   buttons, which keeps the pair together and reads as a caption. */
.extra .voice-done-row { gap: 4px 10px; }
.extra .voice-len { flex-basis: 100%; font-size: .76rem; }
.extra .voice-done-row .link-btn { white-space: nowrap; }

.craft-reset { align-self: start; font-size: .82rem; }

/* Available to assistive tech, invisible on screen. Used for the colour names
   on swatches, which are otherwise unlabelled circles. */
.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ===== Preview page ===== */

.preview-page { background: var(--paper); }

.preview-section { padding: 30px 0 70px; }
.preview-head { text-align: center; margin-bottom: 24px; }
.preview-head h1 { font-size: clamp(1.7rem, 4.6vw, 2.2rem); }

/* Phone frame gives the card context and contains the animation,
   which is what stops the old overlap/clipping problem. */
.phone-frame {
    /* No width cap. The frame takes whatever its column gives it, which on a desktop
       is the page's content width — and that width is what buys the PHOTO room to be
       seen. A capped frame meant a capped envelope meant a thumbnail-sized photo, no
       matter how much of the window was empty on either side. */
    margin: 0 auto;
    padding: 10px;
    border-radius: 34px;
    background: #ded3c6;
    box-shadow: 0 22px 50px -18px rgba(60,30,40,.4), 0 0 0 1px rgba(0,0,0,.05) inset;
}

.phone-screen {
    border-radius: 26px;
    overflow: hidden;
    background: var(--paper);
}

/* Scoped envelope stage inside the phone — own stacking context.
   Taller than it looks like it needs to be, and the reason is the CARD, not the
   envelope: the envelope here is ~190px tall, but the card that comes out of it is
   ~250px (its chrome is absolute pixels and does not shrink with the envelope — see
   --card-maxh), it rises most of its own height on the way out, and it now SETTLES low
   over the envelope rather than beside it. So the stage has to hold, top to bottom:
   the peak of the rise, the envelope, the card hanging below it, and the flip button
   under that, with the Close it button parked at the bottom.

   Measured at 650 and 390px wide: peak card top 53px below the stage's top edge, settled
   card bottom 469, flip button 513..549, Close it 588. At 500px tall — the height this
   was before the stack settled low — the flip button landed ON TOP of Close it, which is
   the bug this height fixes.

   650px, and it was 620px: the flip button moved 15px further down (see .env-flip in
   envelope.css and the override below) and Close it is parked at the stage's BOTTOM
   edge, so the two closed on each other — at 320px wide the gap went to -1px, the same
   collision again. The stage centres the envelope, so half of any height added here goes
   above the envelope and travels down with it: 30px of stage buys back exactly the 15px
   of clearance the button spent, which is why this went up by twice the offset. */
.mini-stage {
    position: relative;
    height: 650px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 18px 14px 22px;
    background:
        radial-gradient(ellipse 80% 55% at 50% 0%, var(--bg-1), transparent 70%),
        linear-gradient(170deg, var(--bg-1), var(--bg-2));
    overflow: hidden;
}

/* The envelope has to be big enough to look like it CONTAINS the card. A 165px-tall
   envelope behind a 250px card reads as a triangle of paper poking out from behind a
   letter, which is what an 88% shell produced here.

   The full width of the screen's content box, because the card and the photo are no
   longer a ROW: they are one stack, the width of the card alone, so nothing here has to
   leave room beside the envelope any more. 3:2, so a 100% shell of this ~285px content
   box is ~190px tall — still shorter than the card, as it should be, since a card
   sticks up out of its envelope, but close enough that the envelope reads as the thing
   it came from. Bigger is also SHORTER-carded: --card-maxh is a fraction of the
   envelope's height, so growing the envelope buys headroom at both ends. */
.mini-stage .envelope-shell { width: 100%; }
/* The card here is ~250px against a ~190px envelope — proportionally taller than the
   delivered card's, because this context shrinks the letter's type and padding but not
   in step with the envelope. --rise derives from --card-floor-base, so leaving the
   card page's 340px floor in place made the rise mostly overshoot: the peak went
   straight out of the top of this stage, which is overflow: hidden. */
.mini-stage .envelope { --card-floor-base: 250px; }
/* No max-height and no --slide. envelope.css caps the card against the ENVELOPE
   (cqh) and places the settled card as a fraction of it, so both the fit and the
   travel scale with this envelope automatically — which is what the hand-tuned
   numbers here were approximating for one fixed width. */
.mini-stage .letter { padding: 18px 16px 16px; }
.mini-stage .letter-greeting { font-size: 1rem; margin-bottom: 8px; }
.mini-stage .letter-message { font-size: .87rem; line-height: 1.6; margin-bottom: 12px; }
.mini-stage .letter-sign { font-size: .74rem; }
.mini-stage .letter-sign strong { font-size: .95rem; }
.mini-stage .letter-mark { width: 20px; height: 20px; margin-bottom: 9px; }
.mini-stage .seal { width: 44px; height: 44px; margin-left: -22px; margin-bottom: -22px; }
.mini-stage .seal svg { width: 19px; height: 19px; }
/* This push-down exists so a card rising out of the envelope stays on screen — the card
   travels UP, so the envelope moves down to make room above it.

   A PERCENTAGE, not a pixel constant, and that is the fix for the stack climbing out of
   the top of the frame. The stage centres the ENVELOPE, but after the exit the subject
   is the settled CARD, whose top edge is --card-top = 22% of the envelope's height above
   it and whose bottom hangs below the envelope. So the amount to push down is a fraction
   of that same height.

   18%, not the 26% this was when the card settled beside the envelope: the card now
   settles LOW over it and hangs further below, so most of the room the stack needs is at
   the bottom, and a bigger push spends it. translateY(%) resolves against the element's
   own height, which here IS the envelope's height, so this tracks the envelope at every
   frame width automatically. */
.mini-stage .envelope-shell.opened { transform: translateY(18%); }

/* Below the CARD, not below the envelope — which is what the delivered card's
   `100% + 35px` means and why it cannot be reused here. The card in this context is
   ~250px whatever the frame's width, while the envelope shrinks with it (228px on a
   desktop, 152px at 320px wide), so how far the card hangs past the envelope's bottom
   edge GROWS as the screen narrows. Any constant offset from the envelope is therefore
   wrong at one end: 40px cleared the card on a desktop and was buried 25px inside it at
   320px wide.

   So this is the card's own bottom edge, restated: the card's top is --card-top = -22%
   of the envelope's height (see envelope.css) and it is 250px tall, leaving 47px of air
   under it — which is what the delivered card's `100% + 35px` works out to below ITS
   card edge (~48px), so the same amount of air separates button from card in both
   contexts. It was 32px, and the extra 15px is the same 15px .env-flip took on.
   -22% and not --card-top itself because the custom properties live on
   .envelope, which is this button's SIBLING — and because the voice-note case needs no
   adjustment: --card-top subtracts --voice-extra precisely so the card's bottom edge
   holds still.

   Measured clearances above (card) and below (Close it), at 1200/390/360/320px wide:
   66/44/47/47 and 65/39/28/14. The stage heights are sized around the second row — the
   button hangs this far down, and Close it is parked at the stage's bottom edge — so
   both of them went up by 30px when this offset went up by 15px, which is what keeps
   that row identical to what it was at 282px. */
.mini-stage .env-flip { top: calc(-22% + 297px); }

/* ---- Desktop preview ----
   The frame has no width cap, so on a desktop this screen is the page's content width —
   around 500px, where a 100% shell would make a 500px envelope and a card wider than
   the delivered one. The preview would then flatter the product: the sender would be
   shown a card bigger than the one the recipient gets. 73% of a ~480px screen is a
   ~350px envelope and a ~250px card, which is the size the card page settles on too.

   The height goes up with it. Everything below the envelope's bottom edge — the card's
   overhang, the flip button — is roughly constant in pixels, but the envelope itself
   grows with the frame, so a taller envelope needs a taller stage to keep the same
   clearance above the Close it button. Measured at 1200: peak card top 53px inside the
   stage, settled card bottom 441, flip 507..543, Close it 608. */
@media (min-width: 580px) {
    .mini-stage { height: 670px; }
    .mini-stage .envelope-shell { width: 73%; }
}

/* No travel overrides here, still, and now for a simpler reason than before: the card
   and the photo are one stack the width of the card, so nothing has to be positioned
   around a row that is wider than its envelope. The overrides stay gone either way —
   they hand-tuned positions that are now derived, and would drift from the delivered
   card the moment the settle changed. */

/* Centred with translateX(-50%), so every transform on this button must repeat
   it — including the hover lift below. Dropping it makes the button jump right
   by half its width on mouse-over. */
.btn-open-mini {
    position: absolute;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%) translateY(0);
    padding: 10px 24px;
    font-size: .84rem;
    z-index: 30;
    margin: 0;
    box-shadow: 0 6px 16px -6px rgba(70,20,30,.4);
}

.btn-open-mini:hover {
    transform: translateX(-50%) translateY(-2px);
    box-shadow: 0 10px 22px -6px rgba(70,20,30,.5);
}

.edit-line { text-align: center; margin: 18px 0 30px; }

/* ---- The choice block: where price finally appears ---- */

.choice-block {
    background: var(--paper-2);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    padding: 26px 22px;
}

.choice-title { font-size: 1.35rem; text-align: center; margin-bottom: 18px; }

.includes {
    list-style: none;
    margin: 0 auto 20px;
    max-width: 320px;
    display: flex;
    flex-direction: column;
    gap: 9px;
}

.includes li {
    position: relative;
    padding-left: 26px;
    font-size: .88rem;
    color: var(--ink-soft);
    line-height: 1.5;
}

/* Tick drawn in CSS — no icon font, no extra request */
.includes li::before {
    content: '';
    position: absolute;
    left: 6px;
    top: .42em;
    width: 9px;
    height: 5px;
    border-left: 1.8px solid var(--accent);
    border-bottom: 1.8px solid var(--accent);
    transform: rotate(-45deg);
}

/* ---- Tier picker ---- */

.tiers { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }

.tier {
    position: relative;
    display: block;
    padding: 14px 16px 13px;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    cursor: pointer;
    transition: border-color .18s ease, box-shadow .18s ease;
}

.tier:hover { border-color: var(--accent); }

/* The ring is derived from --accent rather than the fixed --accent-wash,
   because preview.html carries the occasion theme — a pink ring around an
   orange border looked like a mistake on the birthday card. */
.tier.selected {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-wash);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}

/* The native radio is hidden but still focusable and still announced. */
.tier input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}

.tier input:focus-visible ~ .tier-main { outline: 2px solid var(--accent); outline-offset: 3px; }

.tier-main {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.tier-name { font-size: .96rem; font-weight: 500; color: var(--ink); }

.tier-price {
    font-family: var(--font-heading);
    font-size: 1.5rem;
    font-weight: 600;
    color: var(--ink);
    line-height: 1;
    white-space: nowrap;
}

.tier-note {
    display: block;
    margin-top: 5px;
    font-size: .82rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

/* A tier that cannot be chosen — currently only ₹49 with a photo attached.
   Faded, but the reason line below stays at full contrast: a greyed-out option
   with no explanation is the thing that makes people leave. */
.tier-disabled {
    cursor: not-allowed;
    background: var(--paper-2);
}

.tier-disabled:hover { border-color: var(--line); }

.tier-disabled .tier-main,
.tier-disabled .tier-note { opacity: .5; }

.tier-blocked {
    display: block;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed var(--line);
    font-size: .8rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

/* ---- When it opens ---- */

/* The lock is opt-in inside the ₹99 tier, so it gets a real checkbox rather
   than being implied by the price. */
.seal-toggle {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    cursor: pointer;
}

.seal-toggle input {
    /* Sized up: the default 13px box is a genuinely hard target on a phone. */
    width: 18px;
    height: 18px;
    margin: 1px 0 0;
    flex: none;
    accent-color: var(--accent);
    cursor: pointer;
}

.seal-toggle b {
    display: block;
    font-size: .88rem;
    font-weight: 500;
    color: var(--ink);
}

.seal-toggle i {
    display: block;
    margin-top: 3px;
    font-size: .8rem;
    font-style: normal;
    line-height: 1.5;
    color: var(--ink-soft);
}

.seal-when {
    margin-top: 15px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}

.seal-picker {
    padding: 15px 16px 16px;
    margin-bottom: 18px;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
}

.seal-picker-label {
    font-size: .84rem;
    font-weight: 500;
    margin-bottom: 10px;
    color: var(--ink);
}

.seal-presets { display: flex; flex-wrap: wrap; gap: 7px; }

.seal-preset {
    padding: 8px 14px;
    font-family: var(--font-body);
    font-size: .83rem;
    color: var(--ink-soft);
    background: var(--paper-2);
    border: 1px solid var(--line);
    border-radius: 100px;
    cursor: pointer;
    transition: border-color .16s ease, color .16s ease, background .16s ease;
}

.seal-preset:hover { border-color: var(--accent); color: var(--accent); }
.seal-preset.on { background: var(--accent); border-color: var(--accent); color: #fff; }

.seal-custom { margin-top: 12px; }

.seal-custom-label {
    display: block;
    font-size: .78rem;
    color: var(--ink-faint);
    margin-bottom: 6px;
}

.seal-input {
    width: 100%;
    padding: 11px 13px;
    font-family: var(--font-body);
    font-size: .92rem;
    color: var(--ink);
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
}

.seal-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-wash);
}

/* The resolved instant. Stated absolutely so nobody pays for a surprise. */
.seal-resolved {
    margin-top: 11px;
    font-size: .84rem;
    color: var(--ink);
    font-weight: 500;
}

.seal-error { margin-top: 11px; font-size: .82rem; color: #c0392b; }

/* Smaller and fainter than the Razorpay reassurance line above it: that line is
   information the sender wants (which payment methods work), this one is a
   consent notice they need available but rarely read. Same size would make the
   block below the button read as two competing footnotes. */
.pay-terms { margin-top: 7px; font-size: .74rem; line-height: 1.5; }
.pay-terms a { color: var(--ink-soft); }
.pay-terms a:hover { color: var(--accent); }

.choice-block .btn { margin-top: 2px; }
.choice-block .btn:disabled { opacity: .5; }
.choice-block .reassure { margin-top: 11px; }

/* ===== Success page ===== */

#confettiCanvas {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 40;
}

.done-section { padding: 44px 0 70px; }
.done-head { text-align: center; margin-bottom: 26px; }

.done-tick {
    width: 46px;
    height: 46px;
    margin: 0 auto 16px;
    border-radius: 50%;
    background: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
    animation: pop .5s cubic-bezier(.34,1.56,.64,1);
}

.done-tick svg { width: 22px; height: 22px; fill: none; stroke: #fff; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }

@keyframes pop { 0% { transform: scale(0); } 100% { transform: scale(1); } }

.done-head h1 { font-size: clamp(1.7rem, 4.6vw, 2.2rem); margin-bottom: 8px; }
.done-head .lede { font-size: .93rem; }

.link-row { display: flex; gap: 8px; margin: 16px 0 10px; }

.link-row input {
    flex: 1;
    min-width: 0;
    padding: 13px 15px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: #fff;
    font-family: var(--font-body);
    font-size: .84rem;
    color: var(--ink-soft);
}

.btn-copy {
    padding: 13px 20px;
    background: var(--ink);
    color: #fff;
    border: none;
    border-radius: var(--r-sm);
    font-family: var(--font-body);
    font-size: .85rem;
    font-weight: 500;
    cursor: pointer;
    transition: background .2s ease;
    white-space: nowrap;
}

.btn-copy:hover { background: #17110e; }
.btn-copy.copied { background: #35624a; }

/* ---- Sealed confirmation + sender reminder ---- */

/* success.html has no occasion theme, so the fixed wash is correct here. */
.sealed-note {
    margin: 4px 0 20px;
    padding: 15px 17px 16px;
    background: var(--accent-wash);
    border: 1px solid #eccfd5;
    border-radius: var(--r-md);
}

.sealed-note-line {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: .92rem;
    color: var(--ink);
}

.sealed-note-line strong { font-weight: 600; }

.sealed-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent);
    flex: none;
}

.sealed-note-sub {
    margin-top: 6px;
    padding-left: 17px;
    font-size: .82rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

.remind {
    margin-top: 15px;
    padding-top: 14px;
    border-top: 1px solid #eccfd5;
}

.remind-label { font-size: .84rem; font-weight: 500; margin-bottom: 9px; }

.remind-opts { display: flex; gap: 7px; margin-bottom: 12px; }

.remind-opt {
    padding: 7px 13px;
    font-family: var(--font-body);
    font-size: .8rem;
    color: var(--ink-soft);
    background: rgba(255,255,255,.7);
    border: 1px solid #eccfd5;
    border-radius: 100px;
    cursor: pointer;
    transition: border-color .16s ease, color .16s ease, background .16s ease;
}

.remind-opt:hover { border-color: var(--accent); color: var(--accent); }
.remind-opt.on { background: var(--accent); border-color: var(--accent); color: #fff; }

.remind-btn { background: rgba(255,255,255,.75); }

.remind-note {
    margin-top: 9px;
    font-size: .76rem;
    line-height: 1.5;
    color: var(--ink-faint);
}

.link-note { text-align: center; margin-bottom: 26px; }
.link-note a { color: var(--ink-faint); font-size: .8rem; text-decoration: none; }
.link-note a:hover { color: var(--accent); }

.again-block { text-align: center; padding-top: 26px; border-top: 1px solid var(--line); }
.again-block p { font-size: .88rem; color: var(--ink-soft); margin-bottom: 13px; }

/* ===== Policy pages =====
   Five pages of prose — terms, privacy, refund, shipping, contact — which are
   the only pages on this site made of running text rather than a flow. They
   share one set of rules because they are one document in five files, and
   because Razorpay's reviewer reads all five in a row: a refund page that
   looks like a different site from the terms page reads as copy-paste.

   Selectors are descendants of .legal-body rather than utility classes on each
   element. Normally the wrong instinct, but here it is the right one: this is
   the one place where the markup is prose, an author adding a paragraph should
   not have to remember a class name, and there is no CMS to enforce one. */

.legal-section { padding: 44px 0 64px; }

.legal-head { margin-bottom: 30px; }
.legal-head h1 { font-size: clamp(1.9rem, 5.5vw, 2.5rem); }

/* The date matters legally — "as of when?" is the first question about any
   policy — so it is stated in the header rather than buried at the bottom. */
.legal-updated { font-size: .8rem; color: var(--ink-faint); margin-top: 10px; }

.legal-body { color: var(--ink-soft); font-size: .94rem; line-height: 1.75; }

/* Generous space ABOVE each heading and little below it, so a heading sits with
   the text it introduces instead of floating between two sections. */
.legal-body h2 {
    font-size: 1.3rem;
    color: var(--ink);
    margin: 34px 0 10px;
}

.legal-body h3 {
    font-family: var(--font-body);
    font-size: .95rem;
    font-weight: 600;
    color: var(--ink);
    margin: 22px 0 6px;
}

.legal-body p { margin-bottom: 13px; }
.legal-body ul, .legal-body ol { margin: 0 0 15px 20px; }
.legal-body li { margin-bottom: 7px; }
.legal-body strong { color: var(--ink); font-weight: 600; }
.legal-body a { color: var(--accent); }

/* A pulled-out statement of fact, for the two or three places where the honest
   answer is short and load-bearing — "there is nothing to ship", "we cannot
   read a ₹49 card". Left rule rather than a filled box: these are emphasis, not
   warnings, and a tinted panel on a legal page reads as a disclaimer. */
.legal-note {
    margin: 0 0 18px;
    padding: 14px 18px;
    border-left: 2px solid var(--accent);
    background: var(--paper-2);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    font-size: .9rem;
}

.legal-note p:last-child { margin-bottom: 0; }

/* Definition-style rows for "what we store / why / how long". A table would be
   more natural but tables do not reflow on a 320px screen, and this content is
   read on phones like everything else here. */
.legal-dl { margin: 0 0 18px; }

.legal-dl dt {
    font-weight: 600;
    color: var(--ink);
    font-size: .9rem;
    margin-top: 14px;
}

.legal-dl dd { margin: 3px 0 0; font-size: .9rem; }

/* Contact rows. Uses .legal-body's type, so only the layout lives here. */
.contact-block {
    margin: 0 0 20px;
    padding: 20px 22px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--paper-2);
}

.contact-label {
    font-size: .72rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: 4px;
}

/* The email is the ONLY support channel, so it is set at heading scale — a
   support address in body copy on a page titled "Contact Us" is a page that
   makes you hunt. Word-break because a long address must not push the card
   sideways on a narrow screen. */
.contact-value {
    font-family: var(--font-heading);
    font-size: 1.3rem;
    color: var(--ink);
    word-break: break-word;
}

.contact-value a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--accent); }
.contact-value a:hover { color: var(--accent); }

/* ===== 404 =====
   Deliberately NOT the .card-page stage: a full-viewport centred error with a
   drifting-hearts background would be the site pretending a dead link is a
   gift. Plain page, real header, real footer — so the five policy links are
   still reachable from a mistyped URL, which is also the one place a reviewer
   is most likely to land by hand. */

.gone-section { padding: 70px 0 80px; text-align: center; }

/* An envelope glyph rather than "404" at display size. The number tells the
   visitor nothing they can act on; the mark keeps the page recognisably part of
   this site while the words below do the explaining. */
.gone-mark {
    font-size: 2.6rem;
    line-height: 1;
    margin-bottom: 18px;
    /* The theme accent, dimmed: at full strength a 2.6rem heart competes with
       the heading for the first look. */
    opacity: .55;
}

.gone-section h1 { font-size: clamp(1.9rem, 5.5vw, 2.5rem); margin-bottom: 12px; }
.gone-section .lede { max-width: 420px; margin: 0 auto 26px; }

/* Two ways out, ranked. The primary is "write one", not "go home": someone who
   reached a dead URL on a card site is more likely to want a card than a
   homepage tour. */
.gone-actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }

/* Reuses .legal-note's rule-and-tint, but this section is centred and the note
   is a paragraph — centred prose of this length is harder to read, so the text
   goes back to the left inside it. */
.gone-hint { margin-top: 34px; text-align: left; }

/* ===== Responsive ===== */

@media (max-width: 860px) {
    .hero { padding: 36px 0 50px; }
    .hero-grid { grid-template-columns: 1fr; gap: 34px; }
    .hero-copy { text-align: center; }
    .hero-copy .lede { margin-left: auto; margin-right: auto; }
    /* TALLER than the desktop panel, which looks like a mistake and is not. It was
       shorter (440px against 500px) while the demo was text only, because a narrower
       panel makes a smaller envelope and the envelope was the tallest thing in it.

       What inverted it is the flip button, which arrived with the demo photo. Everything
       BELOW the envelope's bottom fold — how far the settled card hangs past it, and the
       button under that — is set in absolute pixels, while the envelope itself is 82% of
       the panel's width. So as the panel narrows the envelope shrinks out from under a
       card that does not, and the stack's lowest point moves DOWN the panel rather than
       up with it: at 1200px wide the button ends 523px into the panel, at 375px it ends
       560px in. The narrow case is therefore the tall one.

       580px, measured at 390/375/360: no peak clipping at all, and the button's bottom
       edge lands 20..22px inside the panel. At 320px it was ~4px outside, because the
       label wrapped to two lines there and the button was 52px tall instead of 36px.

       Past tense throughout the last two paragraphs on purpose: there is no button in
       this panel any more — the demo flips itself now (see wireDemo() in js/main.js) — so
       the 320px overhang it describes has gone with it, and the lowest element is the
       settled card. The height is unchanged all the same, for the reason the desktop
       note above gives at length: the measurement that still binds is the card's peak
       clipping, and reclaiming the button's share of the panel means re-measuring the
       flight rather than subtracting 36px on paper. */
    .hero-demo { min-height: 580px; order: -1; }
}

@media (max-width: 520px) {
    .occasion-grid { grid-template-columns: 1fr 1fr; gap: 12px; }
    .occasion { padding: 20px 16px; }
    .occasion h3 { font-size: 1.06rem; }
    .occasion p { font-size: .79rem; }
    .choice-block { padding: 20px 16px; }
    .step-pill { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .done-tick { animation: none; }
    * { transition-duration: .01ms !important; }
}
