
    *, *::before, *::after { box-sizing: border-box; }
    [hidden] { display: none !important; }
    :root {
      color-scheme: light;
      --primary: var(--sponica-light-primary);
      /* Deep primary ink. 30 of its 30 uses are either text on --primary-soft
         or the .btn hover fill, so it tracks the token's pressed step. */
      --primary-dark: var(--sponica-light-primary-pressed);
      --primary-soft: var(--sponica-light-primary-container);
      --primary-softer: color-mix(in srgb, var(--sponica-light-primary-container) 55%, var(--sponica-light-surface));
      /* NOT token-derived on purpose. public.brand_config serves this exact
         value and overrides us via element.style at runtime, so the CSS default
         must match it or the pre-hydration paint flashes the wrong hue. Aurora
         gradient stop 3 (#8A82E8) is the family member, but --accent is used
         both as text on white and as a fill under white text and that stop only
         reaches ~3:1 in the first role. See the $note in the token JSON. */
      --accent: #5B52C4;
      --amber: #b36b00;
      /* Aurora's secondary. Added for icon colour roles. The agent-speaking
         mic used to carry a loose #96712f gold that belonged to no family. */
      --secondary: var(--sponica-light-secondary);
      --rose: var(--sponica-light-error);
      /* Matches Android (MaterialTheme.colorScheme.error at 78% alpha) and
         iOS (Color.sponicaError at 78% opacity) ,  derive from --rose instead
         of a hand-picked hex that can drift from the token. Relative-color
         syntax (not color-mix with transparent, which can shift the result
         toward black) so this is exactly --rose's own RGB at 78% alpha. */
      --rose-soft-dark: rgb(from var(--rose) r g b / 0.78);
      /* Deliberately NOT var(--sponica-light-primary): Aurora's spine has no
         success family, and aliasing it to the brand green makes a success
         chip chromatically identical to a primary action. Kept as its own
         value until the spine names a status family (Phase 5). */
      --success: #2e7d32;
      --warning: #b36b00;
      --danger: var(--sponica-light-error);
      --info: var(--accent);
      /* Ring colour only; the Aurora bloom around it is --glow-focus below. */
      --focus: var(--sponica-light-primary);
      --bg: var(--sponica-light-background);
      --surface: var(--sponica-light-surface);
      --surface-2: color-mix(in srgb, var(--sponica-light-surface-variant) 55%, var(--sponica-light-surface));
      /* Opaque panel behind a run of fields (profile sections). Full
         surface-variant, not the 55% --surface-2 mix, so it separates from the
         page ground AND from the near-white field boxes sitting on it. */
      --panel: var(--sponica-light-surface-variant);
      --surface-3: var(--sponica-light-surface-variant);
      --line: var(--sponica-light-outline);
      /* Mixed toward the OUTLINE, not the surface: at 38% this read lighter
         than the hex it replaced, which weakened every secondary control edge.
         Non-text contrast (WCAG 1.4.11) wants 3:1 on control boundaries. */
      --line-strong: color-mix(in srgb, var(--sponica-light-outline) 55%, var(--sponica-light-surface));
      --text: var(--sponica-light-on-surface);
      --text-soft: color-mix(in srgb, var(--sponica-light-on-surface) 82%, var(--sponica-light-on-surface-variant));
      --text-muted: var(--sponica-light-on-surface-variant);

      /* --- Aurora families (Phase 3). The generated token file authors these
         per mode; the app is light-only (color-scheme: light, no dark block),
         so we alias the light set. A gradient is never a colour: it may only
         land on `background`/`background-image`, never on `color`. --------- */
      --gradient-accent: var(--sponica-light-gradient-accent);
      --gradient-accent-hover: var(--sponica-light-gradient-accent-hover);
      --gradient-accent-pressed: var(--sponica-light-gradient-accent-pressed);
      --gradient-display-text: var(--sponica-light-gradient-display-text);
      --aurora-wash: var(--sponica-light-aurora-wash);
      /* Ink that pairs with the accent GRADIENT is dark in both modes on
         purpose ,  the gradient is bright at every stop, so white fails on it.
         Ink that pairs with the SOLID --primary fill stays white. */
      --on-gradient: var(--sponica-light-on-primary);
      --on-primary-solid: var(--sponica-light-on-primary-solid);
      --glass-fill: var(--sponica-light-glass-fill);
      --glass-fill-strong: var(--sponica-light-glass-fill-strong);
      --glass-hairline: var(--sponica-light-glass-hairline);
      --glass-highlight: var(--sponica-light-glass-highlight);
      --glass-blur: var(--sponica-light-glass-blur);
      /* The voice ring's rim. The spine ships the accent ramp as three stops
         plus the linear gradients built from them; a ring needs the same three
         hues swept around a circle, so it is composed here from the same stop
         tokens rather than re-typing any colour. Repeating stop 1 at 100%
         closes the sweep without a seam. */
      --gradient-ring: conic-gradient(
        from 200deg,
        var(--sponica-light-gradient-stop-1) 0%,
        var(--sponica-light-gradient-stop-2) 38%,
        var(--sponica-light-gradient-stop-3) 68%,
        var(--sponica-light-gradient-stop-1) 100%);
      --glow-pulse-rest: var(--sponica-light-glow-pulse-rest);
      --glow-pulse-peak: var(--sponica-light-glow-pulse-peak);
      --glow-focus: var(--sponica-light-glow-focus);
      --glow-focus-error: var(--sponica-light-glow-focus-error);
      --glow-primary: var(--sponica-light-glow-primary);
      --glow-primary-hover: var(--sponica-light-glow-primary-hover);
      --elevation-raised: var(--sponica-light-elevation-raised);
      --elevation-card: var(--sponica-light-elevation-card);
      /* Motion. Every duration/easing below comes from the token file so the
         three clients share one motion language. */
      --motion-fast: var(--sponica-motion-duration-fast-ms);
      --motion-base: var(--sponica-motion-duration-base-ms);
      --motion-slow: var(--sponica-motion-duration-slow-ms);
      --motion-drift: var(--sponica-motion-duration-drift-ms);
      --ease-standard: var(--sponica-motion-easing-standard);
      --ease-decelerate: var(--sponica-motion-easing-decelerate);
      --spotka-wordmark-bg: #064329;
      --spotka-wordmark-fg: #fff2d9;
      --shadow: var(--sponica-elevation-card);
      /* 53px fits the 28px mark + centered gap + 9px build label while staying
         below the former 56px sticky-header height. */
      --topbar-h: 53px;
      --bottom-h: 74px;
      /* Type, weight, line-height and the shape radii come from
         /app/sponica-design-tokens.css (generated from the shared token JSON).
         They used to be duplicated here, which meant the token file could not
         actually change the look. The runtime brand layer still overrides both
         via element.style in applyRuntimeBrandPresentation(). */
      --radius: var(--sponica-shape-control-radius-px, 10px);
    }
    html {
      min-height: 100%;
      margin: 0;
      font-size: 16px;
      /* Root leftover deltaX otherwise becomes Chrome history-Back
         (Settings → Library; task #1368). Nested contain on .sp2-rail
         does not stop that (#1304) ,  the Settings page is the scroller. */
      overscroll-behavior-x: none;
    }
    body {
      min-height: 100%;
      margin: 0;
      background: var(--bg);
      color: var(--text);
      font-family: var(--brand-font-family);
      font-size: 16px;
      font-weight: var(--brand-weight-regular);
      line-height: var(--brand-line-height-body);
      -webkit-font-smoothing: antialiased;
    }
    body { min-height: 100vh; min-height: 100dvh; overflow-x: hidden; }
    button, input, select, textarea { font: inherit; }
    button { -webkit-tap-highlight-color: transparent; }
    :focus-visible {
      outline: 2px solid var(--focus);
      outline-offset: 2px;
      box-shadow: var(--glow-focus);
    }

    /* ── Temporary private-beta entrance ───────────────────────────────────
       Deliberately shares the live Landa/LandB acquisition system: Newsreader
       display type, DM Sans UI, paper/card greens, compact proof cards and the
       same rounded interaction language. App entitlement remains server-owned. */
    /* The same button as a link (the ambassador card's WhatsApp chat). */
    /* Same shape as .beta-feature-summary above it ,  divider, left aligned,
       serif heading ,  so the completed card reads as one card and not as a
       promo bolted onto the end of it. */
    /* Its own class, never `.beta-ambassador p`: a descendant selector scores
       0,1,1 and would outrank .beta-inline-error and .beta-ambassador-done
       (0,1,0 each), silently painting the error and the confirmation in body
       grey. Verified in the browser ,  both had lost their colour. */
    /* Same row shape as .beta-step-actions: the commitment on the left, the
       way to read about it first on the right. */
    /* Same metrics as .beta-ambassador-lead it replaces, or the block changes
       scale when the offer is taken ,  it inherited the card's larger size once
       the descendant `p` rule went away, and jumped on mobile. */
    /* A stored identity is only a presentation hint, never entitlement. While
       the server checks it, show a neutral loader instead of flashing launch
       marketing at a returning approved member. */
    /* Shared icon rules. Web draws inline SVG, Android draws Material Icons and
       iOS draws SF Symbols, but all three read the same size scale and stroke
       weight out of the token spine ,  see docs/architecture/spotka-icon-system.md
       for the concept→glyph mapping table that keeps the three libraries from
       drifting apart. Colour is never set on the glyph: every icon inherits
       `currentColor` from a token colour role on its container. */
    /* Tagline stays optically centred under the wordmark; the language flag
       sits to its right, flush with the card's right edge. The 1fr/auto/auto
       grid keeps the tagline centred regardless of the flag's width. */
    /* Subdued machine-readable stamp under the large wordmark (matches header). */
    /* The build stamp consumes the old wordmark-to-form whitespace rather than
       making the login composition taller. */
    /* Matches the APK wordmark surface: a rounded bitmap, not text. */
    /* Dark backdrop for the two OAuth buttons ,  both render as Apple/Google's
       official LIGHT (white) button styles, which need a dark surface behind
       them for contrast. Apple's own guideline is a white button on dark
       backgrounds (black button on light) ,  see developer.apple.com HIG. */
    #gsi-container { min-height: 44px; display: flex; justify-content: center; width: 100%; }
    /* Redirect-flow Google button (staging previews only ,  see
       shared/google-redirect-sign-in.js). Off-token colours on purpose: these
       are Google's mandated sign-in button values (white surface, #dadce0
       hairline, #1f1f1f label) and must not drift with our theme, the same
       reason .btn.oauth-apple below pins Apple's white-on-dark HIG values.
       The 44px floor and 8px icon gap are copied from that rule so the two
       buttons in the row are the same object; 44px is also the minimum touch
       target both platforms specify. */
    /* align-content:start ,  a .form-row sibling (Gender radios) can stretch
       this box; packing tracks at the start keeps the input under its label
       instead of sitting in a taller empty track (task #1774). */
    /* min-width:0 keeps the picture from widening its grid track ('.field' is a grid). */
    /* Removable thumbnail strip ,  one tile per attached image (max 5). Wraps on
       phone widths rather than scrolling sideways. */
    /* Field label on the left, its trailing control (checkbox / picker) pinned
       to the right of the same row. min-width:0 keeps the label from pushing
       the row wider than the '.field' grid track. */
    /* Task #1631 ,  same on/off family as Settings `.toggle` (`--primary`
       green). `--accent` is the gold/purple fill token and painted the
       Who-can-see-you checkboxes a different hue from every other control. */
    /* Task #1596 ,  a switch whose precondition is not met reads as unavailable,
       not broken; the note underneath says what is missing. */
    /* The compact action on a Settings row. Was an inline style repeated on
       each such control, which meant a new one (task #1181's Email us) had to
       copy four literals by hand to sit level with the Set/Change button next
       to it. One class, so the row's controls cannot drift apart. */
    /* Task #1572 ,  the auto-save confirmation for one privacy switch, next to
       the switch itself. It fades IN and then stays put until the JS hides it
       four seconds later; it must not fade back out on its own, or a member
       glancing over at second three sees an empty row and cannot tell whether
       a privacy write landed. */
    @keyframes profile-toggle-saved-in { from { opacity: 0; } to { opacity: 1; } }
    input, textarea, select {
      width: 100%;
      min-width: 0;
      border: 1px solid var(--line-strong);
      border-radius: var(--radius);
      background: var(--surface);
      color: var(--text);
      /* DESIGN.md §9: inputs and select rows are the `md` control size, the
         same as .btn. They used to render ~47px against a 44px button, so a
         field and the button under (or beside) it never lined up, and the two
         did not share a horizontal inset either. Same tokens as .btn on both
         axes ,  textarea overrides min-height below to stay multi-line. */
      min-height: var(--sponica-control-md-height-px);
      padding: var(--sponica-spacing-step2-px) var(--sponica-control-md-h-pad-px);
      outline: none;
    }
    textarea { min-height: 88px; resize: vertical; }
    input:focus, textarea:focus, select:focus {
      border-color: var(--primary);
      /* The Aurora focus bloom, same token the buttons and :focus-visible use,
         so every focused control in the app glows identically. */
      box-shadow: var(--glow-focus);
    }
    /* The global :focus-visible bloom is the same specificity as .btn but is
       declared earlier, so .btn's own box-shadow would win and a focused
       button would never show it. Compose both here instead. */
    /* These three use the `background` shorthand, which already clears the
       gradient image inherited from .btn ,  but not its glow, so they reset that
       explicitly. Only the primary action carries the Aurora bloom. */
    /* `.btn.secondary` is the same specificity as `.btn:disabled` and sits
       after it, so an empty-disabled secondary control kept the enabled fill
       (Feedback Send to Fern, task #1659). Beat both that rule and :hover. */
    /* A destructive confirm is still a button. Borderless red text read as a
       caption next to the outlined "Go back" it sits beside, so the safe
       option looked like the only pressable one. Rose-tinted outline over the
       card surface: unmistakably a control, still visibly not the primary. */
    /* An inline field call-out: a value the app cannot use. Reads as a
       warning, not as another muted hint next to it (task #1310). */

    /* ── Tap feedback ────────────────────────────────────────────────────────
       One behaviour shared with Android (SponicaTapFeedback.kt) and iOS
       (SponicaTapFeedback.swift): every pressable thing visibly depresses the
       instant it is touched, and only an action that is still working after the
       shared grace period additionally greys and grows a "…".

       The press half is pure CSS and always on ,  it cannot delay anything, so
       an instant action still feels instant. `button` carries
       -webkit-tap-highlight-color: transparent (above), which removed the
       platform's own touch highlight and left bare buttons with no press
       feedback at all; this restores it in the app's own visual language.

       Declared after .btn so the transition list wins over .btn's box-shadow-only
       one at equal specificity, instead of being silently dropped. */
    /* Applied by button-feedback.mjs, never by hand. pointer-events:none is the
       point as much as the greying: it is what stops a second submit while the
       first is still in flight. */

    /* Quiet text links on the email auth panel (password/code switch). */
    #authCode {
      letter-spacing: 0.28em;
      font-size: 1.15rem;
      font-weight: 700;
      text-align: center;
    }
    /* One-question onboarding slides followed by the Fern interview handoff.
       Metrics here are deliberately literal (design-metrics baseline): this is
       the port of the sequence that was visually reviewed on spotka.cc
       (2026-08-07/08); re-gridding the reviewed values onto tokens belongs to
       the visual-overhaul pass, not to this rescue. */
    /* Full-band opaque pointer trap (task #1387): Home event cards must not
       receive the leftover click if this overlay unmounts mid-gesture.
       Tokens only ,  no new spacing literals (design-metrics ratchet). */
    /* Every text block on a slide centers ,  kicker, title, helper copy, and
       any paragraph a future slide adds. That breadth is deliberate, not an
       over-broad selector: Android's column is CenterHorizontally and even
       its error text is TextAlign.Center + fillMaxWidth, and iOS's
       slideHeading matches, so a new web paragraph should center too rather
       than be the one left-aligned thing on the screen. Web used to center
       only the three hero slides, so the same member saw a different layout
       on the web app than on their phone. Inputs and option rows keep their
       left-aligned labels on all three clients, so long lists stay scannable. */
    /* Slot matches .pre-interview-avatar's 128px circle ,  the member's own
       photo carries the same visual weight Fern's does one slide later. */
    /* Meet Fern centers around her portrait ,  the one circle-hero slide left
       now that the affirmation slide is gone (2026-09-19). */
    /* Full Screen Event view: the event takes over the whole screen. The top
       bar keeps the home-linked Spotka wordmark, language switcher and close
       button; title, messages, account and bottom navigation stay out. */
    /* Match the authenticated content column: 720px on desktop, then the same
       16px/12px horizontal gutters as .content on narrower viewports. The bar
       background remains full-bleed while its controls align with the cards. */
    /* Brand mark + always-visible build stamp. Column width follows the mark
       (wordmark or app icon); the stamp sits under it and does not grow chrome. */
    /* The mark ends at 40.5px in the 53px bar. A 1.75px gap centers the 9px
       version line inside the remaining 12.5px before the section divider. */
    /* Title inset whenever the brand column is present (mark and/or stamp). */
    /* Language / Messages / Account stay visible at every density; only their
       footprint shrinks so a long title + 3 actions still fit at 360px. */
    /* Compact neutral "Get the app" pill ,  phone icon + short label on wider
       headers; icon-only when the top bar is tight so it never reads Android-only. */
    /* Keep the download pill visually separate from the circular language
       control. The compact header intentionally removes the generic action
       gap, so this relationship needs its own stable spacing. */
    /* Task #1748: Events pins `.invitation-sticky-chrome` under the topbar.
       Profile is the first avatar-menu row, so that strip can sit on its hit
       box (Settings is lower and still worked). While the menu is open, let
       clicks pass through the page body to the dropdown. */
    /* Autosave feedback: a check that fades in, holds a beat, and fades away.
       `forwards` keeps it invisible until the JS clears the node. */
    @keyframes save-tick-fade {
      0% { opacity: 0; transform: scale(0.7); }
      12% { opacity: 1; transform: scale(1); }
      70% { opacity: 1; }
      100% { opacity: 0; }
    }
    /* The legend hangs above the border. Zeroing this margin tucked the first
       heading ,  Messages "Your AI Agent" ,  under the sticky topbar (#1757).
       A spacing step at least that overhang keeps the label fully below the
       bar, the way Library index rows already sit. */
    /* A profile group is one panel under its head, so the head is the
       only thing naming it ,  no floating label notch on top as well. The panel
       is an opaque tint rather than a hairline outline: at six of them stacked,
       outlines read as a wireframe, and the fill also gives the white field
       boxes inside something to sit on. */
    /* ---- Profile cards (Phase 2 of devtasks/spotka-profile-redesign) ----
       The profile used to be one long open form: forty controls, all editable
       at once, with no way to see at a glance what was actually answered. A
       card shows the ANSWER, quietly labelled, and editing happens in a sheet.
       An unanswered card says what it would ask instead of showing a blank
       box, so "what have I filled in" is readable from the stack itself. */
    /* An unanswered card reads as an invitation, not as data. */
    /* Chips inside a card are a READ-ONLY summary, not controls. They keep
       their own compact size ,  the 44px touch floor below is for chips you can
       actually press ,  and a soft fill, because a white outlined pill on a
       white card is nearly invisible. */
    /* Secondary (terracotta) is reserved for Next up. The words travel with
       the colour so the marker is never colour-only. */
    /* One framed block per registry surface. The 7 px bar matches the
       strength track in height and fill; the count in words travels with it
       so colour is never meaning-alone. */

    /* The editor sheet. Rises from the bottom on a phone, which is where the
       thumb is; centres on a wide screen, where a bottom sheet reads as a
       mistake. */
    /* Compound selector on purpose: .modal-backdrop is defined further down
       the sheet and would otherwise win on source order and re-centre this. */
    /* Quiet divider-label for a run of fields inside a group. */
    /* A subhead's rule means "everything under this line is Allergies". The
       folded rows that close a section have no subhead of their own, so they
       were reading as part of whatever the last subhead was. Detach them. */
    /* Collapsed blocks: social links, private contact details, the likeness
       description. Closed they are one tappable row; open they are ordinary
       fields indented under it. */
    /* Inside a tinted panel the folded rows need the opposite treatment to
       stand out: an opaque card fill, not another outline. */

    /* Collapsible card sections (About you / What you like doing / Who you
       want to meet): the summary row itself says what's still missing, so
       nothing needs opening just to see what's outstanding. */
    /* Interview summary card at the top of Profile: compact by default, with
       Extend / Redo always reachable underneath. */
    /* Fern-led Home entry cards: larger portrait so the conversation lead is obvious */
    /* Messages inbox preview (task #1470). A pasted URL is one unbreakable
       token, so with the default overflow-wrap it left the row entirely; and
       nothing capped the line count, so a long message set the row's height.
       Two lines, ellipsed, and long tokens allowed to break. */
    /* The row itself is the button that opens the conversation, so a URL in the
       preview is marked as a link, not made separately clickable ,  nesting
       interactives is invalid HTML (same rule as the Home card heart). */
    /* Known-profile facts reflected back to the member (Home card, signal-gap, selection reasons). */
    /* Messages inbox row: numeric unread ownership (parity with nav chrome badge). */
    /* Profile Strength ,  compact fern-row progress on the home interview card */
    /* Profile Strength panel ,  the top of the profile screen (Phase 1 of
       devtasks/spotka-profile-redesign). Same meter Home paints, moved next to
       the answers that move it. Spacing routed through the token spine, and
       type reuses the shell's existing classes rather than minting new sizes:
       the panel must not become a second, private scale. */
    /* A real button, not a list row: the whole width is the target, gaps
       included. Phase 1 shipped these as inert text. */
    /* "7 of 13" beside a section heading. Moves on every answer, which the
       headline percent currently does not (tasks #1537 / #632). */
    /* Bug #372: stack strength + cue + desc full-width under the Fern lead row
       so cue text left-aligns into the portrait gutter and has more room. */
    @keyframes aurora-drift {
      from { background-position: 0% 0%, 100% 0%, 50% 100%; }
      to   { background-position: 12% 6%, 88% 10%, 58% 92%; }
    }
    .calendar-segment-tab.active {
      background: var(--primary-soft);
      color: var(--primary-dark);
      box-shadow: var(--shadow);
    }
    /* Same inset as .calendar-sync-section / .calendar-preferences-head.
       The Reminders card zeroes its own padding, so a caption without this
       sits flush on the outer edge (task #1618). Task #1828 moves the line
       under Push; Email still needs the row divider the adjacent-setting
       rule would have drawn. */
    /* Give-feedback sheet (task #1673): attendance chips + textarea + stars +
       meet-again rows routinely exceed an 800px-tall viewport. Without a height
       cap the card spills past the backdrop; wheel then scrolls Calendar behind
       it and Send stays unreachable. Same contract as .attendees-sheet. */
    /* The status chip is also the control that changes the answer it states,
       so on a row you can leave it takes a caret and a pointer. It keeps the
       chip's own colours ,  it reads as your answer first, a menu second. */
    /* One long item ("Cancel your attendance") ,  let it set the width rather
       than wrap to two lines inside a 185px menu. */
    /* Connected state replaces the Connect button, so it must read as status,
       not as another thing to press. */
    /* Task #1149: Add to calendar sits at the bottom of a glass `.card`.
       Chrome clips overflowing descendants of backdrop-filter even when
       overflow is visible, so an absolutely-positioned overlay leaves
       Outlook / Office 365 / Yahoo / Apple Calendar unreachable. Keep the
       open menu in normal flow so the card grows around every provider.
       Task #1431: that in-flow menu must sit *below* the wrapping pill
       row. Putting it inside `.calendar-expanded-actions` with
       flex: 1 1 100% consumed the first flex line and hid Map. */
    /* Staging-only marker on Home. Full soft fill, no left-edge bar.
       Solid fallbacks stay first for older embedded WebViews without
       color-mix(), same as the error-card pattern above. */
    /* Keep the visible word inside its tab. The span is a grid item whose
       default min-width:auto is the full word, so on a phone it paints into
       the next tab (task #2017). Direct child only ,  the Fern fallback lives
       inside .nav-pill. */
    .nav-item.active { color: var(--primary); }
    .nav-item.active .nav-pill { background: var(--primary-soft); }
    /* Calendar's badge in Home v2: a quiet dot, no count. Your own plans are
       not unread mail ,  the red counted badge is for a queue someone else
       filled (messages). Brand teal rather than alarm red. The 2px surface
       ring keeps the dot legible on the pill; the badge itself hides while
       Calendar is open and after those plans have been read (#1402).
       The count still reaches screen readers via the nav button's aria-label. */
    /* Unread badge on the top-bar messages icon (no .nav-pill wrapper). */
    /* Round account button in the top-right. */

    /* Profile hero: centred photo, name, one fact line, bio, then the two
       real actions. Replaces the old left-aligned .profile-head row for the
       Profile screen only ,  .profile-head/.profile-preview-btn above stay for
       any other caller. */ /* component size, not spacing ,  see design-metrics-baseline.json */

    /* Photo strip under the hero: room to see and add more without opening
       the manage-photos sheet first. Every tile opens that same sheet. */ /* no padding-bottom needed ,  scrollbar hidden by default on these platforms */
    /* Editable avatar: pencil sits inside the circle (half-transparent). Replaces the old separate "Change photo" button. */
    @keyframes profile-avatar-spin { to { transform: rotate(360deg); } }
    /* Profile photo grid: 3-up squares. Every cell ,  photo or "Add photo" ,  is
       the same square, because the per-photo invite flag rides ON the tile
       instead of a caption row that made photo cells taller than the add cell. */
    /* align-items:start ,  two-column rows must not stretch a short field
       (Nationality) to the height of a tall sibling (Gender radios). Stretch
       is what detached the Nationality input from its label (task #1774). */
    /* Same row as .prefix-field, columns the other way round: the wish field
       takes free text up to 200 chars, so the INPUT gets the space and the
       button hugs its label. .prefix-field's auto/1fr suits a short token
       (a language name) and squeezes a sentence into a stub. */
    /* A chip is drawn from 12px text and 7px of padding, so the pill is about
       30px tall. That is under the 44px minimum touch target, and a tap a few
       pixels above or below the pill did nothing. Grow the pill itself on
       touch devices; a mouse keeps the compact row. Matches the 44pt floor on
       iOS and Material's 48dp on Android. */
    /* Proposal sheet: guest-adjustable meeting length (1h/1.5h/2h/3h). */
    /* Acquisition "not for me" chip state (allow_reject cycle): struck-through,
       never a filled/selected look, so picked vs rejected read at a glance. */
    /* Idea card "View guide" ,  chip-styled link out to the activity guide page. */
    /* Matches interest-unlock card: per-chip count of hidden events it surfaces. */
    /* Profile Acquisition + meet-prefs: compact chip density (bug #371). Keep
       chips ≥ ~28px tall for taps; tighten only inter-chip/card gaps. */
    /* Bug #380: meet-prefs Save is a lone full-width bar ,  keep compact 36px
       so it does not read as an oversized chip-card footer. Home / Ideas
       `.acquire-actions` Save sits beside sibling pills and must keep the
       shared `.btn` md size (task #1589); a Save-only 36px pin left it
       shorter and vertically misaligned. */
    /* Post-Fern feedback opens as a bottom sheet: rating first, with the
       existing questionnaire progressively disclosed after a star choice. */
    @keyframes ifb-listening-pulse { 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--rose) 15%, transparent); } }
    .segment.active { background: var(--primary-soft); color: var(--primary-dark); border-color: rgba(31, 107, 58, 0.28); }
    /* Task #1350 ,  native Chrome paints an unstyled blue range thumb. Tint
       this slider with the same green as the Noise Suppression toggle. */

    /* ── Conversation thread (Messenger/IG-style column) ─────────────────── */
    /* Delayed fade-in avoids a flash on fast cache/network responses. */
    @keyframes thread-loading-reveal {
      to { opacity: 1; }
    }
    @keyframes thread-spin { to { transform: rotate(360deg); } }
    @keyframes thread-skeleton-shimmer {
      0% { background-position: 100% 0; }
      100% { background-position: -100% 0; }
    }
    /* Fern suggested_route chip ,  sits under the bubble, indented past the avatar. */
    /* Attachment + link-preview inside a chat bubble. */
    /* Full-screen message attachment viewer (dark, aspect-preserving). */
    /* Rich Spotka invite card under a message bubble. Sized like a bubble so a
       full-size hero image can never blow out the thread width/height. */
    /* Pending attachment preview above the composer. */
    /* Shared composer grid (text interview + messages). Thread dock styles
       below only restyle the messaging pill; interview keeps the plain form. */
    /* Tappable quick-reply answer chips under the latest host question. */
    /* Multi-select: selected chips need a stronger fill than the soft suggestion default. */
    /* Done is an action, not a toggled answer ,  same primary fill as a
       selected chip would read as "just another interest"; bold weight +
       a heavier border make it read as the affirmative button it is. */
    .interview-tab-row .segment.active {
      background: transparent;
      color: var(--primary);
      border-bottom: 3px solid var(--primary);
    }
    @keyframes sponica-progress {
      0% { transform: translateX(-100%); }
      100% { transform: translateX(560%); }
    }
    @keyframes sponica-spin {
      to { transform: rotate(360deg); }
    }

    /* Ring + dotted thinking-orb halo. Orb canvas sits behind Fern's portrait;
       ring classes remain for solid disc / avatar / spinner affordances. */
    /* Bleeds 14px past the wrap on every side: the outer ring overshoots the
       logical orb size at bloom peaks (radius x radialPulse + audioBoost + dot
       radius) and the canvas edge was shaving the outermost dots. Geometry
       still uses the logical size, so the halo does not move.
       Width and height are load-bearing: `inset` does not stretch a canvas,
       whose auto size is its bitmap, so without them the box grew with every
       width = css x devicePixelRatio (476 -> 952 -> ... -> 4096 at dpr 2). */
    /* Before the room is up the rim is quiet: no accent sweep, no bloom. */
    /* Rotating Fern host avatar ,  fills the mic ring when one is loaded. */
    /* Never a spinner over Fern's face; the halo shows connecting. */
    @keyframes spin { to { transform: rotate(360deg); } }
    /* Listening/thinking waves are now owned by the dotted orb canvas. */
    /* The ring breathes while the room is live. Opt-in by construction: it
       lives inside a no-preference query, so prefers-reduced-motion never
       starts it and the rest state above is what a reduced-motion user sees. */
    @keyframes ring-breathe {
      0%, 100% { box-shadow: var(--glow-pulse-rest); }
      50%      { box-shadow: var(--glow-pulse-peak); }
    }
    @keyframes think-bounce {
      0%, 100% { opacity: 0.3; transform: translateY(0); }
      50% { opacity: 1; transform: translateY(-2px); }
    }
    @keyframes rec-blink {
      0%, 100% { opacity: 1; transform: scale(1); }
      50% { opacity: 0.35; transform: scale(0.85); }
    }
    /* ── Fern voice interview (redesign approved 2026-09-15; spec:
       design/fern-interview-redesign/IMPLEMENTATION.md). One screen between
       the app's top bar and tab bar: the portrait follows the height that is
       left and steps back while the question card shows, so nothing scrolls.
       Invitation assist and gatherings keep the plain card layout. ── */
    @keyframes voice-eq { 0%, 100% { transform: scaleY(0.35); } 50% { transform: scaleY(1); } }
    /* Fern's language: one small button with the code; every language Fern can
       switch a live call to sits in its menu, each in its own name. */
    /* The question Fern asked, with the answers to tap under it. */
    /* Done is an action, not one more option: under the answers, past a line,
       squarer corners and an arrow, next to a count of what it will send. */
    /* The Events return sits beside Talk again; wrap onto its own line rather
       than squeezing both when the viewport is narrow. */
    #assistReturnBtn:not([hidden]) { min-width: min(100%, 220px); }
    /* :not([hidden]) so these layout rules never win over the UA [hidden]
       display:none (ID/class specificity would otherwise keep them shown). */
    #resumeVoiceBtn:not([hidden]) { display: inline-flex; align-items: center; gap: 7px; }
    #resumeVoiceBtn svg { width: var(--sponica-icon-size-small-px); height: var(--sponica-icon-size-small-px); }
    /* Separate the just-ended thank-you / feedback cards from the action row
       above (Switch to Text was flush against the card) and gap the cards. */
    /* Attendees + invite hub: a tall narrow column, never a wide short one. The
       roster plus three invite actions plus the email list is more than one
       viewport on a short screen, so the card scrolls inside itself instead of
       spilling past the backdrop. */
    /* Music tab */
    .music-btn.like.active { background: var(--primary-softer); border-color: var(--accent); color: var(--accent); }
    .music-btn.dislike.active { background: #fdecf0; border-color: var(--rose); color: var(--rose); }
    /* ── Spotka Home v2 (sp2-*) ,  30 Jul redesign, gated by homeEventsRail ──
       Green + blush, white cards, quiet overlays. The rail keeps its left
       margin via scroll-padding (mandatory snap eats it otherwise). */
    /* min-width:0 is what actually lets these shrink: a column flex item
       defaults to min-width:auto, so the chip row's max-content width was
       pushing the whole home wider than the phone instead of wrapping. */
    /* `.app-shell` is a grid and `.content` is a grid item with the default
       min-width:auto, so the events rail's min-content width propagated all the
       way up and stretched the whole home to 586px inside a 420px phone ,  which
       is why the profile chips ran off screen instead of wrapping. `contain`
       stops my subtree contributing an intrinsic width, fixing it here rather
       than editing `.content`, which every other screen shares. */
    .sp2{display:flex;flex-direction:column;gap:14px;min-width:0;max-width:100%;
      contain:inline-size}
    /* Home v2 (devtask §20.1) widens the section gap; other .sp2-* surfaces
       (the Ideas-tab acquisition card, notification-style lists) keep the
       original 14px so this does not leak past Home. */
    .screen-home-v2 .sp2{gap:28px}
    /* The payoff screen (onboarding guide §8.2). Calm and centred: it is the
       first thing a member sees after answering everything, so it reads as an
       arrival rather than another form. The CTA is pinned so leaving is always
       within reach ,  nobody is held here by a spinner. */
    /* Named work, never a percentage: reached lines settle, the running one
       stays full strength, the rest wait their turn. */
    /* Fern's sentence ,  the actual "what Fern heard" ,  and the one quiet line
       shown while she is still writing it (bounded, see onboarding-payoff.mjs). */
    @keyframes payoff-pending-pulse{0%,100%{opacity:.55}50%{opacity:1}}
    /* The photo ask: a centered circle that IS the button. The member's
       picture if we have one, a plain placeholder if not, a camera badge so it
       reads as tappable, and one short label under it. */
    .sp2 > *{min-width:0;max-width:100%}
    /* ── Home redesign (Sonia, 14–15 Sep 2026; devtask §20) ───────────────
       The prototype's look, carried over as drawn: a warm ground, white cards
       with a soft shadow, one accent (forest), small monospace labels, photos
       that carry the colour. It replaces the July "green + blush" look on
       purpose ,  the build does not have to match the app's older styling. */
    /* rows: white cards; a second row sits close to the first */
    .sp2 > .sp2-slot + .sp2-slot{margin-top:-18px}
    /* tint ONLY when a person is waiting on the member (the settled colour rule) */
    /* the eyebrow: a small monospace label that says what the card is */
    /* the line under the title is always ONE line: it ellipsises, never grows the card */
    /* every row ends in a labelled pill; three tiers, one shape. The visual is
       33px tall; the tap area is the whole row anyway (the pill is inert). */
    /* a person behind a row: their face, the row's glyph as a small badge.
       No overflow:hidden on the face ,  the badge overhangs its corner; the
       photo rounds itself instead. */
    /* the Fern row: the way back into onboarding, with its own progress line */
    /* section heading */
    .sp2-h{font-size:22px;font-weight:600;letter-spacing:-.035em;margin:0 0 14px;color:var(--sp2-ink)}
    /* events rail: photography carries it; the text sits under the photo, no
       card chrome. The rail bleeds to the screen edge, so its negative margin
       equals the shell's real horizontal padding at that width. Proximity
       snap, not mandatory (#1141). */
    .sp2-rail{display:flex;gap:14px;overflow-x:auto;scroll-snap-type:x proximity;
      margin:0 -16px;padding:0 16px 6px;scroll-padding:0 16px;scrollbar-width:none;
      overscroll-behavior-x:contain}
    .sp2-rail::-webkit-scrollbar{display:none}
    /* prev/next stay for mouse users on a wide screen (there is no swipe
       there); on touch and at phone width the cut-off card is the cue. */
    @media (max-width: 430px){
      .sp2-rail{margin:0 -12px;padding:0 12px 6px;scroll-padding:0 12px}
    }
    /* The card and its heart are SIBLINGS, not nested buttons. */
    .sp2-ev-wrap{position:relative;flex:none;width:230px;scroll-snap-align:start}
    .sp2-ev{display:block;width:100%;background:none;border:0;font:inherit;color:inherit;text-align:left;
      border-radius:0;overflow:visible;cursor:pointer;padding:0;box-shadow:none}
    .sp2-hero{display:block;aspect-ratio:4/3;border-radius:var(--sp2-r);overflow:hidden;
      background:rgba(20,26,18,.06);position:relative}
    .sp2-hero img{width:100%;height:100%;object-fit:cover;display:block;-webkit-user-drag:none}
    /* quieter than a label competing with the photo (Sonia, 14 Sep) */
    .sp2-seats{position:absolute;left:10px;bottom:10px;background:rgba(255,255,255,.68);
      backdrop-filter:blur(5px);color:var(--sp2-soft);font-size:10.5px;font-weight:500;
      padding:4px 9px;border-radius:999px;white-space:nowrap}
    /* the member's own save ,  a real <button> laid over the hero. 34px drawn;
       a transparent 44px frame around it is the accessible tap target. */
    .sp2-title{display:block;font-size:16px;font-weight:600;letter-spacing:-.02em;line-height:1.3;margin:12px 2px 0;
      white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .sp2-when{display:block;font-family:var(--sp2-mono);font-size:10.5px;font-weight:500;letter-spacing:.12em;
      text-transform:uppercase;line-height:1.3;color:var(--sp2-faint);margin:5px 2px 0}
    /* the ranker's one-line "why we chose this" ,  matched cards only */
    /* the door: flex:none so the rail overflows and can scroll (#1141) */
    /* bring people together ,  one card */
    /* three small idea photos, overlapping in a straight row (Sonia, 14 Sep) */
    /* the profile card: a tinted card of its own (Sonia, 14 Sep), so it does
       not read as another needs-you row; its chips/track/pill go white. */
    /* the acquisition block loses its own card chrome inside ours */
    /* Ideas-tab copy of the same block keeps the older row style */

    /* Welcome-screen language dropdown: visible and labeled (not a bare flag),
       assumed English until the member picks otherwise. */
    /* Pre-interview basics: single-select chip groups (district, gender). */


html,body{margin:0;min-width:0;background:#f5f7f0;overflow-x:hidden}body{padding:32px 14px 18px;font-family:Arial,sans-serif;color:#203b2c}.preview-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}.preview-head b{font-size:20px}.preview-head img{width:32px;height:32px;border-radius:50%}.sp2{padding:0;max-width:none}.sp2-events{margin:0 0 22px}.sp2-rail{margin:0 -14px;padding:0 14px 6px}.sp2-ev-wrap{width:230px}
body{--sp2-ground:#F4F2EE;--sp2-card:#fff;--sp2-ink:#151513;--sp2-soft:#6D6962;
      --sp2-faint:#A29D94;--sp2-fill:#EFECE7;--sp2-forest:#1B4332;--sp2-tint:#E5EFE7;--sp2-r:24px;
      --sp2-shadow:0 1px 2px rgba(21,21,19,.03),0 6px 20px rgba(21,21,19,.045);
      --sp2-mono:"IBM Plex Mono",ui-monospace,Menlo,monospace;background:var(--sp2-ground);color:var(--sp2-ink)}

body{background:var(--sp2-ground,#F4F2EE);color:#151513}
.preview-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;padding:0 2px 10px;border-bottom:1px solid #e6e3dc}.preview-brand{display:inline-flex;align-items:center;gap:7px;color:#1b4332;font:600 17px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;letter-spacing:-.025em}.preview-brand img{display:block;width:25px;height:25px;object-fit:contain}.preview-actions{display:flex;align-items:center;gap:8px}.preview-icon{display:grid;place-items:center;width:32px;height:32px;border:0;border-radius:50%;background:#fff;color:#34332f;box-shadow:0 2px 8px rgba(21,21,19,.08)}.preview-icon svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.sp2{display:flex;flex-direction:column;min-height:calc(100vh - 100px)}
.sp2-reminder{margin:0 0 14px;padding:13px 14px;background:#edf4ee;border:1px solid #d5e4d7;border-radius:17px;box-shadow:0 2px 4px rgba(21,21,19,.05),0 7px 18px rgba(27,67,50,.09)}
.sp2-reminder-head{display:flex;align-items:center;gap:7px;color:#1b4332;font:600 13px/1.2 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;letter-spacing:0}
.sp2-reminder-head small{margin-left:auto;font:400 10px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#938f86}
.sp2-bell{display:grid;place-items:center;width:19px;height:19px;border-radius:6px;background:#1b4332;color:#fff;font:600 12px/1 Georgia,serif}
.sp2-reminder-body{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:8px}
.sp2-reminder-body b,.sp2-reminder-body span{display:block}
.sp2-reminder-body b{font:600 17px/1.25 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#151513;letter-spacing:0}
.sp2-reminder-body span{margin-top:3px;font:400 14px/1.3 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#6d6962}
.sp2-reminder-body strong{padding:5px 8px;border-radius:999px;background:#e5efe7;color:#1b4332;font:500 10px/1.2 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;white-space:nowrap}
.sp2-reminder-people{display:flex;align-items:center;margin-top:8px;padding-top:7px;border-top:1px solid #efede8}
.sp2-reminder-people img{width:22px;height:22px;border:2px solid #fff;border-radius:50%;object-fit:cover}
.sp2-reminder-people img+img{margin-left:-5px}
.sp2-reminder-people span{margin-left:7px;font:400 10px/1.2 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#6d6962}
.sp2-events{margin:0}
.sp2-events .sp2-h{font-size:22px;line-height:1.2;margin:0 0 13px}
.sp2-rail{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:0;padding:0;overflow:visible}
.sp2-ev-wrap{width:auto;min-width:0}
.sp2-ev{width:100%;height:100%;padding:0 0 10px;overflow:hidden;border-radius:14px;text-align:left}
.sp2-hero{height:auto;aspect-ratio:1.24;display:block;overflow:hidden}
.sp2-hero img{width:100%;height:100%;object-fit:cover}
.sp2-title{display:block;margin:9px 9px 0;font:600 16px/1.2 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#151513}
.sp2-when{display:block;margin:6px 9px 0;font:500 13px/1.25 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#2e6a49;letter-spacing:.03em}
.sp2-seats{font-size:11px;padding:5px 7px}
.sp2-create{display:flex;align-items:center;gap:11px;width:100%;margin-top:12px;padding:11px 12px;border:1px dashed #a9c3ad;border-radius:14px;background:#e9f0e8;color:#1b4332;text-align:left}
.sp2-create-plus{display:grid;place-items:center;width:30px;height:30px;flex:none;border-radius:50%;background:#fff;color:#287a55;font:400 22px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;box-shadow:0 1px 3px rgba(21,21,19,.07)}
.sp2-create-copy b,.sp2-create-copy span{display:block}
.sp2-create-copy b{font:600 16px/1.2 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
.sp2-create-copy span{margin-top:3px;color:#6d6962;font:400 13px/1.3 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
.sp2-bottom{display:grid;grid-template-columns:repeat(4,1fr);align-items:center;gap:2px;margin-top:auto;padding:10px 0 2px;border-top:1px solid #e6e3dc}
.sp2-nav-item{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;position:relative;color:#858177;font:500 11px/1.1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
.sp2-nav-item svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.sp2-nav-item.active{color:#1b4332}
.sp2-nav-item .nav-dot{position:absolute;top:0;left:calc(50% + 5px);width:6px;height:6px;border-radius:50%;background:#e4745f;border:1px solid #f4f2ee}
.sp2-nav-avatar{width:22px;height:22px;border-radius:50%;object-fit:cover}
@media(max-width:430px){body{padding:24px 12px 16px}.sp2-rail{gap:8px}.sp2-title{margin-inline:8px;font-size:14px}.sp2-when{margin-inline:8px;font-size:11.5px}.sp2-reminder-body strong{font-size:10px;padding-inline:6px}}
