
      :root {
        /* "only light": browsers (Samsung Internet especially) must
           not force-darken Manna — the theme picked in Settings is
           the only thing that repaints the app. */
        color-scheme: only light;
        --page-bg: #f0e3c0;
        --gold: #c9ae74; --gold-2: #e8d194; --gold-deep: #96793c; --gold-text: #8a6d2f;
        --app-bg: #fbf4e2; --app-bg2: #f4e8c8; --card: #fffaf0; --card2: #f7ecd0;
        --ink: #2e1d07; --ink-soft: #6d5230; --ink-faint: #a07c4e;
        --line: #e0cd9a; --line-soft: #eee0b8;
        --header-bg: #a3803e; --header-ink: #fff6dd;
        --door-bg: linear-gradient(150deg, #fbf0d2, #ecdcae 78%);
        --door-ink: #4a2f0c; --door-sub: #7a5a26;
        --door-chip-bg: rgba(210,154,31,.16); --door-chip-line: rgba(150,105,14,.38);
        --field: #fffdf8; --shadow: 0 10px 30px rgba(96,62,14,.18);
        --chip: #f1e2b6; --danger: #b3402a; --live: #c73325;
        --seg-bg: #efdfae; --overlay: rgba(37,21,5,.55);
        --heart: #e0243a; --ok: #557d1e; --market: #1c7a44;
        --radius: 18px;
      }
      /* Color themes (Settings → Appearance) — full palettes applied as
         body classes so every screen repaints at once. The :root palette
         above is Light Beige, the default; the rest live below, each
         overriding the same variables so text stays readable on every
         screen. The phone's own dark-mode setting never repaints the
         app — only the theme the user picks here does. */
      body.theme-sand {
        --gold: #c9ae74; --gold-2: #e8d194; --gold-deep: #96793c; --gold-text: #8a6d2f;
        --page-bg: #f0e3c0; --app-bg: #fbf4e2; --app-bg2: #f4e8c8; --card: #fffaf0; --card2: #f7ecd0;
        --line: #e0cd9a; --line-soft: #eee0b8; --chip: #f1e2b6; --seg-bg: #efdfae;
        --header-bg: #a3803e;
        --door-bg: linear-gradient(150deg, #fbf0d2, #ecdcae 78%);
      }
      body.theme-olive {
        --gold: #c9973f; --gold-2: #e2b96a; --gold-deep: #9a742a; --gold-text: #e9cb7d;
        --page-bg: #333d22; --app-bg: #333d22; --app-bg2: #3b4527; --card: #444e2e; --card2: #4c5734;
        --ink: #ecead8; --ink-soft: #cfc9ae; --ink-faint: #9d9a83;
        --line: #55613a; --line-soft: #48532f;
        --header-bg: #29311c; --header-ink: #ecead8;
        --door-bg: linear-gradient(150deg, #444e2e, #333d22 78%);
        --door-ink: #ecead8; --door-sub: #c9c4a8;
        --door-chip-bg: rgba(201,151,63,.18); --door-chip-line: rgba(201,151,63,.40);
        --field: #3e4729; --shadow: 0 10px 30px rgba(10,14,4,.45);
        --chip: #4c5734; --seg-bg: #414c2b; --ok: #a9c46a;
      }
      /* Nipsey Blue — the Marathon royal blue (renamed and
         recolored from Deep Blue, Casey's call, 2026-10-31).
         Same internal "blue" id, so a saved Deep Blue choice
         lands here seamlessly; the palette is the old blue
         theme's shape re-derived from the #0047AB anchor. */
      body.theme-blue {
        --gold: #0047ab; --gold-2: #7ea6e6; --gold-deep: #002e6b; --gold-text: #002e6b;
        --page-bg: #d5e1f8; --app-bg: #f0f4ff; --app-bg2: #dde7fb; --card: #ffffff; --card2: #e7eefc;
        --ink: #081c3a; --ink-soft: #33507e; --ink-faint: #7590ba;
        --line: #b6cbf0; --line-soft: #d9e4f9;
        --header-bg: #003a8a; --header-ink: #eef4ff;
        --door-bg: linear-gradient(150deg, #e8eefc, #c3d4f5 78%);
        --door-ink: #0a2148; --door-sub: #3a5a94;
        --door-chip-bg: rgba(0,71,171,.14); --door-chip-line: rgba(0,46,107,.36);
        --field: #ffffff; --shadow: 0 10px 30px rgba(0,40,110,.18);
        --chip: #c9daf6; --seg-bg: #b9cdf3;
      }
      /* The lively set (Casey's call, 2026-10-24 — "some more
         lively looking colors"): brighter, saturated palettes
         beside the quieter ones above — a hot sunset orange, a
         royal purple, and a berry pink. (A lagoon teal stood in
         this set until Casey retired it, 2026-10-31.) Same
         variable shape as every other theme, so every screen
         repaints at once, and each keeps a deep shade of its
         own color for text-bearing accents so words stay
         readable on the bright ground. */
      body.theme-royal {
        --gold: #7b3fe4; --gold-2: #b794f6; --gold-deep: #55239e; --gold-text: #55239e;
        --page-bg: #e9defc; --app-bg: #f7f2ff; --app-bg2: #eee4fd; --card: #fdfbff; --card2: #f0e7fd;
        --ink: #22103f; --ink-soft: #5b4483; --ink-faint: #9c85c4;
        --line: #d3bdf2; --line-soft: #e7dbfa;
        --header-bg: #5a2aa8; --header-ink: #f3ecff;
        --door-bg: linear-gradient(150deg, #f1e8fe, #dbc8fa 78%);
        --door-ink: #2c1454; --door-sub: #63478f;
        --door-chip-bg: rgba(123,63,228,.14); --door-chip-line: rgba(85,35,158,.36);
        --field: #fdfbff; --shadow: 0 10px 30px rgba(80,40,160,.2);
        --chip: #e2d2fb; --seg-bg: #d8c4f8;
      }
      body.theme-bw {
        --gold: #5c5c5c; --gold-2: #c9c9c9; --gold-deep: #1c1c1c; --gold-text: #1c1c1c;
        --page-bg: #e7e7e7; --app-bg: #ffffff; --app-bg2: #f1f1f1; --card: #ffffff; --card2: #f0f0f0;
        --ink: #111111; --ink-soft: #454545; --ink-faint: #7d7d7d;
        --line: #d3d3d3; --line-soft: #e6e6e6;
        --header-bg: #141414; --header-ink: #ffffff;
        --door-bg: linear-gradient(150deg, #ffffff, #e3e3e3 78%);
        --door-ink: #111111; --door-sub: #555555;
        --door-chip-bg: rgba(0,0,0,.07); --door-chip-line: rgba(0,0,0,.28);
        --field: #ffffff; --shadow: 0 10px 30px rgba(0,0,0,.16);
        --chip: #e2e2e2; --seg-bg: #dedede;
        --danger: #3d3d3d; --live: #1c1c1c; --heart: #e0243a; --ok: #3d3d3d;
      }
      body.theme-bw .btn-gold { background: #141414; color: #ffffff; }
      body.theme-bw .pill-gold, body.theme-bw .bk-cat, body.theme-bw .cat-chip.active { background: #141414; color: #ffffff; border-color: #141414; }
      body.theme-bw .fab { background: #141414; color: #ffffff; }
      /* Mostly Black — the dark half of Black & White (Casey's
         call, 2026-10-31: tapping Black & White in Settings now
         offers Mostly Black or Mostly White; the theme-bw
         palette above is Mostly White, the original B&W look).
         Strictly monochrome: near-black ground, dark surfaces,
         white text, gray mid-tones, hairline borders that stay
         visible on the dark cards. The primary button inverts
         with the mirror rules below, the same way Mostly White
         flattens its button to near-black. */
      body.theme-bw-black {
        color-scheme: dark;
        --gold: #d6d6d6; --gold-2: #9a9a9a; --gold-deep: #e8e8e8; --gold-text: #f2f2f2;
        --page-bg: #000000; --app-bg: #0a0a0a; --app-bg2: #121212; --card: #171717; --card2: #1f1f1f;
        --ink: #f5f5f5; --ink-soft: #bcbcbc; --ink-faint: #8b8b8b;
        --line: #3a3a3a; --line-soft: #262626;
        --header-bg: #000000; --header-ink: #ffffff;
        --door-bg: linear-gradient(150deg, #202020, #0f0f0f 78%);
        --door-ink: #f5f5f5; --door-sub: #a6a6a6;
        --door-chip-bg: rgba(255,255,255,.12); --door-chip-line: rgba(255,255,255,.35);
        --field: #171717; --shadow: 0 10px 30px rgba(0,0,0,.6);
        --chip: #262626; --seg-bg: #262626; --overlay: rgba(0,0,0,.65);
        --danger: #b5b5b5; --live: #f5f5f5; --heart: #e0243a; --ok: #b5b5b5; --market: #b5b5b5;
      }
      body.theme-bw-black .btn-gold { background: #f5f5f5; color: #111111; }
      body.theme-bw-black .pill-gold, body.theme-bw-black .bk-cat, body.theme-bw-black .cat-chip.active { background: #f5f5f5; color: #111111; border-color: #f5f5f5; }
      body.theme-bw-black .fab { background: #f5f5f5; color: #111111; }
      /* Dark, styled after X's lights-out: pure black ground, crisp
         near-white text, hairline gray lines, the Manna gold kept
         bright so it pops instead of going dull. */
      body.theme-dark {
        color-scheme: dark;
        --gold: #eab639; --gold-2: #f6d67c; --gold-deep: #d9a92f; --gold-text: #eab639;
        --page-bg: #000000; --app-bg: #000000; --app-bg2: #0b0b0d; --card: #16181c; --card2: #1d1f23;
        --ink: #e7e9ea; --ink-soft: #a9adb3; --ink-faint: #71767b;
        --line: #2f3336; --line-soft: #232629;
        --header-bg: #000000; --header-ink: #f7f9f9;
        --door-bg: linear-gradient(150deg, #1e2024, #101214 78%);
        --door-ink: #f2f0e2; --door-sub: #a49d8a;
        --door-chip-bg: rgba(234,182,57,.15); --door-chip-line: rgba(234,182,57,.42);
        --field: #16181c; --shadow: 0 10px 30px rgba(0,0,0,.6);
        --chip: #1d1f23; --seg-bg: #1d1f23; --overlay: rgba(91,112,131,.4);
        --danger: #f4212e; --live: #f4212e; --heart: #ff2e4d; --ok: #00ba7c; --market: #2fbf71;
      }
      * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
      /* The page itself can never scroll or rest shifted
         sideways (Casey's bleed reports, 2026-10-21): every
         scroll in the app lives inside the shell, so any
         sideways give at the root only ever shows up as the
         page background bleeding in at one edge — the right
         edge of Shorts was the latest place it showed.
         overflow-x: clip (not hidden) forbids the sideways
         scroll without turning the root into a scroll box. */
      html, body { height: 100%; overscroll-behavior: none; overflow-x: clip; }
      html { background: var(--app-bg); }
      body {
        margin: 0;
        font-family: "Public Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
        color: var(--ink);
        /* The page behind the app wears the app's own background
           (Casey's report, 2026-10-29): the old decorative
           backdrop — gold and brown glows over the cream page
           color, a leftover from the centered-mock days — showed
           as brown blocks above and below the app whenever the
           shell and the page disagreed about the screen height
           by even a few pixels. Now any sliver that ever shows
           is the app's own color, in whatever scheme is running. */
        background: var(--app-bg);
        display: flex; align-items: center; justify-content: center;
        min-height: 100dvh;
        font-size: 16px;
        line-height: 1.45;
      }
      /* The app fills the whole screen at every size (Casey's
         call, 2026-10-21): no centered phone mock, no capped
         height — the scrollable area is the full screen, edge to
         edge, so a swipe or scroll anywhere on the screen moves
         the content under it. */
      .phone {
        /* Pinned to the screen itself (Casey's report,
           2026-10-29): viewport units (100vh / 100dvh) and the
           page's own height disagree by a few pixels in some
           browsers and WebViews, and every disagreement opened
           a band of page background at the top and bottom.
           Fixed inset-0 sizing follows the visible screen
           exactly, so the app is full screen, edge to edge,
           with nothing behind it left to show. */
        position: fixed;
        inset: 0;
        background: var(--app-bg);
        overflow: hidden;
        display: flex; flex-direction: column;
      }
      /* No text selection anywhere in the app (Casey's call,
         2026-10-24): a long press — on the Kinsmen button, on a
         sheet title, on anything — must never summon the
         phone's Copy / Select all / Share menu. Copying still
         happens where Manna offers it on purpose, through its
         own buttons, which set the clipboard themselves. The
         fields you type in stay selectable so you can edit
         what you wrote. */
      .phone, .phone * { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
      .phone input, .phone textarea, .phone [contenteditable="true"] { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }
      main {
        flex: 1 1 auto; min-height: 0;
        overflow-y: auto; overscroll-behavior: none;
        padding: 0 18px 52px;
        background: linear-gradient(180deg, var(--app-bg), var(--app-bg2));
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
      }
      main::-webkit-scrollbar { display: none; }
      h1, h2, h3 { margin: 0; }
      .display { font-family: "Marcellus", Georgia, serif; font-weight: 400; letter-spacing: .01em; }
      .muted { color: var(--ink-soft); }
      .faint { color: var(--ink-faint); }
      .small { font-size: 12.5px; }
      .tiny { font-size: 11px; }
      button { font: inherit; cursor: pointer; }
      input, textarea, select { font: inherit; color: var(--ink); }
      /* Typing room (Casey's call, 2026-10-21): fields keep a
         margin when scrolled into view, so the keyboard-lift
         helper never parks a field flush against the header
         or the bottom edge. */
      input, textarea, select { scroll-margin-top: 76px; scroll-margin-bottom: 28px; }
      a { color: var(--gold-text); }

      /* ---------- Pull to refresh ---------- */
      /* The pill that follows the finger when a main screen is
         pulled down from its top (Casey's call, 2026-10-24). */
      #ptr {
        position: absolute; left: 50%; top: 10px; z-index: 55;
        transform: translate(-50%, -64px); opacity: 0;
        display: flex; align-items: center; gap: 8px;
        background: var(--card); color: var(--ink);
        border: 1px solid var(--line); border-radius: 999px;
        padding: 7px 14px 7px 10px; font-size: 13px; font-weight: 600;
        box-shadow: 0 6px 18px rgba(0,0,0,.18);
        pointer-events: none;
      }
      #ptr .ptr-ring {
        width: 15px; height: 15px; border-radius: 50%; flex: none;
        border: 2px solid var(--line); border-top-color: var(--gold-2);
      }
      #ptr.on .ptr-ring { animation: ptrspin .7s linear infinite; }
      @keyframes ptrspin { to { transform: rotate(360deg); } }
      @media (prefers-reduced-motion: reduce) { #ptr.on .ptr-ring { animation: none; } }

      /* ---------- Splash ---------- */
      #splash {
        position: absolute; inset: 0; z-index: 60;
        background: linear-gradient(165deg, #faf1dc, #eedcae 68%);
        display: flex; flex-direction: column; align-items: center; justify-content: center;
        text-align: center;
        transition: opacity .45s ease;
      }
      #splash.gone { opacity: 0; pointer-events: none; }
      /* Warm sitting (flag read in the head script): the splash
         never paints — a mid-session reload returns straight to
         the app instead of flashing the logo screen. */
      html.manna-warm #splash { display: none !important; }
      .splash-word { font-family: "Marcellus", Georgia, serif; font-size: 37px; letter-spacing: .34em; text-indent: .34em; color: #57400f; margin-top: 16px; }
      .splash-sub { color: #8a6d2f; font-size: 12px; letter-spacing: .22em; margin-top: 8px; text-transform: uppercase; }

      /* ---------- Crow intro (login arrival) ---------- */
      #crow-intro {
        position: absolute; inset: 0; z-index: 400;
        background: linear-gradient(165deg, #f8f0da, #eee0ba 70%);
        align-items: center; justify-content: center;
        overflow: hidden;
      }
      #ci-stage { position: relative; width: min(86%, 58vh, 430px); aspect-ratio: 1 / 1; display: none; }
      #ci-m, #ci-perch { position: absolute; inset: 0; width: 100%; height: 100%; }
      #ci-perch { opacity: 0; will-change: opacity, transform; }
      #ci-bird { position: absolute; left: 0; top: 0; width: 42.5%; aspect-ratio: 880 / 830; opacity: 0; transform-origin: 50% 48.19%; will-change: transform, left, top; }
      #ci-flip { position: absolute; inset: 0; }
      .ci-pose { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; transition: opacity .04s linear; }
      .ci-pose.on { opacity: 1; }
      #ci-bread { position: absolute; left: 37.19%; top: 21.19%; width: 4.44%; aspect-ratio: 71 / 54; opacity: 0; will-change: transform, opacity; }
      #ci-static { position: absolute; inset: 0; width: 100%; height: 100%; display: none; }
      /* The dot the manna becomes (Casey, 2026-10-31): centered on
         the bread's rest spot under the middle of the M (48.45%,
         85.72%), sized from the renewed mark itself — the auth
         emblem's dot (r 4.5 in a 100-unit viewBox whose M stands
         58 units tall) scaled to the stage M's height (0.452 of
         the stage) gives a diameter of 7.01% of the stage. Its
         fill is the emblem's own gold, #c9973f. */
      #ci-dot { position: absolute; left: 44.945%; top: 82.215%; width: 7.01%; aspect-ratio: 1 / 1; border-radius: 50%; background: #c9973f; opacity: 0; will-change: transform, opacity; }
      /* The lockup's words, faded in under the composition at the
         end: wide-spaced Marcellus caps in the splash tagline's
         gold-brown. Font size is set from the stage width by the
         intro script so it scales with the stage. */
      #ci-tag { position: absolute; left: 0; right: 0; top: 92.6%; text-align: center; font-family: "Marcellus", Georgia, serif; font-size: 15px; letter-spacing: .30em; text-indent: .30em; text-transform: uppercase; color: #8a6d2f; opacity: 0; white-space: nowrap; will-change: opacity; }

      /* ---------- First-run permissions ---------- */
      /* The welcome screen a first-time member meets (and the
         same three rows inside Settings → Permissions): a
         full-screen layer over the phone frame, one card per
         permission, each with its own Turn on that runs the
         browser's real ask. */
      .perm-overlay {
        position: absolute; inset: 0; z-index: 75;
        background: linear-gradient(180deg, var(--app-bg), var(--app-bg2));
        overflow-y: auto; -webkit-overflow-scrolling: touch;
        padding: calc(26px + var(--sat)) 20px calc(22px + var(--sab));
      }
      .perm-inner { max-width: 430px; margin: 0 auto; }
      .perm-mark { display: flex; justify-content: center; margin-bottom: 14px; }
      .perm-overlay h2 { font-family: "Marcellus", Georgia, serif; font-weight: 400; font-size: 24px; line-height: 1.25; text-align: center; color: var(--ink); }
      .perm-lede { color: var(--ink-soft); font-size: 14px; line-height: 1.6; text-align: center; margin: 10px 0 18px; }
      .perm-row {
        display: flex; gap: 13px; align-items: flex-start;
        background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
        box-shadow: var(--shadow); padding: 15px; margin-bottom: 12px;
      }
      /* Inside the Settings card the rows sit flush — the card
         carries the border and shadow, the rows just divide. */
      .set-card .perm-row { background: transparent; border: none; border-radius: 0; box-shadow: none; margin-bottom: 0; }
      .set-card .perm-row + .perm-row { border-top: 1px solid var(--line-soft); }
      .perm-ic {
        width: 44px; height: 44px; flex: none; border-radius: 50%;
        background: var(--chip); color: var(--gold-text);
        display: inline-flex; align-items: center; justify-content: center;
      }
      .perm-ic svg { width: 22px; height: 22px; }
      .perm-main { flex: 1; min-width: 0; }
      .perm-t { font-weight: 700; font-size: 15.5px; color: var(--ink); display: block; }
      .perm-s { color: var(--ink-soft); font-size: 13px; line-height: 1.5; margin-top: 2px; display: block; }
      .perm-state { font-size: 12.5px; font-weight: 700; margin-top: 7px; color: var(--ink-faint); display: block; }
      .perm-state svg { width: 14px; height: 14px; vertical-align: -2px; margin-right: 3px; }
      .perm-state.on { color: var(--ok); }
      .perm-state.blocked { color: var(--danger); }
      .perm-note { font-size: 12.5px; line-height: 1.55; color: var(--ink-soft); margin-top: 6px; display: block; }
      .perm-turnon {
        flex: none; align-self: center;
        background: var(--gold); color: #fff8e8; border: 1px solid var(--gold-deep);
        font-weight: 700; font-size: 13.5px; padding: 9px 14px; border-radius: 999px;
        white-space: nowrap;
      }
      .perm-turnon:disabled { opacity: .6; }
      .perm-foot-note { text-align: center; color: var(--ink-faint); font-size: 12.5px; line-height: 1.55; margin: 14px 4px 16px; }

      /* ---------- Generic ---------- */
      .hidden { display: none !important; }
      .appbar {
        display: flex; align-items: center; gap: 10px;
        margin: 0 -18px 16px; padding: 10px 14px;
        background: var(--app-bg); border-bottom: 1px solid var(--line-soft);
        position: sticky; top: 0; z-index: 4;
      }
      .appbar .ttl {
        font-family: "Marcellus", Georgia, serif; font-size: 19px; letter-spacing: .06em;
        text-transform: uppercase; flex: 1; text-align: center; min-width: 0;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--ink);
      }
      .appbar .ttl.left { text-align: left; }
      .ahead-spacer { width: 38px; flex: none; }
      .back-btn, .icon-btn {
        width: 38px; height: 38px; border-radius: 50%; flex: none;
        display: inline-flex; align-items: center; justify-content: center; color: var(--ink);
      }
      .back-btn { border: 1px solid transparent; background: transparent; }
      .icon-btn { border: 1px solid var(--line); background: var(--card); position: relative; }
      .back-btn:active, .icon-btn:active { transform: scale(.94); }
      .icon-btn svg, .back-btn svg { width: 20px; height: 20px; }
      /* The bell badge (Casey's report, 2026-10-21: nothing told
         him notifications were even there): a red count pill on
         the bell itself, on Home and on every screen whose top
         bar carries the bell, so waiting hearts, comments,
         reportions, kinsmen and requests announce themselves
         before he ever opens the screen. */
      .bell-badge, .dm-badge {
        position: absolute; top: -5px; right: -5px; min-width: 19px; height: 19px; padding: 0 5px;
        border-radius: 999px; background: var(--heart); color: #fff; font-size: 11px; font-weight: 800;
        display: inline-flex; align-items: center; justify-content: center; line-height: 1;
        box-shadow: 0 0 0 2px var(--card), 0 2px 6px rgba(0,0,0,.3); pointer-events: none;
      }
      /* The online count on the Social door (Casey's call,
         2026-10-22): a small pill at the TOP LEFT of the Social
         screen's top bar — just a glowing green dot and the
         number counter saying how many people are in the app
         right now (the people icon came out the same day, at
         his word). It is an aggregate count only: it names
         nobody and marks nobody, so it does not revive the
         retired per-person online light. */
      .online-chip {
        flex: none; display: inline-flex; align-items: center; gap: 6px;
        height: 38px; padding: 0 12px 0 10px; border-radius: 999px;
        border: 1px solid var(--line); background: var(--card); color: var(--ink);
        font-size: 13px; font-weight: 700; line-height: 1; white-space: nowrap;
      }
      .online-chip .oc-dot {
        width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: none;
        animation: ocPulse 2.4s ease-in-out infinite;
      }
      @keyframes ocPulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
      @media (prefers-reduced-motion: reduce) { .online-chip .oc-dot { animation: none; } }
      /* The count itself rides as an old-school split-flap
         counter (Casey's call, 2026-10-22, from his reference
         photo): white flip tiles with black digits and the
         split line across the middle of each tile. The tiles
         stay paper-white in every theme — that white-tile look
         IS the counter. When the number changes, a changed
         tile really flips: its old top half folds down and the
         new bottom half falls in behind it — paintOnlineChip()
         lays the .flap-leaf pair over the tile for that.
         Each half shows its slice of one full-height digit:
         the digit rides an <i> twice the half's height,
         centered, clipped by the half's overflow. */
      .flap-counter { display: inline-flex; gap: 3px; align-items: center; }
      .flap {
        position: relative; flex: none; width: 19px; height: 27px; border-radius: 4px;
        background: #fbfaf6; color: #191919;
        font-weight: 800; font-size: 16.5px; line-height: 1;
        box-shadow: 0 1px 2px rgba(74,50,8,.35), 0 3px 6px rgba(74,50,8,.14), inset 0 0 0 1px rgba(0,0,0,.07);
        perspective: 140px;
      }
      .flap-half, .flap-leaf { position: absolute; left: 0; width: 100%; height: 50%; overflow: hidden; }
      .flap-half i, .flap-leaf i {
        position: absolute; left: 0; width: 100%; height: 200%;
        display: flex; align-items: center; justify-content: center; font-style: normal;
      }
      .flap-top, .flap-leaf-top { top: 0; border-radius: 4px 4px 0 0; background: linear-gradient(180deg, #ffffff, #f3f0e8); }
      .flap-bottom, .flap-leaf-bottom { bottom: 0; border-radius: 0 0 4px 4px; background: linear-gradient(180deg, #efede4, #faf8f1); }
      .flap-top i, .flap-leaf-top i { top: 0; }
      .flap-bottom i, .flap-leaf-bottom i { bottom: 0; }
      /* The split line across the middle of every tile. */
      .flap::after {
        content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px; margin-top: -1px;
        background: rgba(20,16,8,.34); z-index: 5; pointer-events: none;
      }
      .flap-leaf { z-index: 3; backface-visibility: hidden; }
      .flap-leaf-top { transform-origin: 50% 100%; animation: flap-fall .24s ease-in both; }
      .flap-leaf-bottom { transform-origin: 50% 0; transform: rotateX(90deg); animation: flap-land .24s ease-out .24s both; box-shadow: 0 2px 3px rgba(74,50,8,.22); }
      @keyframes flap-fall { from { transform: rotateX(0deg); } to { transform: rotateX(-90deg); } }
      @keyframes flap-land { from { transform: rotateX(90deg); } to { transform: rotateX(0deg); } }
      @media (prefers-reduced-motion: reduce) { .flap-leaf { display: none; } }
      /* ---------- X-style search ----------
         The creator search works the way X search does
         (Casey's call, 2026-10-21): the field rides IN the
         top bar beside the back arrow — no title, no inbox,
         no gear — pinned while the people list scrolls under
         it, and the list is flat full-width rows with hairline
         separators instead of cards. Because the field already
         sits at the very top of the screen, the keyboard
         rising changes nothing above it: no lift, no
         re-centering, the names never move. */
      .xsearch-bar {
        display: flex; align-items: center; gap: 8px;
        margin: 0 -18px 6px; padding: 10px 12px;
        background: var(--app-bg); border-bottom: 1px solid var(--line-soft);
        position: sticky; top: 0; z-index: 5;
      }
      .xsearch-pill {
        flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
        background: var(--field); border: 1px solid var(--line); border-radius: 999px;
        padding: 0 6px 0 13px; min-height: 44px;
      }
      .xsearch-pill:focus-within { border-color: var(--gold); }
      .xsearch-pill > svg { width: 18px; height: 18px; flex: none; color: var(--ink-faint); }
      .xsearch-pill input {
        flex: 1; min-width: 0; border: none; background: transparent; outline: none;
        font-size: 16px; padding: 11px 0; color: var(--ink);
        scroll-margin-top: 0; scroll-margin-bottom: 0;
      }
      .xsearch-pill input::placeholder { color: var(--ink-faint); }
      .xsearch-clear {
        width: 30px; height: 30px; border-radius: 50%; flex: none; border: none;
        background: var(--line); color: var(--ink); font-size: 13px; line-height: 1;
        display: inline-flex; align-items: center; justify-content: center; padding: 0;
      }
      .xsearch-clear:active { transform: scale(.92); }
      .xsearch-hint {
        font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
        color: var(--ink-faint); padding: 10px 2px 2px;
      }
      .xrow {
        display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
        background: transparent; border: none; border-bottom: 1px solid var(--line-soft);
        padding: 12px 2px; cursor: pointer; font-family: inherit; color: var(--ink);
      }
      .xrow:active { background: var(--field); }
      .xrow .xrow-main { flex: 1; min-width: 0; }
      .xrow .xrow-name { font-weight: 800; font-size: 15px; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .xrow .xrow-sub { font-size: 13px; color: var(--ink-soft); display: block; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .xrow > svg { width: 18px; height: 18px; flex: none; color: var(--ink-faint); }
      .spinner {
        width: 34px; height: 34px; margin: 48px auto; border-radius: 50%;
        border: 3px solid var(--line-soft); border-top-color: var(--gold-deep);
        animation: spin 0.8s linear infinite;
      }
      @keyframes spin { to { transform: rotate(360deg); } }
      /* ---------- Lane switching: smooth screen transitions ----------
         Switching lanes used to be two hard cuts — the screen wiped
         to a bare spinner, then the whole feed popped in at once.
         Now the view itself glides: entering a lane slides in from
         the right, coming back to the lane picker slides in from
         the left, and content that swaps in place (a lane painting
         from its cache, then its fresh copy landing) crossfades.
         animateView() in the script drives the classes; the
         animation lives on the scroll container so the whole
         screen — header included — moves as one piece. */
      #view.vin-r { animation: vin-r .32s cubic-bezier(.22,.8,.3,1) both; }
      #view.vin-l { animation: vin-l .32s cubic-bezier(.22,.8,.3,1) both; }
      #view.vin-f { animation: vin-f .22s ease both; }
      @keyframes vin-r { from { opacity: 0; transform: translateX(30px); } to { opacity: 1; transform: translateX(0); } }
      @keyframes vin-l { from { opacity: 0; transform: translateX(-30px); } to { opacity: 1; transform: translateX(0); } }
      @keyframes vin-f { from { opacity: .3; } to { opacity: 1; } }
      @media (prefers-reduced-motion: reduce) {
        #view.vin-r, #view.vin-l, #view.vin-f { animation: none; }
      }
      /* The lane loading skeleton: on a first visit the lane's own
         header arrives with the slide and soft pulsing placeholder
         cards stand where the portions will land, instead of the
         whole screen blanking to a lone spinner. */
      .skel {
        background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
        box-shadow: var(--shadow); padding: 16px; margin-bottom: 12px;
        animation: skel-pulse 1.15s ease-in-out infinite;
      }
      .skel .sk-row { display: flex; align-items: center; gap: 10px; }
      .sk-ava { width: 40px; height: 40px; border-radius: 50%; background: var(--line-soft); flex: none; }
      .sk-line { display: block; height: 11px; border-radius: 6px; background: var(--line-soft); margin: 8px 0 2px; }
      @keyframes skel-pulse { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
      @media (prefers-reduced-motion: reduce) { .skel { animation: none; } }
      .empty {
        text-align: center; color: var(--ink-soft); padding: 22px 20px;
        background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
        box-shadow: var(--shadow); font-size: 14px; line-height: 1.5;
      }
      .empty .big { font-weight: 800; font-size: 15.5px; color: var(--ink); margin-bottom: 6px; }
      .err-box {
        background: rgba(179,64,42,.1); border: 1px solid var(--danger); color: var(--danger);
        border-radius: 13px; padding: 12px 14px; margin: 14px 0; font-size: 13.5px; line-height: 1.5; word-break: break-word;
      }
      .ok-box {
        background: rgba(85,125,30,.12); border: 1px solid var(--ok); color: var(--ok);
        border-radius: 13px; padding: 12px 14px; margin: 14px 0; font-size: 13.5px; line-height: 1.5;
      }
      #toast {
        position: fixed; left: 50%; bottom: 96px; transform: translateX(-50%) translateY(20px);
        background: var(--header-bg); color: var(--header-ink);
        padding: 11px 18px; border-radius: 999px; font-size: 13.5px; font-weight: 600;
        max-width: min(88vw, 400px);
        opacity: 0; pointer-events: none; transition: opacity .3s, transform .3s; z-index: 80; text-align: center;
        box-shadow: 0 10px 30px rgba(0,0,0,.35);
      }
      #toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
      #toast.err { background: var(--danger); color: #fff; }
      /* A toast carrying an action (Undo a reportion, Casey's
         call, 2026-10-22): the words and a gold Undo button ride
         side by side, and the toast itself takes taps until it
         fades — plain toasts stay tap-through as before. */
      #toast.has-action {
        pointer-events: auto; display: inline-flex; align-items: center; gap: 14px;
        text-align: left; padding: 9px 9px 9px 18px;
      }
      #toast .toast-undo {
        flex: none; border: none; border-radius: 999px; cursor: pointer;
        background: var(--gold); color: #241503; font: inherit; font-weight: 800; font-size: 13px;
        padding: 8px 15px; letter-spacing: .02em;
      }
      #toast .toast-undo:active { transform: scale(.95); }
      #toast .toast-undo:disabled { opacity: .55; }

      /* ---------- Forms ---------- */
      label.f { display: block; font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); margin: 14px 0 6px; }
      .input, textarea.input, select.input {
        width: 100%; background: var(--field); border: 1px solid var(--line); border-radius: 13px;
        padding: 13px 14px; font-size: 15px; outline: none;
      }
      .input:focus { border-color: var(--gold); }
      /* Password fields (built by pwField) carry an eye toggle at
         the field's right edge: tap to read the password as typed,
         tap again to hide it behind dots. */
      .pw-field { position: relative; }
      .pw-field .input { padding-right: 52px; }
      .pw-eye {
        position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
        width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
        background: none; border: none; border-radius: 10px; padding: 0;
        color: var(--ink-soft); cursor: pointer; -webkit-tap-highlight-color: transparent;
      }
      .pw-eye:active { background: var(--line); }
      .pw-eye svg { width: 22px; height: 22px; }
      textarea.input { min-height: 90px; resize: vertical; line-height: 1.5; }
      /* The portion composer bubble (Casey's call, 2026-10-26):
         never pullable by hand — it grows only as the writing
         itself grows (the composer sizes it from the words). */
      #c-text { resize: none; overflow-y: auto; }
      .p-seemore { background: none; border: none; color: var(--gold-deep); font-weight: 700; font-size: inherit; font-family: inherit; cursor: pointer; padding: 0; }
      select.input option { background: var(--field); }
      /* Closed selects wear Manna's own chevron (Casey's call,
         2026-10-24): the phone's default arrow goes away, and
         tapping the field opens Manna's list sheet (below)
         instead of the phone's own list. */
      select.input, .mk-place select {
        appearance: none; -webkit-appearance: none;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23a8791f' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
        background-repeat: no-repeat; background-position: right 12px center;
        padding-right: 36px;
      }
      /* Manna's own list sheet: radio rows in Manna's dress,
         the current choice wearing a gold ring and dot. */
      .mpick-sheet { max-height: 80%; display: flex; flex-direction: column; }
      .mpick-list { overflow-y: auto; margin-top: 12px; border: 1px solid var(--line); border-radius: 14px; overscroll-behavior: contain; }
      .mpick-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; padding: 13px 14px; background: var(--card); border: none; border-bottom: 1px solid var(--line-soft); font: inherit; font-size: 16px; color: var(--ink); cursor: pointer; -webkit-tap-highlight-color: transparent; }
      .mpick-row:last-child { border-bottom: none; }
      .mpick-row:active { background: var(--chip); }
      .mpick-row .mpick-dot { width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--line); flex: none; position: relative; box-sizing: border-box; }
      .mpick-row.on { font-weight: 700; }
      .mpick-row.on .mpick-dot { border-color: var(--gold-deep); }
      .mpick-row.on .mpick-dot::after { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--gold-deep); }
      .mpick-row:disabled { opacity: .45; }
      /* Manna picker fields (Casey's standing rule, 2026-10-31):
         the native <select> itself never stays on screen — it
         goes backstage hidden (still holding the value), and a
         Manna field wearing the app's own dress stands in its
         place and opens Manna's list sheet. The phone's dropdown
         has no control left to open from. */
      .mp-native-hidden { display: none !important; }
      button.mp-field {
        display: flex; align-items: center; justify-content: space-between; gap: 10px;
        text-align: left; cursor: pointer; font-family: inherit; color: var(--ink);
        -webkit-tap-highlight-color: transparent;
      }
      button.mp-field .mp-field-txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      button.mp-field .mp-chev {
        flex: none; width: 12px; height: 8px;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23a8791f' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
        background-repeat: no-repeat; background-position: center;
      }
      button.mp-field:active { border-color: var(--gold); }
      button.mp-field:disabled { opacity: .5; cursor: default; }
      .mk-place button.mp-field { flex: 1; min-width: 0; width: auto; border: none; background: transparent; padding: 10px 0; font-size: 14.5px; }
      /* Manna Gallery styles — RETIRED 2026-10-24: Gallery
         now goes straight to the phone's own pictures with
         no Manna page first (Casey's settled call), so no
         sheet wears these anymore. Kept only so any stray
         markup still renders sanely; the door itself is
         openMannaGallery's direct dispatch. */
      .mgal-sheet { height: 100%; max-height: 100%; border-radius: 0; display: flex; flex-direction: column; }
      .mgal-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; overflow-y: auto; margin-top: 10px; overscroll-behavior: contain; flex: 1 1 auto; min-height: 0; align-content: start; }
      .mgal-bar { display: flex; gap: 10px; margin-top: 10px; flex: none; }
      .mgal-bar .btn-ghost { flex: 1; margin-top: 0; }
      .mgal-tile { position: relative; aspect-ratio: 1 / 1; border: none; padding: 0; margin: 0; background: #000; cursor: pointer; display: block; width: 100%; -webkit-tap-highlight-color: transparent; }
      .mgal-tile img, .mgal-tile video { width: 100%; height: 100%; object-fit: cover; display: block; }
      .mgal-tile .mgal-ring { position: absolute; top: 6px; right: 6px; width: 22px; height: 22px; border-radius: 50%; border: 2px solid rgba(255,255,255,.92); background: rgba(20,12,2,.35); box-sizing: border-box; pointer-events: none; }
      .mgal-tile.sel .mgal-ring { background: var(--gold-deep); border-color: #fff; }
      .mgal-tile.sel img, .mgal-tile.sel video { opacity: .72; }
      .mgal-tile .mgal-play { position: absolute; left: 6px; bottom: 6px; width: 24px; height: 24px; border-radius: 50%; background: rgba(20,12,2,.72); color: #ffe9b0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
      .mgal-tile .mgal-play svg { width: 12px; height: 12px; }
      .mgal-tile.busy { opacity: .5; pointer-events: none; }
      /* The gallery's phone tile: the door out to the phone's
         whole library, drawn as the LAST tile of the grid —
         the pictures in Manna lead, the phone waits at the
         end (and on the bar below) for what isn't in Manna
         yet (Casey's call, 2026-10-24). */
      .mgal-phone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 7px; background: var(--card2); color: var(--gold-text); }
      .mgal-phone svg { width: 30px; height: 30px; }
      .mgal-phone span { font-size: 11.5px; font-weight: 700; line-height: 1.25; text-align: center; padding: 0 6px; }
      /* Camera cover (Casey's call, 2026-10-24): while a camera
         stage opens or flips, this branded cover stands over
         the picture area so the raw empty-video screen — the
         gray box with the browser's own black play button — is
         never what he sees. It fades the moment the live feed
         paints, and comes back across every flip, so the swap
         reads as one covered transition. Never takes a tap. */
      .cam-cover { position: absolute; inset: 0; z-index: 5; background: linear-gradient(180deg, var(--app-bg), var(--app-bg2)); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; opacity: 1; transition: opacity .38s ease; pointer-events: none; }
      .cam-cover.off { opacity: 0; }
      @media (prefers-reduced-motion: reduce) { .cam-cover { transition: none; } }
      /* Full-screen picture viewer (Casey's call, 2026-10-24):
         tap the picture on a portion and the picture itself
         fills the screen — not just the post it rides on. A
         guarded overlay, so the phone's back button closes
         it like every other layer. */
      #imgview-overlay { position: absolute; inset: 0; z-index: 85; background: rgba(14,8,1,.97); display: flex; align-items: center; justify-content: center; }
      #imgview-overlay img { width: 100%; height: 100%; object-fit: contain; display: block; }
      /* The picture's own words along the bottom of the
         full-screen view, Facebook-style (Casey's call,
         2026-10-24): white words over a shade that rises
         from the bottom edge, so they read against any
         photo. The bar exists only when the picture
         actually has words — never an empty strip. A long
         caption scrolls inside the bar rather than
         covering the picture. */
      #imgview-overlay .iv-caption { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; padding: 36px 18px calc(16px + var(--sab)); background: linear-gradient(to top, rgba(0,0,0,.82), rgba(0,0,0,.45) 62%, transparent); color: #fff; font-size: 15.5px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: break-word; max-height: 46%; overflow-y: auto; pointer-events: none; }
      #imgview-overlay .iv-close { position: absolute; top: calc(10px + var(--sat)); right: 12px; z-index: 2; }
      img[data-imgview] { cursor: pointer; }
      .btn-gold {
        background: linear-gradient(160deg, var(--gold-2), var(--gold) 55%, var(--gold-deep));
        color: #241503; border: none; font-weight: 700; border-radius: 14px;
        padding: 13px 18px; width: 100%; font-size: 15px; margin-top: 20px;
        box-shadow: 0 6px 16px rgba(138,95,19,.45);
        display: inline-flex; align-items: center; justify-content: center; gap: 8px;
      }
      .btn-gold:active { transform: translateY(1px); }
      .btn-gold:disabled { opacity: .55; }
      /* The dating answer button: heart red, the way dating
         wears red everywhere else in the app. */
      .btn-heart {
        background: var(--heart); color: #fff; border: none; font-weight: 700;
        border-radius: 14px; padding: 13px 18px; font-size: 15px;
        display: inline-flex; align-items: center; justify-content: center; gap: 8px;
        box-shadow: 0 6px 16px rgba(120,10,25,.35);
      }
      .btn-heart:active { transform: translateY(1px); }
      .btn-heart:disabled { opacity: .55; }
      .btn-heart svg { width: 18px; height: 18px; flex: none; }
      .btn-dark {
        background: var(--header-bg); color: var(--header-ink); border: none; font-weight: 700;
        border-radius: 14px; padding: 13px 18px; width: 100%; font-size: 15px;
        display: inline-flex; align-items: center; justify-content: center; gap: 8px;
      }
      .btn-ghost {
        background: var(--card); border: 1px solid var(--line); color: var(--ink);
        border-radius: 14px; padding: 13px 18px; font-weight: 700; font-size: 15px;
        display: inline-flex; align-items: center; justify-content: center; gap: 8px;
      }
      .btn-ghost:disabled { opacity: .55; }
      .btn-danger {
        background: var(--card); border: 1px solid var(--danger); color: var(--danger);
        border-radius: 14px; padding: 13px 18px; font-weight: 700; font-size: 15px;
        display: inline-flex; align-items: center; justify-content: center; gap: 8px;
      }
      /* Icons inside buttons are always small — never default SVG size. */
      .btn-danger svg, .btn-danger-solid svg, .btn-gold svg, .btn-ghost svg, .btn-dark svg { width: 18px; height: 18px; flex: none; }
      .btn-sm { padding: 8px 13px; font-size: 13px; border-radius: 11px; width: auto; margin-top: 0; }

      /* ---------- Auth ---------- */
      .auth-wrap { padding-top: 34px; }
      .auth-logo { text-align: center; }
      .auth-word { text-align: center; font-family: "Marcellus", Georgia, serif; font-size: 21px; letter-spacing: .3em; text-indent: .3em; margin-top: 6px; color: var(--ink); }
      .auth-tag { text-align: center; color: var(--ink-soft); margin: 10px 0 26px; font-size: 13.5px; }
      .tabs { display: flex; background: var(--seg-bg); border-radius: 13px; padding: 4px; gap: 4px; }
      .tabs button { flex: 1; background: transparent; border: none; color: var(--ink-soft); padding: 9px 4px; border-radius: 10px; font-weight: 700; font-size: 13.5px; }
      .tabs button.on { background: var(--card); color: var(--ink); box-shadow: 0 2px 8px rgba(0,0,0,.12); }
      .auth-note { color: var(--ink-faint); font-size: 11px; margin-top: 18px; line-height: 1.5; }
      .auth-link {
        display: block; margin: 14px auto 0; background: none; border: none; padding: 6px 10px;
        color: var(--gold-text); font-weight: 700; font-size: 14px;
        text-decoration: underline; text-underline-offset: 3px;
      }
      .auth-link:disabled { opacity: .55; text-decoration: none; }
      .stay-row { display: flex; align-items: center; gap: 9px; margin-top: 16px; cursor: pointer; }
      .stay-row input { width: 19px; height: 19px; flex: none; margin: 0; accent-color: var(--gold); }
      .stay-row .stay-txt { font-size: 14px; font-weight: 600; color: var(--ink); }
      .stay-hint { color: var(--ink-faint); font-size: 12px; margin-top: 5px; line-height: 1.45; }

      /* ---------- Avatars ---------- */
      .ava {
        border-radius: 50%; flex: none; display: inline-flex; align-items: center; justify-content: center;
        color: #fff; font-weight: 800; background-size: cover; background-position: center;
      }
      .ava.brown { background: linear-gradient(140deg, #eecf87, #d9a83f); color: #3a2708; }
      /* Tapping a profile picture anywhere opens its enlarged
         preview (Casey's call, 2026-10-20): a dark stage with the
         photo blown up large and clear, and a way back to exactly
         where you were. */
      [data-ava] { cursor: pointer; }
      .ava-preview {
        position: absolute; inset: 0; z-index: 90; background: rgba(24, 15, 3, .93);
        display: flex; flex-direction: column; align-items: center; justify-content: center;
        padding: 26px 22px; animation: avp-in .18s ease-out;
      }
      @keyframes avp-in { from { opacity: 0; } to { opacity: 1; } }
      .ava-preview .avp-x {
        position: absolute; top: 14px; right: 14px; width: 38px; height: 38px; border-radius: 50%;
        background: rgba(250,241,220,.14); color: #fdf6e0; border: 1px solid rgba(250,241,220,.35);
        display: inline-flex; align-items: center; justify-content: center; font-size: 16px;
      }
      .ava-preview .avp-photo {
        width: min(76vw, 320px); height: min(76vw, 320px); border-radius: 50%; flex: none;
        object-fit: cover; display: block; box-shadow: 0 0 0 3px var(--gold), 0 18px 50px rgba(0,0,0,.55);
        background: linear-gradient(140deg, #eecf87, #d9a83f);
      }
      .ava-preview .avp-initial {
        width: min(76vw, 320px); height: min(76vw, 320px); border-radius: 50%; flex: none;
        display: flex; align-items: center; justify-content: center;
        font-size: 120px; font-weight: 800; color: #3a2708;
        background: linear-gradient(140deg, #eecf87, #d9a83f);
        box-shadow: 0 0 0 3px var(--gold), 0 18px 50px rgba(0,0,0,.55);
      }
      .ava-preview .avp-name { font-family: "Marcellus", Georgia, serif; font-size: 23px; color: #fdf6e0; margin-top: 20px; text-align: center; }
      .ava-preview .avp-sub { color: rgba(253,246,224,.72); font-size: 13px; margin-top: 5px; text-align: center; line-height: 1.5; }
      .ava-preview .avp-btns { display: flex; gap: 10px; margin-top: 20px; flex-wrap: wrap; justify-content: center; }

      /* ---------- Home ---------- */
      .greet { margin: 4px 0 16px; }
      .greet .name { font-family: "Marcellus", Georgia, serif; font-size: 25px; color: var(--ink); }
      .greet .hi { color: var(--ink-soft); font-size: 12.5px; margin-top: 5px; }
      .sec-head { display: flex; align-items: center; gap: 8px; margin: 22px 0 10px; }
      .sec-head h3 { font-family: "Marcellus", Georgia, serif; font-size: 18px; flex: 1; color: var(--ink); }
      .sec-head .tiny { font-weight: 700; }
      .pill {
        display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 800;
        letter-spacing: .07em; text-transform: uppercase; border-radius: 99px; padding: 4px 9px;
      }
      
      .pill.pill-pin { padding: 0; gap: 0; line-height: 0; background: transparent; border: none; color: var(--gold); filter: drop-shadow(0 1px 2px rgba(20,10,2,.40)); }
      .pill-pin svg { width: 17px; height: 17px; transform-origin: 50% 85%; animation: pin-stick 3s ease-in-out infinite; }
      @keyframes pin-stick { 0% { transform: translateY(-6px) rotate(-16deg); } 10% { transform: translateY(0) rotate(4deg); } 15% { transform: translateY(-1.5px) rotate(-2.5deg); } 20%, 100% { transform: translateY(0) rotate(0deg); } }
      .portion:has(.pin-top) { position: relative; }
      .pill-pin.pin-top { position: absolute; top: -9px; left: 50%; transform: translateX(-50%); z-index: 4; }
      @media (prefers-reduced-motion: reduce) { .pill-pin svg { animation: none; } }.pill-gold { background: var(--gold); color: #241503; }
      .pill-lane { background: var(--chip); color: var(--ink-soft); }
      .pill-live { background: var(--live); color: #fff; }
      .pill-priv { background: transparent; border: 1px solid var(--line); color: var(--ink-faint); }
      /* Portion of the Day, kept a bit smaller (Casey's call,
         2026-10-20): same content and spot, tighter footprint. */
      .potd-hero {
        border-radius: 16px; padding: 13px 14px; border: 1.5px solid var(--gold);
        background: linear-gradient(160deg, var(--card2), var(--card) 70%); box-shadow: var(--shadow);
      }
      .potd-hero .potd-text { font-family: "Marcellus", Georgia, serif; font-size: 16.5px; line-height: 1.5; margin: 8px 0 0; }
      .potd-hero .potd-verse { font-size: 13px; line-height: 1.6; margin: 8px 0 0; padding-top: 8px; border-top: 1px solid var(--line-soft); }
      .potd-hero .potd-verse b { color: var(--gold-text); font-size: 12.5px; }
      /* Portion of the Day docks to the side of the screen at the
         top as a small tab (Casey's call, 2026-10-20): it hugs the
         right edge, and tapping it expands the full portion open;
         tapping again tucks it back to the side. */
      .potd-dock { display: flex; flex-direction: column; align-items: flex-end; margin-bottom: 6px; }
      .potd-tab {
        display: inline-flex; align-items: center; gap: 6px;
        margin-right: -18px; padding: 9px 15px 9px 13px;
        border: 1.5px solid var(--gold); border-right: none;
        border-radius: 99px 0 0 99px;
        background: var(--gold); color: #241503;
        font-size: 11px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase;
        box-shadow: var(--shadow); font-family: inherit; cursor: pointer;
      }
      .potd-tab .potd-chev { font-size: 14px; line-height: 1; transition: transform .3s ease; }
      .potd-dock.open .potd-tab .potd-chev { transform: rotate(90deg); }
      .potd-panel {
        width: 100%; max-height: 0; opacity: 0; overflow: hidden;
        transition: max-height .38s ease, opacity .3s ease, margin-top .38s ease;
      }
      .potd-dock.open .potd-panel { max-height: 420px; opacity: 1; margin-top: 10px; }
      .stack { display: flex; flex-direction: column; gap: 10px; }
      .stack-lg { display: flex; flex-direction: column; gap: 14px; }
      .card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); }
      .kinrow {
        display: flex; align-items: center; gap: 11px; width: 100%; padding: 12px 14px;
        background: var(--card); border: 1px solid var(--line); border-radius: 16px;
        cursor: pointer; text-align: left; font-family: inherit; color: var(--ink);
      }
      .kinrow:active { background: var(--card2); }
      .kinrow .kin-main { flex: 1; min-width: 0; }
      .kinrow .kin-top { font-size: 12.5px; }
      .kinrow .ksnip {
        font-size: 12.5px; color: var(--ink-soft); line-height: 1.45; display: -webkit-box;
        -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-top: 2px;
      }
      .newdot { width: 8px; height: 8px; border-radius: 50%; background: var(--live); flex: none; }
      .streakrow { display: flex; gap: 9px; }
      .streakchip {
        flex: 1; background: var(--card); border: 1px solid var(--line); border-radius: 16px;
        padding: 12px 8px 10px; text-align: center; box-shadow: var(--shadow);
      }
      .streakchip b { font-size: 19px; display: block; line-height: 1.1; font-variant-numeric: tabular-nums; }
      .streakchip span {
        font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
        color: var(--ink-soft); display: block; margin-top: 3px; line-height: 1.3;
      }
      .goalglance { padding: 13px 15px; width: 100%; text-align: left; font-family: inherit; color: var(--ink); cursor: pointer; }
      .goalglance + .goalglance { margin-top: 9px; }
      .goalglance .gg-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
      .goalglance .gg-top b { font-size: 14.5px; }
      .goalglance p { font-size: 11px; color: var(--ink-faint); margin: 7px 0 0; }
      .progress { height: 8px; border-radius: 99px; background: var(--seg-bg); overflow: hidden; margin-top: 9px; }
      .progress i { display: block; height: 100%; background: linear-gradient(90deg, var(--gold-2), var(--gold-deep)); border-radius: 99px; }

      /* ---------- Doors ---------- */
      .door {
        border-radius: 22px; padding: 22px 20px; cursor: pointer; color: var(--door-ink);
        border: 1px solid var(--door-chip-line); width: 100%; text-align: left; font-family: inherit;
        background: var(--door-bg);
        box-shadow: 0 10px 24px rgba(122,82,20,.20); display: block;
      }
      .door:active { transform: scale(.99); }
      .door h3 { font-family: "Marcellus", Georgia, serif; font-size: 24px; letter-spacing: .04em; color: var(--door-ink); }
      .door p { margin: 5px 0 0; font-size: 13px; color: var(--door-sub); line-height: 1.45; }
      .door .dicon {
        width: 46px; height: 46px; border-radius: 14px; background: var(--door-chip-bg);
        border: 1px solid var(--door-chip-line); display: flex; align-items: center; justify-content: center;
        margin-bottom: 12px; color: var(--gold-deep); position: relative;
      }
      .door .dicon svg { width: 24px; height: 24px; }
      .door.mini { padding: 14px 12px; border-radius: 22px; }
      .door.mini .dicon { width: 36px; height: 36px; border-radius: 11px; margin-bottom: 8px; }
      .door.mini .dicon svg { width: 19px; height: 19px; }
      .door.mini h3 { font-size: 15.5px; }
      .door.mini p { font-size: 11px; margin-top: 3px; }
      /* Home doors run a little smaller (Casey's call, 2026-10-20):
         same doors, order and labels — tighter footprint. Scoped to
         Home so the full-size lane doors in Social keep their size.
         And they stand in TWO ROWS (Casey's call, 2026-10-21):
         three doors to a row, the whole house visible at a glance
         instead of one tall stack to scroll through. Cells run
         compact. The World Map door is NOT one of the cells
         (Casey's call, 2026-10-21): it stands full-width UNDER
         all the other doors, wearing the wide .door-world style
         below — globe beside its words, like it did before the
         two-rows change. */
      .home-doors { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
      /* The two rows stand a touch shorter (Casey's call,
         2026-10-22): a little less vertical padding, a slightly
         smaller icon chip and a tighter gap under it — just a
         bit, not a redesign. Only the grid doors; the wide
         World Map door under them keeps its size. */
      .home-doors .door { padding: 11px 12px; border-radius: 18px; height: 100%; }
      .home-doors .door h3 { font-size: 17px; }
      .home-doors .door p { font-size: 11.5px; margin-top: 4px; line-height: 1.4; }
      .home-doors .door .dicon { width: 34px; height: 34px; border-radius: 11px; margin-bottom: 6px; }
      .home-doors .door .dicon svg { width: 19px; height: 19px; }
      .home-door-under { margin-top: 10px; }
      /* Lane blocks run a little smaller (Casey's call,
         2026-10-24): the lane doors on the Social picker —
         the full blocks and the mini blocks alike — shed a
         little padding and wear a slightly smaller icon
         chip. Scoped by [data-lane] so Home's doors and
         the wide Events door keep their size. */
      .door[data-lane] { padding: 14px 14px; }
      .door[data-lane] .dicon { width: 34px; height: 34px; border-radius: 11px; margin-bottom: 7px; }
      .door[data-lane] .dicon svg { width: 18px; height: 18px; }
      .door.mini[data-lane] { padding: 9px 9px; }
      .door.mini[data-lane] .dicon { width: 27px; height: 27px; margin-bottom: 5px; }

      /* ---------- Lanes ---------- */
      .mini-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 14px; }
      .sec-label { font-family: "Marcellus", Georgia, serif; font-size: 18px; color: var(--ink); margin: 20px 0 10px; }

      /* ---------- Portions ---------- */
      .portion {
        background: var(--card); border: 1px solid var(--line); border-radius: 20px;
        padding: 15px 16px 10px; margin-bottom: 12px; width: 100%; text-align: left;
        color: var(--ink); display: block; box-shadow: var(--shadow); font-family: inherit;
        cursor: pointer;
      }
      button.portion { cursor: pointer; }
      .portion:active { background: var(--card2); }
      .p-head { display: flex; align-items: center; gap: 10px; }
      .p-head .p-who { flex: 1; min-width: 0; }
      .p-user { font-weight: 700; color: var(--ink); font-size: 14.5px; display: block; }
      .p-time { color: var(--ink-faint); font-size: 12.5px; display: block; }
      /* The handle line of a portion's head: the @handle and,
         right beside it, the Follow button (Casey's call,
         2026-10-22). The handle gives up room first — it
         ellipsizes — so the button never gets pushed off. */
      .p-user-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
      .p-user-line .p-user { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      /* The Follow button itself: a small gold pill. Following
         here walks the app's kinsmen road — the button carries
         data-kinsmen, so the hold-to-confirm sheet, the request
         states and the repaint-every-button rule are the app's
         own; only the wording is Follow-shaped. Once a request
         is out or the tie is made, the pill quiets down (.on). */
      .p-follow {
        flex: none; border: none; cursor: pointer; font: inherit;
        font-size: 12px; font-weight: 700; line-height: 1.25;
        padding: 4px 12px; border-radius: 999px;
        background: linear-gradient(160deg, var(--gold-2), var(--gold) 55%, var(--gold-deep));
        color: #241503; -webkit-tap-highlight-color: transparent;
      }
      .p-follow.on { background: var(--card2); color: var(--ink-soft); box-shadow: inset 0 0 0 1px var(--line); }
      .p-follow:active { transform: translateY(1px); }
      .p-body { font-size: 15px; line-height: 1.55; margin: 10px 0 4px; white-space: pre-wrap; word-break: break-word; }
      .p-actions {
        display: flex; align-items: center; justify-content: space-around;
        border-top: 1px solid var(--line-soft); margin-top: 10px; padding-top: 6px;
      }
      .p-act {
        display: inline-flex; align-items: center; gap: 6px; color: var(--ink-soft);
        font-size: 13px; font-weight: 700; background: none; border: none; padding: 7px 8px; border-radius: 9px;
        font-variant-numeric: tabular-nums;
      }
      span.p-act { cursor: pointer; }
      .p-act svg { width: 18px; height: 18px; }
      .p-act.active { color: var(--gold-deep); }
      .p-act.active svg { fill: currentColor; }
      .p-act.hearted { color: var(--heart); }
      .p-act.hearted svg { fill: var(--heart); stroke: var(--heart); }
      /* A hearted heart burns bright red with a soft red glow. Tapping
         it pops the heart (heart-pop) and a burst of little hearts
         floats up and fades — heartBurst() in the script spawns the
         burst spans; the glow stays as long as the heart stays on. */
      .p-act { position: relative; }
      .p-act.hearted svg { filter: drop-shadow(0 0 4px var(--heart)) drop-shadow(0 0 11px rgba(255,45,85,.5)); }
      .p-act svg.pop, .shorts-act svg.pop { animation: heart-pop .45s ease; transform-origin: center; }
      @keyframes heart-pop {
        0% { transform: scale(1); }
        35% { transform: scale(1.5) rotate(-8deg); }
        62% { transform: scale(.9); }
        100% { transform: scale(1); }
      }
      .heart-burst { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
      .heart-burst i { position: absolute; left: 50%; top: 50%; width: 11px; height: 11px; color: var(--heart); opacity: 0; animation: heart-fly .8s ease-out forwards; }
      .heart-burst i svg { width: 100%; height: 100%; display: block; fill: currentColor; stroke: currentColor; filter: drop-shadow(0 0 3px rgba(255,45,85,.6)); }
      @keyframes heart-fly {
        0% { opacity: 0; transform: translate(-50%, -50%) scale(.35) rotate(0deg); }
        18% { opacity: 1; }
        100% { opacity: 0; transform: translate(calc(-50% + var(--dx, 0px)), calc(-50% + var(--dy, -34px))) scale(1.05) rotate(var(--rot, 0deg)); }
      }
      /* A fresh reportion celebrates in gold (Casey's call,
         2026-10-22): a single tap reportions the X way — no sheet,
         no picker — so the button itself carries the feedback.
         The icon pops with a swing and a little burst of gold
         reportion glyphs floats up and fades; reportionBurst()
         in the script spawns the burst spans. */
      .p-act svg.repop, .shorts-act svg.repop { animation: reportion-pop .5s ease; transform-origin: center; }
      @keyframes reportion-pop {
        0% { transform: scale(1) rotate(0deg); }
        35% { transform: scale(1.5) rotate(16deg); }
        62% { transform: scale(.9) rotate(-6deg); }
        100% { transform: scale(1) rotate(0deg); }
      }
      .rep-burst { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
      .rep-burst i { position: absolute; left: 50%; top: 50%; width: 11px; height: 11px; color: var(--gold-deep); opacity: 0; animation: heart-fly .8s ease-out forwards; }
      .rep-burst i svg { width: 100%; height: 100%; display: block; fill: currentColor; stroke: currentColor; filter: drop-shadow(0 0 3px rgba(201,150,44,.55)); }
      /* The reaction bar (Casey's call, 2026-10-24): a TAP on a
         portion's heart stays the plain heart it has always been;
         HOLDING the heart slides a bar of faces out above the
         button. Picking a face trades the heart glyph for that
         face on the button, folds the pick into the count beside
         it, and leaves a summary of chips under the action row.
         The bar is one shared element (openReactionBar in the
         script), fixed-positioned over the button it grew from;
         its faces pop in one after another as it slides out. */
      .react-bar {
        position: fixed; z-index: 300; display: flex; gap: 2px; padding: 6px 9px;
        background: var(--card); border: 1px solid var(--line); border-radius: 999px;
        box-shadow: 0 12px 32px rgba(0,0,0,.5);
        transform-origin: bottom center;
      }
      .react-bar.opening { animation: react-slide-in .24s cubic-bezier(.2,1.35,.4,1) forwards; }
      .react-bar.closing { animation: react-slide-out .15s ease-in forwards; }
      @keyframes react-slide-in {
        from { opacity: 0; transform: translateY(12px) scale(.55); }
        to { opacity: 1; transform: translateY(0) scale(1); }
      }
      @keyframes react-slide-out {
        to { opacity: 0; transform: translateY(9px) scale(.65); }
      }
      /* The faces stay alive while the bar is up (Casey's
         call, 2026-10-24, same as the message hold menu):
         after the staggered pop-in they keep moving — the
         heart beats, the laughing face rocks, the rest bob
         and sway, each on its own timing. Hovering or
         pressing a face makes it cheer, and the picked face
         leaps up before the bar closes. */
      .react-bar button {
        font-size: 29px; line-height: 1; background: none; border: none; padding: 5px 6px;
        border-radius: 50%; cursor: pointer; font-family: inherit;
        -webkit-tap-highlight-color: transparent;
        animation: react-face-pop .34s cubic-bezier(.2,1.9,.4,1) backwards, react-face-bob 2.6s ease-in-out .55s infinite;
        transition: transform .12s ease;
      }
      .react-bar button:nth-child(1) { animation-name: react-face-pop, react-face-beat; animation-duration: .34s, 1.7s; }
      .react-bar button:nth-child(2) { animation-delay: .04s, .62s; animation-duration: .34s, 2.9s; }
      .react-bar button:nth-child(3) { animation-delay: .08s, .7s; animation-duration: .34s, 2.2s; }
      .react-bar button:nth-child(4) { animation-name: react-face-pop, react-face-rock; animation-delay: .12s, .78s; animation-duration: .34s, 1.9s; }
      .react-bar button:nth-child(5) { animation-delay: .16s, .86s; animation-duration: .34s, 2.4s; }
      .react-bar button:nth-child(6) { animation-delay: .2s, .94s; animation-duration: .34s, 2.7s; }
      @keyframes react-face-pop {
        0% { transform: scale(.1) translateY(12px) rotate(-16deg); opacity: 0; }
        60% { transform: scale(1.28) translateY(-4px) rotate(6deg); opacity: 1; }
        100% { transform: scale(1) translateY(0) rotate(0deg); }
      }
      @keyframes react-face-bob {
        0%, 100% { transform: translateY(0) rotate(0deg); }
        30% { transform: translateY(-4px) rotate(-4deg); }
        65% { transform: translateY(2px) rotate(3.5deg); }
      }
      @keyframes react-face-beat {
        0%, 100% { transform: scale(1); }
        12% { transform: scale(1.24); }
        24% { transform: scale(1); }
        36% { transform: scale(1.16); }
        52% { transform: scale(1); }
      }
      @keyframes react-face-rock {
        0%, 100% { transform: rotate(0deg) translateY(0); }
        25% { transform: rotate(-9deg) translateY(-2px); }
        75% { transform: rotate(9deg) translateY(-2px); }
      }
      .react-bar button:hover, .react-bar button:active { transform: scale(1.38) translateY(-4px); animation: react-face-cheer .38s ease-in-out infinite alternate; }
      @keyframes react-face-cheer { from { transform: scale(1.3) translateY(-3px) rotate(-7deg); } to { transform: scale(1.52) translateY(-6px) rotate(7deg); } }
      .react-bar button.picked { animation: react-face-picked .22s ease-in forwards; }
      @keyframes react-face-picked {
        35% { transform: scale(1.85) rotate(16deg); opacity: 1; }
        100% { transform: scale(2.3) translateY(-10px) rotate(-10deg); opacity: .85; }
      }
      .react-bar button.on { background: var(--chip); box-shadow: inset 0 0 0 1.5px var(--gold-deep); }
      /* A button wearing a picked face: the face stands where the
         heart stood, and the button takes the gold of a chosen
         reaction instead of the heart's red. */
      .p-act .p-emoji, .shorts-act .p-emoji { font-size: 17px; line-height: 1; }
      .p-act.reacted, .shorts-act.reacted[data-heart] { color: var(--gold-deep); }
      /* The reaction summary: one small chip per face, biggest
         first, under a card's action row. An empty summary takes
         no room at all. */
      .p-reacts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
      .p-reacts:empty { display: none; }
      .p-reacts .pr-chip {
        display: inline-flex; align-items: center; gap: 4px;
        font-size: 12.5px; font-weight: 700; color: var(--ink-soft);
        background: var(--card2); border: 1px solid var(--line-soft);
        border-radius: 999px; padding: 3px 9px 3px 7px;
        font-variant-numeric: tabular-nums;
      }
      .p-reacts .pr-chip.mine { border-color: var(--gold-deep); color: var(--gold-deep); }
      @media (prefers-reduced-motion: reduce) {
        .react-bar.opening, .react-bar.closing, .react-bar button, .react-bar button:hover, .react-bar button:active, .react-bar button.picked { animation: none; }
      }
      .p-act.bookmarked { color: var(--gold-deep); }
      .p-act.bookmarked svg { fill: currentColor; }
      /* Send-to-Debate-Hall vote: my cast vote lights the scales
         gold; a portion already standing in the hall wears the
         same gold and never takes another vote. */
      .p-act.debate-send.voted { color: var(--gold-deep); }
      .p-act.debate-send.voted svg { fill: currentColor; }
      .p-act.debate-send.sent { color: var(--gold-deep); }
      .p-act.debate-send.sent svg { fill: currentColor; }
      /* The card a "take me to it in the Debate Hall" landing
         scrolls to flashes its rim twice so the eye finds it. */
      .portion.flash-card { animation: flash-card 1.1s ease 2; }
      @keyframes flash-card {
        0%, 100% { box-shadow: 0 0 0 0 rgba(210,154,31,0); }
        50% { box-shadow: 0 0 0 3px var(--gold-deep); }
      }
      @media (prefers-reduced-motion: reduce) { .portion.flash-card { animation: none; box-shadow: 0 0 0 3px var(--gold-deep); } }
      .p-act:disabled { opacity: .55; }
      .p-del {
        width: 32px; height: 32px; border-radius: 50%; flex: none;
        border: 1px solid var(--line); background: var(--card); color: var(--danger);
        display: inline-flex; align-items: center; justify-content: center; padding: 0;
      }
      .p-del svg { width: 15px; height: 15px; }
      .p-del:active { transform: scale(.92); }
      .p-del:disabled { opacity: .55; }
      .p-menu {
        width: 32px; height: 32px; border-radius: 50%; flex: none; margin-left: auto;
        border: 1px solid var(--line); background: var(--card); color: var(--ink-soft);
        display: inline-flex; align-items: center; justify-content: center; padding: 0;
        font-size: 17px; font-weight: 800; line-height: 1; cursor: pointer;
      }
      .p-menu:active { transform: scale(.92); }
      .p-menu:disabled { opacity: .55; }
      .btn-danger-solid {
        background: var(--danger); color: #fff; border: none; font-weight: 700;
        border-radius: 14px; padding: 13px 18px; width: 100%; font-size: 15px; margin-top: 20px;
        display: inline-flex; align-items: center; justify-content: center; gap: 8px;
      }
      .btn-danger-solid:disabled { opacity: .55; }
      .kin-acts { display: flex; flex-direction: column; gap: 1px; flex: none; }
      .kin-acts .p-act { padding: 3px 6px; font-size: 12px; }
      .kin-acts .p-act svg { width: 16px; height: 16px; }
      .kinsmen-row { margin-top: 10px; }
      .feed-tools { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
      .feed-tools .ft-t { flex: 1; color: var(--ink-soft); font-size: 12.5px; }
      /* (The old per-lane Live section header styles went with the
         section itself — lives now live behind each lane's Lives
         tab, with the Go live button centered and no heading.) */

      /* ---------- Detail / comments ---------- */
      .comment { padding: 12px 0; border-bottom: 1px solid var(--line-soft); }
      .comment .c-head { display: flex; gap: 8px; align-items: center; }
      .comment .c-user { font-weight: 700; color: var(--ink); font-size: 14.5px; cursor: pointer; }
      .comment .c-time { color: var(--ink-faint); font-size: 12.5px; }
      .comment .c-body { margin-top: 3px; font-size: 15px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
      .comment-box { display: flex; gap: 10px; margin-top: 16px; align-items: flex-end; }
      .comment-box textarea { flex: 1; min-height: 48px; max-height: 120px; }
      .comment-box .btn-gold { width: auto; margin-top: 0; padding: 13px 18px; }
      /* The GIF option lives inside the comment bubble itself
         (Casey's call, 2026-10-20): a small GIF chip tucked in
         the input's corner — no standalone button under the box. */
      .comment-input-wrap { position: relative; flex: 1; min-width: 0; }
      .comment-input-wrap textarea { width: 100%; padding-right: 12px; padding-bottom: 42px; }
      .gif-in-btn {
        position: absolute; right: 8px; bottom: 9px; z-index: 2;
        border: 1.5px solid var(--line); background: var(--card2); color: var(--ink-soft);
        font-size: 11px; font-weight: 800; letter-spacing: .07em; line-height: 1;
        border-radius: 7px; padding: 5px 7px; cursor: pointer; font-family: inherit;
      }
      .gif-in-btn:active { background: var(--chip); }

      /* ---------- Assistant ---------- */
      .tiles { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
      .tile {
        background: var(--card); border: 1px solid var(--line); border-radius: 18px;
        padding: 16px 12px; cursor: pointer; text-align: center; font-family: inherit;
        color: var(--ink); box-shadow: var(--shadow);
      }
      .tile:active { background: var(--card2); }
      .tile .tic {
        width: 44px; height: 44px; margin: 0 auto 8px; border-radius: 13px; background: var(--chip);
        display: flex; align-items: center; justify-content: center; color: var(--gold-text);
      }
      .tile .tic svg { width: 21px; height: 21px; }
      .tile b { font-size: 14px; display: block; }
      .tile span { font-size: 11.5px; color: var(--ink-soft); display: block; margin-top: 2px; line-height: 1.35; }
      .item {
        background: var(--card); border: 1px solid var(--line); border-radius: 16px;
        padding: 14px 16px; margin-bottom: 10px; width: 100%; text-align: left;
        color: var(--ink); display: block; box-shadow: var(--shadow); font-family: inherit;
      }
      button.item { cursor: pointer; }
      button.item:active { background: var(--card2); }
      /* Goal progress (Casey's call, 2026-10-26): a goal with a
         target wears a bar under its card — log progress and the
         bar moves toward the target. */
      .goal-wrap { margin-bottom: 10px; }
      .goal-wrap .item { margin-bottom: 0; }
      .goal-wrap.has-prog .item { border-radius: 16px 16px 0 0; border-bottom: none; }
      .goal-prog { background: var(--card); border: 1px solid var(--line); border-top: none; border-radius: 0 0 16px 16px; padding: 2px 16px 13px; box-shadow: var(--shadow); }
      .goal-bar { height: 10px; border-radius: 99px; background: var(--chip); overflow: hidden; }
      .goal-bar i { display: block; height: 100%; background: var(--gold); border-radius: 99px; }
      .goal-prog-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 8px; }
      .item .i-t { font-weight: 700; font-size: 15px; }
      .item .i-s { color: var(--ink-soft); font-size: 14px; margin-top: 2px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
      .item .i-meta { color: var(--ink-faint); font-size: 12.5px; margin-top: 5px; }
      .item.done .i-t, .item.done .i-s { text-decoration: line-through; color: var(--ink-faint); }
      .check-row { display: flex; gap: 13px; align-items: flex-start; }
      .check-dot {
        width: 26px; height: 26px; flex: none; border-radius: 8px; border: 2px solid var(--line);
        display: flex; align-items: center; justify-content: center; color: transparent; background: var(--field);
      }
      .item.done .check-dot { background: var(--gold); border-color: var(--gold); color: #fff; }
      .check-dot svg { width: 15px; height: 15px; }
      .fab {
        position: absolute; right: 18px; bottom: calc(88px + var(--sab)); z-index: 40;
        width: 56px; height: 56px; border-radius: 50%; border: none;
        background: linear-gradient(160deg, var(--gold-2), var(--gold) 55%, var(--gold-deep)); color: #241503;
        display: flex; align-items: center; justify-content: center;
        box-shadow: 0 6px 16px rgba(138,95,19,.45);
      }
      .fab svg { width: 26px; height: 26px; }
      .fab:active { transform: translateY(1px); }
      /* The profile compose bubble (Casey's call, 2026-10-31):
         the crow alone, facing left, with a bare gold plus, floating at the
         bottom right of profile screens only — his own page and
         every member's — one tap from any profile into the
         portion composer. It lives on <body> like the upload
         bubbles do, and a watcher (see syncComposeBubble) keeps
         it standing only while a profile is the screen and no
         sheet or player layer stands over it. Its seat is the
         .fab seat (right 18px, 88px over the bottom nav); when
         an upload bubble stack is live it climbs above the
         stack instead of sitting on it (the sync measures the
         stack). z 45: above the profiles' left rail (z 30) and
         the fabs (z 40), below every sheet and player (z 66+).
         The cream face is the mark's own ground, kept in every
         theme the way the share sheet keeps its brand colors. */
      #compose-bubble {
        position: fixed; right: 18px; bottom: calc(88px + var(--sab)); z-index: 45;
        width: 56px; height: 56px; border-radius: 50%; padding: 0; cursor: pointer;
        background: #f7edd6; border: 1.5px solid var(--gold);
        box-shadow: 0 6px 16px rgba(138,95,19,.45);
        display: none; align-items: center; justify-content: center;
        transition: bottom .25s ease;
        -webkit-tap-highlight-color: transparent;
      }
      #compose-bubble.on { display: flex; }
      #compose-bubble:active { transform: translateY(1px); }
      #compose-bubble img { width: 46px; height: 46px; display: block; pointer-events: none; }
      #compose-bubble .cb-plus {
        /* Bare gold plus only (Casey's correction, 2026-10-31):
           no badge circle, no disc, no ring behind it — just the
           glyph at the bubble's rim, with a whisper of shadow so
           it stays legible over dark and light screens alike. */
        position: absolute; right: -2px; bottom: -2px; width: 19px; height: 19px;
        color: var(--gold); background: none; border: none; box-shadow: none;
        filter: drop-shadow(0 0 2px rgba(247,237,214,.9)) drop-shadow(0 1px 2px rgba(36,21,3,.55));
        display: flex; align-items: center; justify-content: center; pointer-events: none;
      }
      #compose-bubble .cb-plus svg { width: 19px; height: 19px; }
      .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
      .chip {
        border: 1px solid var(--line); background: var(--card); color: var(--ink);
        border-radius: 999px; padding: 8px 14px; font-weight: 600; font-size: 13px;
      }
      .chip.on { background: var(--header-bg); color: var(--header-ink); border-color: var(--header-bg); }
      .chip:disabled { opacity: .45; cursor: not-allowed; }
      .total-bar { cursor: pointer;
        background: linear-gradient(165deg, var(--card2), var(--card)); border: 1.5px solid var(--gold);
        border-radius: var(--radius); box-shadow: var(--shadow);
        padding: 16px; margin-bottom: 16px; display: flex; align-items: baseline; justify-content: space-between;
      }
      .total-bar .tb-l { color: var(--ink-soft); font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
      .total-bar .tb-v { font-family: "Marcellus", Georgia, serif; font-size: 28px; color: var(--ink); }
      .amt-pos { color: var(--ok); font-weight: 700; }
      .amt-neg { color: var(--danger); font-weight: 700; }
      .txn-row { display: flex; gap: 12px; align-items: baseline; }
      .txn-row .txn-amt { margin-left: auto; font-size: 16px; flex: none; }
      .txn-row .pill svg { width: 11px; height: 11px; }
      .txn-row .i-t { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
      /* Assistant: gentle nudge card, finance pie, calendar */
      .nudge {
        display: flex; gap: 12px; align-items: flex-start;
        background: var(--card); border: 1px solid var(--line); border-radius: 16px;
        padding: 14px 16px; margin-bottom: 16px; box-shadow: var(--shadow);
      }
      .nudge .n-ic {
        width: 40px; height: 40px; flex: none; border-radius: 12px; background: var(--chip);
        display: flex; align-items: center; justify-content: center; color: var(--gold-text);
      }
      .nudge .n-ic svg { width: 20px; height: 20px; }
      .nudge .n-t { font-weight: 800; font-size: 12px; letter-spacing: .05em; text-transform: uppercase; color: var(--gold-text); }
      .nudge .n-b { font-size: 14.5px; line-height: 1.5; margin-top: 2px; }
      .nudge .n-x {
        margin-left: auto; flex: none; width: 28px; height: 28px; border-radius: 50%;
        border: 1px solid var(--line-soft); background: var(--card); color: var(--ink-faint);
        display: inline-flex; align-items: center; justify-content: center; cursor: pointer; font-size: 13px; padding: 0;
      }
      .pie-wrap { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
      .pie { width: 148px; height: 148px; border-radius: 50%; flex: none; position: relative; }
      .pie::after { content: ""; position: absolute; inset: 27px; border-radius: 50%; background: var(--card); }
      .pie-legend { flex: 1; min-width: 150px; }
      .pie-row { display: flex; align-items: center; gap: 8px; font-size: 13.5px; padding: 4px 0; }
      .pie-dot { width: 11px; height: 11px; border-radius: 3px; flex: none; }
      .pie-row .pl-amt { margin-left: auto; font-weight: 700; white-space: nowrap; }
      /* Money Pie: the interactive split planner in Finance */
      .mp-center { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; pointer-events: none; text-align: center; }
      .mp-center b { font-family: "Marcellus", Georgia, serif; font-size: 22px; color: var(--ink); line-height: 1; }
      .mp-center span { font-size: 9px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-faint); margin-top: 3px; }
      .mp-row { padding: 10px 0 4px; border-top: 1px solid var(--line-soft); }
      .mp-line { display: flex; align-items: center; gap: 8px; }
      .mp-name { flex: 1; min-width: 0; border: none; border-bottom: 1px solid transparent; background: transparent; font: inherit; font-weight: 700; font-size: 14.5px; color: var(--ink); padding: 3px 0; outline: none; border-radius: 0; }
      .mp-name:focus { border-bottom-color: var(--gold); }
      .mp-amt { flex: none; font-weight: 800; font-variant-numeric: tabular-nums; font-size: 14px; }
      .mp-pctw { display: inline-flex; align-items: center; gap: 2px; flex: none; color: var(--ink-soft); font-weight: 700; font-size: 13.5px; }
      .mp-pct { width: 50px; border: 1px solid var(--line); background: var(--field); border-radius: 9px; padding: 5px 4px; font-size: 13.5px; text-align: right; color: var(--ink); }
      .mp-del { width: 26px; height: 26px; flex: none; border-radius: 50%; border: 1px solid var(--line-soft); background: var(--card); color: var(--ink-faint); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; font-size: 12px; padding: 0; }
      .mp-del:active { transform: scale(.92); }
      .mp-range { width: 100%; margin: 8px 0 2px; accent-color: var(--gold-deep); height: 26px; }
      .mp-status { margin-top: 12px; font-size: 13.5px; font-weight: 600; color: var(--ink-soft); line-height: 1.5; }
      .cal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
      .cal-title { flex: 1; text-align: center; font-family: "Marcellus", Georgia, serif; font-size: 19px; }
      .cal-nav {
        width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--line);
        background: var(--card); color: var(--ink); cursor: pointer; font-size: 16px;
        display: inline-flex; align-items: center; justify-content: center; padding: 0;
      }
      .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
      .cal-dow { text-align: center; font-size: 11px; font-weight: 800; color: var(--ink-faint); padding: 4px 0; text-transform: uppercase; letter-spacing: .05em; }
      .cal-day {
        aspect-ratio: 1; border-radius: 11px; border: 1px solid transparent; background: none;
        color: var(--ink); font-family: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
        position: relative; display: flex; align-items: center; justify-content: center; padding: 0;
      }
      .cal-day.today { border-color: var(--gold); }
      .cal-day.sel { background: var(--gold); color: #241503; }
      .cal-day .cdot { position: absolute; bottom: 5px; left: 50%; transform: translateX(-50%); width: 5px; height: 5px; border-radius: 50%; background: var(--gold-deep); }
      .cal-day.sel .cdot { background: #241503; }
      body.theme-bw .cal-day.sel { background: #141414; color: #ffffff; }
      body.theme-bw .cal-day.sel .cdot { background: #ffffff; }
      body.theme-bw-black .cal-day.sel { background: #f5f5f5; color: #111111; }
      body.theme-bw-black .cal-day.sel .cdot { background: #111111; }

      /* Assistant: habit streaks — brick by brick */
      .habit-total {
        background: linear-gradient(165deg, var(--card2), var(--card)); border: 1.5px solid var(--gold);
        border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px; margin-bottom: 16px;
      }
      .habit-total .ht-row { display: flex; gap: 10px; text-align: center; margin-top: 12px; }
      .habit-total .ht-cell { flex: 1; }
      .habit-total .ht-v { font-family: "Marcellus", Georgia, serif; font-size: 26px; color: var(--ink); font-variant-numeric: tabular-nums; }
      .habit-total .ht-l { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); margin-top: 2px; line-height: 1.3; }
      .habit-card { padding: 14px 16px; }
      .habit-top { display: flex; gap: 12px; align-items: flex-start; }
      .habit-mark {
        width: 46px; height: 46px; flex: none; border-radius: 50%; cursor: pointer; padding: 0;
        border: 2px solid var(--line); background: var(--field); color: transparent;
        display: flex; align-items: center; justify-content: center;
      }
      .habit-mark svg { width: 22px; height: 22px; }
      .habit-mark:active { transform: scale(.94); }
      .habit-card.done-today .habit-mark { background: var(--gold); border-color: var(--gold-deep); color: #241503; }
      .habit-name { font-weight: 800; font-size: 16px; line-height: 1.3; }
      .habit-sub { color: var(--ink-soft); font-size: 13px; margin-top: 2px; line-height: 1.45; }
      .habit-streak { margin-left: auto; flex: none; text-align: center; color: var(--gold-text); }
      .habit-streak .hs-v { font-family: "Marcellus", Georgia, serif; font-size: 25px; line-height: 1; display: flex; align-items: center; gap: 3px; justify-content: center; }
      .habit-streak .hs-v svg { width: 17px; height: 17px; }
      .habit-streak .hs-l { font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; margin-top: 3px; }
      .habit-week { display: flex; gap: 6px; margin-top: 12px; }
      .habit-day {
        flex: 1; text-align: center; font-size: 10px; font-weight: 700; letter-spacing: .04em;
        text-transform: uppercase; color: var(--ink-faint); padding: 6px 0 5px;
        border-radius: 9px; border: 1px solid var(--line-soft); background: var(--field);
      }
      .habit-day i { display: block; width: 8px; height: 8px; border-radius: 50%; margin: 0 auto 4px; background: var(--line); }
      .habit-day.on { color: var(--ink); border-color: var(--gold); }
      .habit-day.on i { background: var(--gold-deep); }
      .habit-day.today { border-style: dashed; border-color: var(--gold-deep); }
      .habit-mile {
        margin-top: 11px; font-size: 13px; line-height: 1.5; color: var(--gold-text);
        background: var(--chip); border-radius: 11px; padding: 9px 12px; font-weight: 600;
      }
      .habit-acts { display: flex; gap: 8px; margin-top: 11px; }
      .p-edited {
        font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
        color: var(--ink-faint); border: 1px solid var(--line-soft); border-radius: 999px;
        padding: 1px 7px; margin-left: 7px; vertical-align: 1px; white-space: nowrap;
      }

      /* Home: the World door's revolving globe */
      /* The Events door wears a little smaller than the rest
         of the hub (Casey's call, 2026-10-31): a tighter pad
         and a smaller globe — labels and balance untouched. */
      .door-world { display: flex; gap: 14px; align-items: center; padding: 16px 18px; }
      .door-world .globe-box { width: 68px; height: 68px; flex: none; }
      .door-world .globe-box canvas { width: 100%; height: 100%; display: block; }
      .door-world .door-world-txt { flex: 1; min-width: 0; }
      .door-world .globe-cap { color: var(--gold-deep); font-weight: 700; }
      body.theme-dark .door-world .globe-cap { color: var(--gold-2); }

      /* ---------- Profile ---------- */
      /* The cover photo and the profile picture stay BIG (Casey's
         call, 2026-10-22): only the bio block under them shrinks.
         Both grew a step the same day: cover 150 → 172 tall, the
         picture circle 80 → 96 across, overlapping by half.
         And a little bigger again (Casey, 2026-10-22): cover
         172 → 192 tall, the picture circle 96 → 112 across,
         still overlapping by half. */
      /* overflow hidden: a framed cover (cover_pos — dragged and
         zoomed in Edit profile's Adjust cover) is transformed past
         the box edges, and the overflow stays inside the box. */
      .prof-cover { margin: 0 -18px; position: relative; overflow: hidden; }
      .prof-cover img { width: 100%; height: 192px; object-fit: cover; display: block; }
      /* Your own cover photo is a long-press target (Casey's call,
         2026-10-22): holding it opens cover editing — change the
         photo or adjust its framing — alongside the gear route to
         Edit profile. Selection and the touch callout stay off so
         the hold never selects or pops the system menu instead. */
      #p-cover-img { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
      .prof-cover .cover-btn {
        position: absolute; top: 9px; width: 38px; height: 38px; border-radius: 50%;
        background: rgba(250,241,220,.94); color: #57400f; border: 1px solid rgba(150,105,14,.45);
        box-shadow: 0 2px 10px rgba(96,62,14,.35);
        display: inline-flex; align-items: center; justify-content: center;
      }
      .prof-cover .cover-btn svg { width: 19px; height: 19px; }
      /* ---------- Cover framing adjuster ----------
         Opened from Edit profile's "Adjust cover" button: a stage
         with the cover box's own proportions (about 11:5, the profile
         cover's width-to-height) where the photo is dragged and
         pinched / slid into place. The stage photo carries no
         transition — the framing follows the finger exactly. */
      .cadj-overlay {
        position: absolute; inset: 0; z-index: 92; background: var(--app-bg);
        overflow-y: auto; padding: 18px 18px calc(18px + var(--sab));
      }
      .cadj-stage {
        position: relative; width: 100%; aspect-ratio: 11 / 5; margin-top: 14px;
        border-radius: 14px; overflow: hidden; background: #171208;
        border: 1px solid var(--line); touch-action: none; cursor: grab;
      }
      .cadj-stage:active { cursor: grabbing; }
      .cadj-stage img {
        width: 100%; height: 100%; object-fit: cover; display: block;
        pointer-events: none; user-select: none; -webkit-user-select: none;
      }
      .cadj-zoom { display: flex; align-items: center; gap: 10px; margin-top: 16px; font-size: 13px; font-weight: 700; color: var(--ink-soft); }
      .cadj-zoom input[type="range"] { flex: 1; }
      .cadj-zoom .cadj-pct { min-width: 42px; text-align: right; font-variant-numeric: tabular-nums; color: var(--ink); }
      .prof-head { text-align: center; margin-top: -56px; position: relative; padding: 0; }
      .prof-avatar {
        width: 112px; height: 112px; margin: 0 auto; border-radius: 50%;
        box-shadow: 0 0 0 2px var(--app-bg), 0 0 0 4.5px var(--gold);
        display: flex; align-items: center; justify-content: center;
        font-size: 41px; font-weight: 800; color: #3a2708;
        background: linear-gradient(140deg, #eecf87, #d9a83f);
      }
      .prof-name { font-family: "Marcellus", Georgia, serif; font-size: 19px; line-height: 1.15; color: var(--ink); margin-top: 3px; }
      /* The @handle is the tap target for the About this
         account page (Casey's call, 2026-10-24 — one tap
         replaced the long-press): selection and the touch
         callout stay off so a tap never selects the text or
         pops the system menu instead. */
      .prof-handle { color: var(--ink-faint); font-size: 12px; line-height: 1.3; margin-top: 1px; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
      #u-handle, #p-handle { cursor: pointer; }
      /* The bio block stays SHORT on the profile itself (Casey's
         call, 2026-10-22, repeated): the bio shows as ONE compact
         line, so even a long bio can't stretch the header — the
         full text still lives in Edit Profile. The rows around it
         (meta line, stats, buttons, tabs) are tightened to match,
         while the cover and picture above stay big. */
      .prof-bio {
        color: var(--ink); font-size: 13px; line-height: 1.3; margin: 3px auto 0; max-width: 330px;
        white-space: pre-wrap; overflow-wrap: anywhere;
        display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden;
      }
      .prof-from { color: var(--ink-soft); font-size: 12.5px; line-height: 1.3; margin: 2px auto 0; max-width: 340px; }
      /* Links ride one wrapping row, and the from line sits
         underneath it on its own line. (The Joined line that used
         to ride this row left the profile on 2026-10-22 — the
         join date now lives only on the About this account page,
         opened by tapping the @handle.) */
      .prof-meta {
        display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
        gap: 2px 9px; margin-top: 2px;
      }
      /* The bio area on your own profile sits under the stats row,
         outside the centered .prof-head — this wrapper keeps the
         bio, links, and where-you're-from line in the
         middle too, same as the picture and @name (Casey's call,
         2026-10-22). */
      .prof-bioarea { text-align: center; }
      .prof-actions {
        display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
        gap: 6px; margin-top: 6px;
      }
      .prof-actions .btn-gold, .prof-actions .btn-ghost {
        margin: 0 !important; padding: 7px 11px !important;
      }
      /* The Follow pill in a profile's actions row matches the
         row's other buttons in size (Casey's call, 2026-10-24). */
      .prof-actions .p-follow { font-size: 13px; padding: 7px 13px; }
      .prof-edit-btn { margin-top: 7px !important; padding: 10px 18px !important; }
      /* The own-profile button row is two across (Casey's call,
         2026-10-22): Break Bread and Subscriptions — Share profile
         left the row to ride as a floating bubble on the left
         side of the profile (see .prof-bubbles below). Icon over
         label keeps "Subscriptions" whole on narrow phones
         instead of squeezing it beside its icon. */
      .prof-share-row { display: flex; gap: 8px; margin-top: 5px; }
      .prof-share-row .btn-ghost {
        flex: 1; min-width: 0; flex-direction: column; gap: 3px;
        padding: 9px 4px; font-size: 13px; white-space: nowrap;
      }
      /* Share profile and Block ride as floating bubbles down the
         LEFT side of every profile (Casey's call, 2026-10-22):
         round bubbles pinned to the left edge just under the
         cover, staying put while the profile scrolls under them,
         so neither one junks up the bio or the button rows. The
         bubbles live in the profile's own markup, so they leave
         with the screen; sheets (z 70) and the toast (z 80) still
         ride above them.
         The bubbles are DRAGGABLE too (Casey's call, 2026-10-22):
         press one and slide it up or down the left edge; it stays
         where you leave it. Each bubble carries its own top, so
         Share and Block move independently — a plain tap still
         opens Share / Block (see makeBubbleDraggable). The rail
         itself never takes a touch: only the bubbles do. */
      .prof-bubbles {
        position: fixed; left: 10px; top: 0; bottom: 0; width: 50px;
        z-index: 30; pointer-events: none;
      }
      .prof-bubble {
        position: absolute; left: 0; top: 120px;
        width: 50px; min-height: 50px; padding: 5px 2px 6px; border-radius: 999px;
        background: var(--card); border: 1px solid var(--line); color: var(--ink);
        box-shadow: 0 6px 18px rgba(96,62,14,.28);
        display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
        font-family: inherit; cursor: pointer; pointer-events: auto;
        touch-action: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
      }
      /* The rail rides HIGH (Casey's call, 2026-10-24): the
         stack used to start at 178px, which put the lowest
         bubble's bottom edge right where the profile's
         actions row begins — riding over the Kinsmen
         button. The whole stack sits higher now, up on the
         cover, so Bread clears that row with room to spare.
         (A spot saved by dragging still wins for the
         session — see profBubbleTops.) */
      .prof-bubble:nth-child(2) { top: 180px; }
      .prof-bubble:nth-child(3) { top: 240px; }
      /* Break Bread rides the rail too (Casey's call,
         2026-10-23): same bubble, gold-tinted so the gift
         button reads at a glance beside Share and Block. */
      .prof-bubble.bread { color: var(--gold-deep); border-color: var(--gold); background: linear-gradient(160deg, var(--card2), var(--card) 70%); }
      /* Break Bread breathes (Casey's call, 2026-10-28 — it
         should be "more animated than that"): the loaf bobs
         and tilts on its own, and a gold ring swells off the
         bubble and fades, on the same easy clock. Only
         transform and opacity move, and neither rides the
         bubble's own transform — the tap squash and the
         dragging lift keep their hands on that — so the
         three never fight. Dragging stills both. */
      .prof-bubble.bread > span[aria-hidden="true"] { display: inline-block; animation: bread-bob 2.8s ease-in-out infinite; }
      @keyframes bread-bob {
        0%, 100% { transform: translateY(0) rotate(0deg); }
        30% { transform: translateY(-3px) rotate(-7deg); }
        55% { transform: translateY(0) rotate(0deg); }
        75% { transform: translateY(-2px) rotate(5deg); }
      }
      .prof-bubble.bread::after {
        content: ""; position: absolute; inset: -1px; border-radius: 999px;
        border: 1.5px solid var(--gold); opacity: 0; pointer-events: none;
        animation: bread-ring 2.8s ease-out infinite;
      }
      @keyframes bread-ring {
        0% { transform: scale(1); opacity: .65; }
        55% { transform: scale(1.28); opacity: 0; }
        100% { transform: scale(1.28); opacity: 0; }
      }
      .prof-bubble.bread.dragging > span[aria-hidden="true"], .prof-bubble.bread.dragging::after { animation-play-state: paused; }
      @media (prefers-reduced-motion: reduce) {
        .prof-bubble.bread > span[aria-hidden="true"], .prof-bubble.bread::after { animation: none; }
      }
      .prof-bubble svg { width: 20px; height: 20px; }
      .prof-bubble .pb-l {
        font-size: 8.5px; font-weight: 800; letter-spacing: .04em;
        text-transform: uppercase; line-height: 1.1;
      }
      .prof-bubble:active { transform: scale(.93); }
      .prof-bubble.dragging {
        transform: scale(1.07); cursor: grabbing; z-index: 2;
        border-color: var(--gold-deep);
        box-shadow: 0 12px 28px rgba(96,62,14,.42);
      }
      .prof-bubble.danger { color: var(--danger); border-color: var(--danger); }
      .ava img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }
      /* About this account rows: where the account is based, the
         date it joined, and which app it is on. */
      .acct-row { display: flex; align-items: center; gap: 12px; padding: 12px 2px; border-top: 1px solid var(--line-soft); }
      .acct-ic {
        width: 38px; height: 38px; flex: none; border-radius: 12px; background: var(--chip);
        color: var(--gold-text); display: inline-flex; align-items: center; justify-content: center;
      }
      .acct-ic svg { width: 19px; height: 19px; }
      .acct-main { flex: 1; min-width: 0; }
      .acct-l { display: block; font-size: 11px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-faint); }
      .acct-v { display: block; font-size: 15.5px; font-weight: 700; color: var(--ink); margin-top: 2px; }
      .stat-row { display: flex; margin: 4px 0 0; text-align: center; }
      .stat { flex: 1; }
      .stat .s-v { font-size: 15px; line-height: 1.2; font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }
      .stat .s-l { font-size: 10.5px; line-height: 1.25; color: var(--ink-faint); letter-spacing: .05em; text-transform: uppercase; margin-top: 0; }
      /* Tappable profile stats (Casey's call, 2026-10-24): the
         followers and following numbers are doors — tap one and
         the real list of people opens (showFollowList). */
      .stat-tap { cursor: pointer; border-radius: 10px; }
      .stat-tap:active { background: var(--line-soft); }

      /* ---------- Lane tags ---------- */
      .lane-tag {
        display: inline-flex; align-items: center; gap: 4px;
        background: linear-gradient(160deg, var(--gold-2), var(--gold) 60%, var(--gold-deep));
        color: #241503; border: none; border-radius: 999px;
        padding: 5px 12px; font-size: 12.5px; font-weight: 800; letter-spacing: .02em;
        box-shadow: 0 3px 8px rgba(138,95,19,.35); font-family: inherit;
      }
      .lane-tag:active { transform: scale(.96); }
      .lane-tag-row { margin: 9px 0 0; }
      .kinrow .lane-tag { padding: 3px 9px; font-size: 11px; box-shadow: none; }

      /* ---------- Lane favorites ---------- */
      .door-wrap { position: relative; }
      .lane-star {
        position: absolute; top: 10px; right: 10px; z-index: 2;
        width: 34px; height: 34px; border-radius: 50%;
        border: 1px solid rgba(150,105,14,.4); background: rgba(250,241,220,.85);
        color: var(--gold-deep); display: inline-flex; align-items: center; justify-content: center; padding: 0;
      }
      .lane-star svg { width: 18px; height: 18px; }
      .lane-star.on { color: var(--gold-deep); border-color: var(--gold-deep); background: #f6cd67; }
      .lane-star.on svg { fill: var(--gold-deep); }
      .lane-star:active { transform: scale(.92); }
      .lane-star.fav-pop { animation: laneFavPop .6s cubic-bezier(.2,2.2,.4,1); }
      .lane-star.fav-pop svg { animation: laneFavGlow .6s ease; }
      @keyframes laneFavPop { 0% { transform: scale(1); } 35% { transform: scale(1.38) rotate(-10deg); } 60% { transform: scale(.92) rotate(6deg); } 100% { transform: scale(1) rotate(0deg); } }
      @keyframes laneFavGlow { 0% { filter: drop-shadow(0 0 0 rgba(246,205,103,0)); } 40% { filter: drop-shadow(0 0 10px rgba(246,205,103,.95)); } 100% { filter: drop-shadow(0 0 0 rgba(246,205,103,0)); } }
      @media (prefers-reduced-motion: reduce) { .lane-star.fav-pop, .lane-star.fav-pop svg { animation: none; } }
      .lane-star:disabled { opacity: .6; }

      /* ---------- Settings ---------- */
      .set-group { margin-bottom: 22px; }
      .set-group > h3 { font-family: "Marcellus", Georgia, serif; font-size: 17px; margin: 0 0 4px; color: var(--ink); }
      .set-group > p { margin: 0 0 10px; }
      /* The Support bubble pinned to the top of Settings (Casey's
         call, 2026-10-22): support is reached from Settings, and
         the bubble is the first thing on the screen — a gold
         speech bubble, tail and all, one tap to the Support
         screen. */
      .support-bubble {
        display: flex; align-items: center; gap: 13px; width: 100%;
        background: var(--card2); border: 1.5px solid var(--gold);
        border-radius: 20px; border-bottom-left-radius: 5px;
        padding: 15px 16px; margin: 0 0 18px; cursor: pointer;
        font-family: inherit; text-align: left; color: var(--ink);
        box-shadow: var(--shadow);
      }
      .support-bubble:active { transform: scale(.985); }
      .support-bubble .sb-ic {
        flex: none; width: 46px; height: 46px; border-radius: 50%;
        background: var(--gold); color: #fff8e8;
        display: inline-flex; align-items: center; justify-content: center;
      }
      .support-bubble .sb-ic svg { width: 25px; height: 25px; }
      .support-bubble .sb-main { flex: 1; min-width: 0; }
      .support-bubble .sb-t { font-family: "Marcellus", Georgia, serif; font-size: 17.5px; display: block; }
      .support-bubble .sb-s { color: var(--ink-soft); font-size: 12.5px; display: block; margin-top: 2px; line-height: 1.45; }
      .support-bubble .sb-chev { flex: none; color: var(--gold-deep); display: inline-flex; }
      .support-bubble .sb-chev svg { width: 20px; height: 20px; }
      .set-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
      .set-row { display: flex; align-items: center; gap: 12px; padding: 13px 15px; }
      .set-row + .set-row { border-top: 1px solid var(--line-soft); }
      .set-row .sr-main { flex: 1; min-width: 0; }
      .set-row .sr-t { font-weight: 700; font-size: 14.5px; display: block; }
      .set-row .sr-s { color: var(--ink-soft); font-size: 12.5px; display: block; margin-top: 1px; line-height: 1.4; }
      .switch {
        width: 50px; height: 30px; border-radius: 999px; border: 1px solid var(--line);
        background: var(--seg-bg); position: relative; flex: none; padding: 0;
        transition: background .2s;
      }
      .switch::after {
        content: ""; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px;
        border-radius: 50%; background: var(--card); box-shadow: 0 1px 4px rgba(0,0,0,.3);
        transition: left .2s;
      }
      .switch.on { background: var(--gold); border-color: var(--gold-deep); }
      .switch.on::after { left: 23px; background: #fff8e8; }
      .seg { display: flex; background: var(--seg-bg); border-radius: 12px; padding: 4px; gap: 4px; }
      .seg button { flex: 1; background: transparent; border: none; color: var(--ink-soft); padding: 8px 4px; border-radius: 9px; font-weight: 700; font-size: 13px; white-space: nowrap; }
      .seg button.on { background: var(--card); color: var(--ink); box-shadow: 0 2px 8px rgba(0,0,0,.12); }
      /* LIVE tab "not yet" shake (Casey's call, 2026-10-31): the
         LIVE pill itself never changes — tapping it while live is
         locked gives it one short side-to-side shake, the signal
         that it doesn't work yet; the pane and its wording below
         carry the explanation. */
      @keyframes live-nope-shake { 0%, 100% { transform: translateX(0); } 15% { transform: translateX(-6px); } 35% { transform: translateX(5px); } 55% { transform: translateX(-4px); } 75% { transform: translateX(3px); } 90% { transform: translateX(-1px); } }
      #lane-tabs button.shake-nope { animation: live-nope-shake .4s ease; }
      @media (prefers-reduced-motion: reduce) { #lane-tabs button.shake-nope { animation: none; } }
      /* The Lives tab is the LIVE badge alone (Casey's call,
         2026-10-20): the word "Lives" came off the tab and only
         the live symbol stays — a small red pill with a dot, so
         the tab is spotted at a glance in every lane that has
         one. Shorts has no Lives tab at all, so it never carries
         this badge. */
      .lives-live-badge {
        display: inline-flex; align-items: center; gap: 3px;
        background: var(--live); color: #fff;
        font-size: 8.5px; font-weight: 800; letter-spacing: .07em; line-height: 1;
        padding: 3px 6px 3px 5px; border-radius: 99px; vertical-align: 1px;
        text-transform: uppercase; white-space: nowrap;
      }
      .lives-live-badge .live-dot { font-size: 7px; line-height: 1; }
      .swatches { display: flex; gap: 10px; }
      .swatch { width: 42px; height: 42px; border-radius: 50%; border: 2px solid var(--line); padding: 0; flex: none; }
      .swatch.on { border-color: var(--gold-deep); box-shadow: 0 0 0 2px var(--card), 0 0 0 4.5px var(--gold); }
      .theme-grid { display: flex; flex-wrap: wrap; gap: 14px 12px; margin-top: 12px; }
      .theme-opt {
        display: flex; flex-direction: column; align-items: center; gap: 6px;
        background: none; border: none; padding: 0; cursor: pointer; font-family: inherit; width: 64px;
      }
      .theme-opt .swatch { width: 46px; height: 46px; }
      .theme-opt.on .swatch { border-color: var(--gold-deep); box-shadow: 0 0 0 2px var(--card), 0 0 0 4.5px var(--gold); }
      .theme-opt .t-name { font-size: 11.5px; font-weight: 700; color: var(--ink-soft); text-align: center; line-height: 1.25; }
      .theme-opt.on .t-name { color: var(--gold-text); }
      .lane-set-row { display: flex; align-items: center; gap: 8px; padding: 10px 12px; }
      .lane-set-row + .lane-set-row { border-top: 1px solid var(--line-soft); }
      .lane-set-row .ls-name { flex: 1; min-width: 0; font-weight: 700; font-size: 14px; }
      .lane-set-row.is-hidden-lane .ls-name { color: var(--ink-faint); text-decoration: line-through; }
      .mini-btn {
        width: 32px; height: 32px; border-radius: 9px; border: 1px solid var(--line);
        background: var(--card2); color: var(--ink); display: inline-flex; align-items: center; justify-content: center;
        font-size: 15px; font-weight: 800; flex: none; padding: 0;
      }
      .mini-btn:disabled { opacity: .35; }
      .mini-btn svg { width: 16px; height: 16px; }
      /* The trending-alert switch shares the lane row with the reorder
         buttons, so it runs a touch smaller than a settings-row switch. */
      .lane-set-row .switch { width: 44px; height: 26px; }
      .lane-set-row .switch::after { width: 18px; height: 18px; }
      .lane-set-row .switch.on::after { left: 21px; }

      /* ---------- Profile tabs & community ---------- */
      .prof-tabs { display: flex; background: var(--seg-bg); border-radius: 13px; padding: 3px; gap: 4px; margin: 6px 0 8px; }
      .prof-tabs button { flex: 1; background: transparent; border: none; color: var(--ink-soft); padding: 7px 4px; border-radius: 10px; font-weight: 700; font-size: 13px; }
      .prof-tabs button.on { background: var(--card); color: var(--ink); box-shadow: 0 2px 8px rgba(0,0,0,.12); }
      .gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
      .gallery figure { margin: 0; }
      .gallery img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 13px; border: 1px solid var(--line); display: block; background: var(--card2); }
      .gallery figcaption { font-size: 11px; color: var(--ink-soft); margin-top: 4px; line-height: 1.35; word-break: break-word; }
      /* Community Visions tiles (Casey's call, 2026-10-24):
         every tile taps open; video tiles carry the video
         itself as the picture, with a play chip riding it. */
      .gallery figure.tap { cursor: pointer; }
      .gtile-vid { position: relative; display: block; }
      .gtile-vid video { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 13px; border: 1px solid var(--line); display: block; background: #000; }
      .gtile-play { position: absolute; right: 6px; bottom: 6px; width: 26px; height: 26px; border-radius: 50%; background: rgba(20,12,2,.72); color: #ffe9b0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
      .gtile-play svg { width: 14px; height: 14px; }
      .gtkm-row { display: flex; gap: 10px; padding: 11px 15px; align-items: baseline; }
      .gtkm-row + .gtkm-row { border-top: 1px solid var(--line-soft); }
      .gtkm-row .g-k { flex: 0 0 112px; font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-faint); }
      .gtkm-row .g-v { flex: 1; font-size: 14px; min-width: 0; word-break: break-word; }
      /* Favorite activities live in their own slots (Casey's call,
         2026-10-22): one activity per slot in the editor — never one
         free-text box — and one chip per activity on the profile. */
      .act-slots { display: flex; flex-direction: column; gap: 8px; }
      .act-slot { display: flex; align-items: center; gap: 8px; }
      .act-slot .input { flex: 1; min-width: 0; }
      .act-slot-x {
        flex: none; width: 38px; height: 38px; border-radius: 11px; border: 1px solid var(--line);
        background: var(--card); color: var(--ink-faint); font-size: 15px; line-height: 1;
        display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
        -webkit-tap-highlight-color: transparent;
      }
      .act-slot-x:active { background: var(--line-soft); }
      .act-chips { display: flex; flex-wrap: wrap; gap: 6px; }
      .act-chip {
        display: inline-flex; align-items: center; background: var(--chip); color: var(--ink);
        border-radius: 99px; padding: 5px 11px; font-size: 13px; font-weight: 600;
      }
      .gtkm-video { position: relative; margin-top: 10px; border-radius: 14px; overflow: hidden; background: #000; aspect-ratio: 16 / 9; box-shadow: var(--shadow); }
      .gtkm-video iframe { width: 100%; height: 100%; border: 0; display: block; }
      /* The favorite music video starts on its own, muted, the
         moment its frame is on screen (Casey's call, 2026-10-22):
         the video's own picture covers the frame until it is
         playing, and the pill in the corner is the unmute button —
         the same pill the video portions wear. */
      .gtkm-video .gtkm-poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
      .gtkm-video .p-vid-mute { z-index: 3; }

      /* ---------- Soundtrack: three covers on the Community tab ----------
         Three album or single covers that carry a person's music
         taste and the vibe of their life (Casey's call, 2026-10-22).
         Each cover keeps its YouTube Music link: tap the cover and
         the song or album opens on YouTube Music and plays there. */
      .st-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
      .st-cell { min-width: 0; }
      .st-sq {
        position: relative; display: block; width: 100%; aspect-ratio: 1; padding: 0;
        border-radius: 13px; overflow: hidden; border: 1px solid var(--line);
        background: var(--card2); cursor: pointer; font-family: inherit; color: var(--ink);
      }
      .st-sq img { width: 100%; height: 100%; object-fit: cover; display: block; }
      .st-sq:active { transform: scale(.98); }
      .st-play {
        position: absolute; right: 6px; bottom: 6px; width: 26px; height: 26px; border-radius: 50%;
        background: rgba(20,10,0,.72); color: #fdf6e0;
        display: inline-flex; align-items: center; justify-content: center;
      }
      .st-play svg { width: 13px; height: 13px; }
      .st-sq.empty {
        border: 1.5px dashed var(--line); background: var(--card);
        display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
        color: var(--ink-faint);
      }
      .st-sq.empty .st-plus { font-size: 24px; font-weight: 400; line-height: 1; color: var(--gold-deep); }
      .st-sq.empty .st-add { font-size: 10.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; padding: 0 6px; text-align: center; line-height: 1.35; }
      .st-cap { margin-top: 5px; min-width: 0; }
      .st-cap .st-t { font-size: 12px; font-weight: 700; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .st-cap .st-a { font-size: 11px; color: var(--ink-faint); line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .st-cap .st-hint { font-size: 11px; color: var(--ink-faint); line-height: 1.3; }
      .st-preview { display: flex; gap: 12px; align-items: flex-start; margin-top: 14px; }
      .st-preview > img { width: 84px; height: 84px; border-radius: 11px; object-fit: cover; border: 1px solid var(--line); flex: none; background: var(--card2); }
      .st-preview .st-pf { flex: 1; min-width: 0; }
      .st-preview .st-pf label.f { margin-top: 10px; }
      .st-preview .st-pf label.f:first-child { margin-top: 0; }

      /* ---------- Devotion ---------- */
      /* Worship playlist player (Casey's call, 2026-10-28):
         a song row shows only its number, its name and a
         Play button — the raw link never prints — and a
         YouTube song plays right here in this overlay. */
      #song-player { position: absolute; inset: 0; z-index: 86; background: rgba(14,8,1,.96); display: flex; align-items: center; justify-content: center; padding: 18px; }
      #song-player .sp-card { width: 100%; max-width: 560px; }
      #song-player .sp-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
      #song-player .sp-title { flex: 1; min-width: 0; font-family: "Marcellus", Georgia, serif; font-size: 17px; color: #fdf6e0; }
      #song-player .mini-btn { background: rgba(253,246,224,.12); color: #fdf6e0; }
      #song-player .sp-frame { position: relative; width: 100%; padding-top: 56.25%; border-radius: 14px; overflow: hidden; background: #000; }
      #song-player .sp-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
      .song-play-btn { flex: none; display: inline-flex; align-items: center; gap: 6px; background: var(--gold); color: #241a02; border: none; border-radius: 999px; padding: 7px 14px; font-weight: 800; font-size: 13px; cursor: pointer; font-family: inherit; }
      .song-play-btn:active { transform: scale(.96); }
      .votd {
        border-radius: 20px; padding: 18px; border: 1.5px solid var(--gold); margin-bottom: 16px;
        background: linear-gradient(160deg, var(--card2), var(--card) 70%); box-shadow: var(--shadow);
      }
      .votd .v-text { font-family: "Marcellus", Georgia, serif; font-size: 18px; line-height: 1.6; margin: 10px 0 0; }
      .votd .v-ref { color: var(--gold-text); font-weight: 800; font-size: 13px; margin-top: 9px; }
      .verse-card { padding: 14px 16px; margin-bottom: 10px; width: 100%; text-align: left; font-family: inherit; color: var(--ink); cursor: pointer; display: block; }
      button.verse-card:active { background: var(--card2); }
      .verse-card .vc-ref { font-weight: 800; font-size: 14.5px; color: var(--gold-text); }
      .verse-card .vc-text { font-family: "Marcellus", Georgia, serif; font-size: 15.5px; line-height: 1.55; margin-top: 4px; }
      .verse-card .vc-notes { font-size: 13px; color: var(--ink-soft); margin-top: 7px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; border-top: 1px solid var(--line-soft); padding-top: 7px; }
      .grate-q { border-radius: 20px; padding: 18px; border: 1.5px solid var(--gold); margin-bottom: 16px; background: linear-gradient(160deg, var(--card2), var(--card) 70%); box-shadow: var(--shadow); }
      .grate-q .g-q { font-family: "Marcellus", Georgia, serif; font-size: 19px; line-height: 1.5; margin: 10px 0 0; }
      .grate-q .g-sub { font-size: 13.5px; color: var(--ink-soft); line-height: 1.55; margin-top: 7px; }
      .grate-entry { padding: 14px 16px; margin-bottom: 10px; width: 100%; text-align: left; font-family: inherit; color: var(--ink); cursor: pointer; display: block; }
      button.grate-entry:active { background: var(--card2); }
      .grate-entry .g-date { font-size: 12px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--gold-text); }
      .grate-entry .g-body { font-size: 15px; line-height: 1.6; margin-top: 6px; white-space: pre-wrap; word-break: break-word; }
      .stone-card { padding: 14px 16px; margin-bottom: 10px; width: 100%; text-align: left; font-family: inherit; color: var(--ink); cursor: pointer; display: block; }
      button.stone-card:active { background: var(--card2); }
      .stone-card .st-date { font-size: 12px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--gold-text); }
      .stone-card .st-prayer { font-family: "Marcellus", Georgia, serif; font-size: 15.5px; line-height: 1.55; margin-top: 5px; white-space: pre-wrap; word-break: break-word; }
      .stone-card .st-answer { font-size: 14px; line-height: 1.55; margin-top: 8px; white-space: pre-wrap; word-break: break-word; }
      .stone-card .st-answer b { color: var(--gold-text); }
      .stone-card .st-scripture { font-size: 13px; color: var(--ink-soft); margin-top: 7px; border-top: 1px solid var(--line-soft); padding-top: 7px; }
      .dev-sec { font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-faint); margin: 20px 0 10px; }
      .fast-now { border-radius: 20px; padding: 18px; border: 1.5px solid var(--gold); margin-bottom: 16px; background: linear-gradient(160deg, var(--card2), var(--card) 70%); box-shadow: var(--shadow); width: 100%; text-align: left; font-family: inherit; color: var(--ink); cursor: pointer; display: block; }
      button.fast-now:active { background: var(--card2); }
      .fast-now .f-day { font-family: "Marcellus", Georgia, serif; font-size: 23px; line-height: 1.35; margin-top: 9px; }
      .fast-now .f-int { font-size: 15.5px; line-height: 1.55; margin-top: 5px; white-space: pre-wrap; word-break: break-word; }
      .fast-now .f-meta { font-size: 13px; color: var(--ink-soft); line-height: 1.55; margin-top: 8px; }
      /* The current fast's card stands collapsed to just its
         "Day X of Y" line (Casey's call, 2026-10-24): the
         intention, dates, scripture and note wait inside
         .f-more until a double-tap expands the card; a second
         double-tap folds it shut again. touch-action keeps the
         browser from treating the double-tap as a zoom. */
      .fast-now { touch-action: manipulation; }
      .fast-now .f-more { display: none; }
      .fast-now.expanded .f-more { display: block; }
      .fast-now .f-hint { display: flex; align-items: center; justify-content: flex-end; gap: 5px; margin-top: 10px; font-size: 12px; color: var(--ink-soft); }
      .fast-now .f-hint svg { width: 14px; height: 14px; transition: transform .25s ease; transform: rotate(90deg); }
      .fast-now.expanded .f-hint svg { transform: rotate(-90deg); }
      @media (prefers-reduced-motion: reduce) { .fast-now .f-hint svg { transition: none; } }
      .fast-card { padding: 14px 16px; margin-bottom: 10px; width: 100%; text-align: left; font-family: inherit; color: var(--ink); cursor: pointer; display: block; }
      button.fast-card:active { background: var(--card2); }
      .fast-card .fc-int { font-weight: 700; font-size: 15px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
      .fast-card .fc-meta { font-size: 12.5px; color: var(--gold-text); font-weight: 700; margin-top: 5px; }
      .fast-card .fc-note { font-size: 13.5px; color: var(--ink-soft); line-height: 1.5; margin-top: 6px; white-space: pre-wrap; word-break: break-word; }

      /* ---------- Sheets, emoji, comments ---------- */
      .sheet-overlay {
        position: absolute; inset: 0; z-index: 70; background: var(--overlay);
        display: flex; align-items: flex-end; justify-content: center;
      }
      .sheet {
        background: var(--card); width: 100%; max-height: 84%; overflow-y: auto; overflow-x: clip;
        border-radius: 22px 22px 0 0; padding: 20px 18px calc(18px + var(--sab));
        box-shadow: 0 -12px 40px rgba(0,0,0,.35);
      }
      /* Clean rounded corners (Casey's call, 2026-11-01): a
         first child carrying its own background used to paint
         square bits into the sheet's rounded top corners —
         the first child's top corners now inherit the round,
         and nothing may bleed sideways past the sheet's edge.
         The top-hung variant gets the same care at its
         bottom corners. */
      .sheet > :first-child { border-top-left-radius: 22px; border-top-right-radius: 22px; }
      .sheet-overlay.top .sheet > :last-child { border-bottom-left-radius: 22px; border-bottom-right-radius: 22px; }
      /* Top-anchored sheet variant: drops down from the top of the
         screen instead of rising from the bottom. Used by the
         profile settings chooser, whose gear lives at the top of
         the cover (Casey's call, 2026-10-22). */
      .sheet-overlay.top { align-items: flex-start; }
      .sheet-overlay.top .sheet {
        border-radius: 0 0 22px 22px;
        padding: calc(20px + var(--sat)) 18px 18px;
        box-shadow: 0 12px 40px rgba(0,0,0,.35);
        animation: sheet-drop .22s ease-out;
      }
      @keyframes sheet-drop { from { opacity: 0; transform: translateY(-18px); } to { opacity: 1; transform: translateY(0); } }
      @media (prefers-reduced-motion: reduce) { .sheet-overlay.top .sheet { animation: none; } }
      /* Centered popup variant: a small card in the middle of the
         screen instead of a sheet rising from the bottom. The
         Direct Portions delete asks use it (Casey's call,
         2026-10-24): a simple "Delete message — are you sure?"
         pop-up, and that's it. */
      .sheet-overlay.center { align-items: center; padding: 24px; }
      .sheet-overlay.center .sheet {
        width: 100%; max-width: 340px;
        border-radius: 18px; padding: 22px 20px 18px;
        box-shadow: 0 18px 50px rgba(0,0,0,.4);
        text-align: center;
        animation: confirm-pop-in .22s cubic-bezier(.2,1.4,.4,1);
      }
      @keyframes confirm-pop-in { from { opacity: 0; transform: scale(.9) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
      @media (prefers-reduced-motion: reduce) { .sheet-overlay.center .sheet { animation: none; } }
      .emoji-bar { display: flex; flex-wrap: wrap; gap: 2px; margin-top: 8px; }
      .emoji-btn { background: none; border: none; font-size: 20px; padding: 5px 6px; border-radius: 9px; line-height: 1; }
      .emoji-btn:active { background: var(--chip); }
      .c-actions { display: flex; align-items: center; gap: 4px; margin-top: 4px; }
      .c-act { padding: 5px 7px; font-size: 12.5px; }
      .c-act svg { width: 15px; height: 15px; flex: none; }
      .c-actions [data-cdel] svg { width: 15px; height: 15px; flex: none; }
      .c-replies { margin: 2px 0 0 14px; padding-left: 12px; border-left: 2px solid var(--line-soft); }
      .c-replies .comment { padding: 9px 0; }
      .comment.pinned { background: rgba(210,154,31,.08); border-radius: 12px; padding-left: 10px; padding-right: 10px; }
      .comment .pill svg { width: 11px; height: 11px; }
      .reply-banner {
        display: flex; align-items: center; gap: 8px; margin-top: 14px;
        background: var(--chip); border: 1px solid var(--line); border-radius: 11px;
        padding: 8px 12px; font-size: 13px; color: var(--ink-soft);
      }
      .reply-banner b { color: var(--ink); }
      .reply-banner button { margin-left: auto; background: none; border: none; color: var(--ink-soft); font-size: 15px; padding: 2px 6px; }
      .p-via { display: flex; align-items: center; gap: 5px; color: var(--ink-faint); font-size: 12px; font-weight: 700; margin-top: 9px; }
      .p-via svg { width: 14px; height: 14px; }
      /* A contribution's banner (Casey's call, 2026-10-22): a
         portion written by holding reportion names the portion
         it answers, right under its head — gold, tappable, and
         the name only (2026-10-24): the original's words are
         never quoted here, since the whole original stands in
         the card below. */
      .p-contrib { display: flex; align-items: center; gap: 5px; color: var(--gold-text); font-size: 12px; font-weight: 700; margin-top: 6px; cursor: pointer; min-width: 0; }
      .p-contrib svg { width: 14px; height: 14px; flex: none; }
      /* The original portion embedded under a contribution's
         words (Casey's call, 2026-10-22): the portion it
         answers, standing as its own post inside a bordered
         inset — author and time up top, its words and media
         below, the video previewing like any card's. */
      .p-origin { border: 1px solid var(--line); border-radius: 14px; background: var(--card2); padding: 10px 12px 12px; margin-top: 10px; }
      .p-origin-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
      .p-origin-head .p-who { min-width: 0; }
      .p-origin-tap { cursor: pointer; margin-top: 6px; }
      .p-origin .p-vid-wrap { margin-top: 8px; position: relative; }

      /* ---------- Polls ---------- */
      .poll-opts { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
      .poll-opt {
        position: relative; overflow: hidden; text-align: left; width: 100%;
        background: var(--card2); border: 1px solid var(--line); border-radius: 12px;
        padding: 11px 13px; font-weight: 700; font-size: 14px; color: var(--ink);
        display: flex; align-items: center; gap: 8px;
      }
      .poll-opt .po-bar { position: absolute; inset: 0; right: auto; background: rgba(210,154,31,.28); }
      .poll-opt .po-label { position: relative; flex: 1; min-width: 0; }
      .poll-opt .po-pct { position: relative; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
      .poll-opt.mine { border-color: var(--gold-deep); }
      .poll-opt:disabled { cursor: default; }
      .poll-meta { margin-top: 10px; color: var(--ink-faint); font-size: 12.5px; font-weight: 700; }

      /* ---------- Custom emoji icons ---------- */
      .custom-emoji { line-height: 1; font-style: normal; }
      .dicon .custom-emoji { font-size: 25px; }
      .door.mini .dicon .custom-emoji { font-size: 20px; }
      .lane-set-row .ls-icon { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 9px; background: var(--chip); color: var(--gold-deep); flex: none; cursor: pointer; }
      .lane-set-row .ls-icon svg { width: 17px; height: 17px; }
      .lane-set-row .ls-icon .custom-emoji { font-size: 17px; }
      .emoji-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-top: 10px; }
      .emoji-grid button { background: none; border: none; font-size: 25px; padding: 7px 0; border-radius: 10px; line-height: 1; }
      .emoji-grid button:active { background: var(--chip); }
      .emoji-picker .emoji-grid { margin-top: 2px; }
      .emoji-group-label { font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--gold-text); margin: 18px 0 2px; }
      .emoji-group-label:first-child { margin-top: 6px; }

      /* ---------- Avatar, stories ---------- */
      /* ---------- The story ring ----------
         The ring around a profile picture means ONE thing: the
         person has a story up right now (Casey's call, 2026-10-22,
         superseding the same-day green online light — the ring no
         longer says anything about who is online). While any of
         their 24-hour stories are live, a ring of gold light
         circles the picture on every surface the picture shows,
         turning slowly (Casey's call, 2026-10-31: a different
         color from the old static red-and-gold bands, and
         animated). The ring is a conic sweep of gold tones on a
         ::before disc tucked behind the picture, masked to the
         same band the old box-shadow rings drew, so the ring's
         size and placement never change — only its color and
         motion do. Its golds are fixed hexes, not the theme's
         --gold: some accent themes turn that variable green,
         and the story ring stays gold in every theme. When the
         last story expires, the ring is gone. */
      .prof-avatar.story-ring, .ava.story-ring { position: relative; }
      .prof-avatar.story-ring { cursor: pointer; }
      .prof-avatar.story-ring::before, .ava.story-ring::before {
        content: ""; position: absolute; border-radius: 50%; z-index: -1; pointer-events: none;
        background: conic-gradient(from 0deg, #96690e, #d29a1f, #f6cd67, #fff3cf, #f6cd67, #d29a1f, #96690e);
        animation: story-ring-spin 3.6s linear infinite;
      }
      .ava.story-ring::before {
        inset: -6px;
        -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
        mask: radial-gradient(closest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
      }
      .prof-avatar.story-ring::before {
        inset: -8.5px;
        -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 6.5px), #000 calc(100% - 6.5px));
        mask: radial-gradient(closest-side, transparent calc(100% - 6.5px), #000 calc(100% - 6.5px));
      }
      .shorts-rail [data-short-ava] .ava.story-ring { overflow: visible; box-shadow: 0 2px 8px rgba(0,0,0,.5); }
      .shorts-rail [data-short-ava] .ava.story-ring::before {
        inset: -4px;
        -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
        mask: radial-gradient(closest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
      }
      @keyframes story-ring-spin { to { transform: rotate(360deg); } }
      @media (prefers-reduced-motion: reduce) { .prof-avatar.story-ring::before, .ava.story-ring::before { animation: none; } }
      .prof-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }
      .avatar-plus {
        position: absolute; left: 50%; bottom: 4px; transform: translateX(46px);
        width: 30px; height: 30px; border-radius: 50%; border: 2.5px solid var(--app-bg);
        background: var(--gold); color: #241503; font-size: 19px; font-weight: 800; line-height: 1;
        display: inline-flex; align-items: center; justify-content: center; z-index: 3;
      }
      .story-viewer { position: absolute; inset: 0; z-index: 90; background: linear-gradient(180deg, var(--app-bg), var(--app-bg2)); display: flex; flex-direction: column; }
      .story-viewer .sv-media { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
      .story-viewer .sv-media img, .story-viewer .sv-media video { max-width: 100%; max-height: 100%; object-fit: contain; }
      .story-viewer .sv-top { display: flex; gap: 5px; padding: 12px 14px 6px; }
      .story-viewer .sv-seg { flex: 1; height: 3.5px; border-radius: 99px; background: rgba(46,29,7,.22); overflow: hidden; }
      .story-viewer .sv-seg i { display: block; height: 100%; background: var(--gold-deep); border-radius: 99px; }
      .story-viewer .sv-head { display: flex; align-items: center; gap: 9px; padding: 6px 14px 10px; color: var(--ink); font-weight: 700; font-size: 14px; }
      .story-viewer .sv-head button { margin-left: auto; background: none; border: none; color: var(--ink); font-size: 21px; padding: 4px 8px; }
      .story-viewer .sv-head button + button { margin-left: 0; }
      .story-viewer .sv-head button svg { width: 19px; height: 19px; display: block; }
      /* Story text + attached portion, in the viewer (Casey's call,
         2026-10-22): words laid over the picture in the font and
         color picked in the editor, and the portion card riding at
         the bottom — tapping it opens the portion itself. Photo
         stories carry both baked into the picture; video stories
         carry them as an overlay the viewer paints on. */
      .story-viewer .sv-stage { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
      .story-caption {
        position: absolute; transform: translate(-50%, -50%); z-index: 3;
        max-width: 86%; text-align: center; line-height: 1.3; font-weight: 700;
        white-space: pre-wrap; word-break: break-word; padding: 4px 10px; border-radius: 12px;
        text-shadow: 0 1px 10px rgba(20,10,2,.45);
      }
      .story-caption.boxed { background: rgba(20,10,2,.58); text-shadow: none; }
      /* The Gold backing is a fixed gold (#d29a1f, the same gold
         the photo bake paints), never the theme's --gold: some
         accent themes turn that variable green, and a backing
         named Gold must be gold (Casey's walkthrough, 2026-10-31). */
      .story-caption.goldbg { background: #d29a1f; color: #241503 !important; text-shadow: none; }
      .sv-portion {
        position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%); z-index: 3;
        width: min(88%, 430px); text-align: left; cursor: pointer; font-family: inherit;
        background: rgba(20,10,2,.74); color: #fff6dd; border: 1px solid rgba(246,205,103,.55);
        border-radius: 14px; padding: 10px 13px; box-shadow: 0 8px 24px rgba(0,0,0,.4);
      }
      .sv-portion .svp-label { display: block; font-size: 10px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: var(--gold-2); }
      .sv-portion .svp-body {
        display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
        font-size: 13.5px; line-height: 1.45; margin-top: 3px; color: #fff6dd;
      }
      .sv-portion .svp-open { display: block; font-size: 11.5px; font-weight: 700; color: var(--gold-2); margin-top: 4px; }
      /* A written portion standing as a story (Casey's call,
         2026-10-26): its own words fill the stage. */
      .sv-text-story { max-width: 88%; max-height: 100%; overflow-y: auto; text-align: center; font-size: 23px; line-height: 1.5; font-weight: 700; color: var(--ink); white-space: pre-wrap; word-break: break-word; padding: 12px 8px 76px; }
      .story-editor .se-text-stage { max-width: 88%; max-height: 100%; overflow-y: auto; text-align: center; font-size: 23px; line-height: 1.5; font-weight: 700; color: #fff6dd; white-space: pre-wrap; word-break: break-word; padding: 12px 8px 76px; }

      /* ---------- Story editor (Casey's call, 2026-10-22) ----------
         Picking a picture or video for a story no longer posts it
         on the spot: it opens this editor first, TikTok-stories
         style — lay text over the picture (your words, your font,
         your color, drag it where it belongs), attach one of your
         portions to ride along, and only then does Add to story
         publish it. */
      .story-editor {
        position: absolute; inset: 0; z-index: 95; background: #140a02; color: #fff6dd;
        display: flex; flex-direction: column;
      }
      .story-editor .se-top {
        display: flex; align-items: center; gap: 10px; padding: 10px 12px 6px; flex: none;
      }
      .story-editor .se-x {
        width: 38px; height: 38px; border-radius: 50%; flex: none; border: 1px solid rgba(250,241,220,.35);
        background: rgba(250,241,220,.1); color: #fdf6e0; font-size: 16px;
        display: inline-flex; align-items: center; justify-content: center; padding: 0;
      }
      .story-editor .se-title { flex: 1; text-align: center; font-family: "Marcellus", Georgia, serif; font-size: 17px; letter-spacing: .08em; text-transform: uppercase; }
      .story-editor .se-stage {
        position: relative; flex: 1; min-height: 0; overflow: hidden;
        display: flex; align-items: center; justify-content: center;
      }
      .story-editor .se-stage img, .story-editor .se-stage video { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
      .story-editor .se-caption { cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; z-index: 4; }
      .story-editor .se-caption.editing { outline: 1.5px dashed rgba(253,246,224,.85); outline-offset: 3px; }
      .story-editor .se-attached {
        position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%); z-index: 3;
        width: min(88%, 430px); text-align: left;
        background: rgba(20,10,2,.74); color: #fff6dd; border: 1px solid rgba(246,205,103,.55);
        border-radius: 14px; padding: 10px 34px 10px 13px; box-shadow: 0 8px 24px rgba(0,0,0,.4);
      }
      .story-editor .se-attached .svp-label { display: block; font-size: 10px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: var(--gold-2); }
      .story-editor .se-attached .svp-body {
        display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
        font-size: 13.5px; line-height: 1.45; margin-top: 3px;
      }
      .story-editor .se-attached .se-att-x {
        position: absolute; top: 6px; right: 6px; width: 26px; height: 26px; border-radius: 50%;
        border: 1px solid rgba(250,241,220,.4); background: rgba(250,241,220,.12); color: #fdf6e0;
        font-size: 12px; display: inline-flex; align-items: center; justify-content: center; padding: 0;
      }
      .story-editor .se-tools { display: flex; gap: 10px; padding: 12px 14px 4px; flex: none; }
      .story-editor .se-tool {
        flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
        background: rgba(250,241,220,.1); border: 1px solid rgba(250,241,220,.35); color: #fdf6e0;
        border-radius: 999px; padding: 11px 10px; font-weight: 700; font-size: 14px; font-family: inherit;
      }
      .story-editor .se-tool:active { transform: scale(.97); }
      .story-editor .se-tool .se-aa { font-family: "Marcellus", Georgia, serif; font-size: 17px; line-height: 1; }
      .story-editor .se-tool svg { width: 18px; height: 18px; }
      .story-editor .se-actions {
        display: flex; gap: 10px; padding: 10px 14px calc(14px + var(--sab)); flex: none;
      }
      .story-editor .se-actions .btn-ghost { flex: none; background: rgba(250,241,220,.1); border-color: rgba(250,241,220,.35); color: #fdf6e0; }
      .story-editor .se-actions .btn-gold { flex: 1; margin-top: 0; }
      .story-editor .se-panel {
        flex: none; background: #241503; border-top: 1px solid rgba(246,205,103,.35);
        padding: 12px 14px calc(12px + var(--sab));
        max-height: 62%; overflow-y: auto;
      }
      .story-editor .se-panel textarea.input { background: #fffdf8; min-height: 56px; }
      .se-panel-label { font-size: 11px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; color: var(--gold-2); margin: 14px 0 7px; }
      .se-fonts { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; scrollbar-width: none; }
      .se-fonts::-webkit-scrollbar { display: none; }
      .se-font-chip {
        flex: none; border: 1.5px solid rgba(250,241,220,.35); background: rgba(250,241,220,.08); color: #fdf6e0;
        border-radius: 999px; padding: 8px 14px; font-size: 15px; cursor: pointer;
      }
      .se-font-chip.on { border-color: var(--gold-2); background: var(--gold); color: #241503; }
      .se-colors { display: flex; gap: 10px; flex-wrap: wrap; }
      .se-swatch {
        width: 34px; height: 34px; border-radius: 50%; flex: none; cursor: pointer; padding: 0;
        border: 2px solid rgba(250,241,220,.4);
      }
      .se-swatch.on { border-color: #fff6dd; box-shadow: 0 0 0 2.5px var(--gold); transform: scale(1.08); }
      .se-styles { display: flex; gap: 8px; }
      .se-style-chip {
        flex: 1; border: 1.5px solid rgba(250,241,220,.35); background: rgba(250,241,220,.08); color: #fdf6e0;
        border-radius: 11px; padding: 9px 6px; font-weight: 700; font-size: 13.5px; cursor: pointer; font-family: inherit;
      }
      .se-style-chip.on { border-color: var(--gold-2); background: var(--gold); color: #241503; }
      .se-size-row { display: flex; align-items: center; gap: 12px; }
      .se-size-row input[type="range"] { flex: 1; accent-color: var(--gold); height: 28px; }
      /* The portion picker rides over the editor (and over the
         picture sheet) as its own sheet. */
      .spp-overlay { z-index: 97; }
      .spp-row {
        display: block; width: 100%; text-align: left; background: var(--card); cursor: pointer;
        border: 1px solid var(--line); border-radius: 14px; padding: 12px 14px; margin-top: 9px;
        font-family: inherit; color: var(--ink);
      }
      .spp-row:active { background: var(--card2); }
      .spp-row .spp-body {
        font-size: 14.5px; line-height: 1.5; display: -webkit-box;
        -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; white-space: pre-wrap; word-break: break-word;
      }
      .spp-row .spp-meta { font-size: 12px; color: var(--ink-faint); margin-top: 5px; }

      /* ---------- Podcast & live media ---------- */
      .pod-frame { position: relative; margin-top: 10px; border-radius: 14px; overflow: hidden; background: #140a02; }
      .pod-frame video { width: 100%; aspect-ratio: 9 / 16; max-height: 540px; object-fit: cover; display: block; }
      .pod-frame img.pod-thumb { width: 100%; aspect-ratio: 9 / 16; max-height: 540px; object-fit: cover; display: block; }
      .pod-cover { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; border-radius: 14px; margin-top: 10px; border: 1px solid var(--line); background: var(--card2); }

      /* ---------- Video portion previews: muted autoplay + unmute ----------
         (Casey's call, 2026-10-22): a video portion starts playing
         the moment it scrolls into view, but muted; the pill in the
         corner is the unmute button — tap it to hear the video right
         on the card, tap again to mute it back. */
      .p-vid-wrap { position: relative; margin-top: 10px; }
      .p-vid-wrap video.p-vid-preview { width: 100%; display: block; border-radius: 14px; background: var(--card2); max-height: 430px; }
      .p-vid-mute { position: absolute; right: 10px; bottom: 10px; z-index: 2; display: inline-flex; align-items: center; gap: 6px; padding: 7px 12px; border: none; border-radius: 999px; background: rgba(20,10,2,.72); color: #fff6dd; font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,.4); }
      .p-vid-mute svg { width: 16px; height: 16px; flex: none; }
      .p-vid-mute:active { transform: scale(.96); }
      /* Closed captions (Casey's call, 2026-10-22): a very small
         CC button rides right on every video itself — never buried
         in Settings. Tap it and captions come on; tap it again and
         they go off. One switch drives every video on screen at
         once. The words captioned are the video's own SPOKEN words
         — what the person in the video says, written on screen as
         they say it — never the portion's written text (Casey
         corrected this 2026-10-22: captioning is speech, not the
         post's words). The button sits at the BOTTOM of the video,
         close to the mute / unmute (Casey's call, 2026-10-22): on
         feed and profile previews it shares the bottom row with
         the Unmute pill in the opposite corner; on players with
         their own control bar (portion page, comments, podcasts,
         stories, playlists, DMs, live) it rests just above that
         bar, beside the bar's own mute. */
      .cc-wrap { position: relative; margin-top: 10px; }
      .cc-wrap.fit { width: fit-content; max-width: 100%; }
      .cc-wrap > video { margin-top: 0 !important; }
      .cc-btn { position: absolute; top: 10px; left: 10px; z-index: 6; padding: 3px 7px; border-radius: 5px; border: 1px solid rgba(255,246,221,.5); background: rgba(20,10,2,.62); color: #fff6dd; font: inherit; font-size: 10px; font-weight: 800; letter-spacing: .6px; line-height: 1.2; cursor: pointer; box-shadow: 0 1px 5px rgba(0,0,0,.35); }
      .cc-btn.cc-on { background: var(--gold); border-color: var(--gold); color: #241503; }
      .cc-btn:active { transform: scale(.94); }
      .cc-wrap .cc-btn, .pod-frame .cc-btn { bottom: 52px; }
      .sv-stage .cc-btn { bottom: 64px; }
      .cc-bar { display: none; position: absolute; left: 50%; transform: translateX(-50%); bottom: 48px; z-index: 5; max-width: 84%; padding: 5px 11px; border-radius: 6px; background: rgba(0,0,0,.8); color: #fff; font-size: 13.5px; font-weight: 600; line-height: 1.4; text-align: center; pointer-events: none; }
      .cc-bar.cc-on { display: block; }
      .p-vid-wrap .cc-bar { bottom: 54px; }
      .sv-stage .cc-bar { bottom: 64px; }
      .live-player .cc-btn { bottom: 100px; left: 10px; }
      .live-player .cc-bar { bottom: 96px; }
      /* In Shorts the caption bar is its own strip of spoken
         words riding above the bottom block (Casey's call,
         2026-10-22): the short's written caption stays ordinary
         post text underneath — CC never borrows it. */
      .short-slide .cc-bar { bottom: 118px; max-width: 72%; }
      .shorts-act .cc-txt { font-size: 12px; font-weight: 800; letter-spacing: .4px; }
      .shorts-act.cc-on { color: #ffd970; }

      /* Background uploads (2026-10-31): a confirmed post hands
         its video to the app-level uploader and the member walks
         away free; the job reports from this small bubble pinned
         above the bottom nav — progress while it climbs, Posted
         when it lands, a plain failure with Retry when it can't.
         The stack lives on <body>, so it rides over every screen
         the member wanders to while the upload finishes. */
      #up-bubbles { position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(78px + var(--sab)); width: min(430px, calc(100vw - 20px)); z-index: 450; display: flex; flex-direction: column; gap: 8px; pointer-events: none; }
      .up-bub { pointer-events: auto; display: flex; gap: 10px; align-items: center; background: var(--card); color: var(--ink); border: 1px solid var(--line); border-radius: 16px; box-shadow: 0 10px 30px rgba(20,10,2,.35); padding: 10px 12px; }
      .up-bub img.up-thumb { width: 44px; height: 44px; border-radius: 10px; object-fit: cover; flex: none; background: var(--card2); }
      .up-bub .up-main { flex: 1; min-width: 0; }
      .up-bub .up-name { font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .up-bub .up-sub { font-size: 11.5px; color: var(--muted); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .up-bub .up-track { height: 5px; border-radius: 3px; background: var(--card2); margin-top: 6px; overflow: hidden; }
      .up-bub .up-fill { height: 100%; width: 0%; border-radius: 3px; background: var(--gold); transition: width .25s ease; }
      .up-bub .up-fill.indet { width: 40% !important; animation: up-slide 1.1s ease-in-out infinite alternate; }
      @keyframes up-slide { from { margin-left: 0; } to { margin-left: 60%; } }
      .up-bub .up-act { flex: none; display: flex; gap: 6px; align-items: center; }
      .up-bub .up-pct { font-size: 12.5px; font-weight: 800; color: var(--gold-deep, var(--gold)); min-width: 38px; text-align: right; }
      .up-bub.done .up-pct { color: #2f7d4e; }
      .up-bub.failed .up-pct { color: var(--danger); }
      .up-bub .up-retry { font: inherit; font-size: 12px; font-weight: 800; padding: 6px 10px; border-radius: 999px; border: 1px solid var(--gold); background: var(--gold); color: #241503; cursor: pointer; }
      .up-bub .up-x { font: inherit; font-size: 15px; line-height: 1; padding: 4px 7px; border-radius: 999px; border: 1px solid var(--line); background: transparent; color: var(--muted); cursor: pointer; }
      /* The composer's optional captions-file row (2026-10-31):
         a .vtt rides beside an attached video, nothing more. */
      .c-cap-row { margin-top: 12px; border: 1px dashed var(--line); border-radius: 12px; padding: 10px 12px; }
      .c-cap-row .f { margin-top: 0; }
      /* A portrait video portion stands TALL at its own shape
         (Casey's call, 2026-10-22): regular video portions post
         in 9:16 too, not just Shorts — so when a portion's video
         is taller than it is wide, its frame is a tall one,
         centered on the card, instead of the picture being
         squeezed small inside the wide 430px-capped box.
         watchTallVideo() adds .p-vid-tall (and .has-tall on the
         wrap, which shrink-wraps and centers so the unmute pill
         still rides the picture's own corner) once the video's
         real dimensions are known. Landscape and square videos
         keep the wide frame above, untouched. */
      .p-vid-wrap.has-tall { width: fit-content; max-width: 100%; margin-left: auto; margin-right: auto; }
      .p-vid-wrap video.p-vid-preview.p-vid-tall { width: auto; max-width: 100%; height: min(560px, 78dvh); max-height: none; background: #000; }
      /* The portion page's own player follows the same rule. */
      .p-vid-detail { width: 100%; display: block; border-radius: 14px; margin-top: 10px; background: var(--card2); max-height: 430px; }
      .p-vid-detail.p-vid-tall { width: auto; max-width: 100%; height: min(560px, 78dvh); max-height: none; margin-left: auto; margin-right: auto; background: #000; }

      /* ---------- GIF attachments & picker ---------- */
      .p-gif { width: 100%; display: block; border-radius: 14px; margin-top: 10px; border: 1px solid var(--line); background: var(--card2); }
      /* GIFs live on comments only — capped so a comment stays a comment. */
      .comment .p-gif { max-width: 300px; }
      .gif-search-row { display: flex; gap: 8px; align-items: stretch; }
      .gif-search-row .input { flex: 1; min-width: 0; margin-top: 0; }
      .gif-search-row .btn-gold, .gif-search-row .btn-ghost { flex: none; margin-top: 0; width: auto; }
      .gif-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 10px; }
      .gif-cell { padding: 0; border: 2px solid transparent; border-radius: 10px; overflow: hidden; background: var(--card2); cursor: pointer; line-height: 0; }
      .gif-cell img { width: 100%; height: 84px; object-fit: cover; display: block; pointer-events: none; }
      .gif-cell.on { border-color: var(--gold-deep); }
      .gif-picked { position: relative; margin-top: 10px; }
      .gif-picked img { width: 100%; display: block; border-radius: 14px; border: 1px solid var(--line); background: var(--card2); }
      .gif-picked button { position: absolute; top: 8px; right: 8px; width: 30px; height: 30px; border-radius: 50%; border: none; background: rgba(20,10,2,.72); color: #fff6dd; font-size: 15px; line-height: 1; cursor: pointer; }

      /* ---------- Gallery button (comments & direct portions) ----------
         Casey's call, 2026-10-24: press the gallery button in
         a portion or a direct portion and the PHONE'S gallery
         pops up — the whole phone library, like in X — never a
         grid of just the pictures already shared in Manna.
         The .gal-panel below only holds the hidden pick input
         now; it never shows. */
      .gal-panel { margin-top: 10px; border: 1px solid var(--line); border-radius: 16px; background: var(--card); overflow: hidden; }
      .gal-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px 8px; }
      .gal-title { flex: 1; min-width: 0; font-weight: 800; font-size: 14px; }
      .gal-strip { display: flex; gap: 6px; overflow-x: auto; padding: 0 12px 8px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
      .gal-strip::-webkit-scrollbar { display: none; }
      .gal-strip .gal-cell { flex: 0 0 62px; width: 62px; height: 62px; border-radius: 10px; }
      .gal-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; max-height: 258px; overflow-y: auto; padding: 0 3px 3px; overscroll-behavior: contain; }
      .gal-cell { position: relative; padding: 0; border: none; border-radius: 4px; overflow: hidden; background: var(--card2); cursor: pointer; line-height: 0; aspect-ratio: 1; font-family: inherit; }
      .gal-strip .gal-cell { aspect-ratio: auto; }
      .gal-cell img, .gal-cell video { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
      .gal-cell.on { box-shadow: inset 0 0 0 3px var(--gold-deep); }
      .gal-dur { position: absolute; right: 4px; bottom: 4px; background: rgba(20,10,2,.72); color: #fff6dd; font-size: 10.5px; font-weight: 800; line-height: 1; padding: 3px 5px; border-radius: 99px; pointer-events: none; }
      .gal-cam { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; color: var(--gold-deep); background: var(--card2); font-size: 10.5px; font-weight: 800; line-height: 1.1; }
      .gal-cam svg { width: 21px; height: 21px; }
      .gal-status { padding: 0 12px 10px; font-size: 12.5px; color: var(--ink-faint); line-height: 1.5; }
      .gal-status:empty { display: none; }
      .gal-foot { padding: 2px 12px 12px; }
      .gal-foot .btn-ghost { width: 100%; margin-top: 0; display: inline-flex; align-items: center; justify-content: center; gap: 7px; }
      .gal-foot .btn-ghost svg { width: 16px; height: 16px; }
      .live-badge { position: absolute; top: 10px; left: 10px; z-index: 2; box-shadow: 0 2px 8px rgba(0,0,0,.4); }
      /* A live's card frame fits the stream's OWN shape (Casey's
         call, 2026-10-21): lives are not vertical-only — 9:16,
         landscape, square, whatever the camera gave — so the
         thumbnail / first frame shows whole, never cropped
         into a forced 9:16 box. */
      .live-frame { position: relative; margin-top: 10px; border-radius: 14px; overflow: hidden; background: #000; }
      .live-frame .live-media { width: 100%; height: auto; max-height: 480px; object-fit: contain; display: block; margin: 0 auto; }
      /* Live view: a full-screen stage that fits the stream's own
         aspect ratio (Casey's call, 2026-10-21) — the video sits
         whole inside the screen, letterboxed where its shape
         differs, never cropped; the full-screen button takes it
         onto the whole device screen. */
      .live-player { position: absolute; inset: 0; z-index: 90; background: #000; }
      .live-player video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
      /* The broadcaster's stage fits the camera's real shape too:
         the live preview and the stream review show the whole
         frame, not a vertical crop of it. */
      #live-stage .rec-fill { object-fit: contain; background: #000; }
      .live-player .lp-top {
        position: absolute; top: 0; left: 0; right: 0; z-index: 3;
        display: flex; align-items: flex-start; gap: 10px;
        padding: calc(12px + var(--sat)) 14px 30px;
        background: linear-gradient(to bottom, rgba(0,0,0,.55), rgba(0,0,0,0));
      }
      .live-player .lp-title { flex: 1; min-width: 0; color: #fff6dd; font-weight: 700; font-size: 15px; line-height: 1.4; text-shadow: 0 1px 6px rgba(0,0,0,.65); }
      .live-player .lp-bottom {
        position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
        padding: 30px 14px calc(14px + var(--sab));
        background: linear-gradient(to top, rgba(0,0,0,.55), rgba(0,0,0,0));
      }
      .live-player .lp-who { color: #fff6dd; font-weight: 700; font-size: 13.5px; margin-bottom: 10px; text-shadow: 0 1px 6px rgba(0,0,0,.65); }

      /* ---------- Shorts: the full-screen feed ----------
         Shorts opens straight onto the first short, full screen,
         TikTok-style: one short fills the whole screen and a swipe
         or scroll brings the next one up, on and on — the feed
         loops and never dead-ends. No portions list, no tab
         row — the only furniture is the slim top bar (back, the
         lane label, and search: no Settings gear anywhere in
         Shorts) and each short's author / caption / action rail.
         The record button rides that rail under the bookmark —
         it is the one door into recording a short; there is no
         big record button in the middle of the screen. */
      .shorts-player { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 60; background: #000; display: flex; flex-direction: column; overflow: hidden; }
      /* The player fades in as it is built, so picking a different
         lane on the rail crossfades to the new lane's shorts
         instead of hard-cutting the whole stage. */
      .shorts-player { animation: shorts-in .24s ease-out both; }
      @keyframes shorts-in { from { opacity: 0; } to { opacity: 1; } }
      @media (prefers-reduced-motion: reduce) { .shorts-player { animation: none; } }
      .shorts-scroll { flex: 1 1 auto; min-height: 0; width: 100%; overflow-y: auto; overflow-x: hidden; scroll-snap-type: y mandatory; overscroll-behavior: contain; scrollbar-width: none; -webkit-overflow-scrolling: touch; touch-action: pan-y; }
      .shorts-scroll::-webkit-scrollbar { display: none; }
      .short-slide { position: relative; width: 100%; height: 100%; scroll-snap-align: start; scroll-snap-stop: always; overflow: hidden; background: #000; }
      /* The picture fills the whole screen, edge to edge
         (Casey's call, 2026-10-21: a bleed showed on the video
         at the right side, by the heart and reportion buttons —
         cleared up WITHOUT shrinking the picture or leaving a
         black strip down the side: the video stays full-screen,
         each slide clips at its own edges, and the transparent
         rail buttons ride on top of the picture; see
         .shorts-act below). When the bleed came BACK later the
         same day, the hunt found the stage itself was sound —
         the page under it was not: the pre-warm holder lived
         outside the shell at negative offsets and let the whole
         view rest shifted left (see warmShorts), and fractional
         widths could still round the picture a hair short of the
         right edge on his screen. So the scroller and each slide
         now carry an explicit full width, and the picture
         OVERSCANS by 2px on each side — a touch WIDER than the
         slide, never narrower — with the slide's own clip
         trimming the excess, so no rounding can ever open a
         seam at the right edge again. */
      .short-slide video { position: absolute; top: 0; bottom: 0; left: -2px; width: calc(100% + 4px); height: 100%; object-fit: cover; display: block; background: #000; opacity: 0; transition: opacity .18s ease; touch-action: pan-y; }
      /* The picture only paints once its first frame is decoded
         (Casey's report, 2026-10-21): an unloaded video element
         shows the browser's gray placeholder with a big play
         sign in the middle, and that gray screen used to greet
         him every time he opened Shorts. The slide behind is
         black, the frame fades in the moment it exists, and
         with the pre-warm below it is simply already there. */
      .short-slide video.ready { opacity: 1; }
      .cc-wrap .p-vid-mute, .pod-frame .p-vid-mute { bottom: 38px; }
      .p-head .p-head-bookmark { margin-left: auto; flex: none; }
      .short-poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; pointer-events: none; opacity: 1; transition: opacity .25s ease; }
      .short-slide.face-on .short-poster { opacity: 0; }
      .short-slide.whole .short-poster { object-fit: contain; }
      .shorts-topmid { position: absolute; left: 50%; transform: translateX(-50%); width: max-content; max-width: 74%; }
      .shorts-top #shorts-search { margin-left: auto; }
      button.shorts-act[data-menu-portion] .sa-ic { font-size: 26px; font-weight: 700; letter-spacing: 1px; }
      .shorts-cc-fixed { position: absolute; top: calc(14px + env(safe-area-inset-top)); left: 12px; z-index: 6; }
      /* A short made WIDER than it is tall — a video shot
         full-screen wide (Casey's report, 2026-10-22: "I made
         the video in full screen but it's not showing full
         screen") — is shown WHOLE. The cover fit above blows
         a wide picture up until it fills the tall screen and
         cuts its sides away, so most of the picture he made
         never showed. fitShortVideo() in renderShortsFeed
         marks these .fit-whole the moment the video's real
         shape is known, and the whole picture fits inside the
         screen, centered on the black stage, every edge of
         it visible. Portrait shorts — the shape made to fill
         a phone screen — keep the edge-to-edge cover fit
         above, untouched. */
      .short-slide video.fit-whole { object-fit: contain; }
      .shorts-top {
        position: absolute; top: 0; left: 0; right: 0; z-index: 6;
        display: flex; align-items: center; gap: 10px;
        padding: calc(12px + env(safe-area-inset-top)) 14px 26px;
        background: linear-gradient(to bottom, rgba(0,0,0,.5), rgba(0,0,0,0));
      }
      /* The top bar names the lane you are in (Casey's call,
         2026-10-21, replacing the lone play mark that sat here):
         the lane's own symbol and name at the top center — and
         under All lanes the label follows the short on screen,
         naming that short's own lane as you scroll. Directly
         under the lane label sits the on-screen short's @handle
         (Casey's call, later the same day — it used to sit at
         the bottom left over the caption): the middle column
         stacks the two, centered, and the handle follows the
         scroll the same way the label does. */
      .shorts-topmid { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 2px; }
      .shorts-title { flex: none; max-width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; color: #fff6dd; font-size: 15.5px; font-weight: 800; letter-spacing: .02em; text-shadow: 0 1px 6px rgba(0,0,0,.65); filter: drop-shadow(0 1px 6px rgba(0,0,0,.6)); }
      .shorts-title svg { width: 22px; height: 22px; display: block; flex: none; }
      .shorts-title .custom-emoji { font-size: 20px; line-height: 1; flex: none; }
      .shorts-title-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      /* Shorts lane picker (Casey's call, 2026-10-21): circular
         lane icons stacked down the LEFT edge of the Shorts
         screen — thumb reach, no reaching to the top of the
         phone. The rail runs the FULL height of the screen, top
         edge to bottom edge with no cutoff (Casey's call, later
         the same day), and it scrolls CONTINUOUSLY: the lane
         list is laid down several times back to back and the
         scroller wraps by one whole run whenever it nears an
         end, so scrolling the rail never hits a stop. The
         circle at the rail's middle — the middle of the screen
         — EXPANDS into a pill that names its lane, out of the
         way of the top bar and the caption at the bottom. Picking a lane
         (tap the expanded circle) switches the feed, and the
         rail settles back into plain circles, ready to scroll
         and pick again. No gear joins it; Shorts stays
         gear-free. */
      .shorts-lanes-rail {
        position: absolute; left: 0; top: 0; bottom: 0; z-index: 5;
        display: flex; flex-direction: column; gap: 10px; overflow-y: auto;
        align-items: flex-start;
        padding: calc(12px + env(safe-area-inset-top)) 6px calc(12px + env(safe-area-inset-bottom));
        scrollbar-width: none; -webkit-overflow-scrolling: touch;
        /* Nothing on a lane can be copied (Casey's call,
           2026-10-23): a long press on a lane used to select
           the lane's name like loose text, and the phone's own
           Copy / Select all / Web search / Share bar popped up
           over the short and would not leave the screen. The
           rail and its chips are unselectable and callout-free,
           so a long press selects nothing and no bar ever
           opens — the same lock the cover photo and the
           @handles already wear. */
        -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
        /* The rail owns its vertical drags outright (the same
           pan-y the Shorts feed claims): the browser hands it
           every up and down swipe without weighing in, and a
           drag that reaches a momentary end never chains
           through to the feed or the page behind it. */
        touch-action: pan-y; overscroll-behavior: contain;
      }
      .shorts-lanes-rail::-webkit-scrollbar { display: none; }
      .shorts-lane-chip {
        flex: none; display: flex; align-items: center; height: 40px; max-width: 40px;
        background: rgba(20,10,2,.55); color: #fff6dd;
        border: 1px solid rgba(255,246,221,.4); border-radius: 999px;
        padding: 0; cursor: pointer; font-family: inherit; overflow: hidden;
        transition: max-width .28s ease, background .2s ease, border-color .2s ease;
        /* The chip's name is never selectable text (see the
           rail above): every lane chip wears the lock itself,
           so the composer's and the recorder's lane bars —
           which share this class — are covered too. */
        -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
      }
      .shorts-lane-chip .slc-name, .shorts-lane-chip .slc-badge {
        -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
      }
      .shorts-lane-chip .slc-badge {
        width: 38px; height: 38px; flex: none; display: flex; align-items: center; justify-content: center;
        font-size: 13px; font-weight: 800; letter-spacing: .02em;
      }
      .shorts-lane-chip .slc-badge svg { width: 18px; height: 18px; }
      .shorts-lane-chip .slc-badge .custom-emoji { font-size: 19px; }
      .shorts-lane-chip .slc-name {
        font-size: 12.5px; font-weight: 700; white-space: nowrap; line-height: 1;
        opacity: 0; transition: opacity .18s ease;
      }
      .shorts-lane-chip.focus { max-width: 176px; padding-right: 14px; border-color: var(--gold-2); }
      .shorts-lane-chip.focus .slc-name { opacity: 1; }
      .shorts-lane-chip.on { background: var(--gold); border-color: var(--gold); color: #241503; }
      /* Folded rail (Casey's call, 2026-10-21; two-tap opening
         added 2026-10-22): the rail opens showing ONLY the
         globe — a bare circle, no name on it — centered on the
         left edge. Every other lane circle stays hidden.
         Tapping the globe pops it out into its name pill
         ("All lanes") and nothing more; tapping it AGAIN
         unfolds the rail (the lanes-collapsed class comes off)
         and the full scroll-and-pick rail runs as above.
         The first circle in the rail is always the globe, so
         it is the one circle a folded rail lets through, in
         both its plain and its popped-out form. */
      .shorts-lanes-rail.lanes-collapsed { overflow-y: hidden; justify-content: center; }
      .shorts-lanes-rail.lanes-collapsed .shorts-lane-chip { display: none; }
      .shorts-lanes-rail.lanes-collapsed .shorts-lane-chip:first-child { display: flex; }
      /* Share-a-portion lane bar (Casey's calls, 2026-10-22):
         the composer's lane picker is no longer a wall of
         chips eating the form — it is the Shorts lane rail
         laid FLAT inside the form (horizontal, by Casey's
         correction the same day, so it takes up less of the
         form): a slim horizontal strip of circular lane icons
         on a panel, scrolling WITHOUT END (the lane list is
         laid down back to back and the scroll wraps by one
         whole run, so it never hits a stop). The circle
         resting at the strip's middle expands into a pill
         naming its lane; scrolling browses, and a DOUBLE-TAP
         on the lane you want locks it in — the locked lane
         wears gold (.on, the Shorts rail's own chip dress)
         and Post lights up. The reading lines under the strip
         carry the how-to line, the locked pick, and the
         video-length note, so the strip itself stays one slim
         line across the form. */
      .c-lane-flex { display: flex; flex-direction: column; gap: 10px; }
      .c-lane-bar {
        width: 100%;
        display: flex; flex-direction: row; gap: 10px; align-items: center;
        overflow-x: auto; overflow-y: hidden; padding: 8px 10px;
        background: var(--card); border: 1px solid var(--line); border-radius: 18px;
        scrollbar-width: none; -webkit-overflow-scrolling: touch;
        touch-action: pan-x; overscroll-behavior: contain;
      }
      .c-lane-bar::-webkit-scrollbar { display: none; }
      /* A video under 90 seconds earns the Shorts pick on its
         own: the bar steps aside to that one locked circle,
         centered, with nothing to scroll. */
      .c-lane-bar-single { justify-content: center; overflow-x: hidden; }
      .c-lane-side { min-width: 0; }
      /* The composer symbol bar (Casey's call, 2026-10-23,
         from his X screenshot): everything you can put in a
         portion rides ONE bar at the bottom of the writing
         area — a symbol per thing, press it and it takes you
         into that thing. Gallery picture and gallery video
         are SEPARATE buttons, and Take a picture is its own
         button that opens the in-app camera — no combined
         "gallery / take picture" button stranding you in
         the gallery. No poll button: Polls has its own whole
         lane up top. The symbols are Manna's own — gold
         glyphs drawn for this bar (ic("cPic") and family),
         never emoji. */
      .c-tool-bar {
        display: flex; align-items: flex-start; gap: 2px;
        margin-top: 14px; padding: 10px 6px 8px;
        background: var(--card); border: 1px solid var(--line); border-radius: 18px;
        box-shadow: var(--shadow);
      }
      .c-tool {
        flex: 1 1 0; min-width: 0; background: none; border: none; padding: 2px 1px 0;
        display: flex; flex-direction: column; align-items: center; gap: 5px;
        color: var(--gold-text); font-family: inherit; cursor: pointer;
        -webkit-tap-highlight-color: transparent;
      }
      .c-tool .c-tool-disc {
        width: 46px; height: 46px; border-radius: 50%; flex: none;
        display: inline-flex; align-items: center; justify-content: center;
        background: linear-gradient(160deg, var(--card2), var(--card) 70%);
        border: 1.5px solid var(--gold);
        color: var(--gold-deep);
        box-shadow: 0 3px 9px rgba(138,95,19,.22), inset 0 0 0 3px var(--card);
      }
      .c-tool .c-tool-disc svg { width: 24px; height: 24px; }
      .c-tool .c-tool-label {
        font-size: 10.5px; font-weight: 700; line-height: 1.25; text-align: center;
        color: var(--ink-soft); letter-spacing: .01em;
      }
      .c-tool:active .c-tool-disc { transform: scale(.93); background: var(--gold-2); }
      .c-tool.on .c-tool-disc { background: linear-gradient(160deg, var(--gold-2), var(--gold) 60%, var(--gold-deep)); color: #241503; }
      .c-tool.on .c-tool-label { color: var(--gold-text); }
      /* The caption block's words ride UP off the bottom edge
         (Casey's call, 2026-10-31): the block itself still
         starts at the edge so its fade covers the picture's
         foot, but the bottom padding grew by 32px so the
         title text stands clear of the phone's gesture strip,
         lifted together with the seek bar below it (see
         .shorts-player .shorts-seek) — the whole bottom stack
         rises as one. The words themselves are untouched. */
      .shorts-bottom {
        position: absolute; left: 0; right: 76px; bottom: 0; z-index: 4;
        padding: 30px 14px calc(52px + env(safe-area-inset-bottom));
        background: linear-gradient(to top, rgba(0,0,0,.55), rgba(0,0,0,0));
        color: #fff6dd;
      }
      /* The recording-light pulse: a red record button
         breathes with it so it reads as "record" at a glance.
         The recorder screen's big button wears it (see
         .rec-bigbtn); the pulse rests under
         prefers-reduced-motion (see the reduce block). */
      @keyframes rec-pulse {
        0% { box-shadow: 0 0 0 0 rgba(224,36,58,.55), 0 6px 18px rgba(0,0,0,.5); }
        70% { box-shadow: 0 0 0 17px rgba(224,36,58,0), 0 6px 18px rgba(0,0,0,.5); }
        100% { box-shadow: 0 0 0 0 rgba(224,36,58,0), 0 6px 18px rgba(0,0,0,.5); }
      }
      .shorts-who { display: inline-block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 800; font-size: 14.5px; color: #fff6dd; text-shadow: 0 1px 6px rgba(0,0,0,.65); cursor: pointer; }
      /* The Follow button rides right beside the Shorts
         @handle too (Casey's call, 2026-10-22 — every portion
         carries one): the handle and the button share one
         centered row under the lane label, the handle giving
         up room first. */
      .shorts-who-row { display: flex; align-items: center; justify-content: center; gap: 8px; max-width: 100%; min-width: 0; }
      .shorts-who-row .shorts-who { min-width: 0; }
      .shorts-follow { box-shadow: 0 1px 6px rgba(0,0,0,.4); }
      .shorts-cap { font-size: 13.5px; line-height: 1.45; margin-top: 5px; text-shadow: 0 1px 6px rgba(0,0,0,.65); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
      /* A short that reached this feed because a kinsman
         reportioned it wears its "Suggested" line above the
         caption — who carried it here, named plain. */
      .shorts-via { display: flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 700; color: #ffe9b0; text-shadow: 0 1px 6px rgba(0,0,0,.65); }
      .shorts-via svg { width: 14px; height: 14px; flex: none; }
      .shorts-rail { position: absolute; right: 10px; bottom: calc(60px + env(safe-area-inset-bottom)); z-index: 5; display: flex; flex-direction: column; align-items: center; gap: 16px; }
      .shorts-act { display: flex; flex-direction: column; align-items: center; gap: 3px; background: none; border: none; color: #fff6dd; font-size: 11.5px; font-weight: 700; text-shadow: 0 1px 6px rgba(0,0,0,.65); padding: 0; }
      /* The rail buttons are TRANSPARENT (Casey's call,
         2026-10-21): no disc, no fill, no rim — the button is
         just its symbol and its count riding on the picture,
         and each symbol carries its own colour so the eye
         finds them on any video: the heart RED, the share
         (reportion) WHITE, the comment symbol BLUE, the
         bookmark GOLDEN. A stronger text/icon shadow is what
         keeps the transparent buttons readable — no black
         strip, no backing plate. */
      .shorts-act { text-shadow: 0 1px 4px rgba(0,0,0,.85), 0 0 10px rgba(0,0,0,.45); }
      .shorts-act .sa-ic { width: 46px; height: 46px; border-radius: 50%; background: transparent; border: none; display: inline-flex; align-items: center; justify-content: center; filter: drop-shadow(0 1px 4px rgba(0,0,0,.8)); }
      .shorts-act svg { width: 25px; height: 25px; }
      .shorts-act:active .sa-ic { transform: scale(.93); }
      .shorts-act .sa-ic { position: relative; }
      .shorts-act[data-heart] { color: var(--heart); }
      .shorts-act[data-heart] svg { fill: var(--heart); stroke: var(--heart); }
      .shorts-act.hearted svg { filter: drop-shadow(0 0 5px var(--heart)) drop-shadow(0 0 13px rgba(255,45,85,.55)); }
      .shorts-act[data-reportion] { color: #ffffff; }
      /* Holding the reportion button (Casey's call, 2026-10-21):
         a steady hold opens the write-a-portion sheet instead of
         the tap action. While the hold charges, the button turns
         gold and swells so the press reads as deliberate; the OS
         long-press callout / context menu stays out of the way. */
      [data-reportion], #d-reportion { -webkit-touch-callout: none; }
      .p-act.holding, .shorts-act.holding { color: var(--gold-deep); }
      .p-act.holding svg, .shorts-act.holding svg { transform: scale(1.22); fill: var(--gold-deep); }
      /* The Send to rows (Casey's calls, 2026-10-24): one
         build worn by the reportion options and both Share
         sheets, so sending looks the same throughout the
         app — your people on Manna in a row of faces, then
         the other apps in a row of round tiles, the way the
         big send sheets lay it out. Both rows scroll
         sideways inside the sheet so a long list of people
         never stretches the page. */
      .ro-send-label { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); margin-top: 16px; }
      .ro-people { display: flex; gap: 6px; overflow-x: auto; padding: 10px 2px 4px; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
      .ro-people::-webkit-scrollbar { display: none; }
      .ro-person { flex: 0 0 auto; width: 66px; background: none; border: none; padding: 2px 0; display: flex; flex-direction: column; align-items: center; gap: 5px; cursor: pointer; color: var(--ink); font: inherit; position: relative; }
      .ro-person .ro-pname { font-size: 11.5px; line-height: 1.25; max-width: 66px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      .ro-person.sent { opacity: .55; pointer-events: none; }
      .ro-person .ro-sent-badge { position: absolute; top: 0; right: 8px; width: 20px; height: 20px; border-radius: 50%; background: var(--gold); color: #fff; display: flex; align-items: center; justify-content: center; }
      .ro-person .ro-sent-badge svg { width: 12px; height: 12px; }
      .ro-apps { display: flex; gap: 12px; overflow-x: auto; padding: 12px 2px 6px; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
      .ro-apps::-webkit-scrollbar { display: none; }
      .ro-app { flex: 0 0 auto; width: 72px; background: none; border: none; padding: 2px 0; display: flex; flex-direction: column; align-items: center; gap: 5px; cursor: pointer; color: var(--ink); font: inherit; }
      /* The tiles wear their brands the way Casey's
         screenshot showed them (2026-10-31): big flat
         circles in each platform's own colour, the real
         brand glyph in white on top, the name underneath —
         the clean modern share-sheet look. The colours are
         fixed in every theme, so the row reads as a row of
         real apps, not app furniture. */
      .ro-app .ro-tile {
        width: 58px; height: 58px; border-radius: 50%;
        display: flex; align-items: center; justify-content: center; color: #fff;
        box-shadow: 0 4px 14px rgba(20,10,2,.18);
        transition: transform .12s ease;
      }
      .ro-app:active .ro-tile { transform: scale(.9); }
      @media (prefers-reduced-motion: reduce) { .ro-app .ro-tile { transition: none; } .ro-app:active .ro-tile { transform: none; } }
      .ro-app .ro-tile svg { width: 30px; height: 30px; }
      .ro-app .ro-tname { font-size: 12px; line-height: 1.25; text-align: center; }
      .ro-tile-fb { background: #1877F2; }
      .ro-tile-wa { background: #25D366; }
      .ro-tile-x { background: #000000; }
      .ro-tile-discord { background: #5865F2; }
      .ro-tile-sms { background: #FFFFFF; box-shadow: inset 0 0 0 1.5px #D9DFEA, 0 4px 14px rgba(20,10,2,.10); }
      .ro-tile-msg { background: linear-gradient(150deg, #00B2FF 0%, #006AFF 52%, #A033FF 125%); }
      .ro-tile-snap { background: #FFFC00; }
      .ro-tile-mail { background: #6E6E73; }
      .ro-tile-copy { background: #0B7FFF; }
      .ro-tile-story { background: linear-gradient(145deg, #F2B33D, #CF8317); color: #2e1d07; }
      .ro-tile-more { background: #241C14; }
      /* The faces in the Send to row pop the same way: a gold
         ring and a soft shadow lift each person off the sheet. */
      .ro-person .ava { box-shadow: 0 0 0 2px var(--gold-2), 0 4px 10px rgba(20,10,2,.28); }
      .shorts-act.active svg { fill: currentColor; }
      .shorts-act[data-short-comments] { color: #4da3ff; }
      /* The record button rides the rail under the bookmark
         (Casey's call, 2026-10-21): the record SYMBOL alone —
         a plain red dot in a cream ring — and NO word under
         it. Tap it and you are recording your own short. It
         is the only record button in Shorts; the big one that
         sat in a dock at the bottom center is gone. */
      .shorts-act .sa-rec { width: 30px; height: 30px; border-radius: 50%; background: #e0243a; border: 3px solid rgba(255,246,221,.95); box-shadow: 0 2px 8px rgba(0,0,0,.5); display: block; }
      /* GOLDEN, pinned as a fixed gold (Casey's call,
         2026-10-21: "the bookmark in shorts should be golden
         not blue"): the theme variable it used before runs
         blue in the blue theme, so the bookmark wears this
         gold in every theme. */
      .shorts-act[data-bookmark] { color: #f5b81f; }
      .shorts-act[data-bookmark] svg { stroke: #f5b81f; }
      .shorts-act.bookmarked svg { fill: #f5b81f; }
      .shorts-act.bookmarked svg { fill: currentColor; }
      /* The author's bubble at the top of the rail shows their
         real profile picture (Casey's call, 2026-10-21 — no
         bare initial where a picture exists); a cream ring
         keeps the round photo readable on any frame. The
         picture itself is painted in by paintShortsAvatars. */
      .shorts-rail [data-short-ava] .ava { box-shadow: 0 0 0 2px rgba(255,246,221,.9), 0 2px 8px rgba(0,0,0,.5); overflow: hidden; }
      /* (The paid-tier check keeps its tier colours inside
         Shorts too — beige for Basic, golden for Top Tier.) */
      .shorts-paused { position: absolute; inset: 0; z-index: 3; display: flex; align-items: center; justify-content: center; pointer-events: none; }
      .shorts-paused span { width: 74px; height: 74px; border-radius: 50%; background: rgba(20,10,2,.5); color: #fff6dd; display: inline-flex; align-items: center; justify-content: center; }
      .shorts-paused svg { width: 34px; height: 34px; }
      /* The seek bar (Casey's call, 2026-10-22): every short
         carries a moving bar along its bottom edge, like the
         player in the video he sent — the fill walks as the
         short plays, and a finger can land on the bar and
         slide it back and forth to any part of the short.
         While a finger is down, the track thickens, the knob
         grows, and a small bubble over the knob reads the spot
         it is on ("0:38 / 1:32"); letting go plays on from
         there. The bar spans the full width under the rail —
         the rail rides in a layer above it, so the rail
         buttons keep their taps untouched. The fill is gold
         and alive (Casey's call, 2026-10-22, after the X
         player video he sent): a light keeps sweeping along
         the gold while the video plays, the knob glows and
         breathes a soft halo, and the same bar rides the
         video-reply review player, so every app player wears
         the one animated bar. Reduced motion stills it. */
      .shorts-seek { position: absolute; left: 0; right: 0; bottom: 0; z-index: 4; height: 30px; display: flex; align-items: flex-end; padding: 0 12px calc(5px + env(safe-area-inset-bottom)); touch-action: none; -webkit-tap-highlight-color: transparent; outline: none; }
      /* In the full-screen player the bar rides UP off the
         bottom edge (Casey's call, 2026-10-31): flush with the
         edge it sat inside the phone's gesture strip, so a
         finger aiming at the bar kept hitting Home / Back and
         getting thrown out of the video. The whole bar —
         track, knob and time bubble — lifts by the safe-area
         inset plus 32px of clear air, and its inner padding
         drops the inset it no longer needs (the offset now
         carries it), so the track itself stands 37px plus the
         inset above the screen's bottom edge. The caption
         block lifts the same amount (see .shorts-bottom), so
         the bottom stack rises together. The video-reply
         review stage's bar (the same class on a small stage,
         nowhere near the gesture strip) stays where it was. */
      .shorts-player .shorts-seek { bottom: calc(32px + env(safe-area-inset-bottom)); padding-bottom: 5px; }
      .shorts-seek-track { position: relative; flex: 1 1 auto; height: 4px; border-radius: 99px; background: rgba(20,10,2,.5); box-shadow: inset 0 1px 2px rgba(0,0,0,.5), 0 1px 0 rgba(255,246,221,.18); transition: height .18s ease; }
      .shorts-seek.seeking .shorts-seek-track { height: 7px; }
      .shorts-seek-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0%; border-radius: 99px; background: linear-gradient(90deg, var(--gold-2), var(--gold) 55%, var(--gold-deep)); box-shadow: 0 0 10px rgba(246,205,103,.65); overflow: hidden; transition: width .15s linear; }
      .shorts-seek.seeking .shorts-seek-fill { transition: none; }
      .shorts-seek-fill::after { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 36%; background: linear-gradient(100deg, transparent, rgba(255,255,255,.75), transparent); transform: translateX(-130%) skewX(-18deg); animation: seekSheen 2.3s linear infinite; }
      @keyframes seekSheen { 0% { transform: translateX(-130%) skewX(-18deg); } 55%, 100% { transform: translateX(330%) skewX(-18deg); } }
      .shorts-seek-thumb { position: absolute; top: 50%; left: 0%; width: 12px; height: 12px; border-radius: 50%; background: radial-gradient(circle at 35% 32%, #ffedb3, var(--gold) 58%, var(--gold-deep)); border: 1.5px solid rgba(255,246,221,.9); transform: translate(-50%,-50%); box-shadow: 0 1px 6px rgba(0,0,0,.55), 0 0 0 0 rgba(246,205,103,.55); animation: seekPulse 1.7s ease-out infinite; transition: width .15s ease, height .15s ease, left .15s linear; }
      .shorts-seek.seeking .shorts-seek-thumb { width: 16px; height: 16px; animation: none; transition: width .15s ease, height .15s ease; }
      @keyframes seekPulse { 0% { box-shadow: 0 1px 6px rgba(0,0,0,.55), 0 0 0 0 rgba(246,205,103,.55); } 70% { box-shadow: 0 1px 6px rgba(0,0,0,.55), 0 0 0 8px rgba(246,205,103,0); } 100% { box-shadow: 0 1px 6px rgba(0,0,0,.55), 0 0 0 0 rgba(246,205,103,0); } }
      .shorts-seek-time { position: absolute; bottom: 32px; left: 0%; transform: translateX(-50%); background: rgba(20,10,2,.85); color: #fff6dd; font-size: 12px; font-weight: 700; line-height: 1; padding: 5px 9px; border-radius: 99px; white-space: nowrap; pointer-events: none; }
      @media (prefers-reduced-motion: reduce) { .shorts-seek-fill::after { animation: none; } .shorts-seek-thumb { animation: none; } .shorts-seek-fill, .shorts-seek-thumb { transition: none; } }
      .shorts-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 30px 34px; color: #fff6dd; background: #140a02; }

      /* ---------- Comments panel (app-wide) ----------
         Tapping the comment icon on ANY portion — a short, a
         lane feed card, Home, a profile, search — rolls this
         panel out over the current screen (Casey's calls,
         2026-10-20): the full thread and the "give your portion"
         box, right where you are. Nothing navigates to a
         separate portion screen, and closing the panel (scrim
         tap or ✕) lands you exactly where you were scrolled. */
      /* Pointer discipline (Casey's frozen-app report,
         2026-10-22): the wrap itself never takes a touch —
         only the panel, and the scrim only while the panel is
         open. The wrap used to cover the whole screen on its
         own, so in any state where its visible parts were
         faded or slid away (mid-close, or a close that never
         finished) it stood invisible over the app eating every
         tap and scroll, and the only way out was leaving the
         app and coming back. Now a wrap in that state is
         touch-transparent: the app underneath stays alive. */
      .comments-wrap { position: absolute; inset: 0; z-index: 66; pointer-events: none; }
      .comments-scrim { position: absolute; inset: 0; background: var(--overlay); opacity: 0; transition: opacity .26s ease; pointer-events: none; }
      .comments-panel {
        position: absolute; top: 0; right: 0; bottom: 0; width: min(88%, 360px);
        background: var(--card); border-left: 1px solid var(--line);
        box-shadow: -14px 0 34px rgba(0,0,0,.4);
        display: flex; flex-direction: column; pointer-events: auto;
        transform: translateX(103%); transition: transform .28s cubic-bezier(.32,.72,.24,1);
      }
      .comments-wrap.open .comments-scrim { opacity: 1; pointer-events: auto; }
      .comments-wrap.open .comments-panel { transform: translateX(0); }
      .comments-head { display: flex; align-items: center; gap: 10px; padding: calc(14px + var(--sat)) 14px 12px; border-bottom: 1px solid var(--line-soft); }
      .comments-head h3 { flex: 1; min-width: 0; }
      .comments-list { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 16px 12px; overscroll-behavior: contain; }
      .comments-list .spinner { margin: 34px auto; }
      .comments-foot { border-top: 1px solid var(--line-soft); padding: 10px 14px calc(10px + var(--sab)); max-height: 62%; overflow-y: auto; }
      .comments-foot .reply-banner { margin-top: 0; }
      .comments-foot .comment-box { margin-top: 10px; }
      @media (prefers-reduced-motion: reduce) {
        .comments-scrim, .comments-panel { transition: none; }
      }

      /* ---------- Portion insights panel ----------
         The small chart button on every portion (Casey's call,
         2026-10-20) opens this panel over the current screen,
         riding the comments panel's shell: the portion's views,
         hearts, comments and reportions, counted from the
         tables the way Creator Studio counts them. */
      .ins-row { display: flex; align-items: center; gap: 12px; padding: 14px 2px; border-bottom: 1px solid var(--line-soft); }
      .ins-ic { width: 40px; height: 40px; border-radius: 50%; background: var(--card2); border: 1px solid var(--line); display: inline-flex; align-items: center; justify-content: center; color: var(--gold-deep); flex: none; }
      .ins-ic svg { width: 19px; height: 19px; }
      .ins-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
      .ins-t { font-weight: 800; font-size: 15px; }
      .ins-s { font-size: 12px; color: var(--ink-faint); line-height: 1.45; margin-top: 1px; }
      .ins-n { font-size: 19px; font-weight: 800; font-variant-numeric: tabular-nums; }

      /* ---------- Comment bubble tools (picture · video · GIF · @) ----------
         TikTok-style (Casey's calls, 2026-10-20): the options
         live INSIDE the comment/reply bubble — a small tool row
         tucked along the bubble's bottom edge: attach a picture,
         record a video reply, attach a GIF, or @ someone. */
      .bubble-tools { position: absolute; right: 8px; bottom: 8px; display: flex; gap: 6px; align-items: center; z-index: 2; }
      .bubble-tools .gif-in-btn { position: static; }
      .bubble-tool {
        border: 1.5px solid var(--line); background: var(--card2); color: var(--ink-soft);
        border-radius: 7px; padding: 4px 7px; cursor: pointer; font-family: inherit;
        font-size: 12px; font-weight: 800; line-height: 1; display: inline-flex; align-items: center; justify-content: center;
      }
      .bubble-tool svg { width: 14px; height: 14px; }
      .bubble-tool:active { background: var(--chip); }
      .gif-picked video { width: 100%; display: block; border-radius: 14px; border: 1px solid var(--line); background: #140a02; }
      .p-vid-comment { width: 100%; display: block; border-radius: 14px; margin-top: 10px; border: 1px solid var(--line); background: #140a02; }
      .comment .p-vid-comment { max-width: 300px; }
      .mention-panel { margin-top: 10px; border: 1px solid var(--line); border-radius: 14px; background: var(--card); padding: 10px; }
      .mention-list { margin-top: 8px; max-height: 220px; overflow-y: auto; }
      .mention-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: none; border: none; border-top: 1px solid var(--line-soft); padding: 8px 4px; cursor: pointer; font-family: inherit; color: var(--ink); }
      .mention-row:first-child { border-top: none; }
      .mention-row .m-name { font-weight: 700; font-size: 14px; display: block; }
      .mention-row .m-handle { font-size: 12px; color: var(--ink-faint); font-weight: 700; display: block; }
      /* The comment video recorder: a compact stage over the
         phone — record, review, post the reply. */
      .vidrec-overlay { position: absolute; inset: 0; z-index: 80; background: #140a02; color: #fff6dd; display: flex; flex-direction: column; padding: calc(12px + env(safe-area-inset-top)) 16px calc(14px + env(safe-area-inset-bottom)); overflow-y: auto; }
      .vidrec-head { display: flex; align-items: center; gap: 10px; }
      .vidrec-title { font-family: "Marcellus", Georgia, serif; font-size: 19px; flex: 1; text-align: center; }
      .vidrec-overlay video { width: 100%; border-radius: 16px; background: #000; margin-top: 14px; max-height: 56vh; object-fit: cover; }
      .vidrec-overlay video#vr-preview { object-fit: contain; }
      /* The video-reply review stage (Casey's call, 2026-10-22):
         the take plays in the app's own player — no browser
         controls — with the CC button and caption bar riding it
         and the animated progress bar along its bottom. */
      .vr-stage { position: relative; margin-top: 14px; }
      .vr-stage video { margin-top: 0; display: block; }
      .vr-stage .cc-btn { bottom: 42px; }
      .vr-stage .cc-bar { bottom: 88px; }
      .vr-badge { position: absolute; inset: 0; z-index: 5; display: flex; align-items: center; justify-content: center; pointer-events: none; }
      .vr-badge span { width: 66px; height: 66px; border-radius: 50%; background: rgba(20,10,2,.55); display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 4px 18px rgba(0,0,0,.45); }
      .vr-badge svg { width: 30px; height: 30px; margin-left: 3px; }
      .vidrec-status { font-size: 13.5px; line-height: 1.55; text-align: center; margin: 14px 6px 0; opacity: .88; }
      .vidrec-dock { display: flex; gap: 10px; align-items: center; margin-top: 16px; }
      .vidrec-timer { font-weight: 800; font-variant-numeric: tabular-nums; font-size: 16px; min-width: 46px; text-align: center; }
      /* Comment sort switch (Top / Newest) */
      .comments-sortbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 16px; border-bottom: 1px solid var(--line-soft); }
      .comments-sortbar .cs-label { font-size: 12px; font-weight: 800; color: var(--ink-faint); }
      .c-sort-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 18px; }
      .c-sort-row .sec-label { margin: 0; }
      .seg.c-sort { width: auto; flex: none; margin: 0; }
      .seg.c-sort button { padding: 7px 14px; font-size: 13px; }
      /* ---------- Debate Hall: thumbs votes + two-sided comments ----------
         Only inside the Debate Hall (Casey's calls, 2026-10-21):
         a portion there is voted with thumbs up / thumbs down —
         no heart — and its comments split into two sides,
         Affirmative and Opposition. The same portion back in its
         home lane keeps its heart and its one ordinary thread;
         none of these styles reach it there. */
      .debate-votes { display: inline-flex; align-items: center; gap: 1px; }
      .debate-votes .dv-score { font-weight: 800; font-variant-numeric: tabular-nums; min-width: 22px; text-align: center; font-size: 13.5px; }
      .p-act.vote-on { color: var(--gold-deep); }
      .p-act.vote-on svg { fill: currentColor; }
      .p-act.dv-down.vote-on { color: var(--danger); }
      .p-act.dv-down.vote-on svg { fill: currentColor; }
      .side-pill { border: 1px solid var(--line); background: var(--card2); color: var(--ink-soft); }
      .side-pill.aff { background: rgba(85,125,30,.14); border-color: var(--ok); color: var(--ok); }
      .side-pill.opp { background: rgba(179,64,42,.10); border-color: var(--danger); color: var(--danger); }
      .debate-tally { padding: 10px 16px 12px; border-bottom: 1px solid var(--line-soft); }
      .debate-tally .dt-nums { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 13px; font-weight: 800; }
      .debate-tally .dt-nums .aff { color: var(--ok); }
      .debate-tally .dt-nums .opp { color: var(--danger); }
      .debate-tally .dt-bar { display: flex; height: 8px; border-radius: 99px; overflow: hidden; background: var(--seg-bg); margin-top: 7px; }
      .debate-tally .dt-bar .dt-aff { background: var(--ok); height: 100%; }
      .debate-tally .dt-bar .dt-opp { background: var(--danger); height: 100%; }
      .debate-sec-head {
        display: flex; align-items: center; gap: 8px; margin: 14px 0 2px;
        font-family: "Marcellus", Georgia, serif; font-size: 16.5px; color: var(--ink);
      }
      .debate-sec-head .dsh-n { margin-left: auto; font-family: "Public Sans", sans-serif; font-size: 12px; font-weight: 800; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
      .debate-sec-head.aff { color: var(--ok); }
      .debate-sec-head.opp { color: var(--danger); }
      .side-picker { display: flex; gap: 8px; margin-top: 10px; }
      .side-picker button {
        flex: 1; border: 1.5px solid var(--line); background: var(--card); color: var(--ink-soft);
        border-radius: 12px; padding: 10px 6px; font-weight: 800; font-size: 13.5px; font-family: inherit; cursor: pointer;
        display: inline-flex; align-items: center; justify-content: center; gap: 6px;
      }
      .side-picker button svg { width: 16px; height: 16px; }
      .side-picker button.on[data-side="aff"] { background: var(--ok); border-color: var(--ok); color: #fff; }
      .side-picker button.on[data-side="opp"] { background: var(--danger); border-color: var(--danger); color: #fff; }
      /* ---------- Playlists ---------- */
      .pl-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: none; border: none; border-top: 1px solid var(--line-soft); padding: 13px 16px; cursor: pointer; font-family: inherit; color: var(--ink); }
      .pl-row:first-child { border-top: none; }
      .pl-row .pl-ic { width: 42px; height: 42px; border-radius: 12px; background: var(--card2); border: 1px solid var(--line); display: inline-flex; align-items: center; justify-content: center; color: var(--gold-deep); flex: none; }
      .pl-row .pl-ic svg { width: 20px; height: 20px; }
      .pl-row .pl-main { flex: 1; min-width: 0; }
      .pl-row .pl-name { font-weight: 800; font-size: 15px; display: block; }
      .pl-row .pl-sub { font-size: 12px; color: var(--ink-faint); font-weight: 700; display: block; margin-top: 1px; }
      /* The background must be a palette color that exists:
         this once read var(--bg), a variable nothing defines,
         so the layer painted fully transparent — the screen
         behind showed through while this full-screen layer
         silently ate every tap and scroll (Casey's frozen-app
         report, 2026-10-22). */
      .playlist-view { position: absolute; inset: 0; z-index: 72; background: linear-gradient(180deg, var(--app-bg), var(--app-bg2)); overflow-y: auto; padding: calc(12px + var(--sat)) 16px calc(18px + var(--sab)); }
      .plv-item { display: flex; gap: 11px; align-items: center; width: 100%; text-align: left; background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 10px 12px; margin-top: 9px; cursor: pointer; font-family: inherit; color: var(--ink); box-shadow: var(--shadow); }
      .plv-item.on { border-color: var(--gold); }
      .plv-item .plv-n { font-family: "Marcellus", Georgia, serif; font-size: 17px; color: var(--gold-text); width: 22px; text-align: center; flex: none; }
      .plv-item .plv-main { flex: 1; min-width: 0; }
      .plv-item .plv-t { font-size: 14px; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
      .plv-item .plv-who { font-size: 12px; color: var(--ink-faint); font-weight: 700; margin-top: 2px; }
      /* Playlist editing (Casey's call, 2026-10-31): Edit mode
         swaps a row's tap-to-play for up / down / remove
         mini-buttons — the same reorder pattern the lane
         settings use — in the playlist view and the sidebar. */
      .plv-item.editing { cursor: default; }
      .plv-edit-acts { display: inline-flex; gap: 6px; align-items: center; flex: none; }
      .pl-row.editing { cursor: default; }
      .pl-row .pl-acts { display: inline-flex; gap: 6px; align-items: center; flex: none; }
      .plv-del-pl { width: 100%; margin-top: 16px; color: var(--danger); border-color: var(--danger); }
      /* ---------- Profile playlist strip ----------
         Playlists ride on top of the portions (Casey's call,
         2026-10-22): a row of playlist cards right under the
         profile, above the portions themselves, the way TikTok
         lays them out — a cover, the name, and the count. The row scrolls sideways
         inside itself; the page never does. Casey asked for
         the strip smaller twice the same day — the second time
         "a lot a lot smaller" — so the cards are miniatures
         now: a thumb-wide cover, a one-line name, and type
         just big enough to read at arm's length. */
      .pl-strip { display: flex; gap: 7px; overflow-x: auto; padding: 1px 2px 6px; margin: 0 -2px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
      .pl-strip::-webkit-scrollbar { display: none; }
      .pl-card { flex: 0 0 74px; width: 74px; text-align: left; background: var(--card); border: 1px solid var(--line); border-radius: 9px; padding: 4px; cursor: pointer; font-family: inherit; color: var(--ink); box-shadow: var(--shadow); }
      .pl-card-cover { position: relative; display: block; height: 36px; border-radius: 6px; overflow: hidden; background: var(--card2); }
      .pl-card-cover video, .pl-card-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
      .pl-card-ic { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--gold-deep); }
      .pl-card-ic svg { width: 15px; height: 15px; }
      /* The playlist cover mark (Casey's call, 2026-10-22):
         stacked rounded squares with a play triangle in the
         front square, gold outlines on the cream ground —
         drawn after the reference he sent, in place of the
         first video's frame (which showed as a gray screen
         with a big black play circle). The squares are filled
         with the cover's own ground so each front square hides
         the lines of the ones behind it, like the reference. */
      .pl-card-art { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
      .pl-card-art svg { width: 42px; height: 32px; display: block; }
      .pl-card-art .sq { fill: var(--card2); stroke: var(--gold-deep); stroke-width: 2.6; }
      .pl-card-art .tri { fill: none; stroke: var(--gold-deep); stroke-width: 2.4; stroke-linejoin: round; stroke-linecap: round; }
      /* The subscription symbol inside profiles (Casey's call,
         2026-10-22): a dollar-sign coin overlapping a pill
         labeled SUBS, signal arcs above — after the reference
         he sent, with the mouse cursor left out, the outlines
         gold instead of black, and the ground light beige. It
         rides the Subscriptions button on both profile views
         (his own three-across row and everyone else's action
         row) in place of the old circular-arrows icon; the
         wider-than-tall art needs its own size, past the
         generic 18px button-icon rule. */
      .btn-ghost svg.subs-sym-svg { width: 48px; height: 30px; }
      .prof-actions .btn-ghost svg.subs-sym-svg { width: 42px; height: 26px; }
      .subs-sym-svg .bg { fill: var(--card2); }
      .subs-sym-svg .ln { fill: var(--card2); stroke: var(--gold-deep); stroke-width: 2.4; }
      .subs-sym-svg .arc { fill: none; stroke: var(--gold-deep); stroke-width: 2.2; stroke-linecap: round; }
      .subs-sym-svg .tx { fill: var(--gold-deep); font-weight: 800; }
      .pl-card-count { position: absolute; right: 3px; bottom: 3px; display: inline-flex; align-items: center; gap: 2px; background: rgba(20,10,2,.72); color: #fff; font-size: 9px; font-weight: 800; padding: 1px 4px; border-radius: 99px; }
      .pl-card-count svg { width: 8px; height: 8px; }
      .pl-card-name { display: block; font-weight: 800; font-size: 10.5px; line-height: 1.25; margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .pl-card-sub { display: block; font-size: 9px; color: var(--ink-faint); font-weight: 700; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .pl-card-new { border-style: dashed; background: none; box-shadow: none; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; color: var(--gold-deep); font-weight: 800; font-size: 10.5px; min-height: 70px; }
      .pl-card-new svg { width: 15px; height: 15px; }
      /* The locked playlists card on your own profile (Casey's
         call, 2026-10-24): playlists on a profile stay locked
         until the owner reaches 500 followers — a paid tier
         unlocks them right away, at any follower count. The
         owner sees this card in place of the strip, with the
         count standing where they are; visitors simply never
         see the strip on a locked profile. */
      .pl-locked { padding: 15px 16px; margin-bottom: 6px; }
      .pl-locked .pll-top { display: flex; align-items: center; gap: 11px; }
      .pl-locked .pll-ic { width: 40px; height: 40px; border-radius: 50%; flex: none; background: var(--card2); border: 1px solid var(--line); display: inline-flex; align-items: center; justify-content: center; color: var(--gold-deep); }
      .pl-locked .pll-ic svg { width: 18px; height: 18px; }
      .pl-locked .pll-t { font-weight: 800; font-size: 15px; color: var(--ink); display: block; }
      .pl-locked .pll-s { font-size: 13px; color: var(--ink-soft); line-height: 1.5; display: block; margin-top: 2px; }
      .pl-locked .pll-count { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-top: 12px; font-size: 12.5px; font-weight: 700; color: var(--ink-soft); }
      /* ---------- Direct Portions (DMs) ---------- */
      .dm-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 12px 14px; margin-bottom: 10px; cursor: pointer; font-family: inherit; color: var(--ink); box-shadow: var(--shadow); }
      .dm-row .dm-main { flex: 1; min-width: 0; }
      .dm-row .dm-name { font-weight: 800; font-size: 15px; display: block; }
      .dm-row .dm-snip { font-size: 13px; color: var(--ink-soft); display: block; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .dm-row .dm-time { font-size: 11.5px; color: var(--ink-faint); font-weight: 700; flex: none; }
      /* Manna Dating conversations list in this same inbox
         (Casey's call, 2026-10-21 — the separate Dating Inbox
         is scratched): they sit UNDER the regular direct
         portions, in their own "Manna Dating" section, so a
         dating thread stays tellable from a regular one by
         placement alone. The red itself is an alert, not a
         uniform (Casey's refinement, 2026-10-22): only a
         dating row he hasn't checked yet wears it — the red
         border and wash, the red name and tag, and the
         beating heart badge on the row's corner. Once the
         message is opened and checked, the red and the
         animation rest and the row wears the plain inbox
         look, tag gone quiet — but it stays right here in
         the Manna Dating section. */
      .dm-sec-head { display: flex; align-items: center; gap: 8px; margin: 22px 2px 12px; font-size: 12px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; color: var(--heart); }
      .dm-sec-head svg { width: 15px; height: 15px; fill: currentColor; flex: none; }
      .dm-sec-head::after { content: ""; flex: 1; height: 1px; background: var(--line); }
      .dm-row-dating { position: relative; border: 1.5px solid var(--heart); background: linear-gradient(160deg, rgba(224,36,58,.10), rgba(224,36,58,.03) 70%), var(--card); }
      .dm-row-dating .dm-name { color: var(--heart); }
      .dm-dating-tag { display: inline-block; vertical-align: 2px; margin-left: 7px; background: var(--heart); color: #fff; font-size: 9.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; padding: 2.5px 7px; border-radius: 99px; }
      /* A checked dating row keeps its tag as a quiet label —
         the category still reads at a glance, without the red. */
      .dm-dating-tag.calm { background: var(--card2); color: var(--ink-soft); border: 1px solid var(--line); }
      .dm-dating-heart { position: absolute; top: -10px; right: 14px; width: 25px; height: 25px; border-radius: 50%; background: var(--heart); color: #fff; display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 3px 9px rgba(120,10,25,.4); animation: dm-heart-beat 1.7s ease-in-out infinite; pointer-events: none; }
      .dm-dating-heart svg { width: 13px; height: 13px; fill: currentColor; }
      @keyframes dm-heart-beat { 0%, 100% { transform: scale(1); } 12% { transform: scale(1.24); } 24% { transform: scale(1); } 36% { transform: scale(1.17); } 50% { transform: scale(1); } }
      @media (prefers-reduced-motion: reduce) { .dm-dating-heart { animation: none; } }
      .pill-dating { background: var(--heart); color: #fff; }
      /* Manna Market conversations list in this same inbox
         (Casey's call, 2026-10-23): buyer-and-seller threads
         get their own lane too, under the Manna Dating section —
         a "Manna Market" section of their own, so Market talk
         never mixes with regular portions or dating threads.
         Market wears green the way dating wears red, and it
         carries its OWN animation notification (Casey's call,
         the same day): a market row he hasn't checked yet
         wears the green border and wash, the green tag and New
         pill, and a bag badge on the row's corner that SWINGS
         like a bag being carried — never the dating heartbeat,
         so the lane a new message belongs to reads at a glance.
         Once checked, the green and the swing rest and the row
         keeps the plain look with a quiet tag, still in its
         own Manna Market lane. */
      .dm-sec-head.market { color: var(--market); }
      .dm-row-market { position: relative; border: 1.5px solid var(--market); background: linear-gradient(160deg, rgba(28,122,68,.12), rgba(28,122,68,.03) 70%), var(--card); }
      .dm-row-market .dm-name { color: var(--market); }
      .dm-market-tag { display: inline-block; vertical-align: 2px; margin-left: 7px; background: var(--market); color: #fff; font-size: 9.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; padding: 2.5px 7px; border-radius: 99px; }
      .dm-market-tag.calm { background: var(--card2); color: var(--ink-soft); border: 1px solid var(--line); }
      .dm-market-badge { position: absolute; top: -10px; right: 14px; width: 25px; height: 25px; border-radius: 50%; background: var(--market); color: #fff; display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 3px 9px rgba(10,60,35,.4); animation: dm-bag-swing 2.2s ease-in-out infinite; transform-origin: 50% -6px; pointer-events: none; }
      .dm-market-badge svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2.2; }
      @keyframes dm-bag-swing { 0%, 100% { transform: rotate(0deg); } 18% { transform: rotate(16deg); } 38% { transform: rotate(-12deg); } 56% { transform: rotate(7deg); } 72% { transform: rotate(-4deg); } 86% { transform: rotate(0deg); } }
      @media (prefers-reduced-motion: reduce) { .dm-market-badge { animation: none; } }
      .pill-market { background: var(--market); color: #fff; }
      .dm-new-pill.market { background: var(--market); }
      /* A dating message he hasn't seen yet signs its own
         inbox row (Casey's call, 2026-10-22): a dating
         message's news lives on the inbox, never the bell,
         so the row itself carries it — a red New pill and
         the snippet set in full ink until he opens Direct
         Portions and the mark rests. */
      .dm-new-pill { display: inline-block; vertical-align: 2px; margin-left: 7px; background: var(--heart); color: #fff; font-size: 9.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; padding: 2.5px 7px; border-radius: 99px; }
      .dm-row .dm-snip.unread { color: var(--ink); font-weight: 700; }
      .dm-thread { display: flex; flex-direction: column; gap: 8px; padding: 6px 0 12px; }
      /* The thread rests at the BOTTOM of the screen (Casey's
         call, 2026-10-24): the wrapper fills the screen under
         the header and the messages anchor to its foot, just
         above the composer, instead of stranding at the top
         over a tall empty gap. A long thread simply outgrows
         the wrapper and scrolls the way it always did. There
         is no "Say hello" card (Casey's call, 2026-10-24): a
         new thread is simply empty until the first direct
         portion lands. */
      .dm-screen { display: flex; flex-direction: column; min-height: calc(100dvh - 172px); }
      .dm-screen .dm-thread { flex: 1 1 auto; justify-content: flex-end; }
      .dm-msg { max-width: 82%; padding: 10px 13px; border-radius: 18px; font-size: 14.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
      .dm-msg.them { align-self: flex-start; background: var(--card); border: 1px solid var(--line); border-bottom-left-radius: 6px; }
      .dm-msg.me { align-self: flex-end; background: var(--gold); color: #241503; border-bottom-right-radius: 6px; }
      .dm-msg .dm-when { display: block; font-size: 10.5px; font-weight: 700; margin-top: 4px; opacity: .65; text-align: right; }
      /* Direct portions: hold a message for reactions / delete
         (Casey's call, 2026-10-21). The press must not fight the
         scroll or start a text selection, so bubbles give up
         selection and the callout. Reaction badges ride the
         bubble's bottom corner (the block below). */
      .dm-msg { -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }
      /* Every reaction rides ON the bubble, not in it
         (Casey's calls, 2026-10-22 + 2026-10-31): like
         reacting on X, the glyph itself pins to the bubble's
         bottom edge at the corner that reaches across —
         their messages wear the row on the right corner,
         yours on the left — no pill, no chip around it.
         Every glyph shares the one row now, a little bigger
         than the heart first was (heart 21px, chips 12.5px);
         before 2026-10-31 only the heart wore the corner
         while the faces sat inside the bubble as chips, on
         the wrong side. The row stays alive once it lands:
         the heart keeps a soft heartbeat, the faces sway
         gently, and a fresh landing pops — the heart with
         its full burst (ring and sparks, dmHeartPop() in
         the script), a face with the springy pop
         (dmBadgePop()). */
      .dm-msg { position: relative; }
      .dm-msg.has-reacts { margin-bottom: 9px; }
      .dm-badges { position: absolute; bottom: -13px; z-index: 2; display: flex; align-items: center; gap: 5px; }
      .dm-badges:empty { display: none; }
      .dm-msg.them .dm-badges { right: 8px; }
      .dm-msg.me .dm-badges { left: 8px; }
      .dm-badge { position: relative; display: inline-flex; align-items: center; gap: 2.5px; margin: 0; padding: 0 1px; background: none; border: 0; font-family: inherit; font-size: 21.5px; line-height: 1; color: var(--ink); cursor: pointer; -webkit-tap-highlight-color: transparent; }
      .dm-badge:not(.dm-heart-badge) { filter: drop-shadow(0 1.5px 2.5px rgba(0,0,0,.35)); animation: dm-badge-sway 3.8s ease-in-out infinite; }
      .dm-badge.mine:not(.dm-heart-badge) { filter: drop-shadow(0 0 3.5px rgba(201,151,63,.95)) drop-shadow(0 1.5px 2.5px rgba(0,0,0,.3)); }
      .dm-badge .dm-badge-n { font-size: 11px; font-weight: 800; text-shadow: 0 0 4px var(--app-bg), 0 0 4px var(--app-bg); }
      .dm-badge.pop:not(.dm-heart-badge) { animation: dm-badge-pop .55s cubic-bezier(.2,2.2,.4,1); }
      @keyframes dm-badge-pop {
        0% { transform: scale(0) rotate(-22deg); }
        45% { transform: scale(1.42) rotate(7deg); }
        70% { transform: scale(.9) rotate(-3deg); }
        100% { transform: scale(1) rotate(0deg); }
      }
      @keyframes dm-badge-sway {
        0%, 100% { transform: rotate(-2.5deg) translateY(0); }
        50% { transform: rotate(2.5deg) translateY(-1.2px); }
      }
      .dm-heart-badge { color: var(--heart); }
      .dm-heart-badge svg { width: 25px; height: 25px; fill: var(--heart); stroke: #fff; stroke-width: 1.6; filter: drop-shadow(0 1.5px 3.5px rgba(120,10,25,.45)); display: block; animation: dm-heart-beat 2.6s ease-in-out infinite; transform-origin: center; }
      @keyframes dm-heart-beat {
        0%, 100% { transform: scale(1); }
        7% { transform: scale(1.16); }
        14% { transform: scale(1); }
        21% { transform: scale(1.11); }
        30% { transform: scale(1); }
      }
      .dm-heart-badge .dm-badge-n { color: var(--heart); }
      .dm-heart-badge.pop svg { animation: dm-heart-pop .55s cubic-bezier(.2,2.2,.4,1); transform-origin: center; }
      @keyframes dm-heart-pop {
        0% { transform: scale(0) rotate(-24deg); }
        45% { transform: scale(1.45) rotate(7deg); }
        70% { transform: scale(.88) rotate(-3deg); }
        100% { transform: scale(1) rotate(0deg); }
      }
      .dm-heart-ring { position: absolute; left: 50%; top: 50%; width: 30px; height: 30px; margin: -15px 0 0 -15px; border-radius: 50%; border: 2px solid var(--heart); opacity: 0; pointer-events: none; animation: dm-heart-ring .5s ease-out forwards; }
      @keyframes dm-heart-ring { 0% { opacity: .85; transform: scale(.35); } 100% { opacity: 0; transform: scale(1.25); } }
      .dm-heart-sparks { position: absolute; inset: 0; pointer-events: none; }
      .dm-heart-sparks i { position: absolute; left: 50%; top: 50%; width: 6px; height: 6px; margin: -3px 0 0 -3px; border-radius: 50%; background: var(--heart); opacity: 0; animation: dm-spark-fly .55s ease-out forwards; }
      .dm-heart-sparks i.h { width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px; background: none; border-radius: 0; color: var(--heart); }
      .dm-heart-sparks i.h svg { width: 100%; height: 100%; display: block; fill: currentColor; stroke: currentColor; }
      @keyframes dm-spark-fly {
        0% { opacity: 0; transform: translate(0,0) scale(.4) rotate(0deg); }
        22% { opacity: 1; }
        100% { opacity: 0; transform: translate(var(--dx,0px), var(--dy,0px)) scale(1) rotate(var(--rot,0deg)); }
      }
      @media (prefers-reduced-motion: reduce) {
        .dm-heart-badge svg, .dm-badge, .dm-heart-badge.pop svg, .dm-heart-ring, .dm-heart-sparks i { animation: none; }
      }
      .dm-gif { display: block; max-width: 210px; width: 100%; border-radius: 12px; margin-top: 6px; }
      .dm-gif:first-child { margin-top: 0; }
      /* Voice notes in a direct portion (Casey's call,
         2026-10-22): a player sized to the bubble, not a
         picture frame. */
      .dm-audio { display: block; width: 232px; max-width: 100%; margin-top: 6px; }
      .dm-audio:first-child { margin-top: 0; }
      /* An attached voice note waiting over a composer: the
         player with its remove button riding beside it. */
      .att-row { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
      .att-row audio { flex: 1; min-width: 0; width: 100%; }
      /* A voice note on a comment plays inline like the
         comment's pictures and videos do. */
      .p-aud-comment { width: 100%; display: block; margin-top: 10px; }
      .comment .p-aud-comment { max-width: 300px; }
      /* ---------- Voice-note player (X-style) ----------
         Casey's calls, 2026-10-24: a voice note is NEVER
         downloadable in the app, and its playback is Manna's
         own player copied from the way X plays voice notes —
         a compact pill: round play/pause button, a row of
         waveform bars that fill as it plays (tap them to
         seek), and the time. The native audio bar is gone
         everywhere a voice note plays, so its menu (the one
         offering Download) never exists; the real <audio>
         rides hidden inside with no controls at all. */
      .vn-player { display: flex; align-items: center; gap: 10px; width: 100%; max-width: 340px; background: var(--card2); border: 1px solid var(--line); border-radius: 999px; padding: 6px 12px 6px 6px; margin-top: 8px; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
      .vn-player:first-child { margin-top: 0; }
      .vn-btn { width: 36px; height: 36px; flex: none; border: none; border-radius: 50%; background: linear-gradient(160deg, var(--gold-2), var(--gold) 55%, var(--gold-deep)); color: #241503; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; }
      .vn-btn svg { width: 17px; height: 17px; fill: currentColor; display: block; }
      .vn-btn:active { transform: scale(.93); }
      .vn-bars { flex: 1; min-width: 36px; height: 26px; display: flex; align-items: center; gap: 2px; cursor: pointer; }
      .vn-bars i { flex: 1; min-width: 2px; border-radius: 2px; background: var(--line); display: block; }
      .vn-bars i.on { background: var(--gold-deep); }
      .vn-time { flex: none; font-size: 12px; font-weight: 700; color: var(--ink-soft); font-variant-numeric: tabular-nums; min-width: 30px; text-align: right; }
      .vn-audio { display: none; }
      /* Inside a message bubble the pill hugs the bubble and
         borrows its dress: on my gold bubbles it sits light,
         on theirs it sits on the card colour. */
      .dm-msg .vn-player { max-width: 248px; margin-top: 2px; background: rgba(255,253,246,.5); border-color: rgba(36,21,3,.16); }
      .dm-msg.them .vn-player { background: var(--card2); border-color: var(--line); }
      .att-row .vn-player { flex: 1; min-width: 0; margin-top: 0; }
      /* The DM composer is one box (Casey's call, 2026-10-24):
         an attached voice note's player fits INSIDE the
         portion box, under the words — never a wide bar
         hanging below it. The wrap carries the border and
         field; the textarea inside goes borderless. */
      .dm-compose { background: var(--field); border: 1px solid var(--line); border-radius: 13px; }
      .dm-compose:focus-within { border-color: var(--gold); }
      .dm-compose textarea.input { border: none; background: transparent; border-radius: 13px; }
      .dm-compose textarea.input:focus { border: none; }
      .dm-compose #dm-gif-picked { padding: 0 10px; }
      .dm-compose #dm-gif-picked:not(:empty) { padding-bottom: 48px; }
      .dm-compose #dm-gif-picked .att-row { margin-top: 2px; }
      .dm-compose #dm-gif-picked .gif-picked { margin-top: 2px; }
      /* The recorder sheet's record button pulses while a
         take runs (shared by both dresses of the sheet). */
      @keyframes vn-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(179,64,42,.45); } 50% { box-shadow: 0 0 0 13px rgba(179,64,42,0); } }
      /* The voice-reply sheet (Casey's call, 2026-10-22, from
         his screenshot): "Voice reply" over "Replying to
         @name", a progress line across the sheet, "Tap to
         record (15 sec max)", and one big red record button
         with a white ring centered at the sheet's foot.
         Direct-portion voice notes wear the same look
         (Casey's call, 2026-10-22): same sheet, grip,
         progress line and red button, titled "Voice note"
         with "Tap to record (2 min max)". */
      .vr-sheet { text-align: center; position: relative; }
      .vr-grip { width: 46px; height: 5px; border-radius: 3px; background: var(--line); margin: 0 auto 16px; }
      .vr-x { position: absolute; top: 10px; right: 10px; }
      .vr-title { font-size: 25px; font-weight: 800; letter-spacing: .2px; }
      .vr-to { color: var(--ink-soft); font-size: 15.5px; margin-top: 5px; }
      .vr-track { height: 7px; border-radius: 4px; background: var(--line); margin: 30px 2px 0; overflow: hidden; }
      .vr-fill { height: 100%; width: 0%; border-radius: 4px; background: #e8112d; }
      .vr-hint { font-size: 16.5px; line-height: 1.5; margin-top: 30px; padding: 0 8px; }
      .vr-recrow { display: flex; justify-content: center; margin-top: 30px; }
      .vr-rec { width: 94px; height: 94px; border-radius: 50%; border: 5px solid #fff; background: #e8112d; box-shadow: 0 0 0 2px rgba(0,0,0,.30), var(--shadow); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; padding: 0; font-family: inherit; }
      .vr-rec .vr-stop { width: 30px; height: 30px; border-radius: 7px; background: #fff; display: block; }
      .vr-rec.rec { animation: vn-pulse 1.3s ease-in-out infinite; }
      @media (prefers-reduced-motion: reduce) { .vr-rec.rec { animation: none; } }
      .dm-emoji-row { display: flex; gap: 6px; margin: 2px 0 12px; }
      .dm-emoji-row button { flex: 1; font-size: 26px; line-height: 1.3; background: var(--card2); border: 1.5px solid var(--line-soft); border-radius: 14px; padding: 7px 0; cursor: pointer; font-family: inherit; }
      .dm-emoji-row button.on { border-color: var(--gold-deep); background: var(--chip); }
      /* The DM hold menu floats AT the message (Casey's call,
         2026-10-21): hold a message and its emoji row pops up
         right on that message — above the bubble when there's
         room, below it when there isn't — never down at the
         bottom of the screen like a sheet. The held message
         lifts with a ring while its menu is up. */
      .dm-msg.dm-held { box-shadow: 0 0 0 2px var(--gold-deep), 0 10px 26px rgba(0,0,0,.35); position: relative; z-index: 71; }
      .dm-pop-overlay { position: absolute; inset: 0; z-index: 70; background: var(--overlay); }
      .dm-pop {
        position: absolute; width: min(330px, calc(100vw - 20px));
        background: var(--card); border: 1px solid var(--line); border-radius: 18px;
        box-shadow: 0 16px 44px rgba(0,0,0,.45); padding: 10px;
        animation: dm-pop-in .3s cubic-bezier(.2,1.5,.4,1);
      }
      @keyframes dm-pop-in { from { opacity: 0; transform: scale(.65) translateY(14px); } to { opacity: 1; transform: scale(1) translateY(0); } }
      .dm-pop .dm-emoji-row { margin: 0; }
      /* The faces in the hold menu are ALIVE (Casey's call,
         2026-10-24 — "a lot more lively and a lot more
         animated"): bigger than before, they spring in one
         after another with an overshoot bounce, and then
         they never sit still — the heart beats, the laughing
         face rocks, and the rest bob and sway, each on its
         own timing so the row feels awake instead of
         marching in step. Pressing one makes it cheer, and
         the one you pick leaps up in a little celebration
         before the menu closes (see the .picked step in
         openDmMenu). */
      .dm-pop .dm-emoji-row button {
        font-size: 28px; padding: 7px 0;
        animation: dm-emoji-in .5s cubic-bezier(.2,2.2,.4,1) backwards, dm-emoji-bob 2.4s ease-in-out .6s infinite;
      }
      .dm-pop .dm-emoji-row button:nth-child(1) { animation-name: dm-emoji-in, dm-emoji-beat; animation-duration: .5s, 1.7s; }
      .dm-pop .dm-emoji-row button:nth-child(2) { animation-name: dm-emoji-in, dm-emoji-rock; animation-delay: .05s, .65s; animation-duration: .5s, 1.9s; }
      .dm-pop .dm-emoji-row button:nth-child(3) { animation-delay: .1s, .7s; animation-duration: .5s, 2.7s; }
      .dm-pop .dm-emoji-row button:nth-child(4) { animation-delay: .15s, .75s; animation-duration: .5s, 2.15s; }
      .dm-pop .dm-emoji-row button:nth-child(5) { animation-delay: .2s, .8s; animation-duration: .5s, 2.5s; }
      .dm-pop .dm-emoji-row button:nth-child(6) { animation-delay: .25s, .85s; animation-duration: .5s, 2.9s; }
      @keyframes dm-emoji-in {
        0% { transform: scale(0) translateY(16px) rotate(-20deg); opacity: 0; }
        55% { transform: scale(1.3) translateY(-5px) rotate(7deg); opacity: 1; }
        78% { transform: scale(.92) translateY(1px) rotate(-2deg); }
        100% { transform: scale(1) translateY(0) rotate(0deg); }
      }
      @keyframes dm-emoji-bob {
        0%, 100% { transform: translateY(0) rotate(0deg); }
        30% { transform: translateY(-4px) rotate(-4deg); }
        65% { transform: translateY(2px) rotate(3.5deg); }
      }
      @keyframes dm-emoji-beat {
        0%, 100% { transform: scale(1); }
        12% { transform: scale(1.24); }
        24% { transform: scale(1); }
        36% { transform: scale(1.16); }
        52% { transform: scale(1); }
      }
      @keyframes dm-emoji-rock {
        0%, 100% { transform: rotate(0deg) translateY(0); }
        25% { transform: rotate(-9deg) translateY(-2px); }
        75% { transform: rotate(9deg) translateY(-2px); }
      }
      .dm-pop .dm-emoji-row button:active { animation: dm-emoji-cheer .32s ease-in-out infinite alternate; }
      @keyframes dm-emoji-cheer { from { transform: scale(1.25) translateY(-3px) rotate(-6deg); } to { transform: scale(1.48) translateY(-6px) rotate(6deg); } }
      .dm-pop .dm-emoji-row button.picked { animation: dm-emoji-picked .24s ease-in forwards; }
      @keyframes dm-emoji-picked {
        35% { transform: scale(1.8) rotate(16deg); opacity: 1; }
        100% { transform: scale(2.3) translateY(-10px) rotate(-10deg); opacity: .85; }
      }
      @media (prefers-reduced-motion: reduce) { .dm-pop, .dm-pop .dm-emoji-row button { animation: none; } }
      /* The GIF library (Casey's call, 2026-10-21): a shared
         shelf of uploaded GIFs plus each member's own favorites,
         opened from the comment GIF picker. */
      /* GIF grid: every GIF in its OWN square tile, never
         stacked or overlaying the next (Casey's call,
         2026-10-24, from his screenshot: the rows were being
         squeezed inside the flex sheet and each GIF spilled
         down over the ones below it). The grid takes the
         sheet's leftover height and scrolls inside itself
         (flex + min-height:0, rows packed from the top with
         align-content:start so they can never be compressed
         to fit), and each cell's GIF is pinned to the cell
         itself — absolute, inset 0 — so it fills its tile
         exactly and can never paint outside it. */
      .giflib-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; overflow-y: auto; padding: 2px; flex: 1 1 auto; min-height: 0; align-content: start; grid-auto-rows: max-content; }
      .giflib-cell { position: relative; border-radius: 12px; overflow: hidden; background: var(--card2); aspect-ratio: 1; min-width: 0; }
      .giflib-cell img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; cursor: pointer; }
      .giflib-star { position: absolute; top: 4px; right: 4px; z-index: 2; width: 30px; height: 30px; border-radius: 50%; border: 1px solid rgba(245,232,207,.5); background: rgba(20,10,2,.62); color: #fff6dd; font-size: 16px; line-height: 1; cursor: pointer; font-family: inherit; }
      .giflib-star.on { color: var(--gold-2); border-color: var(--gold-2); }
      /* View counts on video portions (public, from portion_views) */
      .p-views { cursor: default; }
      .promoted-note { display: flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 800; color: var(--gold-text); margin-top: 8px; }
      /* The paid-promotion disclaimer (Casey's call, 2026-10-22):
         promoting a video is paid, and the promoted video itself
         carries the mark — very small, riding a corner of the
         picture on every surface that plays it. */
      .paid-promo-badge { position: absolute; top: 8px; left: 8px; z-index: 6; padding: 3px 8px; border-radius: 999px; background: rgba(20,10,2,.68); color: #fff6dd; font-size: 10px; font-weight: 700; letter-spacing: .3px; line-height: 1.25; pointer-events: none; box-shadow: 0 1px 5px rgba(0,0,0,.35); }
      .short-slide .paid-promo-badge { top: calc(96px + env(safe-area-inset-top)); left: 64px; }
      /* The Made with AI disclosure (Casey's call, 2026-10-23):
         a small mark riding ON the video itself, top right —
         the Paid promotion mark holds the top left, so the two
         never collide. On a Short it drops below the top bar
         the same way the promo mark does. */
      .ai-made-badge { position: absolute; top: 8px; right: 8px; z-index: 6; padding: 3px 8px; border-radius: 999px; background: rgba(20,10,2,.68); color: #fff6dd; font-size: 10px; font-weight: 700; letter-spacing: .3px; line-height: 1.25; pointer-events: none; box-shadow: 0 1px 5px rgba(0,0,0,.35); }
      .short-slide .ai-made-badge { top: calc(96px + env(safe-area-inset-top)); right: 12px; }
      /* The Manna watermark (Casey's call, 2026-10-23): a small
         TRANSLUCENT Manna mark — the gold M with its drop,
         straight from the app's own symbol — riding at the
         TOP CENTER of a video, between the Paid promotion
         mark (top left) and the Made with AI mark (top
         right), so the three never collide. See-through by
         design: it names the house without sitting on the
         picture. On a Short it drops below the top bar like
         its neighbours; in the recorder's preview it drops
         below the status pill, and it is only ever seen once
         a video is made and previewing — never on the live
         camera, never while recording. */
      .manna-wm { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 6; display: inline-flex; align-items: center; gap: 5px; color: #f6cd67; opacity: .55; pointer-events: none; filter: drop-shadow(0 1px 4px rgba(0,0,0,.55)); }
      .manna-wm svg { width: 15px; height: 15px; display: block; flex: none; }
      .manna-wm-word { font-family: "Marcellus", Georgia, serif; font-size: 11px; letter-spacing: .22em; text-indent: .22em; line-height: 1; text-transform: uppercase; white-space: nowrap; }
      .manna-wm[hidden] { display: none; }
      .short-slide .manna-wm { top: calc(96px + env(safe-area-inset-top)); }
      #rec-wm { top: calc(114px + env(safe-area-inset-top)); }
      /* The Remove watermark lever (Casey's call, 2026-10-23):
         one row on every pre-post surface — the words "Remove
         watermark" and the app's own lever switch beside them.
         The lever rests off and the watermark rides; press it,
         the lever goes over, and the watermark comes off the
         preview then and there. */
      .wm-row { margin-top: 12px; }
      .wm-row-main { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
      .wm-row-label { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); }
      .wm-note { margin-top: 6px; line-height: 1.5; }
      .rec-postbar .wm-row-label { color: #fff6dd; text-shadow: 0 1px 6px rgba(0,0,0,.65); }
      .rec-postbar .wm-note { color: rgba(255,246,221,.82); text-shadow: 0 1px 6px rgba(0,0,0,.65); }
      /* The disclosure question on the recorder's dark post bar
         keeps the light words its neighbours use. */
      .rec-postbar .ai-ask-note { color: rgba(255,246,221,.82); text-shadow: 0 1px 6px rgba(0,0,0,.65); }
      .sub-lock { margin-top: 10px; border: 1.5px dashed var(--gold-deep); border-radius: 14px; background: var(--card2); padding: 20px 16px; text-align: center; color: var(--ink-soft); }
      .sub-lock svg { width: 26px; height: 26px; color: var(--gold-deep); }
      .sub-lock .sub-lock-t { font-family: "Marcellus", Georgia, serif; font-size: 17px; color: var(--ink); margin-top: 6px; }
      .sub-lock p { font-size: 13px; line-height: 1.55; margin: 5px 0 0; }
      .prof-link { display: inline-flex; align-items: center; gap: 5px; margin-top: 0; font-size: 12.5px; line-height: 1.3; font-weight: 700; color: var(--gold-text); text-decoration: none; word-break: break-all; }
      .prof-link svg { width: 13px; height: 13px; flex: none; }

      /* ---------- Manna Dating ----------
         Hinge-style cards (Casey's call, 2026-10-20): members
         come one at a time in a plain scroll — a big photo
         first, prompt cards under it, the basic facts last.
         The photo and every prompt carries its own heart, and
         a heart can carry a short note. Clean and uncluttered,
         generous spacing, Manna's own colors. */
      .dt-card {
        background: var(--card); border: 1px solid var(--line); border-radius: 26px;
        box-shadow: var(--shadow); margin-bottom: 30px; overflow: hidden;
      }
      .dt-photo-wrap { position: relative; }
      .dt-photo {
        display: block; width: 100%; padding: 0; border: none; cursor: pointer;
        background: var(--card2); font-family: inherit;
      }
      .dt-photo img { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
      .dt-photo-none {
        display: flex; align-items: center; justify-content: center; aspect-ratio: 4 / 5;
        font-family: "Marcellus", Georgia, serif; font-size: 96px; color: var(--gold-text);
        background: linear-gradient(165deg, var(--card2), var(--card) 75%);
      }
      .dt-heart {
        position: absolute; right: 14px; bottom: 14px; width: 48px; height: 48px; border-radius: 50%;
        border: 1px solid var(--line); background: var(--card); color: var(--ink-soft); cursor: pointer;
        display: inline-flex; align-items: center; justify-content: center; padding: 0;
        box-shadow: 0 4px 14px rgba(59, 36, 4, .22);
      }
      .dt-heart svg { width: 23px; height: 23px; }
      .dt-heart.on { background: var(--gold); border-color: var(--gold-deep); color: #241503; }
      .dt-heart.on svg { fill: currentColor; }
      .dt-heart:active { transform: scale(.92); }
      .dt-prompt { position: relative; margin: 16px 16px 0; background: var(--card2); border: 1px solid var(--line-soft); border-radius: 18px; padding: 16px 62px 16px 16px; }
      .dt-prompt .dp-q { font-size: 12px; font-weight: 700; color: var(--ink-faint); }
      .dt-prompt .dp-a { font-family: "Marcellus", Georgia, serif; font-size: 19px; line-height: 1.45; color: var(--ink); margin-top: 5px; overflow-wrap: break-word; white-space: pre-line; }
      .dt-prompt .dt-heart { right: 12px; bottom: 12px; width: 40px; height: 40px; box-shadow: var(--shadow); }
      .dt-prompt .dt-heart svg { width: 19px; height: 19px; }
      .dt-note { margin: 12px 16px 0; }
      .dt-note-row { display: flex; gap: 8px; align-items: stretch; }
      .dt-note-row .input { flex: 1; min-width: 0; }
      .dt-note-sent { margin: 12px 16px 0; font-size: 13px; color: var(--ink-soft); line-height: 1.5; display: flex; gap: 7px; align-items: flex-start; }
      .dt-note-sent .dt-sent-ic { color: var(--gold-deep); display: inline-flex; flex: none; margin-top: 1px; }
      .dt-note-sent .dt-sent-ic svg { width: 15px; height: 15px; fill: currentColor; }
      .dt-facts { padding: 20px 18px 18px; }
      .dt-name { font-family: "Marcellus", Georgia, serif; font-size: 25px; color: var(--ink); line-height: 1.25; }
      .dt-handle { font-size: 13px; font-weight: 700; color: var(--ink-faint); margin-top: 2px; }
      .dt-fact-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 13px; }
      .dt-fact { font-size: 13px; font-weight: 600; color: var(--ink-soft); border: 1px solid var(--line); border-radius: 99px; padding: 6px 11px; }
      /* Nationality is a flag pick in Manna Dating (Casey's
         call, 2026-10-21): no nationality word anywhere — a
         grid of flags to choose from, the picked flag standing
         big above the grid. */
      .dt-flag-picked { font-size: 46px; line-height: 1.15; min-height: 54px; display: flex; align-items: center; margin-top: 4px; }
      .dt-flag-grid { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 8px; max-height: 236px; overflow-y: auto; padding: 4px 2px; -webkit-overflow-scrolling: touch; }
      .dt-flag { font-size: 27px; line-height: 1; padding: 8px 9px; border-radius: 12px; border: 1.5px solid var(--line); background: var(--card); cursor: pointer; font-family: inherit; }
      .dt-flag.on { border-color: var(--gold-deep); background: var(--card2); box-shadow: var(--shadow); }
      .dt-open { width: 100%; margin-top: 16px; }

      /* Dating profile setup (first time in Manna Dating):
         the looking-for choice, the know-about-you chips, the
         spiel fields, and the photo slots — main pic first,
         at least two more after it. */
      .dt-look { display: flex; flex-direction: column; gap: 9px; margin-top: 6px; }
      .dt-look button {
        text-align: left; border: 1.5px solid var(--line); background: var(--card); color: var(--ink);
        border-radius: 16px; padding: 15px 16px; font-size: 15.5px; font-weight: 700; cursor: pointer;
        font-family: inherit; line-height: 1.35;
      }
      .dt-look button.on { border-color: var(--gold-deep); background: var(--card2); box-shadow: var(--shadow); }
      .dt-photos { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 10px; }
      .dt-slot {
        position: relative; aspect-ratio: 4 / 5; border-radius: 14px; overflow: hidden; width: 100%;
        border: 1.5px dashed var(--line); background: var(--card2); padding: 0;
        display: flex; align-items: center; justify-content: center; cursor: pointer; font-family: inherit;
      }
      .dt-slot.filled { border-style: solid; cursor: default; }
      .dt-slot img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
      .dt-slot-add { font-size: 30px; line-height: 1; color: var(--gold-text); font-weight: 400; }
      .dt-slot-tag {
        position: absolute; left: 6px; bottom: 6px; background: rgba(24, 14, 2, .78); color: #fff;
        font-size: 10.5px; font-weight: 800; padding: 3px 8px; border-radius: 99px;
      }
      .dt-slot-x {
        position: absolute; top: 6px; right: 6px; width: 27px; height: 27px; border-radius: 50%; border: none;
        background: rgba(24, 14, 2, .78); color: #fff; font-size: 16px; line-height: 1; cursor: pointer; padding: 0;
        display: inline-flex; align-items: center; justify-content: center;
      }

      /* Dating room swipes: the card drags sideways under the
         finger (vertical scrolling stays native), with an
         Interested / Pass stamp fading in by direction. */
      .dt-card { touch-action: pan-y; transition: transform .25s ease, opacity .25s ease; }
      .dt-card.swiping { transition: none; }
      .dt-stamp {
        position: absolute; top: 18px; z-index: 5; pointer-events: none; opacity: 0;
        font-size: 14px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase;
        padding: 7px 12px; border-radius: 10px; border: 2px solid; background: var(--card);
      }
      .dt-stamp-like { left: 16px; color: var(--gold-text); border-color: var(--gold-deep); transform: rotate(-8deg); }
      .dt-stamp-pass { right: 16px; color: var(--ink-soft); border-color: var(--line); transform: rotate(8deg); }

      /* ---------- Manna Dating: the full-page room ----------
         (Casey's call, 2026-10-21) Profiles are browsed one
         full page at a time, Hinge-style: the room takes the
         whole screen and each member owns a full page of the
         snap feed. Only one member's page is ever in the
         room: the next member is not rendered — cannot be
         seen or scrolled to — until the member on screen is
         decided with the dock's X or heart (or the matching
         swipe). Inside a page you scroll down through
         the whole profile — a tall hero photo with the name
         laid over it (Manna's own display), then their
         prompts and pictures interleaved, the facts last.
         When a page rolls into view its pieces rise and
         settle in one after another, the way Hinge's
         profiles animate in. The dock pinned under the feed
         carries Pass and Interested for the member on
         screen; the sideways swipe and the per-piece hearts
         still work inside the page. */
      .dt-room { position: absolute; inset: 0; z-index: 58; display: flex; flex-direction: column; background: linear-gradient(180deg, var(--app-bg), var(--app-bg2)); }
      .dt-top { display: flex; align-items: center; gap: 8px; padding: calc(10px + var(--sat)) 12px 10px; flex: none; }
      .dt-top .dt-ttl { flex: 1; min-width: 0; text-align: center; font-family: "Marcellus", Georgia, serif; font-size: 19px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      /* My own dating profile rides the top bar as a bubble
         (Casey's call, 2026-10-21): my dating profile's main
         picture in a red-ringed circle, set right beside the
         messages button. One tap opens my dating profile to
         view or edit it. The little pencil badge on its
         corner says what the tap does. */
      .dt-bubble { position: relative; flex: none; width: 40px; height: 40px; border-radius: 50%; border: none; background: none; padding: 0; cursor: pointer; font-family: inherit; }
      .dt-bubble .ava { width: 40px; height: 40px; border: 2px solid var(--heart); box-shadow: 0 2px 8px rgba(120,10,25,.28); box-sizing: border-box; }
      .dt-bubble:active { transform: scale(.94); }
      .dt-bubble-edit { position: absolute; right: -2px; bottom: -2px; width: 16px; height: 16px; border-radius: 50%; background: var(--heart); color: #fff; display: inline-flex; align-items: center; justify-content: center; border: 1.5px solid var(--card); pointer-events: none; }
      .dt-bubble-edit svg { width: 9px; height: 9px; }
      .dt-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; scroll-snap-type: y mandatory; overscroll-behavior: contain; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
      .dt-scroll::-webkit-scrollbar { display: none; }
      .dt-page { height: 100%; scroll-snap-align: start; scroll-snap-stop: always; overflow-y: auto; overflow-x: hidden; scrollbar-width: none; padding: 4px 14px calc(18px + var(--sab)); transition: transform .3s ease, opacity .3s ease; }
      .dt-page::-webkit-scrollbar { display: none; }
      .dt-page .dt-card { margin-bottom: 16px; }
      /* Inside a page the pieces stand on their own (the page
         itself is the card now): later photos get their own
         rounded frame, and the facts close the profile in
         their own panel. */
      .dt-page .dt-rise > .dt-photo-wrap { border-radius: 22px; overflow: hidden; box-shadow: var(--shadow); margin-bottom: 16px; }
      .dt-facts-card { background: var(--card); border: 1px solid var(--line); border-radius: 26px; box-shadow: var(--shadow); overflow: hidden; }
      .dt-intro { min-height: 100%; display: flex; flex-direction: column; justify-content: center; padding: 10px 6px 26px; }
      .dt-hero { position: relative; border-radius: 26px; overflow: hidden; border: 1px solid var(--line); box-shadow: var(--shadow); background: var(--card2); margin-bottom: 16px; }
      .dt-hero .dt-photo img { aspect-ratio: auto; height: 56dvh; }
      .dt-hero .dt-photo-none { aspect-ratio: auto; height: 56dvh; }
      .dt-hero-name { position: absolute; left: 0; right: 0; bottom: 0; padding: 52px 18px 15px; background: linear-gradient(to top, rgba(22,11,2,.74), rgba(22,11,2,0)); pointer-events: none; }
      .dt-hero-name .dt-name { color: #fff6dd; font-size: 27px; text-shadow: 0 1px 8px rgba(0,0,0,.45); }
      .dt-hero-name .dt-handle { color: rgba(255,246,221,.78); }
      .dt-hero-name .gold-check { box-shadow: 0 2px 6px rgba(0,0,0,.4); }
      /* Closest-match star (Casey's call, 2026-10-21): the
         member(s) whose fit score tops the room wear a gold
         star on their main photo — a dark pill in the photo's
         top-left corner, so it reads on any picture and in
         either color scheme, with the words beside the star
         saying what it means. It never takes a tap. */
      .dt-topfit { position: absolute; top: 14px; left: 14px; z-index: 3; display: inline-flex; align-items: center; gap: 6px; padding: 7px 12px 7px 9px; border-radius: 999px; background: rgba(22,11,2,.8); border: 1px solid rgba(246,205,103,.6); color: #fff3d6; font-size: 12.5px; font-weight: 800; letter-spacing: .02em; line-height: 1; box-shadow: 0 4px 14px rgba(0,0,0,.35); pointer-events: none; }
      .dt-topfit-star { display: inline-flex; color: var(--gold-2); }
      .dt-topfit-star svg { width: 15px; height: 15px; fill: currentColor; }
      .dt-page .dt-rise { opacity: 0; }
      .dt-page.on .dt-rise { animation: dt-rise .55s cubic-bezier(.22,.8,.3,1) both; animation-delay: calc(var(--i, 0) * 75ms); }
      @keyframes dt-rise { from { opacity: 0; transform: translateY(28px) scale(.985); } to { opacity: 1; transform: translateY(0) scale(1); } }
      @media (prefers-reduced-motion: reduce) {
        .dt-page .dt-rise { opacity: 1; }
        .dt-page.on .dt-rise { animation: none; }
      }
      .dt-dock { flex: none; display: flex; align-items: center; justify-content: center; gap: 24px; padding: 10px 16px calc(12px + var(--sab)); }
      .dt-dock-btn { width: 56px; height: 56px; border-radius: 50%; border: 1px solid var(--line); background: var(--card); color: var(--ink-soft); display: inline-flex; align-items: center; justify-content: center; box-shadow: var(--shadow); cursor: pointer; padding: 0; font-family: inherit; }
      .dt-dock-btn svg { width: 24px; height: 24px; }
      .dt-dock-btn:active { transform: scale(.9); }
      .dt-dock-heart { width: 64px; height: 64px; background: var(--heart); border-color: var(--heart); color: #fff; }
      .dt-dock-heart svg { width: 28px; height: 28px; fill: currentColor; }
      /* The big hearts (Casey's call, 2026-10-21): tapping
         the dock's Interested heart fills the room with
         hearts — dtBigHearts() in the script spawns this
         layer over the room: one big heart swells up off
         the button (--bx / --by mark its center) while a
         shower of smaller hearts rises, drifts and fades
         over the profile. The layer never takes a tap. */
      .dt-bigburst { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 70; }
      .dt-bigburst i { position: absolute; color: var(--heart); opacity: 0; }
      .dt-bigburst i svg { width: 100%; height: 100%; display: block; fill: currentColor; stroke: currentColor; filter: drop-shadow(0 0 6px rgba(255,45,85,.55)); }
      .dt-bigburst .dt-big-one { width: 118px; height: 118px; left: var(--bx, 50%); top: var(--by, 82%); margin: -59px 0 0 -59px; animation: dt-big-one 1.15s ease-out forwards; }
      @keyframes dt-big-one {
        0% { opacity: 0; transform: scale(.25) rotate(-7deg); }
        22% { opacity: 1; transform: scale(1.22) rotate(3deg); }
        45% { transform: scale(1) rotate(0deg); }
        100% { opacity: 0; transform: translateY(-120px) scale(1.1) rotate(5deg); }
      }
      .dt-bigburst .dt-big-fly { left: var(--bx, 50%); top: var(--by, 82%); animation: dt-big-fly 1.35s ease-out forwards; animation-delay: var(--del, 0ms); }
      @keyframes dt-big-fly {
        0% { opacity: 0; transform: translate(0, 0) scale(.4) rotate(0deg); }
        16% { opacity: 1; }
        100% { opacity: 0; transform: translate(var(--dx, 0px), calc(-1 * var(--up, 240px))) scale(1.05) rotate(var(--rot, 0deg)); }
      }
      @media (prefers-reduced-motion: reduce) {
        .dt-bigburst { display: none; }
      }
      .dt-count { font-size: 12px; font-weight: 800; color: var(--ink-faint); min-width: 46px; text-align: center; font-variant-numeric: tabular-nums; }

      /* ---------- Top 10 sidebar ---------- */
      .top10-wrap { position: absolute; inset: 0; z-index: 65; }
      .top10-scrim { position: absolute; inset: 0; background: var(--overlay); }
      .top10-panel {
        position: absolute; top: 0; right: 0; bottom: 0; width: min(87%, 342px);
        background: var(--card); border-left: 1px solid var(--line);
        box-shadow: -14px 0 34px rgba(96,62,14,.28);
        display: flex; flex-direction: column;
      }
      .top10-head { display: flex; align-items: center; gap: 10px; padding: 16px 14px 12px; border-bottom: 1px solid var(--line-soft); }
      .top10-head h3 { font-size: 21px; color: var(--ink); }
      .top10-head .t10-h-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
      .top10-list { flex: 1; overflow-y: auto; padding-bottom: 14px; }
      .t10-row { display: flex; gap: 10px; align-items: flex-start; padding: 11px 12px; cursor: pointer; }
      .t10-row + .t10-row { border-top: 1px solid var(--line-soft); }
      .t10-row:active { background: var(--card2); }
      .t10-rank { font-family: "Marcellus", Georgia, serif; font-size: 20px; color: var(--gold-text); width: 25px; text-align: center; flex: none; line-height: 1.25; }
      .t10-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
      .t10-who { font-size: 11.5px; color: var(--ink-faint); font-weight: 700; }
      .t10-snip { font-size: 13.5px; line-height: 1.4; margin-top: 1px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
      .t10-votes { display: flex; flex-direction: column; align-items: center; gap: 2px; flex: none; }
      .t10-score { font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; }
      .vbtn {
        width: 30px; height: 23px; border-radius: 8px; border: 1px solid var(--line);
        background: var(--card2); color: var(--ink-soft);
        display: inline-flex; align-items: center; justify-content: center; padding: 0;
      }
      .vbtn svg { width: 14px; height: 14px; }
      .vbtn.on { background: var(--gold); border-color: var(--gold-deep); color: #241503; }
      .vbtn:active { transform: scale(.92); }

      /* ---------- Break Bread, Music, Library ---------- */
      .stripe-card {
        background: var(--field); border: 1px solid var(--line); border-radius: 13px;
        padding: 15px 14px; margin-top: 6px;
      }
      .amt-row { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 6px; }
      .amt-row .chip { flex: 1; text-align: center; padding: 13px 6px; font-size: 16px; font-weight: 800; }
      /* Break Bread amount picks light up (Casey's call, 2026-10-28):
         the chosen amount — a number or Custom — glows gold and pops
         once, so nobody wonders whether their tap landed. */
      .amt-row .chip { transition: transform .15s ease, box-shadow .2s ease, background .2s ease, color .2s ease; }
      .amt-row .chip.on {
        background: linear-gradient(160deg, var(--gold-2), var(--gold) 55%, var(--gold-deep));
        color: #2e1d07; border-color: var(--gold-deep);
        box-shadow: 0 0 0 3px rgba(210,154,31,.35), 0 8px 20px rgba(210,154,31,.45);
        animation: bb-pop .3s ease;
      }
      @keyframes bb-pop { 0% { transform: scale(1); } 40% { transform: scale(1.1); } 100% { transform: scale(1); } }

      /* ---------- Manna Premium & Creator Studio ---------- */
      .gold-check {
        display: inline-flex; width: 19px; height: 19px; border-radius: 50%; flex: none;
        background: linear-gradient(160deg, var(--gold-2), var(--gold) 55%, var(--gold-deep));
        color: #241503; align-items: center; justify-content: center;
        vertical-align: -3px; margin-left: 6px; box-shadow: 0 2px 6px rgba(138,95,19,.4);
      }
      .gold-check svg { width: 12px; height: 12px; stroke-width: 3.2; }
      /* Basic's check is beige (Casey's call, 2026-10-21);
         Top Tier's above stays golden. */
      .gold-check.tier-basic { background: #e7d3a7; box-shadow: 0 2px 6px rgba(120,95,40,.35); }
      .door { position: relative; }
      .door-lock {
        position: absolute; top: 13px; right: 13px; background: var(--gold); color: #241503;
        border: 1px solid var(--gold-deep);
      }
      .door-lock svg { width: 11px; height: 11px; stroke-width: 2.4; }
      /* A door that is present but not open yet (Manna Music
         while MUSIC_HIDDEN, Casey's call 2026-10-22): the door
         stands in its place dimmed, a small gold lock badge sits
         on its icon chip, and its label line reads "Coming
         soon" in gold. Tapping it shakes the door and pops the
         in-the-works note (Casey's call, 2026-10-23 — see
         tapLockedMusicDoor), so nothing about it pretends to
         be open. */
      .door.is-coming .dicon, .door.is-coming h3 { opacity: .55; }
      .door.is-coming p { color: var(--gold-text); font-weight: 700; }
      .door .dicon .dicon-lock {
        position: absolute; right: -5px; bottom: -5px; width: 18px; height: 18px;
        border-radius: 50%; background: var(--gold); color: #241503;
        border: 1px solid var(--gold-deep); display: flex; align-items: center; justify-content: center;
      }
      .door .dicon .dicon-lock svg { width: 10px; height: 10px; stroke-width: 2.6; }
      /* Tapping the locked door gives it a little shake — a
         rattled-door "not yet" (Casey's call, 2026-10-23) — and
         then the in-the-works note pops up (see
         tapLockedMusicDoor). Small and quick: a few degrees of
         wobble, done in under half a second. */
      .door.is-coming.shaking { animation: door-shake .45s ease; }
      @keyframes door-shake {
        0%, 100% { transform: translateX(0) rotate(0deg); }
        20% { transform: translateX(-5px) rotate(-.6deg); }
        40% { transform: translateX(5px) rotate(.6deg); }
        60% { transform: translateX(-4px) rotate(-.4deg); }
        80% { transform: translateX(3px) rotate(.3deg); }
      }
      @media (prefers-reduced-motion: reduce) { .door.is-coming.shaking { animation: none; } }
      /* Locked Go live buttons (Casey's call, 2026-10-24):
         every Go live option in Manna wears the same treatment
         as the locked Manna Music door — the button stands in
         its place dimmed, carrying a lock mark and the words
         "Coming soon" as a small tag on the button itself (in
         gold on the ghost button, like the door's gold label
         line). Tapping one shakes it with the same door shake
         and pops the in-the-works note (see tapLockedLive), so
         nothing about it pretends to be open. */
      button.is-coming { opacity: .55; flex-wrap: wrap; }
      button.is-coming .soon-tag {
        display: inline-flex; align-items: center; gap: 4px;
        font-size: 11px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
      }
      button.is-coming .soon-tag svg { width: 11px; height: 11px; stroke-width: 2.6; flex: none; }
      .btn-ghost.is-coming .soon-tag { color: var(--gold-text); }
      button.is-coming.shaking { animation: door-shake .45s ease; }
      /* Casey 2026-10-28: the shake covers the live button AND its symbol, in every place. */
      button.shaking { animation: door-shake .45s ease; }
      .shaking svg, .shaking .soon-tag { animation: door-shake .45s ease; }
      .lives-live-badge.shaking { animation: door-shake .45s ease; }
      @media (prefers-reduced-motion: reduce) { button.is-coming.shaking { animation: none; } }
      .tier-card {
        background: var(--card); border: 1px solid var(--line); border-radius: 20px;
        box-shadow: var(--shadow); padding: 18px; margin-bottom: 14px;
      }
      .tier-card.top-tier { border: 1.5px solid var(--gold); background: linear-gradient(165deg, var(--card2), var(--card) 72%); }
      .tier-card .t-name { font-family: "Marcellus", Georgia, serif; font-size: 20px; color: var(--ink); }
      .tier-card .t-price { font-size: 26px; font-weight: 800; color: var(--ink); margin-top: 6px; font-variant-numeric: tabular-nums; }
      .tier-card .t-price span { font-size: 13px; font-weight: 700; color: var(--ink-soft); }
      .tier-card .t-offer { font-size: 12.5px; font-weight: 700; color: var(--gold-text); margin-top: 3px; line-height: 1.5; }
      .tier-card ul { margin: 12px 0 2px; padding: 0; list-style: none; }
      .tier-card li { font-size: 13.5px; color: var(--ink-soft); line-height: 1.45; padding: 5px 0 5px 24px; position: relative; border-top: 1px solid var(--line-soft); }
      .tier-card li:first-child { border-top: none; }
      .tier-card li svg { position: absolute; left: 0; top: 7px; width: 15px; height: 15px; color: var(--gold-deep); stroke-width: 2.6; }
      /* A creator's subscription tiers, offered for choosing in
         the subscribe sheet (Casey's call, 2026-10-23): each
         tier is a tappable row — its own name, its own monthly
         price — and the picked row wears the gold ring while
         the fee split and the Subscribe button re-price to it.
         One tier reads selected at a time, like a radio. */
      .sub-tier {
        display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
        background: var(--card); border: 1.5px solid var(--line); border-radius: 14px;
        padding: 12px 14px; margin-bottom: 9px; cursor: pointer; font-family: inherit; color: var(--ink);
      }
      .sub-tier .st-dot {
        width: 19px; height: 19px; border-radius: 50%; flex: none;
        border: 2px solid var(--line); background: var(--field); position: relative;
      }
      .sub-tier.on { border-color: var(--gold); background: linear-gradient(165deg, var(--card2), var(--card) 78%); }
      .sub-tier.on .st-dot { border-color: var(--gold-deep); }
      .sub-tier.on .st-dot::after {
        content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--gold-deep);
      }
      .sub-tier .st-main { flex: 1; min-width: 0; }
      .sub-tier .st-name { font-weight: 800; font-size: 15px; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .sub-tier .st-price { font-weight: 800; font-size: 15px; font-variant-numeric: tabular-nums; white-space: nowrap; }
      .sub-tier .st-price span { font-size: 12px; font-weight: 700; color: var(--ink-soft); }
      .sub-tier:active { transform: scale(.99); }
      .gift-banner {
        border: 1.5px solid var(--gold); border-radius: 20px; padding: 16px 18px; margin-bottom: 14px;
        background: linear-gradient(160deg, var(--gold-2), var(--gold) 130%); color: #2e1d07; box-shadow: var(--shadow);
      }
      .gift-banner .gb-t { font-family: "Marcellus", Georgia, serif; font-size: 18px; display: flex; align-items: center; gap: 8px; }
      .gift-banner .gb-t svg { width: 20px; height: 20px; }
      .gift-banner p { font-size: 13px; line-height: 1.55; margin: 7px 0 0; }
      /* The Profile's "Go Premium" pop-up (Casey's call, 2026-10-20):
         it slides in at the very top of the profile, and five seconds
         later it leaves on its own — profiles carry no standing
         Premium fixtures. Tiers live in Settings. */
      .goprem-pop {
        /* Small and compact (Casey's call, 2026-10-20): a little
           pill that hugs its words at the top of the profile
           instead of a full-width banner — it takes up barely
           any room, and still leaves on its own as before. */
        width: auto; max-width: 100%; font-family: inherit; cursor: pointer; color: #2e1d07;
        border: 1.5px solid var(--gold-deep); border-radius: 99px; padding: 6px 11px;
        margin: 0 0 6px; background: linear-gradient(150deg, var(--gold-2), var(--gold) 85%);
        box-shadow: var(--shadow); display: inline-flex; align-items: center; gap: 7px; text-align: left;
        /* A lot smoother, in and out (Casey's call, 2026-10-24):
           the pill used to drop in fast from above while the
           profile was still painting — a pop, not a landing —
           and its exit ran its fade and its collapse on nearly
           the same clock, so the words were still half visible
           while the shrinking height clipped them. Now it
           rises gently into place a beat after the profile
           lands, on the app's own soft curve; and every part
           of the exit rides that same curve on one shared .6s
           clock while the fade runs ahead of it (.3s), so the
           pill is fully gone to the eye before the collapse
           can clip anything, and the profile glides up into
           clean empty space. */
        animation: goprem-in .65s cubic-bezier(.22,.8,.3,1) .12s both;
        /* Smoothness rework (Casey's report, 2026-10-28 — the
           pop-up still looked choppy): nothing on the pill
           itself animates a layout property anymore. In and
           out ride transform and opacity on the GPU; the exit
           collapse moved to the grid wrap below, whose row
           eases shut while the pill's fade runs ahead of it.
           And the pill lives while it stands: a soft shine
           sweeps across it every few seconds (the ::after
           below), transform only. */
        position: relative; overflow: hidden; will-change: transform, opacity;
        transition: opacity .3s ease, transform .6s cubic-bezier(.22,.8,.3,1);
      }
      .goprem-wrap { display: grid; grid-template-rows: 1fr; justify-items: start; transition: grid-template-rows .55s cubic-bezier(.22,.8,.3,1); }
      .goprem-wrap.bye { grid-template-rows: 0fr; overflow: hidden; }
      .goprem-wrap .goprem-pop { min-height: 0; }
      .goprem-pop > * { position: relative; z-index: 1; }
      .goprem-pop::after {
        content: ""; position: absolute; z-index: 0; top: -25%; bottom: -25%; left: 0; width: 42%;
        background: linear-gradient(100deg, rgba(255,255,255,0), rgba(255,255,255,.55), rgba(255,255,255,0));
        transform: translateX(-240%) skewX(-18deg);
        animation: goprem-shine 3.6s ease-in-out infinite;
        pointer-events: none;
      }
      @keyframes goprem-shine {
        0% { transform: translateX(-240%) skewX(-18deg); }
        34% { transform: translateX(360%) skewX(-18deg); }
        100% { transform: translateX(360%) skewX(-18deg); }
      }
      /* The exit (Casey's call, 2026-10-22): the pop-up must not just
         fade and vanish — its height, padding, and margin ease down
         to nothing along with the fade, so the profile below glides
         up into place instead of snapping to the top the moment the
         pop-up is removed. The script pins the live height inline
         first, then this class collapses it. */
      /* The pill's half of the exit is fade and drift only —
         the wrap's grid row does the collapsing (above). */
      .goprem-pop.bye {
        opacity: 0; transform: translateY(-4px); pointer-events: none;
      }
      @media (prefers-reduced-motion: reduce) {
        .goprem-pop { animation: none; transition: opacity .15s ease; }
        .goprem-pop.bye { transform: none; }
        .goprem-pop::after { animation: none; }
        .goprem-wrap { transition: none; }
      }
      .goprem-pop:active { transform: scale(.985); }
      .goprem-pop svg { width: 15px; height: 15px; flex: none; }
      .goprem-pop .gp-t { font-family: "Marcellus", Georgia, serif; font-size: 13.5px; white-space: nowrap; }
      .goprem-pop .gp-s { font-size: 11px; font-weight: 600; opacity: .78; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      @keyframes goprem-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
      .prem-banner {
        width: 100%; text-align: left; font-family: inherit; cursor: pointer; color: #2e1d07;
        border: 1.5px solid var(--gold-deep); border-radius: 20px; padding: 16px 18px; margin-top: 14px;
        background: linear-gradient(150deg, var(--gold-2), var(--gold) 85%); box-shadow: var(--shadow);
        display: flex; align-items: center; gap: 13px;
      }
      .prem-banner .pb-main { flex: 1; min-width: 0; }
      .prem-banner .pb-t { font-family: "Marcellus", Georgia, serif; font-size: 18px; }
      .prem-banner .pb-s { font-size: 12.5px; margin-top: 2px; line-height: 1.45; }
      .prem-banner svg { width: 26px; height: 26px; flex: none; }
      /* A small star rides on everything labeled a Premium
         feature (Casey's call, 2026-10-20) — the door lock
         pills, the Premium banner, the Go Premium pop-up, the
         paid tier names, and the notes that name a Premium
         feature. Gold on light surfaces, dark on gold ones. */
      .prem-star { display: inline-flex; align-items: center; margin-left: 5px; color: var(--gold-deep); vertical-align: -1px; }
      .prem-star svg { width: 11px; height: 11px; fill: currentColor; }
      .door-lock .prem-star, .prem-banner .prem-star, .goprem-pop .prem-star { color: #241503; }
      .growth-bars { display: flex; align-items: flex-end; gap: 10px; height: 92px; margin-top: 12px; }
      .growth-bars .gb-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px; height: 100%; justify-content: flex-end; }
      .growth-bars .gb-bar { width: 100%; max-width: 44px; border-radius: 8px 8px 3px 3px; background: linear-gradient(180deg, var(--gold-2), var(--gold-deep)); min-height: 3px; }
      .growth-bars .gb-n { font-size: 12px; font-weight: 800; font-variant-numeric: tabular-nums; }
      .growth-bars .gb-l { font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); text-align: center; line-height: 1.3; }
      .stat-line { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
      .stat-line .sl { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 700; color: var(--ink-soft); }
      .stat-line .sl svg { width: 14px; height: 14px; }
      .perf-row { width: 100%; text-align: left; font-family: inherit; color: var(--ink); cursor: pointer; background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 12px 14px; box-shadow: var(--shadow); display: block; }
      .perf-row + .perf-row { margin-top: 9px; }
      .perf-row .pr-snip { font-size: 13.5px; line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
      .perf-row .pr-meta { margin-top: 7px; }
      /* The locked Monetization medallion in Creator Studio. */
      .mon-lock-ic svg { width: 20px; height: 20px; display: block; }
      .book-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
      .book-card {
        background: var(--card); border: 1px solid var(--line); border-radius: 16px;
        padding: 15px 14px; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 5px;
      }
      .book-card .bk-title { font-family: "Marcellus", Georgia, serif; font-size: 16.5px; line-height: 1.3; color: var(--ink); }
      .book-card .bk-author { font-size: 12.5px; color: var(--ink-soft); }
      .book-card .bk-meta { font-size: 11.5px; color: var(--ink-faint); margin-top: auto; padding-top: 6px; }
      .book-card .bk-cover { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; border-radius: 10px; display: block; background: var(--card2); }
      .book-card .bk-nocover {
        width: 100%; aspect-ratio: 3 / 4; border-radius: 10px; background: var(--chip);
        display: flex; align-items: center; justify-content: center; color: var(--gold-deep);
      }
      .book-card .bk-nocover svg { width: 44px; height: 44px; }
      .book-card .bk-cat { background: var(--gold); color: #241503; border: 1px solid var(--gold-deep); font-weight: 800; }
      .book-card .bk-desc {
        font-size: 13px; color: var(--ink-soft); line-height: 1.45;
        display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
      }
      .book-card .bk-owner-row { display: flex; gap: 8px; margin-top: 8px; }
      .book-card .bk-owner-row .btn-ghost, .book-card .bk-owner-row .btn-danger { flex: 1; }

      /* ---------- Manna Market ---------- */
      .market-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
      .mk-card {
        background: var(--card); border: 1px solid var(--line); border-radius: 16px;
        padding: 12px; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 5px;
        text-align: left; font-family: inherit; color: var(--ink); cursor: pointer; width: 100%;
      }
      /* Listing photos always show the WHOLE picture (Casey's
         call, 2026-10-20): object-fit contain, never a crop — a
         book cover's title at the top can no longer be clipped
         by the frame. The seller picks the frame shape: square
         (600×600 style) or book cover (tall portrait). */
      .mk-card .mk-photo { width: 100%; aspect-ratio: 1 / 1; object-fit: contain; border-radius: 10px; display: block; background: var(--card2); }
      .mk-card .mk-photo.cover { aspect-ratio: 2 / 3; }
      .mk-card .mk-nophoto {
        width: 100%; aspect-ratio: 1 / 1; border-radius: 10px; background: var(--chip);
        display: flex; align-items: center; justify-content: center; color: var(--gold-deep);
      }
      .mk-card .mk-nophoto.cover { aspect-ratio: 2 / 3; }
      .mk-card .mk-nophoto svg { width: 44px; height: 44px; }
      .mk-card .mk-title { font-family: "Marcellus", Georgia, serif; font-size: 16px; line-height: 1.3; color: var(--ink); }
      .mk-card .mk-price { font-size: 17px; font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }
      .mk-card .mk-seller { font-size: 12px; color: var(--ink-faint); margin-top: auto; padding-top: 4px; }
      /* A listing's location rides on every card (Casey's call,
         2026-10-23): a pin and the city/state, in the gold text
         the app uses for place, with the distance under it when
         the buyer's location is on and the item can be placed. */
      .mk-card .mk-loc {
        display: inline-flex; align-items: center; gap: 5px;
        font-size: 12.5px; font-weight: 700; color: var(--gold-text); line-height: 1.35;
      }
      .mk-card .mk-loc svg { width: 14px; height: 14px; flex: none; }
      .mk-card .mk-loc.unknown { color: var(--ink-faint); font-weight: 600; }
      .mk-card .mk-dist { font-size: 12px; font-weight: 700; color: var(--ink-soft); }
      /* The Find-near-you panel on the storefront (Casey's calls,
         2026-10-23): city and state each get their own bubble to
         type in, radius bands ride as chips, and the 50-mile
         check narrows a city/state search around the place
         searched. The panel is a plain card — the grid below it
         is what changes. */
      .mk-find { padding: 14px 15px 13px; margin: 0 0 14px; }
      .mk-find .mkf-head { display: flex; align-items: center; gap: 8px; }
      .mk-find .mkf-head svg { width: 18px; height: 18px; color: var(--gold-text); flex: none; }
      .mk-find .mkf-head b { font-size: 14.5px; }
      .mk-find .mkf-status { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; margin-top: 6px; }
      .mk-place-row { display: flex; gap: 8px; margin-top: 11px; }
      .mk-place {
        flex: 1; min-width: 0; display: flex; align-items: center;
        background: var(--field); border: 1px solid var(--line); border-radius: 999px; padding: 0 14px;
      }
      .mk-place:focus-within { border-color: var(--gold); }
      .mk-place input {
        flex: 1; min-width: 0; border: none; background: transparent; outline: none;
        font-size: 14.5px; padding: 10px 0; color: var(--ink); scroll-margin-top: 0; scroll-margin-bottom: 0;
      }
      .mk-place input::placeholder { color: var(--ink-faint); }
      .mk-place select {
        flex: 1; min-width: 0; border: none; background: transparent; outline: none;
        font-size: 14.5px; padding: 10px 0; color: var(--ink);
      }
      .mk-radius { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
      .mk-50row { display: flex; align-items: flex-start; gap: 9px; margin-top: 12px; cursor: pointer; }
      .mk-50row input { width: 19px; height: 19px; flex: none; margin: 1px 0 0; accent-color: var(--gold); }
      .mk-50row .mk-50txt { font-size: 13.5px; font-weight: 600; color: var(--ink); line-height: 1.45; }
      .mk-50row .mk-50sub { display: block; font-size: 12px; font-weight: 400; color: var(--ink-soft); margin-top: 1px; }
      .mk-find .mkf-count { font-size: 12.5px; color: var(--ink-soft); margin-top: 10px; line-height: 1.5; }
      .mk-find .mkf-clear {
        border: none; background: none; padding: 2px 4px; color: var(--gold-text);
        font-weight: 800; font-size: 12.5px; cursor: pointer; font-family: inherit;
      }
      /* The listing detail's location line: pin, place, distance. */
      .mk-detail-loc { display: flex; align-items: center; gap: 7px; margin-top: 12px; font-size: 14.5px; font-weight: 700; color: var(--ink); flex-wrap: wrap; }
      .mk-detail-loc svg { width: 17px; height: 17px; color: var(--gold-text); flex: none; }
      .mk-detail-loc .mdl-dist { font-size: 13px; font-weight: 700; color: var(--gold-text); }
      .mk-detail-loc.unknown { color: var(--ink-soft); font-weight: 600; }
      .mk-card.is-sold { opacity: .72; }
      .mk-sold-badge {
        align-self: flex-start; background: var(--danger, #b3402a); color: #fff; border-radius: 999px;
        font-size: 10.5px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; padding: 3px 9px;
      }
      .mk-detail-photo { width: 100%; aspect-ratio: 4 / 3; object-fit: contain; border-radius: 14px; display: block; background: var(--card2); }
      .mk-detail-photo.cover { aspect-ratio: 2 / 3; }
      .mk-detail-nophoto {
        width: 100%; aspect-ratio: 4 / 3; border-radius: 14px; background: var(--chip);
        display: flex; align-items: center; justify-content: center; color: var(--gold-deep);
      }
      .mk-detail-nophoto.cover { aspect-ratio: 2 / 3; }
      .mk-detail-nophoto svg { width: 64px; height: 64px; }
      .mk-form-photo { width: 100%; aspect-ratio: 4 / 3; object-fit: contain; border-radius: 12px; display: block; margin-top: 10px; background: var(--card2); }
      .mk-form-photo.square { aspect-ratio: 1 / 1; }
      .mk-form-photo.cover { aspect-ratio: 2 / 3; }
      /* Every aspect ratio a seller can pick (Casey's call,
         2026-10-24 — the full run, like the big marketplaces).
         The same shape class rides the shelf card, the listing
         page, and the form preview alike; object-fit: contain
         keeps the whole picture inside whichever frame is
         chosen — nothing is ever cropped. */
      .mk-card .mk-photo.square, .mk-detail-photo.square, .mk-detail-nophoto.square, .mk-form-photo.square { aspect-ratio: 1 / 1; }
      .mk-card .mk-photo.p45, .mk-detail-photo.p45, .mk-detail-nophoto.p45, .mk-form-photo.p45 { aspect-ratio: 4 / 5; }
      .mk-card .mk-photo.t34, .mk-detail-photo.t34, .mk-detail-nophoto.t34, .mk-form-photo.t34 { aspect-ratio: 3 / 4; }
      .mk-card .mk-photo.cover, .mk-detail-photo.cover, .mk-detail-nophoto.cover, .mk-form-photo.cover { aspect-ratio: 2 / 3; }
      .mk-card .mk-photo.story, .mk-detail-photo.story, .mk-detail-nophoto.story, .mk-form-photo.story { aspect-ratio: 9 / 16; }
      .mk-card .mk-photo.p43, .mk-detail-photo.p43, .mk-detail-nophoto.p43, .mk-form-photo.p43 { aspect-ratio: 4 / 3; }
      .mk-card .mk-photo.l32, .mk-detail-photo.l32, .mk-detail-nophoto.l32, .mk-form-photo.l32 { aspect-ratio: 3 / 2; }
      .mk-card .mk-photo.wide, .mk-detail-photo.wide, .mk-detail-nophoto.wide, .mk-form-photo.wide { aspect-ratio: 16 / 9; }
      /* The shape chooser in the listing form: a grid of frames,
         each wearing its ratio and its name. */
      .shape-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
      .shape-grid button { border: 1.5px solid var(--line); background: var(--card2); border-radius: 12px; padding: 9px 4px 7px; display: flex; flex-direction: column; align-items: center; gap: 2px; cursor: pointer; color: var(--ink-soft); font-family: inherit; }
      .shape-grid button.on { border-color: var(--gold-deep); background: var(--chip); color: var(--ink); }
      .shape-grid .shape-r { font-weight: 800; font-size: 15px; line-height: 1.1; }
      .shape-grid .shape-n { font-size: 11px; font-weight: 700; letter-spacing: .02em; line-height: 1.1; }
      .cat-rail { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
      /* Library categories ride one endless horizontal row
         (Casey's call, 2026-10-29 — same endless bar as the
         composer lanes): no wrapping, the row scrolls sideways
         and the set repeats in the markup so it never runs
         out. Chips never shrink or stack. */
      .lib-cat-rail {
        flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
        scroll-snap-type: x proximity; scrollbar-width: none;
        -webkit-overflow-scrolling: touch; padding-bottom: 2px;
      }
      .lib-cat-rail::-webkit-scrollbar { display: none; }
      .lib-cat-rail .cat-chip { flex: none; white-space: nowrap; scroll-snap-align: start; }
      .cat-chip {
        border: 1px solid var(--line); background: var(--card); color: var(--ink-soft);
        border-radius: 999px; padding: 7px 13px; font-size: 13px; font-weight: 700; cursor: pointer;
      }
      .cat-chip.active { background: var(--gold); border-color: var(--gold-deep); color: #241503; }
      /* Fasting companion: the fast-type tag rail (Casey's call,
         2026-10-22) — pick a kind of fast from the chips, or make
         your own tag underneath and it joins the rail for good. */
      .fast-tag-note { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; margin: -4px 0 12px; }
      .fast-type-add { display: flex; gap: 8px; align-items: stretch; margin-bottom: 4px; }
      .fast-type-add .input { flex: 1; min-width: 0; margin-bottom: 0; }
      .fast-type-add .btn-ghost { flex: none; white-space: nowrap; }
      /* The Social door lock note on the Fasting companion
         (Casey's call, 2026-10-22): while a fast's lock stands,
         this line says so under the fast's card, with the
         unlock button beneath it. */
      .lock-note { display: flex; gap: 10px; align-items: flex-start; background: var(--card); border: 1px solid var(--gold); border-radius: var(--radius); box-shadow: var(--shadow); padding: 13px 15px; margin-bottom: 12px; font-size: 13.5px; line-height: 1.55; color: var(--ink-soft); }
      .lock-note svg { flex: none; width: 19px; height: 19px; color: var(--gold-text); margin-top: 1px; }
      .lock-note b { color: var(--ink); }
      /* Learn about fasting (Casey's call, 2026-10-22): a single
         tappable block at the very top of the Fasting companion,
         before the fast cards — not a teaching page sitting open
         on the companion itself. Tapping it opens the teaching
         screen (showFastingLearn): what fasting is, why it
         matters, and the biblical stories around fasting,
         grouped by theme. */
      .learn-block {
        display: flex; align-items: center; gap: 13px; width: 100%; text-align: left;
        background: linear-gradient(160deg, var(--card2), var(--card) 70%);
        border: 1.5px solid var(--gold); border-radius: 20px; box-shadow: var(--shadow);
        padding: 15px 16px; margin-bottom: 16px; font-family: inherit; color: var(--ink); cursor: pointer;
      }
      .learn-block:active { background: var(--card2); }
      .learn-block .lb-icon {
        width: 44px; height: 44px; border-radius: 13px; flex: none;
        background: var(--door-chip-bg); border: 1px solid var(--door-chip-line);
        display: flex; align-items: center; justify-content: center; color: var(--gold-deep);
      }
      .learn-block .lb-icon svg { width: 23px; height: 23px; }
      .learn-block .lb-main { flex: 1; min-width: 0; }
      .learn-block .lb-t { font-family: "Marcellus", Georgia, serif; font-size: 18.5px; display: block; }
      .learn-block .lb-s { font-size: 12.5px; color: var(--ink-soft); line-height: 1.45; display: block; margin-top: 2px; }
      .learn-block > svg { width: 20px; height: 20px; flex: none; color: var(--ink-faint); }
      .learn-theme { margin: 24px 0 0; }
      .learn-theme h3 { font-family: "Marcellus", Georgia, serif; font-size: 18.5px; color: var(--ink); }
      .learn-theme .lt-body { font-size: 14.5px; line-height: 1.6; margin: 7px 0 0; }
      .learn-entry { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 13px 15px; margin-top: 10px; }
      .learn-entry .le-ref { font-weight: 800; font-size: 14.5px; color: var(--gold-text); }
      .learn-entry p { font-size: 14px; line-height: 1.6; margin: 4px 0 0; }
      /* Manna Music side scroll (Casey's call, 2026-10-21): the
         genres no longer sit in a wrap of chips over the tracks —
         they stand in a rail down the LEFT side of the Music
         screen, and you scroll the rail itself to move through
         the categories. Going inside a category turns the rail
         over to that category's ARTISTS: scroll the same rail to
         move through the artists, tap one to hear only their
         tracks, and the ‹ Genres entry at the rail's top steps
         back out to the categories. The rail sticks while the
         tracks scroll beside it, and its own scroll loops the
         way the Shorts rail does when the list runs long. */
      .music-layout { display: flex; gap: 12px; align-items: flex-start; }
      .music-main { flex: 1; min-width: 0; }
      .music-side {
        width: 84px; flex: none; position: sticky; top: 66px;
        max-height: calc(100dvh - 176px); overflow-y: auto; overscroll-behavior: contain;
        display: flex; flex-direction: column; gap: 8px; padding: 2px;
        scrollbar-width: none; -webkit-overflow-scrolling: touch;
      }
      .music-side::-webkit-scrollbar { display: none; }
      .mside-btn {
        display: flex; flex-direction: column; align-items: center; gap: 4px;
        width: 100%; padding: 9px 4px; flex: none;
        border: 1px solid var(--line); background: var(--card); color: var(--ink-soft);
        border-radius: 14px; font-family: inherit; font-size: 11.5px; font-weight: 700;
        line-height: 1.25; text-align: center; cursor: pointer;
      }
      .mside-btn:active { background: var(--card2); }
      .mside-btn.active { background: var(--gold); border-color: var(--gold-deep); color: #241503; }
      .mside-btn .mside-name { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      .mside-btn .mside-sub { font-size: 10px; font-weight: 600; color: var(--ink-faint); }
      .mside-btn.active .mside-sub { color: inherit; opacity: .75; }
      .mside-back { color: var(--gold-text); border-color: var(--gold); font-weight: 800; }
      .reader-view { position: absolute; inset: 0; z-index: 90; background: linear-gradient(180deg, var(--app-bg), var(--app-bg2)); display: flex; flex-direction: column; }
      .reader-head { display: flex; align-items: center; gap: 10px; padding: calc(10px + var(--sat)) 14px 10px; border-bottom: 1px solid var(--line-soft); }
      .reader-head .rd-title { flex: 1; min-width: 0; font-family: "Marcellus", Georgia, serif; font-size: 16.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .reader-body { flex: 1; min-height: 0; position: relative; background: #fff; overflow-y: auto; }
      .reader-body canvas { display: block; margin: 0 auto; max-width: 100%; }
      .reader-foot .rd-page { align-self: center; white-space: nowrap; font-size: 12.5px; color: var(--ink-soft); font-weight: 700; }
      .reader-foot { display: flex; gap: 10px; padding: 10px 14px calc(12px + var(--sab)); border-top: 1px solid var(--line-soft); }

      /* ---------- Settings lane rows ---------- */
      .lane-set-row { transition: transform .18s ease; }
      .lane-set-row.is-locked-lane { opacity: .85; }
      .lane-set-row .ls-lock { font-size: 10.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-faint); border: 1px solid var(--line); border-radius: 99px; padding: 3px 8px; flex: none; }

      /* ---------- Short editor ---------- */
      .ed-overlay {
        position: absolute; left: 8px; right: 8px; z-index: 4; text-align: center;
        font-weight: 800; color: #fff; text-shadow: 0 1px 6px rgba(0,0,0,.65), 0 0 2px rgba(0,0,0,.8);
        pointer-events: none; line-height: 1.25; word-break: break-word;
      }
      /* ---------- Text on a photo or video (Casey's call,
         2026-10-21) ----------
         After the picture is taken or the clip is filmed, the
         review's Aa Text button opens this full-screen tool,
         Snapchat-style: the words are typed in the MIDDLE of the
         screen so you can see them as you type, then you drag
         them with a finger to the exact spot they belong, and
         applying burns them into the photo or renders them into
         the video at that spot. TikTok-style (Casey's call,
         2026-10-21): the media fills the WHOLE screen while the
         words go on — the frame is the full stage, the media is
         cover-fit, the control bar overlays the bottom — and a
         font picker offers five looks. Drag positions are taken
         in screen fractions and mapped through the cover crop
         onto the real pixels when the text is baked in, so the
         words land on the media exactly where the finger left
         them. */
      .tx-view { position: absolute; inset: 0; z-index: 30; background: #000; }
      .tx-stage { position: absolute; inset: 0; overflow: hidden; }
      .tx-frame { position: absolute; }
      .tx-media { position: absolute; inset: 0; width: 100%; height: 100%; display: block; object-fit: cover; }
      .tx-placed {
        position: absolute; z-index: 3; transform: translate(-50%, -50%);
        font-weight: 800; line-height: 1.25; white-space: nowrap;
        text-shadow: 0 1px 6px rgba(0,0,0,.65), 0 0 2px rgba(0,0,0,.8);
        padding: 10px 14px; cursor: grab; touch-action: none;
        user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent;
      }
      .tx-placed:active { cursor: grabbing; }
      .tx-input {
        position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 4;
        width: 96%; background: transparent; border: none; outline: none;
        text-align: center; font-weight: 800; line-height: 1.25;
        text-shadow: 0 1px 6px rgba(0,0,0,.65), 0 0 2px rgba(0,0,0,.8);
      }
      .tx-input::placeholder { color: rgba(255,246,221,.55); text-shadow: none; }
      .tx-bar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 5; background: var(--app-bg); border-top: 1px solid var(--line-soft); padding: 12px 14px calc(12px + var(--sab)); display: flex; flex-direction: column; gap: 10px; box-shadow: 0 -8px 24px rgba(0,0,0,.35); }
      .tx-bar .f { margin-top: 0; }
      /* Crop view (Casey's call, 2026-10-24): after a picture is
         taken, Crop stands in the review tools — the picture
         sits whole (contain, never pre-cut) under a gold frame;
         drag the frame to move it, drag its corner knob to size
         it, and what is inside the frame is what posts. */
      .crop-view { position: absolute; inset: 0; z-index: 30; background: #000; }
      .crop-stage { position: absolute; inset: 0; overflow: hidden; }
      .crop-media { position: absolute; inset: 0; width: 100%; height: 100%; display: block; object-fit: contain; }
      .crop-box {
        position: absolute; z-index: 3; box-sizing: border-box;
        border: 2px solid var(--gold-2); box-shadow: 0 0 0 9999px rgba(0,0,0,.45);
        cursor: move; touch-action: none;
      }
      .crop-handle {
        position: absolute; right: -15px; bottom: -15px; width: 30px; height: 30px;
        border-radius: 50%; background: var(--gold-2); border: 2px solid #fff6dd;
        box-shadow: 0 2px 8px rgba(0,0,0,.5); cursor: nwse-resize; touch-action: none; box-sizing: border-box;
      }
      /* Text-size slider on the side of the screen, TikTok-style
         (Casey's call, 2026-10-21): a vertical rail riding the
         right edge of the full-screen stage — slide up for bigger
         words, down for smaller, live while typing and after the
         words are placed. It replaces the old Small/Medium/Large
         menu in the bar: one finger, any size, no stopping. */
      .tx-size-rail {
        position: absolute; right: 8px; top: 50%; transform: translateY(-50%); z-index: 6;
        display: flex; flex-direction: column; align-items: center; gap: 6px;
        padding: 10px 2px; border-radius: 999px; background: rgba(0,0,0,.38);
      }
      .tx-size-rail .tx-cap { color: #fff6dd; font-weight: 800; line-height: 1; font-size: 11px; text-shadow: 0 1px 3px rgba(0,0,0,.8); }
      .tx-size-rail .tx-cap-big { font-size: 19px; }
      .tx-size-rail input[type="range"] {
        writing-mode: vertical-lr; direction: rtl;
        width: 30px; height: 180px; margin: 0; accent-color: var(--gold-deep);
        background: transparent; cursor: pointer; touch-action: none;
      }
      input[type="range"].ed-range { width: 100%; accent-color: var(--gold-deep); }
      /* Speed picker in Edit (TikTok-style, Casey's call,
         2026-10-23): one dark bar, the five speeds as equal
         stops, the picked one lit — 1x is rest. */
      .ed-speeds { display: flex; border-radius: 12px; overflow: hidden; background: rgba(20,14,4,.85); border: 1px solid var(--line); }
      .ed-speed { flex: 1; border: 0; background: transparent; color: #f3e6c4; font: inherit; font-weight: 700; font-size: 14px; padding: 10px 0; cursor: pointer; }
      .ed-speed + .ed-speed { border-left: 1px solid rgba(246,205,103,.18); }
      .ed-speed.on { background: var(--card); color: var(--ink); }
      .ed-speed:active { opacity: .85; }
      /* The sound start-point strip (Snapchat-style): the
         whole sound as a bar of gold ticks, the gold window
         the part of it the clip plays from the chosen start
         — drag the window, or the slider under it. */
      #snd-strip { position: relative; height: 46px; border-radius: 10px; overflow: hidden; cursor: pointer; touch-action: none;
        border: 1px solid var(--line); background: repeating-linear-gradient(90deg, rgba(210,154,31,.45) 0 3px, rgba(210,154,31,.13) 3px 8px); }
      #snd-region { position: absolute; top: 0; bottom: 0; box-sizing: border-box; cursor: grab; touch-action: none;
        background: rgba(246,205,103,.35); border-left: 3px solid var(--gold-deep); border-right: 3px solid var(--gold-deep); }
      #snd-region:active { cursor: grabbing; }
      /* Recorder control dock: Record / Pause / Stop live pinned to the
         bottom edge of the camera preview itself, so they are on screen
         and tappable the moment the recorder opens — no scrolling, ever. */
      .rec-dock {
        position: absolute; left: 0; right: 0; bottom: 0; z-index: 5;
        padding: 30px 12px calc(12px + env(safe-area-inset-bottom));
        background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,.62) 55%);
      }
      .rec-dock .btn-gold, .rec-dock .btn-ghost, .rec-dock .btn-dark { box-shadow: 0 4px 14px rgba(0,0,0,.45); }
      /* The Upload (Gallery) button is the SAME SIZE as the Record
         button (Casey's call, 2026-10-21): in both dock rows every
         button takes an equal share of the row at one locked
         height, so Gallery and Record match exactly. */
      #rec-choose > button, #rec-photo-row > button { flex: 1 1 0; height: 50px; min-width: 0; margin-top: 0; }
      /* The recorder's Record button is the same RED RECORD
         BUTTON as the Shorts feed (Casey's call, 2026-10-21):
         a plain red dot in a cream ring with the
         recording-light pulse — no camera symbol, no gold
         pill. It rides the middle of the choose row, and once
         a take is rolling the SAME red button sits in the
         middle of the recording row: tapping it pauses the
         take, tapping again resumes — TikTok-style, right on
         the record button, no separate Pause button. While
         rolling it wears white pause bars; while paused the
         pulse rests and the plain dot invites the resume tap. */
      #rec-choose, #rec-photo-row, #rec-rec-row { align-items: center; }
      #rec-choose > button.rec-bigbtn { flex: 0 0 auto; height: 64px; }
      .rec-bigbtn {
        width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;
        background: #e0243a; border: 3px solid rgba(255,246,221,.95);
        box-shadow: 0 6px 18px rgba(0,0,0,.5);
        display: inline-flex; align-items: center; justify-content: center;
        padding: 0; margin: 0; cursor: pointer; font: inherit;
        animation: rec-pulse 1.8s ease-out infinite;
      }
      .rec-bigbtn:active { transform: scale(.93); }
      .rec-bigbtn .rb-bars { display: none; align-items: center; gap: 5px; }
      .rec-bigbtn .rb-bars i { display: block; width: 6px; height: 20px; border-radius: 2px; background: #fff6dd; }
      .rec-bigbtn.is-rec .rb-bars { display: inline-flex; }
      .rec-bigbtn.is-paused { animation: none; }
      /* Photo mode: Video/Photo toggle in the dock, a side rail on the
         camera (Aa = text overlay, + = collage), the photo text bar,
         and the full-stage collage builder. */
      .rec-modes {
        display: flex; gap: 4px; margin-bottom: 10px; padding: 4px;
        background: rgba(20,10,2,.55); border-radius: 99px;
        box-shadow: 0 4px 14px rgba(0,0,0,.45);
      }
      .rec-modes button {
        flex: 1; border: none; background: transparent; color: #f5e8cf;
        font: inherit; font-size: 13.5px; font-weight: 700; padding: 8px 0; border-radius: 99px;
      }
      .rec-modes button.on { background: var(--gold-2); color: #251505; }
      .rec-side {
        position: absolute; right: 10px; top: 50%; transform: translateY(-50%); z-index: 6;
        display: flex; flex-direction: column; gap: 12px;
      }
      .rec-side-btn {
        width: 54px; padding: 9px 0 7px; border-radius: 16px;
        border: 1px solid rgba(245,232,207,.4); background: rgba(20,10,2,.55); color: #fff6dd;
        display: flex; flex-direction: column; align-items: center; gap: 2px;
        box-shadow: 0 2px 10px rgba(0,0,0,.35); font: inherit;
      }
      .rec-side-glyph { font-size: 21px; font-weight: 800; line-height: 1; }
      .rec-side-glyph svg { width: 21px; height: 21px; display: block; }
      .rec-side-btn.on { background: var(--gold-2); color: #251505; border-color: rgba(246,205,103,.95); }
      .rec-side-label { font-size: 9.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
      .photo-text-bar {
        position: absolute; left: 12px; right: 12px; bottom: 128px; z-index: 7;
        background: var(--card); border: 1px solid var(--line-soft); border-radius: 16px;
        padding: 14px; box-shadow: var(--shadow);
      }
      .photo-text-bar .f { margin-top: 0; }
      .collage-view {
        position: absolute; inset: 0; z-index: 20; overflow-y: auto;
        background: linear-gradient(180deg, var(--app-bg), var(--app-bg2));
        display: flex; flex-direction: column;
        padding: calc(10px + var(--sat)) 16px calc(14px + var(--sab));
      }
      .collage-head { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
      .collage-head .rec-title { color: var(--ink); text-shadow: none; }
      .collage-head .rec-x { background: var(--card); color: var(--ink); border-color: var(--line); box-shadow: none; }
      /* Upload picker (Casey's call, 2026-10-21): pressing the
         Gallery/Upload button opens ONE page holding the device
         media in two separate sections — Photos and Videos —
         each section its own big tappable block that opens the
         device picker filtered to just that kind. */
      .picker-view {
        position: absolute; inset: 0; z-index: 20; overflow-y: auto;
        background: linear-gradient(180deg, var(--app-bg), var(--app-bg2));
        display: flex; flex-direction: column;
        padding: calc(10px + var(--sat)) 16px calc(14px + var(--sab));
      }
      .picker-pick {
        display: flex; align-items: center; gap: 13px; width: 100%; text-align: left;
        background: var(--card); border: 1.5px solid var(--line); border-radius: 16px;
        padding: 16px; color: var(--ink); font: inherit; cursor: pointer; margin-bottom: 6px;
      }
      .picker-pick:active { transform: translateY(1px); }
      .picker-pick .pp-ic {
        width: 48px; height: 48px; border-radius: 50%; flex: none;
        background: var(--chip); color: var(--gold-text);
        display: inline-flex; align-items: center; justify-content: center;
      }
      .picker-pick .pp-ic svg { width: 23px; height: 23px; }
      .picker-pick .pp-t { display: block; font-weight: 800; font-size: 16.5px; }
      .picker-pick .pp-s { display: block; font-size: 13px; color: var(--ink-soft); margin-top: 2px; line-height: 1.4; }
      .collage-layouts { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 14px; }
      .collage-layout-btn {
        border: 1.5px solid var(--line); background: var(--card); border-radius: 12px;
        padding: 7px 10px 7px 8px; font: inherit; font-size: 12.5px; font-weight: 700; color: var(--ink);
        display: inline-flex; align-items: center; gap: 8px;
      }
      .collage-layout-btn.on { border-color: var(--gold-deep); background: var(--card2); }
      .collage-mini { position: relative; width: 26px; height: 26px; background: var(--card2); border-radius: 4px; overflow: hidden; flex: none; }
      .collage-layout-btn.on .collage-mini { background: #fff; }
      .collage-mini i { position: absolute; background: var(--gold); border: 1px solid #fff; box-sizing: border-box; }
      .collage-grid {
        position: relative; width: 100%; aspect-ratio: 4 / 5; flex: none;
        background: #fff; border-radius: 14px; overflow: hidden; box-shadow: var(--shadow);
      }
      .collage-cell {
        position: absolute; box-sizing: border-box; border: 3px solid #fff;
        background: var(--card2); overflow: hidden; cursor: pointer;
        display: flex; align-items: center; justify-content: center;
      }
      .collage-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
      .collage-cell .cc-plus { font-size: 34px; font-weight: 300; color: var(--ink-faint); line-height: 1; }
      /* Full-screen recorder: the stage covers the whole phone and the
         camera preview fills it edge to edge — Record / Pause / Stop /
         flip all overlay the preview. Once a clip exists the stage flips
         to review mode. The LIVE stage's review settles the preview into
         the top of the screen with its panel beneath it; the SHORTS
         recorder's review keeps the take FULL SCREEN instead (Casey's
         call, 2026-10-21, TikTok-style — the #rec-stage overrides
         below): the lane is picked from the same left rail Shorts
         opens with, the Text / Edit / Sound tools ride the right
         edge of the picture, and the caption + Post bar overlays the
         bottom. The Edit and Add-sound panels open as a sheet over
         the bottom of the still-full-screen take. */
      .rec-stage {
        position: absolute; inset: 0; z-index: 60;
        background: linear-gradient(180deg, var(--app-bg), var(--app-bg2));
        display: flex; flex-direction: column; overflow: hidden;
      }
      .rec-view { position: relative; flex: 1 1 auto; min-height: 0; background: var(--card2); overflow: hidden; }
      .rec-stage.review .rec-view { flex: 0 0 44%; }
      .rec-fill { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
      /* LOCKED RULE (SPEC.md, updated 2026-10-20): camera surfaces show
         true orientation — exactly how other people see you. Playback
         and photo surfaces stay pinned to transform:none. The ONE
         exception is the LIVE front-camera preview (#rec-live.cam-comp):
         on Casey's device the front-camera stream itself arrives
         mirrored from the WebView, so the recorder adds .cam-comp
         (scaleX(-1)) to the front-facing live preview ONLY, flipping the
         device's mirror back to true orientation. The class comes off
         the moment the back camera is active, and it never touches the
         recorded clip — the recording canvas reads the raw stream and
         CSS transforms do not affect drawImage. Do not remove the pin,
         and do not drop the .cam-comp compensation: without it the front
         preview shows mirrored on his phone. */
      .rec-view .rec-fill { transform: none !important; }
      .rec-view #rec-live.cam-comp { transform: scaleX(-1) !important; }
      /* The regular camera (the Share sheet's Take a picture)
         rides the same stage classes as the recorder, with its
         own live preview: the same front-camera compensation
         applies to it, and its dock row matches the recorder's
         photo row sizing. */
      .rec-view #pcam-live.cam-comp { transform: scaleX(-1) !important; }
      #pcam-choose > button { flex: 1 1 0; height: 50px; min-width: 0; margin-top: 0; }
      .rec-stage.review .rec-fill { object-fit: contain; }
      .rec-topbar {
        position: absolute; top: 0; left: 0; right: 0; z-index: 6;
        display: flex; align-items: center; gap: 10px;
        padding: calc(10px + env(safe-area-inset-top)) 12px 18px;
        background: linear-gradient(to bottom, rgba(37,21,5,.5), rgba(37,21,5,0));
      }
      .rec-topbar .rec-title {
        flex: 1; min-width: 0; text-align: center; font-family: "Marcellus", Georgia, serif;
        font-size: 17px; letter-spacing: .06em; text-transform: uppercase;
        color: #fff6dd; text-shadow: 0 1px 6px rgba(0,0,0,.6);
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
      }
      .rec-x {
        width: 38px; height: 38px; border-radius: 50%; flex: none;
        border: 1px solid rgba(245,232,207,.4); background: rgba(20,10,2,.55); color: #f5e8cf;
        display: inline-flex; align-items: center; justify-content: center;
        box-shadow: 0 2px 10px rgba(0,0,0,.35);
      }
      .rec-x svg { width: 20px; height: 20px; }
      .rec-status {
        position: absolute; top: calc(68px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%);
        z-index: 6; width: max-content; max-width: 88%;
        background: rgba(20,10,2,.55); color: #fff6dd;
        padding: 8px 15px; border-radius: 16px;
        font-size: 13px; font-weight: 600; line-height: 1.45; text-align: center;
        transition: opacity .35s ease;
      }
      /* The opening hint fades itself away (Casey's call,
         2026-10-24): added by the recorder's hint timer about
         three seconds after the "That's you, live" line lands. */
      .rec-status.status-gone { opacity: 0; pointer-events: none; }
      /* Front-camera screen flash (TikTok-style, Casey's call,
         2026-10-24): a front camera has no torch a page can reach,
         so the screen itself is the light. While a take rolls the
         view wears a bright warm frame (.lit — the edges throw the
         light, the middle stays see-through so the shot can still
         be framed); a photo snap fires one full bright burst
         (.burst). It floats above the picture but below every
         control, and never takes a tap. */
      .rec-screenflash {
        position: absolute; inset: 0; z-index: 4; pointer-events: none;
        opacity: 0; transition: opacity .18s ease;
        background: radial-gradient(ellipse at center, rgba(255,251,238,0) 34%, rgba(255,251,238,.98) 74%);
        will-change: opacity; transform: translateZ(0);
      }
      .rec-screenflash.lit { opacity: 1; }
      .rec-screenflash.burst { opacity: 1; background: #ffffff; transition: none; }
      /* The two-second brightness warning that rides with the
         screen flash switching on. */
      .rec-flashwarn {
        position: absolute; left: 50%; transform: translateX(-50%);
        bottom: calc(148px + env(safe-area-inset-bottom)); z-index: 7;
        width: max-content; max-width: 88%;
        background: rgba(20,10,2,.82); color: #ffe9b8;
        border: 1px solid rgba(246,205,103,.55); border-radius: 14px;
        padding: 8px 14px; font-size: 12.5px; font-weight: 600; line-height: 1.45; text-align: center;
      }
      /* The photo timer's countdown, standing over the picture. */
      .rec-countdown {
        position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 6;
        font-size: 112px; font-weight: 800; line-height: 1; color: #fff6dd;
        text-shadow: 0 6px 34px rgba(0,0,0,.6); pointer-events: none;
      }
      .rec-msg { position: absolute; left: 12px; right: 12px; bottom: 104px; z-index: 6; }
      .rec-msg:empty { display: none; }
      .rec-msg .err-box { background: #fbe9e2; box-shadow: var(--shadow); margin: 0; }
      .rec-stage.review .rec-msg { bottom: 12px; }
      .rec-panel {
        display: none; flex: 1 1 auto; min-height: 0; overflow-y: auto;
        padding: 4px 18px calc(18px + env(safe-area-inset-bottom));
      }
      .rec-stage.review .rec-panel { display: block; }
      /* Shorts recorder review (scoped to #rec-stage so the live
         stage keeps its settle-into-the-top review): the take never
         shrinks — the view stays full height, the picture keeps
         filling it edge to edge, and the panel only appears as a
         sheet over the bottom while Edit or Add sound is open. */
      #rec-stage.review .rec-view { flex: 1 1 auto; }
      #rec-stage.review .rec-fill { object-fit: cover; }
      #rec-stage.review .rec-msg { bottom: 208px; }
      #rec-stage.review .rec-panel { display: none; }
      #rec-stage.review .rec-panel.sheet-open {
        display: block; position: absolute; left: 0; right: 0; bottom: 0; z-index: 12;
        flex: none; max-height: 68%; overflow-y: auto;
        background: var(--card); border-top: 1px solid var(--line);
        border-radius: 20px 20px 0 0; box-shadow: 0 -14px 34px rgba(0,0,0,.5);
        padding: 6px 18px calc(18px + env(safe-area-inset-bottom));
      }
      /* Add sound sits at the top center of the REVIEW,
         where the "Record a short" title used to be
         (Casey's calls, 2026-10-21, TikTok-style): a pill with
         the music note and the words "Add sound", which
         become the picked sound's name once one is chosen.
         It exists only in the editing — never on the camera
         before a take (Casey's call, 2026-10-21): it starts
         hidden in the markup and setClip reveals it, for
         pictures and videos alike, once a take is on review. */
      .rec-topbar .rec-sound-top {
        flex: 1; min-width: 0; max-width: 250px; margin: 0 auto; cursor: pointer; font: inherit;
        display: inline-flex; align-items: center; justify-content: center; gap: 7px;
        padding: 8px 14px; border-radius: 999px;
        border: 1px solid rgba(245,232,207,.4); background: rgba(20,10,2,.55); color: #fff6dd;
        box-shadow: 0 2px 10px rgba(0,0,0,.35);
        font-size: 13.5px; font-weight: 700; letter-spacing: .02em;
      }
      .rec-topbar .rec-sound-top svg { width: 16px; height: 16px; flex: none; }
      .rec-topbar .rec-sound-top span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .rec-topbar .rec-sound-top:active { transform: scale(.96); }
      /* The review's lane rail: the very same bar that stands on
         the left edge when Shorts first opens — same circular
         chips (.shorts-lanes-rail / .shorts-lane-chip above),
         same scroll, same expanding name pill — dropped just
         below the recorder's top bar so the back button keeps
         its corner. */
      .rec-lane-rail { top: calc(64px + env(safe-area-inset-top)); z-index: 7; }
      /* Folded review rail (Casey's call, 2026-10-24): the lane
         rail never opens showing every lane — it stands folded
         to the one home chip (Open Table, or the lane already
         picked), and only a long press on it unfolds the full
         side scroll to pick from. */
      .rec-lane-rail.lanes-folded { overflow-y: hidden; justify-content: center; }
      .rec-lane-rail.lanes-folded .shorts-lane-chip { display: none; }
      .rec-lane-rail.lanes-folded .shorts-lane-chip.fold-home { display: flex; }
      /* The review's tool rail + post bar, overlaid on the
         full-screen take: Text / Edit / Sound ride the right
         edge the way TikTok's tools do; the caption, the lane
         hint and Post / Draft / Start over ride the bottom. The
         wrapper itself takes no taps — only its children do — so
         the picture underneath stays tappable (tap = play/pause). */
      .rec-actions { position: absolute; inset: 0; z-index: 7; pointer-events: none; }
      .rec-tools {
        position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
        display: flex; flex-direction: column; gap: 14px; pointer-events: auto;
      }
      .rec-tool {
        width: 56px; padding: 9px 0 7px; border-radius: 16px; cursor: pointer;
        border: 1px solid rgba(245,232,207,.4); background: rgba(20,10,2,.55); color: #fff6dd;
        display: flex; flex-direction: column; align-items: center; gap: 3px;
        box-shadow: 0 2px 10px rgba(0,0,0,.35); font: inherit;
      }
      .rec-tool:active { transform: scale(.94); }
      .rec-tool .rt-glyph { font-size: 20px; font-weight: 800; line-height: 1; display: inline-flex; }
      .rec-tool .rt-glyph svg { width: 21px; height: 21px; display: block; }
      .rec-tool .rt-label { font-size: 9.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
      .rec-postbar {
        position: absolute; left: 0; right: 0; bottom: 0; pointer-events: auto;
        padding: 30px 14px calc(12px + env(safe-area-inset-bottom));
        background: linear-gradient(to top, rgba(0,0,0,.68) 55%, rgba(0,0,0,0));
      }
      .rec-postbar .f { margin-top: 0; color: #fff6dd; text-shadow: 0 1px 6px rgba(0,0,0,.65); }
      .rec-postbar .rec-lane-hint { margin: 8px 2px 0; font-size: 12.5px; line-height: 1.45; color: rgba(255,246,221,.9); text-shadow: 0 1px 6px rgba(0,0,0,.65); }
      .rec-postbar #rec-sub-row .small.muted { color: rgba(255,246,221,.82); text-shadow: 0 1px 6px rgba(0,0,0,.65); }
      .rec-postrow { display: flex; gap: 10px; margin-top: 12px; }
      .rec-postrow .btn-gold, .rec-postrow .btn-ghost { box-shadow: 0 4px 14px rgba(0,0,0,.45); }

      /* ---------- World ---------- */
      .world-wrap { text-align: center; padding: 26px 22px 60px; }
      .world-globe { color: var(--gold-deep); display: flex; justify-content: center; margin-bottom: 20px; }
      .world-globe svg { width: 96px; height: 96px; }
      .world-globe-canvas { display: block; margin: 0 auto 4px; width: min(272px, 76vw); aspect-ratio: 1 / 1; }

      /* Events door top: a GPS map of the member's own area in place
         of the revolving globe (Casey, 2026-10-22, modeled on the
         Facebook Events map) — centered on the member when location
         is on, gatherings pinned around them. The map control's own
         attribution stays untouched inside .ev-map. */
      .ev-map-wrap { position: relative; margin: 4px 0 2px; text-align: left; }
      .ev-map {
        position: relative; width: 100%; height: 320px; overflow: hidden;
        border: 1px solid var(--line); border-radius: 16px; background: var(--chip);
      }
      .ev-map-msg {
        position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
        text-align: center; padding: 22px; color: var(--ink-soft); font-size: 14px; line-height: 1.6;
      }
      .ev-map-bar { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
      .ev-map-bar .btn-ghost { flex: none; }
      /* The navigation symbol: once a gathering's pin is tapped,
         this rides the map's top-right corner (clear of the
         control's own attribution, which stays untouched at
         the bottom) and opens real navigation to that
         gathering in Google Maps. A gathering locked to the
         member never drops a pin, so it never gets this
         button either. */
      .ev-nav {
        position: absolute; top: 12px; right: 12px; z-index: 6;
        display: inline-flex; align-items: center; gap: 7px;
        padding: 9px 15px; border: none; border-radius: 999px;
        background: var(--gold); color: #241a02; font: inherit;
        font-size: 13.5px; font-weight: 800; cursor: pointer;
        box-shadow: 0 4px 14px rgba(0,0,0,.38);
      }
      .ev-nav svg { width: 16px; height: 16px; flex: none; }
      .ev-nav.hidden { display: none; }
      /* A gathering pin: gold teardrop carrying the Manna mark,
         the headcount riding as a badge on its shoulder. The
         golds here are FIXED, not the theme vars (Casey's call,
         2026-10-24): the accent themes recolor --gold (the
         graphite theme makes it gray), but the Manna location
         pin is golden in every theme — the same way the "you"
         dot below is always blue. */
      .ev-pin { position: relative; width: 28px; height: 28px; cursor: pointer; }
      .ev-pin i {
        position: absolute; inset: 0; display: block; background: linear-gradient(150deg, #f6cd67, #d29a1f 60%, #b57f14);
        border: 2px solid #fff; border-radius: 50% 50% 50% 4px;
        transform: rotate(-45deg); box-shadow: 0 2px 7px rgba(0,0,0,.35);
      }
      .ev-pin b {
        position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
        color: #fff;
      }
      .ev-pin-mark { width: 14px; height: 14px; display: block; filter: drop-shadow(0 1px 1px rgba(0,0,0,.3)); }
      .ev-pin-n {
        position: absolute; top: -7px; right: -9px; z-index: 2; min-width: 17px; height: 17px;
        padding: 0 4px; border-radius: 99px; background: #96690e; color: #fff;
        border: 1.5px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,.35);
        font-size: 10px; font-weight: 800; font-style: normal; line-height: 1;
        display: flex; align-items: center; justify-content: center;
      }
      .ev-pin--on i { background: #96690e; transform: rotate(-45deg) scale(1.18); }
      /* The member's own dot: blue, ringed, pulsing — it should
         be impossible to miss yourself on this map. */
      .ev-you {
        position: relative; width: 20px; height: 20px; margin-bottom: -10px; border-radius: 50%;
        background: #1a73e8; border: 3px solid #fff;
        box-shadow: 0 0 0 7px rgba(26,115,232,.22), 0 2px 6px rgba(0,0,0,.3);
      }
      .ev-you::after {
        content: ""; position: absolute; inset: -10px; border-radius: 50%;
        border: 2px solid rgba(26,115,232,.55); animation: ev-you-pulse 2.2s ease-out infinite;
      }
      @keyframes ev-you-pulse {
        0% { transform: scale(.5); opacity: .95; }
        70% { transform: scale(1.12); opacity: 0; }
        100% { transform: scale(1.12); opacity: 0; }
      }
      @media (prefers-reduced-motion: reduce) {
        .ev-you::after { animation: none; transform: none; opacity: .55; }
      }
      .ev-card-on { outline: 2px solid var(--gold); outline-offset: 2px; }

      /* Dismissible explainer blocks: a short "what this is" note with
         a single OK button (Casey's call, 2026-10-20: no ✕ — OK only).
         Dismissing saves the choice in the user's synced settings, so
         the note never comes back. */
      .explain-box {
        position: relative; background: var(--chip); border: 1px solid var(--line);
        border-radius: 13px; padding: 12px 14px; margin: 0 0 14px;
        font-size: 13.5px; line-height: 1.55; color: var(--ink-soft);
      }
      .explain-box p { margin: 0; }
      .explain-ok { margin-top: 10px; width: auto; }
      /* Inline description notes — the plain "small muted" how-to
         lines in the composers and sheets — carry the same OK as
         the explainer boxes (Casey's call, 2026-10-22: the
         descriptions are for people new to the app). The button
         sits at the end of the note's own words, or under them
         inside a .desc-wrap for notes whose words change with
         state; a wrapped note's OK hides whenever it does. */
      .desc-ok { margin-left: 8px; padding: 3px 13px; font-size: 12.5px; vertical-align: 1px; }
      .desc-wrap > .desc-ok { margin: 6px 0 0; }
      .desc-wrap:has(> .hidden) > .desc-ok { display: none; }
      .rec-postbar .desc-ok { color: #fff6dd; border-color: rgba(255,246,221,.45); background: rgba(255,246,221,.08); }
      .world-wrap h2 { font-family: "Marcellus", Georgia, serif; font-size: 30px; color: var(--ink); margin-bottom: 10px; }
      .world-wrap p { color: var(--ink-soft); max-width: 320px; margin: 0 auto; line-height: 1.55; }

      /* ---------- Bottom nav ---------- */
      nav.bottom {
        flex: 0 0 auto; z-index: 50;
        background: var(--card); border-top: 1px solid var(--line);
        display: flex; padding: 6px 4px calc(8px + var(--sab));
      }
      nav.bottom button {
        flex: 1; background: none; border: none; color: var(--ink-faint);
        display: flex; flex-direction: column; align-items: center; gap: 2px;
        font-size: 10px; font-weight: 700; padding: 4px 0;
      }
      nav.bottom button svg { width: 21px; height: 21px; }
      nav.bottom button.on { color: var(--gold-text); }
      @media (prefers-reduced-motion: reduce) {
        .spinner { animation-duration: 1.6s; }
        #splash { transition: none; }
        .door:active, .btn-gold:active, .fab:active { transform: none; }
        .potd-panel, .potd-tab .potd-chev { transition: none; }
        .p-act svg.pop, .shorts-act svg.pop { animation: none; }
        .p-act svg.repop, .shorts-act svg.repop { animation: none; }
        .rec-bigbtn { animation: none; }
        .heart-burst { display: none; }
        .rep-burst { display: none; }
      }

      /* ---------- Birthday manna rain ----------
         On your birthday, opening your own profile rains manna
         over it (Casey's call, 2026-10-22): little golden wafers
         drift down the whole profile, swaying as they fall, then
         the sky fades clear. The layer never takes a tap, and it
         sits under sheets and toasts so nothing is ever blocked. */
      .manna-rain {
        position: absolute; inset: 0; overflow: hidden;
        pointer-events: none; z-index: 55;
      }
      .manna-rain.bye { opacity: 0; transition: opacity .8s ease; }
      .manna-flake {
        position: absolute; top: 0; will-change: transform;
        animation-name: manna-fall; animation-timing-function: linear;
        animation-fill-mode: forwards;
      }
      .manna-flake i {
        display: block; width: 100%; height: 100%;
        border-radius: 46% 54% 52% 48% / 55% 45% 55% 45%;
        background: radial-gradient(circle at 35% 30%, #fffbe8, #f6cd67 45%, var(--gold-2) 72%, var(--gold-deep));
        box-shadow: 0 1px 4px rgba(120, 84, 10, .4), 0 0 7px rgba(246, 205, 103, .55), inset 0 0 3px rgba(255, 255, 255, .75);
        animation-name: manna-sway; animation-timing-function: ease-in-out;
        animation-iteration-count: infinite; animation-direction: alternate;
      }
      /* The vivid pass (Casey's call, 2026-10-24): some wafers
         fall big and glowing in the near layer, some spin the
         whole way down, and the words pop up over the rain. */
      .manna-flake.big i {
        background: radial-gradient(circle at 35% 30%, #fffdf2, #f9d976 40%, var(--gold-2) 68%, var(--gold-deep));
        box-shadow: 0 2px 8px rgba(120, 84, 10, .45), 0 0 16px rgba(246, 205, 103, .8), inset 0 0 4px rgba(255, 255, 255, .85);
      }
      .manna-flake.spin i { animation-name: manna-spin; animation-direction: normal; }
      .manna-banner {
        position: absolute; top: 13%; left: 50%; z-index: 3;
        transform: translateX(-50%);
        background: linear-gradient(160deg, var(--gold-2), var(--gold) 55%, var(--gold-deep));
        color: #241503; font-weight: 800; font-size: 17px; line-height: 1.35;
        text-align: center; padding: 13px 20px; border-radius: 18px;
        box-shadow: 0 10px 30px rgba(120, 84, 10, .45);
        max-width: 86%;
        animation: manna-pop .55s cubic-bezier(.2, 1.55, .4, 1) .45s both, manna-bob 2.6s ease-in-out 1.1s infinite alternate;
      }
      .manna-banner small { display: block; font-size: 12.5px; font-weight: 700; opacity: .75; margin-top: 3px; }
      @keyframes manna-pop {
        from { opacity: 0; transform: translateX(-50%) translateY(14px) scale(.82); }
        to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
      }
      @keyframes manna-bob {
        from { transform: translateX(-50%) translateY(0); }
        to { transform: translateX(-50%) translateY(-5px); }
      }
      @keyframes manna-fall {
        from { transform: translate3d(0, -6vh, 0); }
        to { transform: translate3d(var(--drift, 0px), var(--fall, 110vh), 0); }
      }
      @keyframes manna-sway {
        from { transform: translateX(-7px) rotate(-38deg); }
        to { transform: translateX(7px) rotate(38deg); }
      }
      @keyframes manna-spin {
        from { transform: translateX(-6px) rotate(0deg); }
        to { transform: translateX(6px) rotate(360deg); }
      }
      @media (prefers-reduced-motion: reduce) { .manna-rain { display: none; } }
    
      /* ---------- Installed-app bar clearance (v11, 2026-10-31) ----------
         Casey's ruling on v10: full screen like X — the background
         flows edge to edge behind the phone's bars — AND the content
         stays separated from the system bar; nothing overlaps. In
         this WebView env(safe-area-inset-*) resolves to 0, so the
         native shell measures the real insets (status bar + display
         cutout on top, navigation bar at the bottom) and publishes
         them as --manna-top-inset / --manna-bottom-inset on the
         document root. --sat / --sab are the tokens the chrome reads:
         in a browser they are env(), the exact values these rules
         always used, so the website cannot change; in the installed
         app (html.manna-shell, set by app.js only when the native
         bridge exists) the shell's measured values govern. The
         overrides below add clearance to the top chrome that never
         carried an inset of its own; each is shell-gated. Shorts and
         the recorder are deliberately untouched: full-bleed stays
         full-bleed, with the Shorts UI lift as it was. */
      :root { --sat: env(safe-area-inset-top, 0px); --sab: env(safe-area-inset-bottom, 0px); }
      html.manna-shell { --sat: var(--manna-top-inset, 28px); --sab: var(--manna-bottom-inset, env(safe-area-inset-bottom, 0px)); }
      html.manna-shell .appbar { padding-top: calc(10px + var(--sat)); }
      html.manna-shell .story-viewer .sv-top { padding-top: calc(12px + var(--sat)); }
      html.manna-shell .story-editor .se-top { padding-top: calc(10px + var(--sat)); }
      html.manna-shell .auth-wrap { padding-top: calc(34px + var(--sat)); }
      html.manna-shell .cadj-overlay { padding-top: calc(18px + var(--sat)); }
