    :root {
      --bg: #EDF3FF;
      --bg2: #DCE9FF;
      --card: #FFFFFF;
      --ink: #1F2A44;
      --ink-soft: #3B476A; 
      --sun: #FFC93C;
      /* Text-safe version of --sun. --sun itself (#FFC93C) is ~1.5:1 against
         white/card surfaces — fine as a border or a tinted background, but a
         WCAG AA failure as a foreground text colour. Any place --sun was
         being used to colour TEXT should use --sun-ink instead; --sun stays
         exactly as-is for borders, fills, and backgrounds, which is most of
         its uses and where contrast rules don't apply.
         TO REVERT: delete this line and body.theme-night's --sun-ink
         override below, then find/replace every `color: var(--sun-ink)`
         back to `color: var(--sun)` in enhancements.css. */
      --sun-ink: #8A6100;
      --berry: #D81B60; 
      --mint: #00897B;  
      --grape: #7E57C2; 
      --sky: #1565C0;   
      --mascot: var(--mint);
      --shadow: rgba(31,42,68,0.14);
      --radius-lg: 28px;
      --radius-md: 18px;
      --font-scale: 1;
      /* Action green: the hero colour for the core "add food" flow.
         Hardcoded (not theme-overridden) so white text stays legible in every theme.
         Was #0E8C7A, which measured 4.16:1 against white text — a WCAG AA
         fail (needs 4.5:1) on the app's primary CTA colour. Darkened to
         #0B7A6A (5.24:1), a change small enough to be visually
         imperceptible at the sizes this token is used at.
         TO REVERT: change back to #0E8C7A. */
      --sprout-green: #0B7A6A;
      --sprout-green-deep: #00594E;
      --sprout-green-tint: rgba(14,140,122,0.12);
      /* Drinks Station v2 surfaces. The approved mockup hardcoded
         rgba(255,255,255,.04) / rgba(0,0,0,.28), which only reads correctly
         on a dark card — these are the same three layers expressed as
         tokens so every theme gets a legible version. */
      --dv-surface: rgba(31,42,68,0.05);
      --dv-line: rgba(31,42,68,0.16);
      --dv-inset: rgba(31,42,68,0.07);
    }
    body.theme-night {
      --sprout-green-tint: rgba(46,196,182,0.16);
      --dv-surface: rgba(255,255,255,0.04);
      --dv-line: rgba(255,255,255,0.12);
      --dv-inset: rgba(0,0,0,0.28);
    }
    body.theme-contrast, body.theme-contrast-light {
      --sprout-green: #00594E;
    }
    body.theme-contrast {
      --dv-surface: #1A1A1A;
      --dv-line: #FFFFFF;
      --dv-inset: #000000;
    }
    body.theme-contrast-light {
      --dv-surface: #EEEEEE;
      --dv-line: #000000;
      --dv-inset: #FFFFFF;
    }
    body.theme-night {
      --bg: #1B2140;
      --bg2: #131737;
      --card: #262C52;
      --ink: #F3F5FF;
      --ink-soft: #B7BEE0;
      --shadow: rgba(0,0,0,0.35);
      --berry: #FF5D8F;
      --mint: #2EC4B6;
      --grape: #9B6BFF;
      --sky: #3FA9F5;
      --sun-ink: #FFC93C;
    }
    body.theme-rainbow {
      --bg: #FFF1E6;
      --bg2: #FFE1F0;
      --card: #FFFFFF;
      --ink: #2B2250;
      --ink-soft: #5A4F80;
      --berry: #E91E63;
      --mint: #009688;
      --sky: #0288D1;
    }
    body.theme-caregiver {
      --bg: #E0F2FE;
      --bg2: #BAE6FD;
      --card: #FFFFFF;
      --ink: #0369A1;
      --ink-soft: #0284C7;
      --shadow: rgba(3, 105, 161, 0.15);
    }
    body.theme-contrast {
      --bg: #000000;
      --bg2: #1A1A1A;
      --card: #111111;
      --ink: #FFFFFF;
      --ink-soft: #FFFF00;
      --shadow: none;
      --sky: #FFFF00;
      --mint: #00FF00;
      --berry: #FF00FF;
      --sun-ink: #FFC93C;
      border: 2px solid #FFFFFF;
    }
    body.theme-contrast-light {
      --bg: #FFFFFF;
      --bg2: #EEEEEE;
      --card: #FFFFFF;
      --ink: #000000;
      --ink-soft: #1A237E;
      --shadow: none;
      --sky: #0D47A1;
      --mint: #1B5E20;
      --berry: #880E4F;
      border: 2px solid #000000;
    }
    body.theme-low-lume {
      --bg: #0D0F14;
      --bg2: #161A23;
      --card: #1A202C;
      --ink: #E2E8F0;
      --ink-soft: #A0AEC0;
      --shadow: none;
      --sky: #63B3ED;
      --mint: #4FD1C5;
      --berry: #F687B3;
      --sun: #F6E05E;
    }

    body.theme-contrast .calc-card, body.theme-contrast-light .calc-card,
    body.theme-contrast .meal-log-card, body.theme-contrast-light .meal-log-card,
    body.theme-contrast .greeting-card, body.theme-contrast-light .greeting-card,
    body.theme-contrast .drawer, body.theme-contrast-light .drawer {
      border: 3px solid var(--ink) !important;
    }
    body.theme-contrast .step-btn, body.theme-contrast-light .step-btn,
    body.theme-contrast .go-btn, body.theme-contrast-light .go-btn,
    body.theme-contrast .shortcut-share-btn, body.theme-contrast-light .shortcut-share-btn,
    body.theme-contrast .confirm-share-btn, body.theme-contrast-light .confirm-share-btn {
      background: var(--ink-soft) !important;
      color: var(--bg) !important;
      border: 2px solid var(--ink) !important;
      font-weight: 900 !important;
    }
    body.theme-contrast .go-btn:disabled, body.theme-contrast-light .go-btn:disabled {
      opacity: 0.45 !important; cursor: not-allowed !important;
    }
    body.theme-contrast .value-box, body.theme-contrast-light .value-box {
      background: var(--bg) !important;
      border: 2px solid var(--ink) !important;
    }
    body.theme-contrast .value-box input, body.theme-contrast-light .value-box input {
      color: var(--ink-soft) !important;
    }

    #confettiCanvas {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      pointer-events: none;
      z-index: 150;
    }

    .undo-toast {
      position: fixed;
      left: 50%;
      bottom: 24px;
      transform: translateX(-50%) translateY(20px);
      background: var(--ink);
      color: var(--bg);
      padding: 12px 14px 12px 18px;
      border-radius: 100px;
      box-shadow: 0 10px 28px rgba(0,0,0,0.3);
      display: flex;
      align-items: center;
      gap: 12px;
      font-family: 'Nunito', sans-serif;
      font-weight: 800;
      font-size: 0.9rem;
      z-index: 250;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.25s ease, transform 0.25s ease;
      max-width: calc(100vw - 32px);
    }
    .undo-toast.show {
      opacity: 1;
      pointer-events: auto;
      transform: translateX(-50%) translateY(0);
    }
    .undo-toast .undo-toast-msg {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .undo-toast .undo-toast-btn {
      background: var(--sun);
      color: #1F2A44;
      border: none;
      padding: 7px 14px;
      border-radius: 100px;
      font-family: 'Baloo 2', sans-serif;
      font-weight: 800;
      font-size: 0.85rem;
      cursor: pointer;
      flex-shrink: 0;
    }
    .undo-toast .undo-toast-btn:active { transform: scale(0.95); }
    @media (prefers-reduced-motion: reduce) {
      .undo-toast { transition: opacity 0.001ms !important; }
    }

    .achievement-toast {
      position: fixed;
      left: 50%;
      bottom: 24px;
      transform: translateX(-50%) translateY(20px);
      background: var(--card);
      border: 3px solid var(--mint);
      color: var(--ink);
      padding: 10px 18px 10px 12px;
      border-radius: 100px;
      box-shadow: 0 10px 28px var(--shadow);
      display: flex;
      align-items: center;
      gap: 10px;
      z-index: 260;
      opacity: 0;
      pointer-events: none;
      max-width: calc(100vw - 32px);
      transition: opacity 0.25s ease, transform 0.25s ease;
    }
    .achievement-toast.show {
      opacity: 1;
      transform: translateX(-50%) translateY(0);
    }
    .achievement-toast .achievement-toast-icon {
      font-size: 1.6rem;
      line-height: 1;
      flex-shrink: 0;
    }
    .achievement-toast .achievement-toast-text {
      text-align: left;
      line-height: 1.25;
      min-width: 0;
    }
    .achievement-toast .achievement-toast-tag {
      font-family: 'Baloo 2', sans-serif;
      font-size: 0.68rem;
      font-weight: 800;
      color: var(--mint);
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }
    .achievement-toast .achievement-toast-title {
      font-family: 'Baloo 2', sans-serif;
      font-size: 1rem;
      font-weight: 800;
      color: var(--ink);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    @media (prefers-reduced-motion: reduce) {
      .achievement-toast { transition: opacity 0.001ms !important; }
    }

    .modal-overlay {
      position: fixed; inset: 0; background: rgba(0,0,0,0.6);
      z-index: 100; display: none; align-items: center; justify-content: center;
      padding: 15px;
    }
    .modal-content {
      background: var(--card); border-radius: var(--radius-lg);
      padding: 24px 18px; width: 100%; max-width: 440px;
      max-height: 90vh; overflow-y: auto;
      box-shadow: 0 12px 36px rgba(0,0,0,0.4);
    }

    /* ---- Caregiver "Edit Details" modal polish ---- */
    #editModal .modal-content { padding: 0; }
    #editModal .edit-modal-head {
      background: linear-gradient(135deg, var(--sky), #1E88E5);
      color: #fff; padding: 22px 20px 18px; text-align: center;
    }
    #editModal .edit-modal-head h3 { margin: 0; color: #fff; font-size: 1.32rem; }
    #editModal .edit-modal-sub { margin-top: 5px; font-size: 0.85rem; font-weight: 600; opacity: 0.92; }
    #editModal .edit-modal-body { padding: 20px 18px 22px; }
    #editModal input[type="text"] {
      transition: border-color 0.15s ease, box-shadow 0.15s ease;
    }
    #editModal input[type="text"]:focus {
      outline: none; border-color: var(--sky);
      box-shadow: 0 0 0 3px rgba(63,169,245,0.2);
    }
    #editModal .field label { font-size: 0.9rem; }

    /* Share confirmation card */
    .share-instructions-box {
      margin-top: 18px; padding: 16px 16px 14px; border-radius: 16px;
      background: linear-gradient(135deg, #E1F5FE, #EAF7EA);
      border: 2px solid #B3E5FC;
    }
    body.theme-night .share-instructions-box {
      background: linear-gradient(135deg, #12233A, #12261E); border-color: #1E3A5F;
    }
    /* v1.36.1: same audit as .fav-food-chip-pinned/.badges-current-rank —
       theme-low-lume had no override anywhere in this three-rule group
       (box/title/sent-time), so all three fell through to their bright
       pale-blue/green base values under a theme meant to reduce
       brightness. .share-instructions-title's own theme-night override
       (color: var(--sky), a few lines below) already works correctly
       under low-lume as-is, since it references a variable rather than a
       hex — no new rule needed there. These two needed one because their
       base rules hardcode hex values directly. Dark blue/green tones
       picked from the same family as theme-night's own override, not
       reused verbatim, so this doesn't just alias theme-night. */
    body.theme-low-lume .share-instructions-box {
      background: linear-gradient(135deg, #0F1B2E, #0F1E17); border-color: #16283F;
    }
    .share-instructions-title {
      display: flex; align-items: center; gap: 10px;
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 1.05rem;
      color: #0284C7; margin-bottom: 8px;
    }
    body.theme-night .share-instructions-title { color: var(--sky); }
    body.theme-low-lume .share-instructions-title { color: var(--sky); }
    .mini-share-mascot { width: 34px; height: 34px; flex-shrink: 0; }
    .share-sent-time {
      display: inline-block; margin-bottom: 10px;
      font-size: 0.85rem; font-weight: 700; color: #0369A1;
      background: rgba(255,255,255,0.65); border: 1px solid #B3E5FC;
      padding: 6px 11px; border-radius: 10px;
    }
    body.theme-night .share-sent-time {
      color: #8FD0FF; background: rgba(255,255,255,0.06); border-color: #1E3A5F;
    }
    body.theme-low-lume .share-sent-time {
      color: #8FD0FF; background: rgba(255,255,255,0.04); border-color: #16283F;
    }
    .share-instructions-text { font-size: 0.9rem; color: var(--ink-soft); line-height: 1.5; }
    .share-instructions-text ul { margin: 8px 0 0; padding-left: 20px; }
    .share-instructions-text li { margin-bottom: 4px; }
    .edit-item-row { display: flex; gap: 8px; margin-bottom: 10px; align-items: center; width: 100%; }
    .edit-item-row input[type="text"] { flex: 2; padding: 10px; border-radius: 10px; border: 1px solid var(--bg2); color: var(--ink); background: var(--bg); font-weight:700; min-width: 0; }
    .edit-item-row input[type="number"] { flex: 1; padding: 10px; border-radius: 10px; border: 1px solid var(--bg2); color: var(--ink); background: var(--bg); font-weight:700; min-width: 0; text-align: center; }
    .edit-item-row button { background: var(--bg2); border: none; padding: 10px 14px; border-radius: 10px; cursor: pointer; color: var(--berry); font-weight: bold; flex-shrink: 0; }
    .modal-items-total {
      text-align: right;
      font-family: 'Baloo 2', sans-serif;
      font-weight: 800;
      font-size: 1rem;
      color: var(--sky);
      padding-top: 8px;
      margin: 2px 0 16px;
      border-top: 2px dashed var(--bg2);
    }

    .fancy-safety-card {
      margin: 18px 0;
      padding: 14px 16px;
      background: var(--bg);
      border-radius: var(--radius-md);
      border: 2px dashed var(--bg2);
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      cursor: pointer;
      user-select: none;
      -webkit-user-select: none;
      transition: background 0.2s, border-color 0.25s;
    }
    body.theme-night .fancy-safety-card { background: #1B2140; border-color: #262C52; }
    .fancy-safety-card:hover {
      background: var(--bg2);
      opacity: 0.95;
    }
    .fancy-safety-card.activated {
      background: rgba(16, 185, 129, 0.1);
      border: 2px solid var(--mint);
    }
    .fancy-safety-card.activated .switch-track {
      background: var(--mint);
    }
    .fancy-safety-card.activated .switch-knob {
      transform: translateX(20px);
    }
    
    .sprout-validation-gate {
      background: rgba(24, 144, 255, 0.08);
      border: 2px dashed var(--sky);
      border-radius: var(--radius-md);
      padding: 14px;
      margin: 12px 0;
      display: none;
      animation: pop 0.3s ease;
    }
    .sprout-validation-gate-text {
      font-size: 0.88rem;
      font-weight: 800;
      line-height: 1.4;
      color: var(--ink);
      margin-bottom: 10px;
    }
    .sprout-validation-gate-btn {
      width: 100%;
      background: var(--sky);
      color: white;
      border: none;
      padding: 10px;
      border-radius: 10px;
      font-family: 'Baloo 2', sans-serif;
      font-weight: 700;
      font-size: 0.95rem;
      cursor: pointer;
      box-shadow: 0 4px 10px rgba(21,101,192,0.2);
    }
    .carb-ceiling-inline-msg {
      text-align: center;
      font-size: 0.85rem;
      font-weight: 800;
      color: var(--berry);
      margin: 6px 0 2px;
      animation: pop 0.3s ease;
    }
    .carb-ceiling-inline-msg.is-teasing {
      color: var(--sky);
    }
    .sprout-validation-gate-typed-label {
      display: block;
      font-size: 0.8rem;
      font-weight: 700;
      color: var(--ink-soft);
      margin-bottom: 6px;
    }
    .sprout-validation-gate-typed-input {
      width: 100%;
      box-sizing: border-box;
      padding: 10px;
      border-radius: 10px;
      border: 2px solid var(--sky);
      font-family: 'Baloo 2', sans-serif;
      font-weight: 700;
      font-size: 1rem;
      text-align: center;
      background: var(--bg);
      color: var(--ink);
    }
    body.theme-contrast .sprout-validation-gate-typed-input,
    body.theme-contrast-light .sprout-validation-gate-typed-input {
      background: var(--bg) !important;
      border: 2px solid var(--ink) !important;
      color: var(--ink) !important;
    }

    @keyframes inputErrorFlash {
      0%, 100% { box-shadow: 0 0 0 transparent; }
      40% { box-shadow: 0 0 0 4px var(--berry); }
    }
    .flash-error {
      animation: inputErrorFlash 0.7s ease;
      border-radius: 14px;
    }

    .switch-track {
      width: 44px;
      height: 24px;
      background: #B0BEC5;
      border-radius: 100px;
      position: relative;
      flex-shrink: 0;
      transition: background 0.2s ease;
    }
    .switch-knob {
      width: 18px;
      height: 18px;
      background: #FFFFFF;
      border-radius: 50%;
      position: absolute;
      top: 3px;
      left: 3px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.2);
      transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    }

    .privacy-scroll-box {
      background: var(--bg); border: 2px solid var(--bg2); border-radius: var(--radius-md);
      padding: 14px; max-height: 48vh; overflow-y: auto; font-size: 0.82rem; line-height: 1.45;
      color: var(--ink); text-align: left; font-weight: 700; margin-bottom: 16px;
    }
    body.theme-night .privacy-scroll-box { background: #1B2140; border-color: #262C52; }
    .privacy-scroll-box h4 { font-family: 'Baloo 2', sans-serif; font-size: 0.95rem; margin: 12px 0 4px 0; color: var(--sky); text-transform: none; letter-spacing: normal; }
    .privacy-scroll-box h4:first-child { margin-top: 0; }
    .privacy-scroll-box p { margin: 0 0 10px 0; }
    .privacy-scroll-box ul { margin: 0 0 10px 0; padding-left: 18px; }
    .privacy-scroll-box li { margin-bottom: 4px; }
    .privacy-scroll-box blockquote { margin: 10px 0; background: rgba(255,167,38,0.12); padding: 10px; border-radius: 8px; border-left: 4px solid #FF9800; font-size: 0.78rem; }
    
    .privacy-link-container {
      position: sticky;
      bottom: -30px;
      margin-top: 25px;
      background: var(--card);
      padding: 12px 0;
      text-align: right;
      z-index: 10;
      border-top: 1px dashed var(--bg2);
    }
    .privacy-trigger-link {
      color: var(--ink-soft); 
      font-size: 0.75rem; 
      font-weight: 800; 
      text-decoration: underline; 
      background: none; 
      border: none; 
      cursor: pointer; 
      padding: 4px;
    }
    .privacy-trigger-link:hover { color: var(--sky); }

    /* Share icon row: WhatsApp / Facebook / Email / "more" (native share
       sheet). Plain colored circles rather than a text button — icons scan
       faster than a button label for a share row, and each platform link
       is a plain href (no JS needed) except "more", which opens the native
       share sheet / clipboard fallback. */
    .share-icon-row {
      display: flex;
      justify-content: center;
      gap: 14px;
    }
    .share-icon-btn {
      width: 44px;
      height: 44px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      border: none;
      cursor: pointer;
      transition: transform 0.15s ease;
      box-shadow: 0 3px 10px var(--shadow);
    }
    .share-icon-btn:hover { transform: scale(1.08); }
    .share-icon-btn:active { transform: scale(0.94); }
    .share-icon-whatsapp { background: #25D366; }
    .share-icon-facebook { background: #1877F2; }
    .share-icon-email { background: var(--sky); }
    .share-icon-more { background: var(--ink-soft); }

    .footer-links-row {
      display: flex;
      justify-content: center;
      flex-wrap: wrap;
      gap: 16px;
      margin-top: 10px;
    }
    .footer-copyright {
      text-align: center;
      font-size: 0.68rem;
      font-weight: 700;
      color: var(--ink-soft);
      opacity: 0.75;
      margin: 10px 0 4px;
      line-height: 1.4;
    }
    .footer-trigger-link {
      color: var(--sky);
      font-size: 0.75rem;
      font-weight: 800;
      text-decoration: underline;
      background: none;
      border: none;
      cursor: pointer;
      padding: 4px;
    }
    .footer-trigger-link:hover { opacity: 0.85; }

    .disclaimer-mascot-container {
      width: 32px;
      height: 32px;
      display: inline-block;
      flex-shrink: 0;
    }

    * { box-sizing: border-box; }
    html {
      scroll-behavior: smooth; scroll-padding-top: 90px;
      /* Stops Chrome for Android's text-autosizing ("font boosting"), which
         guesses at "too small to read" text blocks (short bubbles like the
         greeting text are a classic trigger) and inflates them well past
         their authored font-size. Firefox doesn't do this, hence the same
         CSS looking fine there and oversized in Chrome. This only disables
         Chrome's own heuristic — it does not affect the Android OS-level
         accessibility font-size setting, which still scales normally. */
      -webkit-text-size-adjust: 100%;
      text-size-adjust: 100%;
    } /* reserves space for the sticky topbar (~78px, brand-mark is 60px tall) + breathing room, for every scroll/scrollIntoView on the page */
    /* Give a little breathing room at the top when we scroll a station into view */

    .sr-only {
      position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
      overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
    }
    .skip-link {
      position: absolute; left: 8px; top: -60px; z-index: 1000;
      background: var(--card); color: var(--ink); padding: 10px 16px;
      border-radius: 10px; font-weight: 800; text-decoration: none;
      box-shadow: 0 6px 18px var(--shadow); transition: top 0.15s ease;
    }
    .skip-link:focus { top: 8px; }

    a:focus-visible, button:focus-visible, input:focus-visible,
    [role="button"]:focus-visible, [role="radio"]:focus-visible,
    [tabindex]:focus-visible {
      outline: 3px solid var(--sky);
      outline-offset: 2px;
    }

    @media (prefers-reduced-motion: reduce) {
      html { scroll-behavior: auto; }
      *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
      }
    }
    body {
      margin: 0; padding: 0; min-height: 100%;
      font-family: 'Nunito', system-ui, sans-serif;
      color: var(--ink);
      background: radial-gradient(circle at 20% 0%, var(--bg2), var(--bg) 60%);
      transition: background .4s ease, color .4s ease;
      -webkit-tap-highlight-color: transparent;
    }
    body.modal-open-freeze {
      overflow: hidden;
    }
    body.modal-open-freeze #mainContent {
      filter: blur(5px);
    }
    h1, h2, h3, .display {
      font-family: 'Baloo 2', sans-serif;
      font-weight: 700;
      margin: 0;
    }
    .app {
      width: 95%;
      max-width: 640px;
      margin: 0 auto;
      min-height: 100vh;
      padding: 16px 16px 40px;
      font-size: calc(1em * var(--font-scale));
      transition: filter 0.3s ease;
    }
    .topbar {
      display: flex; align-items: center; justify-content: space-between;
      margin: 0 -16px 4px; padding: 10px 16px 8px;
      position: sticky; top: 0; z-index: 5;
      background: var(--bg);
    }
    .brand { display: flex; align-items: center; gap: 10px; }
    .brand-mark {
      width: 60px; height: 60px; flex: 0 0 auto;
      display: block; padding: 0; border: none; background: none;
      cursor: pointer;
    }
    .brand-mark svg { display: block; width: 100%; height: 100%; }
    .brand-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
    .brand h1 { font-size: 1.4rem; color: var(--ink); line-height: 1.1; }
    /* ============================================================
       CYCLING TAGLINE (added 26 Aug 2026) — see the matching comment
       block in index.html above #brandTagline for the full revert note.
       This block (down to "END") can be deleted as a single unit.
       Kept deliberately small/muted so it reads as a subtitle, not a
       second competing headline next to "Sprout Count" itself — and
       constrained to one line with its own ellipsis so a future longer
       tagline can never push the sticky header taller or wrap awkwardly
       against the icon buttons on the right. ============ */
    .brand-tagline {
      font-size: 0.7rem; font-weight: 700; color: var(--ink-soft);
      margin: 0; line-height: 1.3; white-space: nowrap; overflow: hidden;
      text-overflow: ellipsis; max-width: 100%;
      opacity: 1; transition: opacity 0.4s ease;
    }
    .brand-tagline.fading { opacity: 0; }
    @media (prefers-reduced-motion: reduce) {
      .brand-tagline { transition: none; }
    }
    /* END cycling tagline */
    
    .gear-btn {
      position: relative;
      width: 44px; height: 44px; border-radius: 50%;
      border: none; background: var(--card); box-shadow: 0 4px 10px var(--shadow);
      display: flex; align-items: center; justify-content: center;
      cursor: pointer; padding: 0;
    }
    .gear-btn:active { transform: scale(0.92); }
    /* Lets someone know there's something new to see in "Make it yours"
       since they last opened it — a badge unlocked, a level up, a new
       garden plant, a meal logged, or any setting they changed themselves.
       Cleared the moment the drawer is opened. */
    /* display scoped so the `hidden` attribute still works — see
       .plate-attention-flag in main.css for why. */
    .settings-notif-dot {
      position: absolute; top: 1px; right: 1px;
      width: 12px; height: 12px; border-radius: 50%;
      background: #FF3B30;
      border: 2px solid var(--card);
      display: none;
    }
    .settings-notif-dot.show { display: block; }

    /* Drill-down dots: the gear icon says "something's new somewhere";
       these say specifically where. Tab dots sit inline next to the tab
       label (part of the flex row, so they just nudge the label over
       slightly); accordion dots are absolutely positioned so they don't
       disturb the existing label/chevron space-between layout. Each
       clears independently — tab dot on switching to that tab, accordion
       dot on opening that specific accordion — so the trail can be
       followed all the way to the actual new content. */
    .tab-notif-dot {
      display: none;
      width: 7px; height: 7px; border-radius: 50%;
      background: #FF3B30;
      margin-left: 4px;
      vertical-align: super;
    }
    .tab-notif-dot.show { display: inline-block; }
    .accordion-notif-dot {
      display: none;
      position: absolute;
      top: 8px; right: 34px;
      width: 8px; height: 8px; border-radius: 50%;
      background: #FF3B30;
    }
    .accordion-notif-dot.show { display: block; }
    /* End of the trail: sits on the specific badge tile that's newly
       unlocked, inside the badges grid. Clears when the badges accordion
       is opened. */
    .badge-notif-dot {
      display: none;
      position: absolute;
      top: 6px; right: 6px;
      width: 8px; height: 8px; border-radius: 50%;
      background: #FF3B30;
      border: 2px solid var(--card);
      z-index: 1;
    }
    .badge-notif-dot.show { display: block; }
    .back-to-top-btn {
      position: fixed; right: 18px; bottom: 22px; z-index: 4;
      width: 46px; height: 46px; border-radius: 50%;
      border: none; background: var(--card); box-shadow: 0 4px 14px var(--shadow);
      display: flex; align-items: center; justify-content: center;
      color: var(--ink);
      font-size: 1.2rem; cursor: pointer; padding: 0;
      opacity: 0; visibility: hidden; transform: translateY(8px) scale(0.9);
      transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
    }
    .back-to-top-btn.visible { opacity: 1; visibility: visible; transform: translateY(0) scale(1); }
    .back-to-top-btn:active { transform: scale(0.9); }
    @media (prefers-reduced-motion: reduce) { .back-to-top-btn { transition: opacity 0.15s linear; } }
    .hamburger-icon {
      width: 18px; height: 14px;
      display: flex; flex-direction: column; justify-content: space-between;
    }
    .hamburger-icon span {
      display: block; width: 100%; height: 3px; background-color: var(--ink-soft);
      border-radius: 4px; transition: background-color 0.2s;
    }
    body.theme-contrast .hamburger-icon span { background-color: var(--ink); }
    body.theme-contrast-light .hamburger-icon span { background-color: var(--ink); }
    
    .greeting-card {
      background: var(--card);
      border-radius: var(--radius-lg);
      padding: 8px 16px;
      box-shadow: 0 8px 24px var(--shadow);
      display: flex; align-items: center; gap: 12px;
      margin-top: 14px;
      margin-bottom: 8px;
      transition: padding 0.25s ease, margin 0.25s ease;
    }
    /* Once the person is actively logging (Step 1 answered), the greeting
       card tightens its own padding/margin a touch to save vertical space —
       but the mascot and text stay full size (previously shrank to 34px /
       0.78rem here, which read as too small to comfortably read). */
    .greeting-card.condensed {
      padding: 5px 14px; margin-top: 10px; margin-bottom: 6px;
    }
    .greeting-mascot { 
      width: 60px; 
      flex: 0 0 auto; 
      height: 60px; 
      cursor: pointer;
      user-select: none;
      -webkit-user-select: none;
      transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    }
    .mascot-avatar-wrap {
      position: relative;
      flex: 0 0 auto;
      width: 60px;
      height: 60px;
      /* Same flicker fix as .go-btn above — the mascot SVG here
         redraws often (idle animation, costume swaps, the confetti
         canvas layered on top), which was likely causing the same
         repeated GPU-layer promotion/demotion flip-flop and seam
         flicker. Forcing a stable layer up front stops it. */
      transform: translateZ(0);
      backface-visibility: hidden;
    }
    /* Confined to exactly the avatar circle (not the corner badges, which
       poke out past this 60x60 box) — used only during the dance easter
       egg, drawn/cleared entirely in JS via startMascotConfetti(). */
    .mascot-confetti-canvas {
      position: absolute;
      top: 0; left: 0;
      width: 60px; height: 60px;
      border-radius: 50%;
      pointer-events: none;
      z-index: 1;
    }

    /* Dance easter egg: a disco ball drops into the top-left corner and
       stays there for the whole dance — the plain 🪩 emoji, not animated
       (no spin), with two small twinkling sparkle glints alongside it for
       a bit of "catching the light" motion, plus a very small continuous
       bounce (starts once the drop-in settles, via animation-delay, so
       the two don't stack awkwardly on top of each other). shown/hidden
       via the "show" class from JS, kept in sync with the dance's own
       duration. */
    .mascot-disco-ball {
      position: absolute;
      top: -6px; left: -6px;
      width: 20px; height: 20px;
      display: none;
      opacity: 0;
      transform: translateY(-40px) scale(0.5);
      z-index: 2;
    }
    .mascot-disco-ball.show {
      display: block;
      animation: discoDropIn 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    }
    .mascot-disco-ball-emoji {
      display: block;
      font-size: 1.05rem;
      line-height: 1;
      filter: drop-shadow(0 2px 3px rgba(0,0,0,0.35));
      animation: discoIdleBounce 1.3s ease-in-out 0.6s infinite;
    }
    .mascot-disco-ball .disco-sparkle {
      position: absolute;
      width: 3px; height: 3px;
      border-radius: 50%;
      background: #fff;
      box-shadow: 0 0 4px 1px rgba(255,255,255,0.95);
      animation: discoSparkle 1s ease-in-out infinite;
    }
    .mascot-disco-ball .disco-sparkle:nth-child(2) { top: 1px; right: -3px; animation-delay: 0s; }
    .mascot-disco-ball .disco-sparkle:nth-child(3) { bottom: 2px; left: -2px; animation-delay: 0.45s; }
    @keyframes discoDropIn {
      0%   { opacity: 0; transform: translateY(-40px) scale(0.5); }
      60%  { opacity: 1; transform: translateY(3px) scale(1.08); }
      80%  { transform: translateY(-2px) scale(0.97); }
      100% { opacity: 1; transform: translateY(0) scale(1); }
    }
    @keyframes discoIdleBounce {
      0%, 100% { transform: translateY(0); }
      50% { transform: translateY(-1.5px); }
    }
    @keyframes discoSparkle {
      0%, 100% { opacity: 0; transform: scale(0.5); }
      50% { opacity: 1; transform: scale(1.2); }
    }
    @media (prefers-reduced-motion: reduce) {
      .mascot-disco-ball.show { animation: none; opacity: 1; transform: translateY(0) scale(1); }
      .mascot-disco-ball-emoji { animation: none; }
      .mascot-disco-ball .disco-sparkle { animation: none; opacity: 0; }
    }
    
    .greeting-mascot:not(.interacting) {
      animation: playfulBounce 0.5s ease-in-out infinite alternate;
    }

    .greeting-mascot.sprout-spin-action {
      animation: spinYOnce 0.65s cubic-bezier(0.4, 0, 0.2, 1);
    }
    .greeting-mascot.sprout-bounce-action {
      animation: quickJump 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.5) 2;
    }
    .greeting-mascot.sprout-shiver-action {
      animation: microTremble 0.1s linear infinite;
    }
    .greeting-mascot.sprout-fart-action {
      animation: blastBack 0.45s cubic-bezier(0.36, 0.07, 0.19, 0.97) 1;
    }
    /* Easter egg: tap Sprout rapidly (6 taps in a row) and it breaks into a
       little dance instead of the usual single-tap reaction. Iteration
       count matches the 9s the dialogue stays up in triggerMascotDance()
       below, so Sprout keeps moving — and the confined confetti in its
       circle keeps falling — for as long as the text is on screen, rather
       than any of the three cutting off before the others. */
    .greeting-mascot.sprout-dance-action {
      animation: discoWiggle 0.5s ease-in-out 18;
    }
    @keyframes discoWiggle {
      0%, 100% { transform: translateY(0) rotate(0deg) scale(1); }
      20% { transform: translateY(-8px) rotate(-14deg) scale(1.08); }
      40% { transform: translateY(0) rotate(10deg) scale(0.96); }
      60% { transform: translateY(-10px) rotate(-10deg) scale(1.1); }
      80% { transform: translateY(0) rotate(14deg) scale(0.96); }
    }

    @keyframes spinYOnce {
      0% { transform: rotate(0deg) scale(1); }
      50% { transform: rotate(180deg) scale(1.25); }
      100% { transform: rotate(360deg) scale(1); }
    }
    @keyframes quickJump {
      0%, 100% { transform: translateY(0) scale(1); }
      50% { transform: translateY(-16px) scale(1.1); }
    }
    @keyframes microTremble {
      0%, 100% { transform: translate(0, 0) scale(1); }
      25% { transform: translate(-1.5px, 1px); }
      75% { transform: translate(1.5px, -1px); }
    }
    @keyframes blastBack {
      0% { transform: scale(1) translateX(0); }
      20% { transform: scale(1.15) translateX(8px) rotate(8deg); }
      50% { transform: scale(0.9) translateX(-12px) rotate(-4deg); }
      100% { transform: scale(1) translateX(0); }
    }
    @keyframes playfulBounce {
      0% { transform: translateY(0); }
      100% { transform: translateY(-6px); }
    }

    /* Rex (welcome-banner presence) removed — he now lives in the
       "Rex's Garden" section title instead (see .rex-garden-title-icon
       further down this file, and the matching markup in index.html
       above .garden-card). This block used to hold
       .rex-group/.rex-avatar/rexHappyHop and their
       shared-bounce-with-Sprout animation; all removed along with
       js/rex.js (deleted) and the matching resync logic in
       js/app.js's resetMascotToIdle(). See docs/NOTES.md for the full
       history of his time in this spot if ever revisiting it. */

    .greeting-text-wrap {
      flex: 1 1 auto;
      min-width: 0;
      display: flex;
      flex-direction: column;
      gap: 6px;
    }
    /* Fixed px base (not rem) so this stays consistent across browsers —
       Chrome for Android scales rem/em text according to the device's
       system "Font size" accessibility setting, but Firefox for Android
       doesn't apply that same scaling, which is why the same rem-based
       size looked fine in Firefox and oversized in Chrome. Using px here
       sidesteps that inconsistency; --font-scale still lets the app's own
       in-app accessibility control resize it on purpose. */
    .greeting-text {
      font-size: calc(15px * var(--font-scale));
      font-weight: 600;
      line-height: 1.35;
    }

    /* Streak badge: mirrors .level-badge-btn below, but sits on the top-right
       corner of the mascot avatar instead of bottom-right, so the two never
       overlap. Only shown once there's a real streak (2+ days). Amber ring
       + gentle pulse when today isn't logged yet and the streak needs a
       save; a small ice tint once the one-day freeze has been spent. */
    .streak-badge-btn:not([hidden]) { display: flex; }
    .streak-badge-btn {
      position: absolute;
      top: -10px;
      right: -10px;
      width: 23px;
      height: 23px;
      border-radius: 50%;
      border: 1.5px solid var(--sun);
      background: var(--card);
      box-shadow: 0 1px 4px var(--shadow);
      /* NOT a plain `display: flex` — see .plate-attention-flag's comment.
         This element is hidden via the `hidden` attribute in JS, and a
         plain display here would silently override it (hidden works via
         the UA stylesheet, the weakest cascade source). */
      align-items: center;
      justify-content: center;
      cursor: pointer;
      padding: 0;
      gap: 0;
      transition: transform 0.15s ease;
      z-index: 2;
    }
    .streak-badge-btn:hover { transform: scale(1.08); }
    .streak-badge-btn:active { transform: scale(0.96); }
    .streak-badge-icon { font-size: 0.62rem; line-height: 1; }
    .streak-badge-num {
      font-family: 'Baloo 2', sans-serif;
      font-weight: 800;
      font-size: 0.62rem;
      line-height: 1;
      color: var(--ink);
    }
    .streak-badge-btn.at-risk {
      border-color: #FF6D00;
      box-shadow: 0 0 0 2px rgba(255,109,0,0.3), 0 1px 4px var(--shadow);
      animation: streakBadgePulse 1.8s ease-in-out infinite;
    }
    @keyframes streakBadgePulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.6; }
    }
    @media (prefers-reduced-motion: reduce) {
      .streak-badge-btn.at-risk { animation: none; }
    }

    /* Achievement button: a compact numbered badge overlaid on the mascot avatar.
       Pushed further into the corner than a badge normally would be — it used
       to overlap deep enough into the mascot's own tap area (helped along by
       its own expanded invisible hit zone below) that Sprout became genuinely
       hard to tap, especially for the rapid-tap dance easter egg. */
    .level-badge-btn {
      position: absolute;
      bottom: -14px;
      right: -14px;
      width: 23px;
      height: 23px;
      border-radius: 50%;
      border: 1.5px solid var(--mint);
      background: var(--card);
      box-shadow: 0 1px 4px var(--shadow);
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      padding: 0;
      transition: transform 0.15s ease;
      z-index: 2;
    }
    .level-badge-btn:hover { transform: scale(1.08); }
    .level-badge-btn:active { transform: scale(0.96); }
    .level-badge-icon {
      display: none;
    }
    .level-badge-num {
      position: static;
      min-width: 0;
      height: auto;
      padding: 0;
      border: none;
      background: transparent;
      color: var(--ink);
      font-family: 'Baloo 2', sans-serif;
      font-weight: 800;
      font-size: 0.8rem;
      line-height: 1;
    }
    .level-badge-btn.maxed {
      border-color: var(--sun);
      background: var(--card);
      box-shadow: 0 0 0 2px rgba(255, 201, 60, 0.35), 0 1px 4px var(--shadow);
    }
    .level-badge-btn.maxed .level-badge-num {
      color: var(--ink);
    }
    /* Achievement card shown above the "Who's eating?" panel in the "Make it yours" drawer */
    .drawer-level-badge {
      width: 100%;
      display: flex;
      align-items: center;
      gap: 10px;
      background: var(--bg);
      border: 1.5px solid var(--mint);
      border-radius: var(--radius-md);
      padding: 10px 12px;
      font-family: inherit;
      text-align: left;
      cursor: pointer;
      transition: transform 0.15s ease;
    }
    .drawer-level-badge:hover { transform: translateY(-1px); }
    .drawer-level-badge:active { transform: scale(0.98); }
    .drawer-level-badge.maxed {
      border-color: var(--sun);
      background: rgba(255, 201, 60, 0.14);
    }
    .drawer-level-badge-icon-wrap {
      flex: 0 0 auto;
      width: 36px;
      height: 36px;
      border-radius: 50%;
      background: rgba(46, 196, 182, 0.16);
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .drawer-level-badge.maxed .drawer-level-badge-icon-wrap {
      background: rgba(255, 201, 60, 0.22);
    }
    .drawer-level-badge-icon {
      font-size: 1.15rem;
      line-height: 1;
    }
    .drawer-level-badge-text {
      flex: 1 1 auto;
      min-width: 0;
      display: flex;
      flex-direction: column;
      gap: 1px;
    }
    .drawer-level-badge-eyebrow {
      font-size: 0.62rem;
      font-weight: 800;
      text-transform: uppercase;
      letter-spacing: 0.06em;
      color: var(--mint);
    }
    .drawer-level-badge.maxed .drawer-level-badge-eyebrow { color: #C98A00; }
    .drawer-level-badge-title {
      font-weight: 800;
      font-size: 0.9rem;
      color: var(--ink);
    }
    .drawer-level-badge-chevron {
      flex: 0 0 auto;
      font-size: 1.2rem;
      color: var(--ink-soft);
    }

    .level-info-icon {
      font-size: 2.6rem;
      width: 72px; height: 72px;
      margin: 0 auto 10px;
      display: flex; align-items: center; justify-content: center;
      background: rgba(46, 196, 182, 0.12);
      border-radius: 50%;
    }
    .level-info-badge-strip {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: 6px;
      margin-top: 14px;
    }
    .level-info-badge-strip span {
      font-size: 1.3rem;
      background: var(--bg);
      border-radius: 50%;
      width: 34px; height: 34px;
      display: flex; align-items: center; justify-content: center;
    }
    body.theme-night .level-info-badge-strip span { background: #1B2140; }
    .level-info-badge-strip .level-info-badge-empty {
      font-size: 0.78rem;
      font-weight: 700;
      color: var(--ink-soft);
      background: none;
      width: auto; height: auto;
    }

    .caregiver-card {
      background: var(--card);
      border-radius: var(--radius-lg);
      padding: 30px 24px;
      box-shadow: 0 12px 32px var(--shadow);
      text-align: center;
      margin-bottom: 18px;
      display: none;
    }
    .caregiver-card.show { display: block; animation: pop .5s ease; }
    .caregiver-badge {
      display: inline-block; background: #E0F2FE; color: #0369A1;
      padding: 6px 14px; border-radius: 20px; font-weight: 800; font-size: 0.85rem;
      text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 16px;
    }
    .caregiver-event-tag {
      font-size: 1.15rem; font-weight: 800; color: #0369A1; margin: 4px 0 16px;
      font-family: 'Baloo 2', sans-serif; display: none; background: #F0F9FF;
      padding: 10px 14px; border-radius: 16px; border: 2px dashed #BAE6FD;
    }
    .caregiver-timestamp {
      font-size: 0.85rem; font-weight: 700; color: #0284C7; margin-bottom: 6px;
    }
    .caregiver-total-box {
      background: #F0F9FF; border: 4px dashed #0284C7; border-radius: 24px;
      padding: 24px; margin: 20px 0;
    }
    .caregiver-num {
      font-family: 'Baloo 2', sans-serif; font-size: 4rem; font-weight: 800;
      color: #0369A1; line-height: 1;
    }
    .caregiver-sub { font-weight: 700; color: #0284C7; font-size: 1.1rem; margin-top: 4px; }
    
    .caregiver-breakdown-title {
      font-family: 'Baloo 2', sans-serif; font-size: 1.15rem; text-align: left;
      margin: 20px 0 10px; color: #0369A1; font-weight: 800;
    }
    .caregiver-list {
      display: flex; flex-direction: column; gap: 8px; margin-bottom: 24px; text-align: left;
    }
    .caregiver-item {
      display: flex; align-items: center; justify-content: space-between;
      background: #F0F9FF; padding: 10px 14px; border-radius: 14px;
      font-size: 0.95rem; font-weight: 700; border: 1px solid #BAE6FD;
    }
    .caregiver-item-details { color: #0284C7; font-size: 0.8rem; font-weight: 600; margin-top: 2px; }
    .caregiver-item-name-row { display: flex; align-items: center; gap: 6px; }
    .caregiver-item-icon { font-size: 1rem; flex-shrink: 0; }
    .caregiver-edited-tag {
      display: inline-block; background: #FEF3C7; color: #92400E;
      font-size: 0.68rem; font-weight: 800; padding: 2px 7px; border-radius: 20px;
      margin-top: 3px;
    }
    .caregiver-disclaimer {
      background: #FEF9C3; border: 2px dashed #FBBF24; color: #713F12;
      font-size: 0.78rem; font-weight: 700; line-height: 1.4; text-align: left;
      padding: 12px 14px; border-radius: 14px; margin-bottom: 18px;
    }

    .return-btn {
      width: 100%; border: none; padding: 14px; background: #0284C7; color: white;
      font-family: 'Baloo 2', sans-serif; font-weight: 700; font-size: 1.1rem;
      border-radius: var(--radius-md); cursor: pointer; transition: background 0.2s;
    }
    .return-btn:hover { background: #0284C7; }

    .calc-card {
      background: var(--card);
      border-radius: var(--radius-lg);
      padding: 18px;
      box-shadow: 0 10px 28px var(--shadow);
      margin-bottom: 12px;
      position: relative;
      overflow: hidden;
    }
    .calc-card.hidden { display: none; }
    
    .soda-fizz-overlay-container {
      position: absolute;
      inset: 0;
      pointer-events: none;
      z-index: 1;
      overflow: hidden;
    }
    .calc-card-interactive-content-wrap {
      position: relative;
      z-index: 2;
    }

    .fizz-bubble {
      position: absolute;
      bottom: -20px;
      background: rgba(63, 169, 245, 0.18);
      border: 1.5px solid rgba(255, 255, 255, 0.4);
      border-radius: 50%;
      animation: upwardFloat linear infinite;
    }
    body.theme-night .fizz-bubble {
      background: rgba(46, 196, 182, 0.15);
    }
    /* Low Luminescence: same mint-tint approach as .afb-bubble above,
       low-lume's own --mint RGB in place of theme-night's. */
    body.theme-low-lume .fizz-bubble {
      background: rgba(79, 209, 197, 0.15);
    }
    body.theme-rainbow .fizz-bubble {
      background: rgba(233, 30, 99, 0.12);
    }

    @keyframes upwardFloat {
      0% {
        transform: translateY(0) translateX(0);
        opacity: 0;
      }
      10% { opacity: 0.8; }
      90% { opacity: 0.8; }
      100% {
        transform: translateY(-420px) translateX(var(--bubble-drift, 15px));
        opacity: 0;
      }
    }

    .field { margin-bottom: 14px; transition: opacity 0.2s ease; }
    .field label {
      display: block; font-weight: 800; font-size: 0.95rem; margin-bottom: 6px;
      color: var(--ink);
    }
    .field .hint { font-weight: 600; color: var(--ink-soft); font-size: 0.8rem; }
    .hint.hint-collapsible {
      margin: -2px 0 8px;
    }
    /* .drink-branching-box / .drink-branch-title removed 2026-08 — dead
       CSS. The live drink-type buttons render directly inside
       .drink-branch-buttons (still used, kept below); these two were
       styles for an outer wrapper/heading that no longer appears in the
       markup (verified via cross-reference audit). */
        .section-divider {
      font-family: 'Baloo 2', sans-serif; font-size: 1.15rem; font-weight: 800;
      color: var(--sky); margin: 6px 0 12px; padding-bottom: 4px; border-bottom: 2px dashed var(--bg2);
      display: flex; justify-content: space-between; align-items: center;
    }
    /* Lets the Rex icon (added 31 Aug 2026, see index.html's own
       comment above .rex-garden-title-icon) sit vertically centered
       next to the "Rex's Garden" text rather than aligning to the
       text baseline, which would look odd for an image this size. */
    .section-divider span:first-child {
      display: flex; align-items: center; gap: 6px;
    }
    .rex-garden-title-icon {
      /* Height-constrained, not width. Artwork swapped 31 Aug 2026 —
         new upload is a head-and-shoulders crop (aspect ~0.83 w/h),
         replacing the previous full sitting-body art (~0.47 w/h) that
         had been here since v1.4.0/v1.4.1. Height-constraint means
         this swap didn't need a size adjustment: at height:48px the
         new crop is proportionately wider than the old one (since
         it's less tall relative to its width), but stays comfortably
         clear of both "Rex's Garden" text and the star pill — checked
         at both a normal (420px) and the narrowest realistic (320px)
         viewport width before shipping. Same cropping technique as
         every prior Rex asset (tightened the SVG's own viewBox to his
         real silhouette, confirmed genuinely transparent, C2PA
         metadata stripped). */
      height: 48px;
      width: auto;
      display: block;
    }

    /* Sprout Score pill — lives in the Garden card's section-divider (which
       is already display:flex; justify-content:space-between), so no extra
       layout rules needed here beyond the pill's own look. Changed from a
       <span> to a <button> so it can open the score breakdown sheet —
       needs the usual button resets (border, background, font, cursor)
       since it now inherits browser button chrome by default. Uses a
       plain ⭐ emoji, not the Sprout-holding-star illustration — that
       artwork is reserved for the breakdown sheet only, so the small
       pill stays a simple star. */
    .sprout-score-pill {
      position: relative;
      display: inline-flex; align-items: center; gap: 4px;
      font-family: 'Nunito', sans-serif; font-size: 0.82rem; font-weight: 800;
      color: var(--sun-ink, #8A6100);
      background: rgba(255, 201, 60, 0.14);
      border: 1px solid rgba(255, 201, 60, 0.35);
      border-radius: 999px;
      padding: 4px 10px;
      white-space: nowrap;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .sprout-score-pill:active { transform: scale(0.96); }
    .sprout-score-pop {
      position: absolute; top: -10px; right: -6px;
      font-size: 0.68rem; font-weight: 800; color: var(--mint, #2EC4B6);
      background: rgba(46, 196, 182, 0.18);
      border-radius: 999px; padding: 1px 6px;
      opacity: 0; pointer-events: none;
    }
    .sprout-score-pop-anim {
      animation: sproutScorePop 1.4s ease-out;
    }
    @keyframes sproutScorePop {
      0%   { opacity: 0; transform: translateY(4px) scale(0.85); }
      15%  { opacity: 1; transform: translateY(-2px) scale(1); }
      75%  { opacity: 1; transform: translateY(-8px) scale(1); }
      100% { opacity: 0; transform: translateY(-14px) scale(0.9); }
    }
    @media (prefers-reduced-motion: reduce) {
      .sprout-score-pop-anim { animation: none; opacity: 1; }
    }

    /* Sprout Score breakdown sheet. Reuses .modal-overlay/.modal-content
       from the shared modal system so it inherits the same open/close
       mechanics (display:flex/none) and mobile-safe sizing (max-height,
       overflow-y) as every other modal in the app; everything below is
       purely this sheet's own look. Presented bottom-sheet style via
       align-items:flex-end on this specific overlay, rather than the
       centered dialog most other modals use, since a bottom sheet reads
       better for a "tap a chip, see more" pattern than a centered card. */
    .sprout-score-sheet-overlay { align-items: flex-end; padding: 0; }
    .sprout-score-sheet-content {
      max-width: 440px;
      border-radius: 26px 26px 0 0;
      padding: 18px 20px 26px;
      animation: sproutScoreSheetUp 0.28s ease-out;
    }
    @keyframes sproutScoreSheetUp {
      from { transform: translateY(100%); }
      to { transform: translateY(0); }
    }
    @media (prefers-reduced-motion: reduce) {
      .sprout-score-sheet-content { animation: none; }
    }
    .sprout-score-sheet-content .sheet-handle {
      width: 40px; height: 5px; border-radius: 999px;
      background: var(--dv-line, rgba(255,255,255,0.18));
      opacity: 0.5;
      margin: 0 auto 14px;
    }
    .sprout-score-sheet-sprout-wrap {
      /* `justify-content: center` was still centering the whole
         Sprout+Rex row as a single flex block (confirmed live 1 Sep
         2026 via screenshots — Sprout visibly shifted ~3-4px depending
         on which Rex costume was selected, since a wider costume
         changes the row's total content width, which changes where a
         centered block's midpoint falls). This is exactly the failure
         mode an earlier version of this comment already described and
         intended to avoid, but the fix was never actually applied to
         the CSS below it — the bug persisted until Jon caught it live
         with two side-by-side screenshots.

         Real fix: Sprout's glow is now positioned independently at a
         fixed horizontal center within this wrapper
         (.sprout-score-sheet-glow uses left: 50%; margin-left: -48px,
         half its own 96px width — a self-contained centering method
         that has nothing to do with Rex or flex layout at all), and
         Rex is positioned relative to Sprout's own fixed edge rather
         than being a flex sibling whose width affects a shared center
         point. Sprout's position is now determined ENTIRELY by his own
         box and this wrapper's width — nothing about Rex, whatever
         costume he's wearing, can ever move Sprout again. This wrapper
         itself no longer uses flex for horizontal layout at all
         (position: relative + absolutely-positioned children instead),
         since flex's shared-space model is exactly what caused this
         bug in the first place. */
      position: relative;
      height: 96px;
      margin-bottom: 6px;
    }
    .sprout-score-sheet-glow {
      /* Absolutely positioned and self-centered (left: 50% + negative
         margin-left of half its own width) rather than a flex child —
         this is what actually makes Sprout's position independent of
         Rex's width. `left: 50%` is 50% of THIS GLOW'S OWN CONTAINING
         BLOCK (.sprout-score-sheet-sprout-wrap, since that's
         position:relative), not 50% of some shared flex space that
         Rex's width could ever influence. */
      position: absolute;
      top: 0;
      left: 50%;
      margin-left: -48px; /* half of the 96px width below, to truly center rather than left-align at the 50% mark */
      width: 96px; height: 96px;
      display: flex; align-items: center; justify-content: center;
    }
    .sprout-score-sheet-glow::before {
      content: "";
      position: absolute; inset: -12px;
      border-radius: 50%;
      background: radial-gradient(circle, rgba(255,201,60,0.35), transparent 70%);
      animation: sproutScoreGlowPulse 2.4s ease-in-out infinite;
    }
    @keyframes sproutScoreGlowPulse {
      0%, 100% { opacity: 0.6; transform: scale(0.96); }
      50% { opacity: 1; transform: scale(1.04); }
    }
    @media (prefers-reduced-motion: reduce) {
      .sprout-score-sheet-glow::before { animation: none; opacity: 0.85; }
    }
    .sprout-score-sheet-img {
      width: 96px; height: 96px; position: relative; z-index: 1;
      filter: drop-shadow(0 6px 14px var(--shadow));
    }
    /* Rex sits OUTSIDE the glow, in .sprout-score-sheet-sprout-wrap's
       own flex row, immediately to Sprout's right — NOT a second child
       inside the glow (see .sprout-score-sheet-sprout-wrap's own
       comment for why that approach puts the glow off-center). Sized
       to 22px — small enough to read as a secondary character rather
       than competing with Sprout for visual weight. Full-body artwork
       (images/rex-fullbody.svg, a dedicated copy separate from
       images/rex.svg's head-and-shoulders crop used in the
       garden-title spot). Vertical alignment is a margin-top offset,
       not a simple box-edge match — see .sprout-score-sheet-rex-img's
       own comment for the full reasoning and the alpha-channel
       measurements it's based on. */
    /* .sprout-score-sheet-rex-frame is the actual flex item now (Rex's
       <img> moved one level deeper — see index.html's comment on this
       markup for the full history of why). This wrapper carries the
       margin-top/margin-left that used to live directly on
       .sprout-score-sheet-rex-img (that offset's own reasoning — the
       alpha-channel measurements, Sprout's visible-vs-box-bottom
       mismatch — is unchanged, just documented on
       .sprout-score-sheet-rex-img below where the underlying numbers
       still apply). width/height match Rex's own rendered size exactly
       (22px / auto, same aspect ratio) since this wrapper's only
       purpose is to be "Rex's box" for the tail to size itself against.
       position: relative makes this a valid anchor for the
       absolutely-positioned tail wrapper inside it — the bug this
       replaced was trying to anchor against .sprout-score-sheet-sprout-wrap
       instead, which had no position:relative of its own and wasn't
       sized to Rex specifically anyway. */
    .sprout-score-sheet-rex-frame {
      /* Positioned absolutely, anchored to the glow's own right edge
         (left: 50% + half the glow's 96px width + the small gap/margin
         that used to come from flex's margin-left: -8px) rather than
         flex flow — this is the change that actually stops Rex's own
         width (which varies per costume — see setRexCostume() in
         js/sprout-score.js) from ever being able to shift Sprout's
         position again. Sprout's glow computes its own center purely
         from ITS OWN box; Rex sits at a fixed offset from that same
         fixed point regardless of how wide Rex himself renders. */
      position: absolute;
      top: 35.1px;
      left: calc(50% + 40px); /* 48px (half glow width) - 8px (the old margin-left gap, kept identical for the same visual spacing) */
      width: 22px; height: auto;
    }
    /* #rexTailWag is positioned/sized/animated entirely by
       js/sprout-score.js (see REX_TAIL_WAG_FRAMES and startRexTailWag())
       — each of the 7 steps sets left/top/width/height/transform-origin/
       rotation directly via inline style, since the animation needs to
       swap which <img> src is showing (tail-a vs tail-b) at the
       midpoint, which a CSS-only @keyframes animation cannot do (it can
       only animate properties of ONE element/image, not swap the image
       itself). This CSS rule only sets what's true at every step:
       position:absolute so left/top are meaningful, and z-index:0 to
       keep it behind .sprout-score-sheet-rex-img (z-index:1) so the
       base tucks behind Rex's hip at every step of the sweep. */
    #rexTailWag {
      position: absolute;
      z-index: 0;
      pointer-events: none;
    }
    #rexTailWag img {
      width: 100%; height: 100%; display: block;
    }
    .sprout-score-sheet-rex-img {
      /* margin-top aligns Rex's visible bottom with Sprout's visible
         bottom — NOT with Sprout's box bottom, which is a different
         point. images/sprout-star.svg has real trailing whitespace
         below the drawn artwork: the visible content only extends to
         ~85.3% of the canvas height (measured via alpha-channel
         inspection: content bottom at row 853 of a 1000px-tall
         render). Matching box edges therefore doesn't match visible
         edges — Sprout's box bottom sits well below where he's
         actually visibly standing. images/rex-fullbody.svg has no
         such padding (visible content reaches 99.2% of its canvas),
         so its box bottom and visible bottom are effectively the same
         point.

         The offset: at Sprout's 96px rendered box height, his visible
         bottom sits at 96 * 0.853 ≈ 81.9px from the top of his box.
         Rex at 22px wide renders ≈46.8px tall (his aspect ratio,
         ~0.47 w/h). Top-aligned, Rex's own bottom would land at
         46.8px — 35.1px short of Sprout's visible bottom — hence
         margin-top: 35.1px pushes his bottom down to match.

         General rule for multi-character SVG alignment in this
         project: check with alpha-channel bounding-box inspection
         whether an asset's box edge matches its visible content edge
         before aligning by box coordinates alone —
         getBoundingClientRect() only reports the box, and a box match
         can still look visibly wrong if either asset has invisible
         padding.

         NOTE (1 Sep 2026): margin-top/margin-left themselves moved up
         to .sprout-score-sheet-rex-frame, the new wrapper one level up
         (see that rule above and index.html's comment on this markup),
         since the tail needs to share Rex's exact positioned box and
         two separate elements each carrying their own copy of the same
         offset is a duplication bug waiting to happen if either one is
         ever tuned without the other. The reasoning/measurements above
         are unchanged and still the source of truth for the number —
         it just lives one element higher now. */
      width: 22px; height: auto; position: relative; z-index: 1;
      filter: drop-shadow(0 6px 14px var(--shadow));
    }
    /* Rex costume picker (added 1 Sep 2026, rebuilt 2 Sep 2026 into a
       horizontally-scrollable strip of real costume thumbnails — see
       index.html's own comment on this markup for the full history of
       why the two earlier versions, a native <select> and then a
       pill+popover menu, were each replaced). Designed for a growing
       list, not just today's seven costumes — Jon's plan is to keep
       adding more — so the strip scrolls horizontally rather than
       wrapping or growing the sheet's own height, and each thumbnail
       is a generic .rex-costume-thumb with no per-costume styling, so
       adding the 50th costume needs zero new CSS, only a new button
       element (see index.html) and a REX_COSTUMES entry in
       js/sprout-score.js.

       Visual language matches this file's other pill/card surfaces
       (rounded corners, soft tinted backgrounds, thin colored borders)
       rather than introducing a one-off style. */
    .sprout-score-sheet-rex-costume-picker {
      margin-top: 8px;
      margin-bottom: 4px;
      position: relative;
    }
    .rex-costume-strip {
      display: flex;
      gap: 10px;
      overflow-x: auto;
      /* Padding on the sides (not just between items) so the first and
         last thumbnails don't sit flush against the sheet's own edge —
         matches the sheet's own 20px side padding
         (.sprout-score-sheet-content) so the strip visually aligns
         with everything else in it. */
      padding: 4px 20px 8px;
      margin: 0 -20px; /* pulls the strip back out to the sheet's full width so its own padding above can still create edge breathing room without double-indenting */
      scroll-snap-type: x proximity;
      -webkit-overflow-scrolling: touch;
      /* No visible scrollbar at all (reworked 2 Sep 2026 — Jon: "that
         scrollbar under the costumes is a travesty"). A bare
         browser-default scroll track looked out of place against this
         app's own warm, rounded, colorful style regardless of how its
         thickness/color were tuned — this sandbox's own Chromium
         wasn't even rendering the earlier ::-webkit-scrollbar rules
         consistently with what Jon saw on a real Android device, which
         is reason enough not to keep tuning a scrollbar's appearance
         instead of just removing it. Scrollability itself is
         communicated instead by the edge-fade gradient below and, more
         importantly, by Captain Canine/Rex-o-naut etc. visibly peeking
         in from the right edge at rest — the same "there's more, keep
         scrolling" cue app store screenshots and story pickers use,
         needing no scrollbar affordance at all. */
      scrollbar-width: none; /* Firefox */
      -ms-overflow-style: none; /* old Edge/IE */
    }
    .rex-costume-strip::-webkit-scrollbar { display: none; } /* Chrome/Safari/Android WebView */
    /* Fade-to-transparent gradient over the strip's right edge, hinting
       there's more to scroll past what's visible — fades out entirely
       once scrolled near the end so it doesn't look like a stray edge
       once every costume has been seen. Purely decorative (no pointer-
       events), sits above the strip via z-index. */
    .rex-costume-picker-fade {
      position: absolute;
      top: 4px;
      right: 0;
      bottom: 8px;
      width: 28px;
      /* Fades to var(--card) — the sheet's own actual background
         (.modal-content { background: var(--card); }), not an invented
         color — so this genuinely blends into the sheet rather than
         showing as its own visible edge. */
      background: linear-gradient(90deg, transparent, var(--card) 85%);
      pointer-events: none;
      opacity: 1;
      transition: opacity 0.15s ease;
    }
    .rex-costume-picker-fade[hidden] { display: none; }

    /* Full-screen costume preview overlay (added 2 Sep 2026). Fixed to
       the viewport (not the sheet) so it genuinely covers the whole
       screen regardless of how far the sheet itself has scrolled —
       this is a step above the Sprout Score sheet, not scoped inside
       it, since the sheet is a bottom-anchored panel that doesn't
       itself fill the screen. z-index deliberately higher than this
       app's other modal layers (checked against .modal-overlay's own
       z-index before picking this value) so it can open from within
       the already-open Sprout Score sheet without being hidden behind
       it. */
    .rex-costume-preview {
      position: fixed;
      inset: 0;
      /* Must sit above .modal-overlay (z-index: 100, used by the
         Sprout Score sheet itself via .sprout-score-sheet-overlay) —
         confirmed by actually checking that rule rather than assuming
         a number, since this preview opens FROM WITHIN that already-open
         sheet and needs to render on top of it, not behind. */
      z-index: 110;
      background: rgba(10, 12, 26, 0.94);
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .rex-costume-preview[hidden] { display: none; }
    .rex-costume-preview-close {
      position: absolute;
      top: 16px; right: 16px;
      width: 36px; height: 36px;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.1);
      border: 1px solid rgba(255, 255, 255, 0.2);
      color: #fff;
      font-size: 1.1rem;
      line-height: 1;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .rex-costume-preview-arrow {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      width: 44px; height: 44px;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.1);
      border: 1px solid rgba(255, 255, 255, 0.2);
      color: #fff;
      font-size: 1.6rem;
      line-height: 1;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .rex-costume-preview-arrow:active { transform: translateY(-50%) scale(0.92); }
    .rex-costume-preview-arrow-left { left: 12px; }
    .rex-costume-preview-arrow-right { right: 12px; }
    /* Hidden (rather than disabled) at either end of the costume list
       — REX_COSTUMES has a fixed order and cycling doesn't wrap, so
       there's genuinely nothing to go back/forward to at the ends;
       hiding the arrow communicates that more directly than a merely
       greyed-out one still sitting there. */
    .rex-costume-preview-arrow[hidden] { display: none; }
    .rex-costume-preview-body {
      display: flex;
      flex-direction: column;
      align-items: center;
      max-width: 320px;
      padding: 0 24px;
    }
    .rex-costume-preview-name {
      font-family: 'Baloo 2', sans-serif;
      font-size: 1.2rem;
      font-weight: 800;
      color: var(--sun, #FFC93C);
      margin-bottom: 16px;
      text-align: center;
    }
    .rex-costume-preview-img {
      width: 200px;
      height: auto;
      max-height: 50vh;
      /* object-fit isn't needed here the way it might be for a fixed
         box — width:200px with height:auto lets each costume's own
         natural aspect ratio (see setRexCostume()'s height-matching
         logic elsewhere) determine its displayed height directly,
         same principle as the thumbnails, just at a much larger size. */
      filter: drop-shadow(0 10px 30px rgba(0, 0, 0, 0.5));
    }
    .rex-costume-preview-actions {
      display: flex;
      gap: 12px;
      margin-top: 28px;
    }
    .rex-costume-preview-cancel,
    .rex-costume-preview-select {
      padding: 12px 24px;
      border-radius: 999px;
      font-family: 'Nunito', sans-serif;
      font-weight: 700;
      font-size: 0.9rem;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      white-space: nowrap;
    }
    .rex-costume-preview-cancel {
      background: rgba(255, 255, 255, 0.08);
      border: 1px solid rgba(255, 255, 255, 0.25);
      color: #fff;
    }
    .rex-costume-preview-select {
      background: var(--sun, #FFC93C);
      border: none;
      color: #1a1a2e;
      font-weight: 800;
    }
    .rex-costume-preview-cancel:active,
    .rex-costume-preview-select:active { transform: scale(0.96); }

    .rex-costume-thumb {
      flex: 0 0 auto;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 4px;
      width: 60px;
      background: none;
      border: none;
      padding: 0;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      scroll-snap-align: start;
    }
    .rex-costume-thumb-img {
      width: 52px;
      height: 52px;
      border-radius: 14px;
      background: var(--card);
      border: 2px solid rgba(255, 201, 60, 0.25);
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: hidden;
      transition: border-color 0.15s ease, transform 0.1s ease;
    }
    .rex-costume-thumb-img img {
      /* Each costume's own art has a different natural aspect ratio
         (see setRexCostume()'s height-matching logic in
         js/sprout-score.js) — height:100% + width:auto here lets each
         thumbnail's image fill the circle vertically and center itself
         horizontally via the parent's flex centering, rather than
         stretching every costume to a fixed square and distorting the
         ones that aren't naturally square. */
      height: 100%;
      width: auto;
      max-width: none;
    }
    .rex-costume-thumb:active .rex-costume-thumb-img {
      transform: scale(0.94);
    }
    .rex-costume-thumb[aria-current="true"] .rex-costume-thumb-img {
      border-color: var(--sun, #FFC93C);
      background: rgba(255, 201, 60, 0.12);
    }
    .rex-costume-thumb-label {
      font-family: 'Nunito', sans-serif;
      font-size: 0.62rem;
      font-weight: 700;
      color: var(--ink);
      text-align: center;
      line-height: 1.15;
      /* Two lines max, ellipsis after — costume names vary a lot in
         length ("Rex" vs "Captain Canine") and the thumbnail column is
         narrow; wrapping to 2 lines handles most names cleanly without
         needing per-name width tuning, and anything longer still
         degrades gracefully rather than breaking the layout. */
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }
    .rex-costume-thumb[aria-current="true"] .rex-costume-thumb-label {
      color: var(--sun-ink, #8A6100);
    }
    .sprout-score-sheet-header {
      display: flex; align-items: center; justify-content: space-between;
      margin-bottom: 2px;
    }
    .sprout-score-sheet-header h3 {
      font-family: 'Baloo 2', sans-serif; font-size: 1.2rem; font-weight: 800;
      margin: 0; color: var(--ink);
    }
    .sprout-score-sheet-content .sheet-close {
      background: var(--dv-surface, rgba(255,255,255,0.08)); border: none;
      color: var(--ink-soft); width: 30px; height: 30px; border-radius: 50%;
      font-size: 1rem; cursor: pointer; flex-shrink: 0;
    }
    .sprout-score-sheet-total { text-align: center; margin: 2px 0 20px; }
    .sprout-score-sheet-bignum {
      font-family: 'Baloo 2', sans-serif;
      font-size: 2.6rem; font-weight: 800; line-height: 1;
      background: linear-gradient(135deg, var(--sun, #FFC93C), #ffe08a);
      -webkit-background-clip: text; background-clip: text; color: transparent;
    }
    .sprout-score-sheet-sub {
      font-size: 0.78rem; color: var(--ink-soft); font-weight: 600; margin-top: 6px;
    }
    .sprout-score-breakdown-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 4px; }
    .sprout-score-breakdown-row {
      position: relative;
      display: flex; align-items: center; gap: 12px;
      background: var(--dv-surface, rgba(255,255,255,0.045));
      border: 1px solid var(--dv-line, rgba(255,255,255,0.09));
      border-radius: 16px;
      padding: 12px 14px 12px 16px;
      overflow: hidden;
    }
    .sprout-score-breakdown-row::before {
      content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
    }
    .sprout-score-breakdown-row.log::before { background: var(--mint); }
    .sprout-score-breakdown-row.scan::before { background: var(--sky); }
    .sprout-score-breakdown-row.badge::before { background: var(--sun); }
    .sprout-score-breakdown-icon {
      width: 42px; height: 42px; border-radius: 13px;
      display: flex; align-items: center; justify-content: center;
      font-size: 1.35rem; flex-shrink: 0;
      box-shadow: inset 0 0 0 1px var(--dv-line, rgba(255,255,255,0.06));
    }
    .sprout-score-breakdown-icon.log { background: rgba(46,196,182,0.16); }
    .sprout-score-breakdown-icon.scan { background: rgba(63,169,245,0.16); }
    .sprout-score-breakdown-icon.badge { background: rgba(255,201,60,0.16); }
    .sprout-score-breakdown-main { flex: 1; min-width: 0; }
    .sprout-score-breakdown-label { font-weight: 800; font-size: 0.92rem; color: var(--ink); }
    .sprout-score-breakdown-sub { font-size: 0.7rem; color: var(--ink-soft); font-weight: 600; margin-top: 1px; }
    .sprout-score-breakdown-value { text-align: right; flex-shrink: 0; }
    .sprout-score-breakdown-points { font-weight: 800; font-size: 1.05rem; color: var(--sun-ink, #8A6100); }
    .sprout-score-breakdown-count { font-size: 0.66rem; color: var(--ink-soft); font-weight: 700; }
    .sprout-score-breakdown-bar-track {
      height: 5px; border-radius: 999px; background: var(--dv-inset, rgba(255,255,255,0.08));
      margin-top: 18px; overflow: hidden; display: flex;
    }
    .sprout-score-breakdown-bar-seg.log { background: var(--mint); }
    .sprout-score-breakdown-bar-seg.scan { background: var(--sky); }
    .sprout-score-breakdown-bar-seg.badge { background: var(--sun); }
    .sprout-score-breakdown-bar-caption {
      display: flex; justify-content: space-between; margin-top: 6px;
      font-size: 0.64rem; color: var(--ink-soft); font-weight: 700;
    }
    .sprout-score-sheet-footer-note {
      text-align: center; font-size: 0.7rem; color: var(--ink-soft); font-weight: 600;
      margin-top: 16px; line-height: 1.4;
    }
    .header-reset-btn {
      background: var(--bg); border: 2px solid var(--bg2); color: var(--ink-soft);
      font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 1.2rem;
      cursor: pointer; padding: 4px 9px; line-height: 1; border-radius: 10px;
      transition: all 0.2s, transform 0.4s ease;
    }
    
    @media (hover: hover) {
      .header-reset-btn:hover { color: var(--berry); border-color: var(--berry); }
    }
    .header-reset-btn:active { color: var(--berry); border-color: var(--berry); }

    @keyframes iconSpin {
      0% { transform: rotate(0deg); color: var(--berry); border-color: var(--berry); }
      100% { transform: rotate(-360deg); color: var(--ink-soft); border-color: var(--bg2); }
    }
    .header-reset-btn.spin-anim {
      animation: iconSpin 0.5s ease-out;
    }

    body.theme-night .header-reset-btn { background: #1B2140; border-color: #262C52; }

    .header-reset-btn.reset-text-btn {
      font-size: 0.76rem; font-weight: 800; padding: 6px 12px;
      letter-spacing: 0.01em;
      position: relative;
    }
    /* Touch-target fix: the visual pill is ~32px tall, below the 44px
       convention — risky for a destructive, no-confirmation action sitting
       right next to the section title. Invisible ::before hitbox trick:
       keeps the compact pill look, makes the actual tappable area 44px.
       TO REVERT: delete this ::before block. */
    .header-reset-btn.reset-text-btn::before {
      content: ''; position: absolute; top: 50%; left: 50%;
      width: 100%; height: 44px; min-width: 44px; transform: translate(-50%, -50%);
    }
    @keyframes textResetPulse {
      0% { color: var(--berry); border-color: var(--berry); transform: scale(1.08); }
      100% { color: var(--ink-soft); border-color: var(--bg2); transform: scale(1); }
    }
    .header-reset-btn.reset-text-btn.spin-anim {
      animation: textResetPulse 0.35s ease-out;
    }

    .fluid-toggle-zone {
      margin: 14px 0 4px;
    }

    /* Bumped from a small pill "invitation" to full CTA weight — a
       small pill read as an afterthought rather than a real option.
       Dashed border instead of solid keeps it visually distinct from
       the solid "Add food" button above (still secondary in the
       food+drink hierarchy) while matching it in size and padding, so
       it reads as a real second station rather than a footnote link.
       Bubbles and gradient fill kept from the original design — same
       glass-of-something-fizzy identity, just given room to be seen. */
    .add-fluid-trigger-btn {
      position: relative;
      overflow: hidden;
      isolation: isolate;
      width: 100%;
      background: linear-gradient(180deg,
        rgba(63, 169, 245, 0.16) 0%,
        rgba(63, 169, 245, 0.06) 55%,
        rgba(63, 169, 245, 0.13) 100%);
      border: 2px dashed rgba(63, 169, 245, 0.55);
      border-radius: 18px;
      color: var(--sky);
      padding: 16px 20px;
      font-family: 'Baloo 2', sans-serif;
      font-weight: 800;
      font-size: 1.05rem;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      text-decoration: none;
      transition: transform 0.18s ease, box-shadow 0.25s ease, border-color 0.25s ease;
      box-shadow: 0 2px 10px rgba(63, 169, 245, 0.14),
                  inset 0 1px 0 rgba(255, 255, 255, 0.35);
    }
    .afb-label { position: relative; z-index: 2; }
    body.theme-night .add-fluid-trigger-btn {
      background: linear-gradient(180deg,
        rgba(63, 169, 245, 0.24) 0%,
        rgba(63, 169, 245, 0.10) 55%,
        rgba(63, 169, 245, 0.20) 100%);
      border-color: rgba(63, 169, 245, 0.55);
      box-shadow: 0 2px 12px rgba(63, 169, 245, 0.20),
                  inset 0 1px 0 rgba(255, 255, 255, 0.12);
    }
    /* Low Luminescence: reuses theme-night's exact OPACITY/gradient
       structure (the 0.24/0.10/0.20 stops, the border/shadow alphas) —
       that part was hand-tuned for "translucent blue against a dark
       card" and low-lume shares that same problem — but swaps in
       low-lume's own --sky RGB (99,179,237) rather than theme-night's
       (99,169,245), since those two blues are close but not identical
       and using the theme's own accent colour is more correct than
       reusing a value that happens to look similar. Unlike the
       gold/green components fixed earlier in this pass (which needed
       different opacity/structure too, not just a different colour),
       this one only needed the colour swapped. */
    body.theme-low-lume .add-fluid-trigger-btn {
      background: linear-gradient(180deg,
        rgba(99, 179, 237, 0.24) 0%,
        rgba(99, 179, 237, 0.10) 55%,
        rgba(99, 179, 237, 0.20) 100%);
      border-color: rgba(99, 179, 237, 0.5);
      box-shadow: 0 2px 12px rgba(99, 179, 237, 0.18),
                  inset 0 1px 0 rgba(255, 255, 255, 0.10);
    }
    .add-fluid-trigger-btn:hover {
      border-color: var(--sky);
      box-shadow: 0 4px 16px rgba(63, 169, 245, 0.26),
                  inset 0 1px 0 rgba(255, 255, 255, 0.4);
      transform: translateY(-1px);
    }
    .add-fluid-trigger-btn:active { transform: scale(0.98); }
    .add-fluid-trigger-btn:focus-visible { outline: 3px solid var(--sun); outline-offset: 3px; }

    .add-fluid-subtext {
      text-align: center;
      font-size: 0.72rem;
      font-weight: 700;
      color: var(--ink-soft);
      margin: 8px 0 4px;
    }

    /* ---- bubbles, confined to the button ---------------------------- */
    .afb-fizz {
      position: absolute;
      inset: 0;
      z-index: 1;
      pointer-events: none;
      overflow: hidden;
      border-radius: inherit;
    }
    /* On a dark card a white rim reads as a bubble's highlight. On the light
       themes it disappears into the background, so those get a sky-tinted
       rim and a slightly stronger fill instead — same bubble, drawn for the
       surface it's actually sitting on. */
    .afb-bubble {
      position: absolute;
      bottom: -12px;
      border-radius: 50%;
      background: rgba(63, 169, 245, 0.22);
      border: 1.5px solid rgba(63, 169, 245, 0.55);
      animation: afbRise linear infinite;
      opacity: 0;
    }
    body.theme-night .afb-bubble {
      background: rgba(46, 196, 182, 0.20);
      border-color: rgba(255, 255, 255, 0.45);
    }
    /* Low Luminescence: same mint-tinted bubble + white-ish rim structure
       as theme-night, with low-lume's own --mint RGB (79,209,197) in
       place of theme-night's --mint (46,196,182) — same reasoning as
       .add-fluid-trigger-btn above: reuse the proven opacity structure,
       swap only the colour that actually differs between the two themes. */
    body.theme-low-lume .afb-bubble {
      background: rgba(79, 209, 197, 0.20);
      border-color: rgba(255, 255, 255, 0.35);
    }
    body.theme-rainbow .afb-bubble {
      background: rgba(233, 30, 99, 0.16);
      border-color: rgba(233, 30, 99, 0.5);
    }
    body.theme-contrast .afb-bubble {
      background: transparent;
      border-color: rgba(255, 255, 255, 0.7);
    }
    body.theme-contrast-light .afb-bubble {
      background: transparent;
      border-color: rgba(0, 0, 0, 0.55);
    }

    /* Hand-placed rather than random: nine bubbles across the width on
       mismatched durations and delays, so the loop never visibly repeats in
       step the way an evenly-spaced set does. */
    .afb-bubble:nth-child(1) { left: 7%;  width: 7px; height: 7px; animation-duration: 3.4s; animation-delay: 0s;   --afb-drift: 5px; }
    .afb-bubble:nth-child(2) { left: 17%; width: 4px; height: 4px; animation-duration: 2.6s; animation-delay: 1.1s; --afb-drift: -4px; }
    .afb-bubble:nth-child(3) { left: 28%; width: 9px; height: 9px; animation-duration: 4.1s; animation-delay: 0.5s; --afb-drift: 7px; }
    .afb-bubble:nth-child(4) { left: 39%; width: 5px; height: 5px; animation-duration: 3.0s; animation-delay: 2.0s; --afb-drift: -6px; }
    .afb-bubble:nth-child(5) { left: 50%; width: 6px; height: 6px; animation-duration: 2.9s; animation-delay: 0.3s; --afb-drift: 4px; }
    .afb-bubble:nth-child(6) { left: 61%; width: 8px; height: 8px; animation-duration: 3.7s; animation-delay: 1.5s; --afb-drift: -5px; }
    .afb-bubble:nth-child(7) { left: 72%; width: 4px; height: 4px; animation-duration: 2.4s; animation-delay: 0.8s; --afb-drift: 3px; }
    .afb-bubble:nth-child(8) { left: 83%; width: 7px; height: 7px; animation-duration: 3.3s; animation-delay: 2.2s; --afb-drift: -4px; }
    .afb-bubble:nth-child(9) { left: 92%; width: 5px; height: 5px; animation-duration: 2.7s; animation-delay: 1.3s; --afb-drift: 6px; }

    @keyframes afbRise {
      0%   { transform: translateY(0) translateX(0) scale(0.6); opacity: 0; }
      15%  { opacity: 0.85; }
      80%  { opacity: 0.7; }
      100% { transform: translateY(-64px) translateX(var(--afb-drift, 5px)) scale(1); opacity: 0; }
    }

    /* Motion is decoration here, so it goes entirely rather than slowing
       down — a still glass reads fine. */
    @media (prefers-reduced-motion: reduce) {
      .afb-fizz { display: none; }
      .add-fluid-trigger-btn:hover { transform: none; }
    }
    
    .fluid-collapsible-content {
      display: none;
      /* Was the generic `pop` scale/fade — swapped for a slide-down-and-
         settle (18 Aug 2026) so a conscious tap ("Add a drink to this
         meal") reads as something arriving, not just fading in place.
         Existing scroll-to-station logic (scrollDrinkStationBelowTopbar)
         is untouched; this only changes how the section looks once it's
         in view. */
      animation: drinkStationReveal 0.5s cubic-bezier(.25,.8,.35,1);
      margin-top: 10px;
      scroll-margin-top: 16px; /* land with a little breathing room, not flush to the top edge */
    }
    @keyframes drinkStationReveal {
      0%   { opacity: 0; transform: translateY(-16px) scale(0.97); }
      100% { opacity: 1; transform: translateY(0) scale(1); }
    }
    /* A brief ring around the station itself, on top of the slide, so the
       eye is drawn straight to it rather than having to notice a general
       layout shift. Fades to nothing well within a second. */
    .drinks-v2 {
      border-radius: 20px;
      animation: drinkStationFocusRing 1.1s ease-out;
    }
    @keyframes drinkStationFocusRing {
      0%   { box-shadow: 0 0 0 3px rgba(63, 169, 245, 0.55); }
      100% { box-shadow: 0 0 0 3px rgba(63, 169, 245, 0); }
    }
    @media (prefers-reduced-motion: reduce) {
      .fluid-collapsible-content, .drinks-v2 { animation: none; }
    }
    .fluid-section-header-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
    .step-btn {
      width: 38px; height: 38px; border-radius: 12px; border: none;
      background: var(--sprout-green); color: #FFFFFF; font-size: 19px; font-weight: 800;
      cursor: pointer; flex: 0 0 auto;
      box-shadow: 0 3px 0 rgba(0,0,0,0.16);
      user-select: none; -webkit-user-select: none;
      line-height: 1;
    }
    .step-btn:active { transform: translateY(2px); box-shadow: none; }
    /* +/- are a matched pair; the sign carries the meaning, not the colour. */
    .step-btn.minus { background: var(--sprout-green); color: #FFFFFF; }
    .value-box {
      flex: 1; text-align: center; background: var(--bg); border-radius: 12px; padding: 6px 6px;
      border: 1.5px solid transparent; transition: border-color 0.15s ease, box-shadow 0.15s ease;
    }
    .value-box.field-focused {
      border-color: var(--sprout-green); box-shadow: 0 0 0 3px var(--sprout-green-tint);
    }
    body.theme-night .value-box { background: #1B2140; }
    body.theme-caregiver .value-box { background: #F0F9FF; }
    .value-box input {
      width: 100%; border: none; background: transparent; text-align: center;
      font-family: 'Baloo 2', sans-serif; font-size: 1.3rem; font-weight: 700;
      color: var(--ink); outline: none;
    }
    .value-box input::-webkit-outer-spin-button,
    .value-box input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
    .value-box input[type=number] { -moz-appearance: textfield; }
    .value-box .unit { font-size: 0.68rem; color: var(--ink-soft); font-weight: 700; letter-spacing: .03em; }
    /* Carbs-per-100g box: the number and its 'g' sit together as one group so
       the unit reads at a glance, while the caption below still wraps onto
       its own line underneath (unchanged from the plain value-box). */
    .go-btn {
      width: 100%; border: none; padding: 16px;
      background: linear-gradient(135deg, var(--sprout-green), var(--sprout-green-deep));
      color: white; font-family: 'Baloo 2', sans-serif; font-weight: 700; font-size: 1.15rem;
      border-radius: 18px; cursor: pointer;
      box-shadow: 0 8px 18px rgba(0,89,78,0.32);
      /* The result panel sits directly above this button in both stations,
         and with no top margin the two were flush — a big green slab welded
         to the total, which read as one control rather than "here's the
         answer, now add it". 12px matches the drinks button's own gap. */
      margin-top: 12px;
      margin-bottom: 4px;
      /* Fix for a thin flickering horizontal line across this
         button's text — same flicker pattern seen on the mascot SVG.
         Cause: the box-shadow + rounded corners here,
         combined with the opacity change on :disabled firing every time
         Step 1's answer state changes, was making the browser repeatedly
         promote/demote this element to and from its own GPU compositing
         layer. The flicker is the seam at that layer's edge failing to
         line up cleanly frame-to-frame during the flip-flop. Forcing a
         stable layer up front (rather than letting the browser decide on
         the fly) stops the flip-flop and the seam with it. Purely a
         rendering hint — no visual or behavioural change otherwise.
         See mascot-avatar-wrap below for the same fix on the other
         reported element. */
      transform: translateZ(0);
      backface-visibility: hidden;
    }
    .go-btn:active { transform: scale(0.98); }
    .go-btn:disabled {
      opacity: 0.45; cursor: not-allowed; box-shadow: none;
      /* v1.31.0: added a dashed border so the disabled state reads as
         "not yet active" from the button's own shape, not just its
         dimmed fill — in the dark theme, a dimmed solid green button
         was easy to misread as a normal (if muted) button rather than
         a genuinely inert one. Border-only change: does NOT touch the
         opacity property itself or add/remove it conditionally, so it
         can't reintroduce the GPU-layer flicker documented above this
         rule (that fix was about the opacity flip-flop specifically,
         not about this element having a border at all). */
      border: 2px dashed rgba(255,255,255,0.35);
    }
    body.theme-night .go-btn:disabled { border-color: rgba(255,255,255,0.22); }
    body.theme-low-lume .go-btn:disabled { border-color: rgba(255,255,255,0.18); }
    .go-btn.shake { animation: goBtnShake 0.45s ease; }
    @keyframes goBtnShake {
      0%, 100% { transform: translateX(0); }
      20% { transform: translateX(-6px); }
      40% { transform: translateX(5px); }
      60% { transform: translateX(-3px); }
      80% { transform: translateX(2px); }
    }
    @media (prefers-reduced-motion: reduce) {
      .go-btn.shake { animation: none; }
    }
    
    /* Drinks are blue — a meaningful, calmer secondary to the green food flow. */
    .drink-go-btn {
      background: linear-gradient(135deg, var(--sky), #0D47A1);
      box-shadow: 0 8px 18px rgba(21,101,192,0.3);
    }
    body.theme-night .drink-go-btn { background: linear-gradient(135deg, var(--sky), #1565C0); }
    body.theme-low-lume .drink-go-btn { background: linear-gradient(135deg, var(--sky), #144A8C); }

    /* Progress ring around the mascot showing how close the child is to the next level. */
    .mascot-avatar-wrap::before {
      content: "";
      position: absolute;
      inset: -4px;
      border-radius: 50%;
      background: conic-gradient(var(--sprout-green) calc(var(--level-progress, 0) * 1%), rgba(31,42,68,0.14) 0);
      -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
              mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
      z-index: 0;
      pointer-events: none;
      transition: background 0.4s ease;
    }
    body.theme-night .mascot-avatar-wrap::before {
      background: conic-gradient(#2EC4B6 calc(var(--level-progress, 0) * 1%), rgba(255,255,255,0.14) 0);
    }
    .mascot-avatar-wrap.ring-maxed::before {
      background: conic-gradient(var(--sun) 100%, var(--sun) 0);
    }
    .greeting-mascot { position: relative; z-index: 1; }
    /* Invisible expanded hit area so small fingers can tap the level badge
       reliably — kept smaller than before (was -9px) since the badge now
       sits further from the mascot; a bigger expansion here would just eat
       back into Sprout's tap area from the new position. */
    .level-badge-btn::before {
      content: "";
      position: absolute;
      inset: -6px;
      border-radius: 50%;
    }

    /* ---- Emoji plate builder ---- */
    .food-emoji-picker { margin: 6px 0 14px; }

    /* Search sits above the row; slightly tighter than the modal version
       since Step 1 is already a tall block. */
    .food-search-wrap { margin-bottom: 10px; padding: 7px 12px; }
    .fav-search-clear {
      background: none; border: none; color: var(--ink-soft); cursor: pointer;
      font-size: 0.85rem; padding: 2px 4px; flex-shrink: 0; line-height: 1;
    }
    .food-search-empty {
      margin: 0 0 10px; font-size: calc(12px * var(--font-scale, 1)); font-weight: 700;
      color: var(--ink-soft); line-height: 1.45;
    }
    /* While filtering, the horizontal strip becomes a wrapping grid. This is
       the actual win: a one-row scroller hides its own contents, so you can't
       tell whether the thing you want is in there without dragging to the end.
       Wrapped, every match is visible at once. */
    .food-emoji-scroll.is-filtering {
      flex-wrap: wrap; overflow-x: hidden; overflow-y: auto;
      max-height: 46vh; padding-bottom: 6px;
    }
    .food-emoji-scroll.is-filtering .is-clone,
    .food-emoji-scroll .food-emoji-chip.is-hidden-by-search { display: none; }
    /* Your own foods come first: they're more specific than a generic chip,
       so if one matches it's almost certainly the thing being looked for. */
    .food-search-group { margin-bottom: 10px; }
    .food-search-group-label {
      margin: 0 0 6px; font-family: 'Baloo 2', sans-serif;
      font-size: calc(12px * var(--font-scale, 1)); font-weight: 700;
      color: var(--ink-soft); letter-spacing: 0.02em;
    }
    .food-search-custom-list { display: flex; flex-direction: column; gap: 6px; }
    /* Each saved size is its own row rather than one row per food, so the
       choice between "13g" and "26g" is made by reading, not by opening a
       second sheet to find out what the options were. */
    .custom-result-chip {
      display: flex; align-items: center; gap: 10px; width: 100%;
      background: var(--bg); border: 1.5px solid var(--bg2); border-radius: 12px;
      padding: 9px 12px; cursor: pointer; text-align: left;
      font-family: 'Nunito', sans-serif; color: var(--ink); min-height: 52px;
    }
    .custom-result-chip:hover, .custom-result-chip:focus-visible { border-color: var(--sprout-green); }
    .custom-result-chip:active { transform: scale(0.985); }
    .custom-result-emoji { font-size: calc(22px * var(--font-scale, 1)); line-height: 1; flex: 0 0 auto; }
    .custom-result-text { flex: 1 1 auto; min-width: 0; }
    .custom-result-name {
      display: block; font-weight: 800; font-size: calc(14px * var(--font-scale, 1));
      line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .custom-result-meta {
      display: block; font-weight: 700; font-size: calc(11.5px * var(--font-scale, 1));
      color: var(--ink-soft); line-height: 1.3; margin-top: 2px;
    }
    .custom-result-meta .cr-carbs { color: var(--sprout-green); }
    body.theme-night .custom-result-meta .cr-carbs { color: var(--mint, #2EC4B6); }
    /* Same star/camera distinction as the favourites drawer — a scanned
       number stays visibly community-sourced even when reached via search. */
    .custom-result-badge { flex: 0 0 auto; font-size: calc(13px * var(--font-scale, 1)); opacity: 0.85; }
    @media (prefers-reduced-motion: reduce) { .custom-result-chip:active { transform: none; } }
    /* A one-line explanation of what this section actually does, since
       "optional" alone doesn't say why — a shortcut, not a form field. */
    .food-picker-purpose {
      margin: 4px 0 10px; font-family: 'Nunito', sans-serif; font-weight: 700;
      font-size: 0.72rem; color: var(--ink-soft); line-height: 1.4;
    }

    .food-emoji-scroll {
      display: flex; gap: 8px; overflow-x: auto; padding: 4px 2px 20px;
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;        /* Firefox */
      -ms-overflow-style: none;     /* old Edge/IE */
    }
    .food-emoji-scroll::-webkit-scrollbar { display: none; } /* Chrome/Safari/Edge */
    /* Desktop only: signal the row is draggable and keep panning smooth. */
    @media (hover: hover) and (pointer: fine) {
      .food-emoji-scroll { cursor: grab; }
      .food-emoji-scroll.dragging { cursor: grabbing; user-select: none; scroll-behavior: auto; }
      .food-emoji-scroll.dragging .food-emoji-chip { pointer-events: none; }
    }
    /* Icon + name label, stacked in ONE element (not a wrapper + sibling).
       This matters structurally, not just visually: the "endless food row"
       loop below clones whichever elements carry the .food-emoji-chip
       class directly as children of the scroll track, so the label has to
       live INSIDE that same element for cloneNode(true) to keep carrying
       it — wrapping it externally would silently break the infinite-scroll
       system, since the clone logic would then find zero direct children
       with this class. */
    .food-emoji-chip {
      flex: 0 0 auto; width: 58px; height: 64px; border-radius: 14px;
      border: 2px solid var(--bg2); background: var(--bg);
      cursor: pointer; position: relative;
      display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
      transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease;
    }
    .food-emoji-chip-icon { font-size: 22px; line-height: 1; }
    .food-emoji-chip-name {
      font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 0.62rem;
      color: var(--ink); line-height: 1.1; max-width: 52px;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    body.theme-night .food-emoji-chip { background: #1B2140; border-color: #262C52; }
    .food-emoji-chip:active { transform: scale(0.9); }
    /* Selected state must out-specify the theme override above, or it's invisible in night mode. */
    .food-emoji-chip.active,
    body.theme-night .food-emoji-chip.active,
    body.theme-contrast .food-emoji-chip.active,
    body.theme-contrast-light .food-emoji-chip.active {
      border-color: var(--sprout-green);
      background: var(--sprout-green-tint);
      transform: scale(1.08);
      box-shadow: 0 0 0 3px var(--sprout-green-tint), 0 4px 12px rgba(14,140,122,0.35);
    }
    .food-emoji-chip.active::after {
      content: "✓";
      position: absolute;
      top: -6px; right: -6px;
      width: 18px; height: 18px;
      background: var(--sprout-green); color: #fff;
      border-radius: 50%; font-size: 11px; font-weight: 900;
      display: flex; align-items: center; justify-content: center;
      box-shadow: 0 1px 3px rgba(0,0,0,0.3);
    }

    /* ---- "Add food" button: pinned outside the scrolling carousel, so
       it's always visible and never affected by the infinite-loop/clone
       logic that powers the built-in food row. ---- */
    .food-picker-row { display: flex; align-items: flex-start; gap: 8px; flex-wrap: wrap; }
    .food-picker-row .food-emoji-scroll { flex: 1; min-width: 120px; }
    /* [hidden] alone isn't reliably specific enough to beat the class rules
       above (same specificity, and these come later in source order) — the
       existing .fav-recent-row[hidden] rule hit the same issue, so this
       follows that precedent rather than relying on the attribute alone. */
    .food-emoji-scroll[hidden] { display: none; }

    /* ---- "The Usual": personalised, time-of-day chip row that stands in
       for the generic scroll above. Same icon+label shape as
       .food-emoji-chip so swapping one row for the other reads as a
       content change, not a layout change. Also deliberately NO ★/📷-style
       badge — those already mean "where this food's data came from", and
       reusing that glyph slot here for "why is this chip here" would
       blur two different meanings onto one symbol. Name label is load-
       bearing here, not decorative: this is a small, personalised set
       where two real foods (two different cereals, two different
       biscuits) can land on the same or a near-identical emoji, unlike
       the generic scroll's ~60 visually distinct icons. ---- */
    .usual-foods-scroll {
      flex: 1; min-width: 120px;
      display: flex; gap: 10px; overflow-x: auto; padding: 4px 2px 20px;
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none; -ms-overflow-style: none;
    }
    .usual-foods-scroll::-webkit-scrollbar { display: none; }
    .usual-foods-scroll[hidden] { display: none; }
    .usual-chip {
      flex: 0 0 auto; width: 58px; height: 64px; border-radius: 14px;
      border: 2px solid var(--bg2); background: var(--bg);
      cursor: pointer; position: relative;
      display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
      transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease;
      -webkit-tap-highlight-color: transparent;
    }
    .usual-chip-icon { font-size: 22px; line-height: 1; }
    .usual-chip-name {
      font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 0.62rem;
      color: var(--ink); line-height: 1.1; max-width: 52px;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    body.theme-night .usual-chip { background: #1B2140; border-color: #262C52; }
    .usual-chip:active { transform: scale(0.9); }
    .usual-chip:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    .usual-chip.add-own { border-style: dashed; border-color: var(--ink-soft); background: transparent; }
    .usual-chip.add-own .usual-chip-icon,
    .usual-chip.add-own .usual-chip-name { color: var(--ink-soft); }

    /* Detail-on-demand amount tooltip — hover (desktop) / long-press
       500ms (touch), the same interaction already used for the badge
       notification dots elsewhere in the app, so it introduces no new
       gesture. Answers "how much exactly", which the name label doesn't
       cover. Solid on/off (no fade) with a fixed, non-variable colour and
       a visible border, so it can't be caught half-transparent and lose
       contrast against whatever theme or background is behind it. */
    .usual-amount-tip {
      position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
      background: #12172B; color: #FFFFFF; padding: 7px 11px; border-radius: 10px;
      font-family: 'Nunito', sans-serif; font-size: 0.74rem; font-weight: 800; white-space: nowrap;
      pointer-events: none; border: 1.5px solid rgba(255,255,255,0.35);
      box-shadow: 0 4px 14px rgba(0,0,0,0.45);
      display: none; z-index: 20;
    }
    .usual-amount-tip::after {
      content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
      border: 5px solid transparent; border-top-color: #12172B;
    }
    .usual-chip.show-tip .usual-amount-tip { display: block; }
    .add-food-chip-pinned {
      flex: 0 0 auto; height: 64px; padding: 0 14px; border-radius: 100px;
      border: 2px dashed var(--sprout-green); background: var(--card);
      color: var(--ink); cursor: pointer; white-space: nowrap;
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 0.86rem; line-height: 1;
      display: flex; align-items: center; justify-content: center; gap: 5px;
      -webkit-tap-highlight-color: transparent;
      transition: transform 0.12s ease, background-color 0.15s ease;
    }
    .add-food-chip-pinned span { font-size: 20px; line-height: 1; color: inherit; }
    .add-food-chip-pinned:active { transform: scale(0.96); }
    .add-food-chip-pinned:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    body.theme-night .add-food-chip-pinned { background: #1B2140; }
    .add-food-chip-pinned.panel-open {
      background: var(--sprout-green); color: #fff; border-style: solid;
    }
    .add-food-chip-pinned.panel-open span { color: #fff; }

    /* ---- "⭐" favourites button: same pinned spot, brighter treatment so
       it reads as "open a collection" rather than "add new". ---- */
    .fav-food-chip-pinned {
      flex: 0 0 auto; width: 58px; height: 64px; border-radius: 14px;
      border: 2px solid var(--sun); background: linear-gradient(160deg, #FFF6DA, var(--sun));
      color: #7A5B00; cursor: pointer;
      display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
      -webkit-tap-highlight-color: transparent;
      box-shadow: 0 2px 6px rgba(255, 201, 60, 0.45);
      transition: transform 0.12s ease;
    }
    .fav-food-chip-pinned .pinned-chip-icon { font-size: 24px; line-height: 1; }
    .fav-food-chip-pinned:active { transform: scale(0.9); }
    .fav-food-chip-pinned:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    body.theme-night .fav-food-chip-pinned { background: linear-gradient(160deg, #4A3C10, #8A6F1E); }
    /* v1.36.1: theme-night got its own dark gradient here, but
       theme-low-lume never did — it has no per-component overrides
       anywhere in this file (only its own variable block near the top),
       so this chip fell through to the bright cream-to-gold base
       gradient (#FFF6DA -> --sun) under a theme specifically meant to
       reduce brightness. Found via a systematic check: every selector
       with a theme-night override was cross-checked against whether a
       theme-low-lume equivalent existed; none did, and this was one of
       three where the base rule's hardcoded hex genuinely clashes
       (the rest were either already var()-driven and fine, or
       deliberately theme-invariant like tooltips/brand icons). Uses
       darker/muted tones from the same gold family as theme-night's
       override rather than reusing theme-night's exact values, since
       low-lume's own --sun (#F6E05E) is already a dimmer gold than the
       default theme's (#FFC93C) — matching that dimmer intent here too. */
    body.theme-low-lume .fav-food-chip-pinned { background: linear-gradient(160deg, #3A3010, #6B5A1A); }

    /* ---- Add-your-own food panel (opened by the "+" chip) ---- */
    .add-food-panel { margin-top: 10px; }
    .add-food-panel[hidden] { display: none; }
    .add-food-panel-row { display: flex; align-items: center; gap: 8px; }
    .custom-food-preview {
      flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
      display: flex; align-items: center; justify-content: center; font-size: 1.2rem;
      background: var(--bg); border: 2px solid var(--sprout-green);
    }
    body.theme-night .custom-food-preview { background: #1B2140; }
    .add-food-name-input {
      flex: 1; min-width: 0; padding: 9px 12px; border-radius: 10px;
      border: 2px solid var(--bg2); background: var(--card);
      /* Was 0.9rem (14.4px) — under Safari's 16px auto-zoom-on-focus
         threshold. TO REVERT: back to 0.9rem. */
      font-family: 'Nunito', sans-serif; font-weight: 700; font-size: 1rem; color: var(--ink);
    }
    body.theme-night .add-food-name-input { background: #1B2140; border-color: #262C52; }
    .add-food-name-input:focus-visible { outline: 3px solid var(--sky); outline-offset: 1px; }

    /* ---- Category picker (savoury / sweet / treats / other) ---- */
    .custom-food-category-row {
      display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap;
      animation: pop 0.2s ease;
    }
    .custom-food-category-row[hidden] { display: none; }
    .category-chip {
      flex: 1 1 auto; min-width: 0; padding: 7px 8px; border-radius: 100px;
      border: 2px solid var(--bg2); background: var(--card); color: var(--ink-soft);
      font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 0.74rem;
      cursor: pointer; -webkit-tap-highlight-color: transparent; white-space: nowrap;
      transition: transform 0.1s ease, border-color 0.12s ease, background-color 0.12s ease, color 0.12s ease;
    }
    .category-chip:active { transform: scale(0.95); }
    .category-chip:focus-visible { outline: 3px solid var(--sky); outline-offset: 1px; }
    .category-chip[aria-pressed="true"] {
      border-color: var(--sprout-green); background: var(--sprout-green); color: #fff;
    }
    body.theme-night .category-chip { background: #1B2140; border-color: #262C52; }
    /* Must come after the plain night-theme rule above: body.theme-night
       .category-chip has higher specificity than .category-chip[aria-pressed],
       so without this override the pressed/highlighted state was invisible
       in night theme — the chip always fell back to its unpressed colours. */
    body.theme-night .category-chip[aria-pressed="true"] {
      border-color: var(--sprout-green); background: var(--sprout-green); color: #fff;
    }

    .custom-food-add-btn {
      flex: 0 0 auto; border: none; padding: 9px 16px; border-radius: 100px;
      background: var(--sprout-green); color: #fff; font-family: 'Baloo 2', sans-serif;
      font-weight: 800; font-size: 0.84rem; cursor: pointer; -webkit-tap-highlight-color: transparent;
      transition: transform 0.12s ease, background-color 0.15s ease;
    }
    .custom-food-add-btn-full { display: block; width: 100%; margin-top: 10px; padding: 10px; }
    .custom-food-add-btn:active { transform: scale(0.95); }
    .custom-food-add-btn:disabled { background: #B9C7BE; cursor: default; }
    .custom-food-note {
      margin-top: 8px; padding: 8px 11px; border-radius: 10px;
      background: var(--card); border-left: 4px solid var(--sprout-green);
      font-family: 'Nunito', sans-serif;
      font-size: 0.78rem; font-weight: 700; color: var(--ink); line-height: 1.4;
    }
    body.theme-night .custom-food-note { background: #1B2140; }

    /* ---- Favourite Foods modal ----
       Rebuilt around three rules, because the old version broke all three:
       (1) one row per FOOD, never one per saved size, so a food with three
       sizes reads as one thing; (2) no destructive control in the default
       view — removal lives behind the Edit button, so a mis-tap can't wipe a
       saved food; (3) exactly one number per row, on a right-hand rail you
       can run your eye down. Everything else here is type and whitespace. */
    /* Anchored near the top rather than vertically centred. A centred list
       that changes height shifts the whole panel every time you filter or
       search, and a long one pushes its bottom edge down behind the phone's
       navigation bar. Starting from the top keeps the header, search box and
       first few foods in the same place whatever the list is doing. */
    #favFoodsModal { align-items: flex-start; padding-top: max(14px, env(safe-area-inset-top)); }
    .fav-foods-modal-content {
      max-width: 460px; position: relative; padding: 20px 16px 18px;
      max-height: calc(100vh - 28px);
      max-height: calc(100dvh - 28px); /* dvh accounts for mobile browser chrome */
    }
    .fav-modal-close-x {
      position: absolute; top: 12px; right: 12px; z-index: 2;
      width: 34px; height: 34px; padding: 0; line-height: 1;
      display: flex; align-items: center; justify-content: center;
      border: none; border-radius: 50%; background: var(--bg);
      color: var(--ink-soft); font-size: 17px; font-weight: 900; cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      transition: background-color 0.15s ease, color 0.15s ease, transform 0.12s ease;
    }
    .fav-modal-close-x:hover { background: var(--bg2); color: var(--ink); }
    .fav-modal-close-x:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    .fav-modal-close-x:active { transform: scale(0.92); }
    body.theme-night .fav-modal-close-x { background: #1B2140; }

    /* Header: title, then a single quiet line carrying the count on the left
       and the Edit switch on the right. The old explanatory sub-caption is
       gone — every row now prints its own numbers, so there is nothing left
       to explain. */
    .fav-modal-head { margin-bottom: 12px; padding-right: 42px; }
    #favFoodsModalTitle {
      font-family: 'Baloo 2', sans-serif; font-size: 1.28rem; font-weight: 800;
      color: var(--ink); margin: 0; line-height: 1.2;
    }
    .fav-modal-head-row {
      display: flex; align-items: center; justify-content: space-between;
      gap: 10px; margin-top: 3px; min-height: 30px;
    }
    .fav-modal-count { font-size: 0.76rem; font-weight: 700; color: var(--ink-soft); }
    .fav-edit-toggle {
      border: none; background: none; cursor: pointer;
      padding: 7px 8px; margin: -7px -8px -7px 0; border-radius: 10px;
      font-family: 'Nunito', sans-serif; font-size: 0.8rem; font-weight: 800;
      color: var(--sky); -webkit-tap-highlight-color: transparent;
      transition: background-color 0.14s ease;
    }
    .fav-edit-toggle[hidden] { display: none; }
    .fav-edit-toggle:hover { background: var(--bg); }
    .fav-edit-toggle.is-active { background: var(--bg2); color: var(--ink); }
    .fav-edit-toggle:focus-visible { outline: 3px solid var(--sky); outline-offset: 1px; }

    #favFoodsSearchWrap { margin-bottom: 10px; }
    /* .fav-search-wrap.show sets display:flex, which beats the [hidden]
       attribute on its own — the ID selector here is what actually wins, so
       the search box really does disappear on the empty state. */
    #favFoodsSearchWrap[hidden] { display: none; }
    .fav-modal-head-row[hidden] { display: none; }

    /* One control strip, not three. Category chips FILTER (they used to only
       scroll you to a section, which meant the long list stayed long), and
       they share the row with the view switch instead of stacking above it.
       The chips scroll horizontally so a fifth category never wraps the row. */
    .fav-filter-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; }
    .fav-filter-bar[hidden] { display: none; }
    .fav-filter-bar.no-filters { justify-content: flex-end; margin-bottom: 10px; }
    .fav-filter-bar.no-filters .fav-filter-scroll { display: none; }
    .fav-filter-scroll {
      display: flex; gap: 6px; flex: 1 1 auto; min-width: 0;
      overflow-x: auto; -webkit-overflow-scrolling: touch;
      scrollbar-width: none; padding: 2px 10px 2px 0;
      /* Fades the last chip out under the view switch instead of guillotining
         it, so an overflowing row reads as "there's more" rather than broken. */
      -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%);
      mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent 100%);
    }
    .fav-filter-scroll::-webkit-scrollbar { display: none; }
    .fav-filter-chip {
      flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
      border: 1.5px solid var(--bg2); background: transparent; color: var(--ink-soft);
      border-radius: 999px; padding: 7px 13px; cursor: pointer;
      font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 0.75rem;
      -webkit-tap-highlight-color: transparent;
      transition: background-color 0.14s ease, color 0.14s ease, border-color 0.14s ease;
    }
    .fav-filter-chip-count { font-weight: 700; opacity: 0.6; font-variant-numeric: tabular-nums; }
    /* Filled with --ink rather than an accent: it stays legible in every
       theme, including both high-contrast ones, without a special case. */
    .fav-filter-chip.is-active { background: var(--ink); border-color: var(--ink); color: var(--card); }
    .fav-filter-chip.is-active .fav-filter-chip-count { opacity: 0.7; }
    .fav-filter-chip:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }

    .fav-view-toggle {
      flex: 0 0 auto; display: flex; gap: 2px;
      background: var(--bg); border-radius: 999px; padding: 3px;
    }
    .fav-view-toggle[hidden] { display: none; }
    body.theme-night .fav-view-toggle { background: #131737; }
    .fav-view-btn {
      width: 34px; height: 30px; border: none; background: transparent; border-radius: 999px;
      color: var(--ink-soft); font-size: 0.92rem; line-height: 1; cursor: pointer;
      display: flex; align-items: center; justify-content: center;
      -webkit-tap-highlight-color: transparent;
      transition: background-color 0.14s ease, color 0.14s ease;
    }
    .fav-view-btn.is-active { background: var(--card); color: var(--ink); box-shadow: 0 1px 3px var(--shadow); }
    .fav-view-btn:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }

    /* Shown only when something scanned is actually on screen. Says once,
       in words, what the old per-chip "≈" badge left you to guess. */
    /* A footnote, so it sits at the foot: below the list, out of the way of
       scanning, next to nothing it has to compete with. */
    .fav-source-legend {
      margin: 16px 6px 0; padding-top: 12px; border-top: 1px solid var(--bg2);
      font-size: 0.7rem; font-weight: 600; line-height: 1.45; color: var(--ink-soft);
    }
    .fav-source-legend[hidden] { display: none; }

    .fav-group-label {
      font-family: 'Nunito', sans-serif; font-size: 0.64rem; font-weight: 800;
      letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-soft);
      margin: 16px 0 4px; opacity: 0.75;
    }
    .fav-group-label:first-child { margin-top: 0; }

    /* ---- List view ---- */
    .fav-list { display: flex; flex-direction: column; }
    .fav-row {
      position: relative; display: flex; align-items: center; gap: 12px;
      padding: 11px 6px; border-radius: 12px; cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      transition: background-color 0.14s ease;
    }
    /* Divider starts after the emoji column — it separates the text blocks
       without slicing through the icon rail. */
    .fav-row + .fav-row::before {
      content: ""; position: absolute; left: 44px; right: 6px; top: 0;
      border-top: 1px solid var(--bg2);
    }
    .fav-row:hover, .fav-row:active { background: var(--bg); }
    body.theme-night .fav-row:hover, body.theme-night .fav-row:active { background: #131737; }
    .fav-row:focus-visible { outline: 3px solid var(--sky); outline-offset: -1px; }
    .fav-row-icon { flex: 0 0 auto; width: 26px; text-align: center; font-size: 1.35rem; line-height: 1; }
    .fav-row-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
    .fav-row-name {
      font-family: 'Baloo 2', sans-serif; font-size: 0.98rem; font-weight: 700;
      color: var(--ink); line-height: 1.2;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .fav-row-meta {
      font-size: 0.72rem; font-weight: 600; color: var(--ink-soft); line-height: 1.3;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    /* The pack label carries the meaning on a row whose other numbers are
       deliberately quiet, so it gets the full ink colour and extra weight
       rather than sitting at the same grey as the grams around it. */
    .fav-pack { font-weight: 800; color: var(--ink); }
    .fav-row.is-incomplete .fav-row-meta { color: #B45309; font-weight: 700; }
    body.theme-night .fav-row.is-incomplete .fav-row-meta { color: #FBBF6B; }
    body.theme-low-lume .fav-row.is-incomplete .fav-row-meta { color: #E0AD5C; }

    /* The one number on the row: carbs in the saved portion. The "carbs"
       label repeats down the rail on purpose — in a carb-counting app used
       around a child's health, an unlabelled number sitting next to a "40g
       portion" is a genuinely dangerous ambiguity, and at this size the
       repetition reads as texture rather than clutter. */
    .fav-value {
      flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end;
      min-width: 46px; line-height: 1;
    }
    .fav-value-num {
      font-family: 'Baloo 2', sans-serif; font-size: 1.02rem; font-weight: 800;
      color: var(--ink); font-variant-numeric: tabular-nums;
    }
    .fav-value-unit {
      font-size: 0.55rem; font-weight: 700; letter-spacing: 0.07em;
      text-transform: uppercase; color: var(--ink-soft); margin-top: 3px; opacity: 0.68;
    }
    /* Was a bare 1.15rem glyph in the muted meta colour — almost invisible,
       and the only thing signalling "this food has more than one size".
       Now a filled chip: full ink on a tinted circle, so it reads as
       something you can press. */
    .fav-value.is-chevron {
      min-width: 0; width: 30px; height: 30px; border-radius: 50%;
      background: var(--bg2); color: var(--ink);
      font-size: 1.2rem; font-weight: 900; line-height: 1;
      display: flex; align-items: center; justify-content: center;
      padding-bottom: 3px; /* optical centring of the › glyph */
      box-shadow: inset 0 0 0 1.5px var(--ink-soft);
    }
    body.theme-night .fav-value.is-chevron { background: #333C70; }
    body.theme-low-lume .fav-value.is-chevron { background: #2A3040; }
    body.theme-contrast .fav-value.is-chevron,
    body.theme-contrast-light .fav-value.is-chevron { border: 2px solid var(--ink-soft); }
    .fav-row:hover .fav-value.is-chevron { background: var(--sky); color: #fff; }
    .fav-value.is-empty { font-size: 1rem; font-weight: 800; color: var(--ink-soft); }

    /* ---- Grid view ---- */
    .fav-tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 10px; }
    .fav-tile {
      position: relative; display: flex; flex-direction: column; align-items: flex-start;
      gap: 4px; padding: 12px 11px; border-radius: 16px; background: var(--bg);
      cursor: pointer; -webkit-tap-highlight-color: transparent;
      transition: transform 0.1s ease, background-color 0.14s ease;
    }
    body.theme-night .fav-tile { background: #131737; }
    .fav-tile:active { transform: scale(0.97); }
    .fav-tile:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    .fav-tile-emoji { font-size: 1.5rem; line-height: 1; }
    .fav-tile-name {
      width: 100%; font-family: 'Baloo 2', sans-serif; font-size: 0.82rem; font-weight: 700;
      color: var(--ink); line-height: 1.2;
      display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
    }
    .fav-tile-value {
      /* Pushed to the bottom so the figures line up across a row of tiles
         whose names run to different lengths. */
      margin-top: auto; padding-top: 2px;
      font-size: 0.71rem; font-weight: 800; color: var(--ink-soft);
      font-variant-numeric: tabular-nums;
    }
    .fav-tile.is-incomplete .fav-tile-value { color: #B45309; }
    body.theme-night .fav-tile.is-incomplete .fav-tile-value { color: #FBBF6B; }
    body.theme-low-lume .fav-tile.is-incomplete .fav-tile-value { color: #E0AD5C; }

    /* ---- Remove control: only ever exists while Edit is on ---- */
    /* Keeps the number rail in one column while editing, on rows that have
       no remove button of their own (multi-size foods). */
    .fav-row-remove-spacer { flex: 0 0 auto; width: 28px; height: 28px; }
    .fav-row-remove, .fav-tile-remove {
      flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%; border: none;
      background: var(--bg2); color: var(--ink-soft);
      font-size: 12px; font-weight: 900; line-height: 1; cursor: pointer;
      display: flex; align-items: center; justify-content: center;
      -webkit-tap-highlight-color: transparent; animation: pop 0.16s ease;
      transition: background-color 0.14s ease, color 0.14s ease;
    }
    .fav-row-remove.confirming, .fav-tile-remove.confirming { background: #E4573D; color: #fff; }
    .fav-row-remove:focus-visible, .fav-tile-remove:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    .fav-tile-remove {
      position: absolute; top: 6px; right: 6px; width: 24px; height: 24px;
      background: var(--card); box-shadow: 0 1px 4px var(--shadow);
    }

    .fav-foods-empty {
      text-align: center; padding: 34px 16px; color: var(--ink-soft);
      font-size: 0.86rem; font-weight: 700; line-height: 1.6;
    }
    .fav-foods-empty-icon { font-size: 2rem; line-height: 1; margin-bottom: 8px; }

    /* High-contrast themes flatten --bg and --bg2 to near-invisible against
       the card, so the surfaces that carry meaning get an explicit edge. */
    body.theme-contrast .fav-tile, body.theme-contrast-light .fav-tile,
    body.theme-contrast .fav-view-toggle, body.theme-contrast-light .fav-view-toggle {
      border: 2px solid var(--ink-soft);
    }
    body.theme-contrast .fav-row + .fav-row::before,
    body.theme-contrast-light .fav-row + .fav-row::before { border-top-color: var(--ink-soft); }
    body.theme-contrast .fav-filter-chip, body.theme-contrast-light .fav-filter-chip {
      border-color: var(--ink-soft);
    }
    /* --card and --bg collapse to near-identical blacks here, so "which view
       am I in" has to be carried by an inversion rather than a tint. */
    body.theme-contrast .fav-view-btn.is-active, body.theme-contrast-light .fav-view-btn.is-active {
      background: var(--ink); color: var(--card);
    }

    @media (prefers-reduced-motion: reduce) {
      .fav-modal-close-x, .fav-row, .fav-tile, .fav-filter-chip,
      .fav-view-btn, .fav-edit-toggle, .fav-row-remove, .fav-tile-remove { transition: none; }
      .fav-modal-close-x:active, .fav-tile:active { transform: none; }
      .fav-row-remove, .fav-tile-remove { animation: none; }
    }

    /* ---- Variant picker bottom sheet — sits above the favourites modal ---- */
    /* Was pinned to the bottom of the screen. Now that the favourites modal
       is anchored to the top, a bottom sheet left a screen-height void between
       the two and put the options at the far end of a tall phone. It opens
       where the row you tapped already was instead. */
    .variant-picker-overlay {
      position: fixed; inset: 0; background: rgba(0,0,0,0.5);
      z-index: 200; display: none; align-items: flex-start; justify-content: center;
      padding: max(14px, env(safe-area-inset-top)) 15px 15px;
    }
    .variant-picker-overlay.show { display: flex; }
    .variant-picker-sheet {
      background: var(--card); border-radius: var(--radius-lg); width: 100%; max-width: 460px;
      padding: 18px 16px 20px;
      max-height: calc(100vh - 30px);
      max-height: calc(100dvh - 30px);
      overflow-y: auto;
      box-shadow: 0 12px 36px rgba(0,0,0,0.4);
      animation: pop 0.18s ease;
    }
    @media (prefers-reduced-motion: reduce) { .variant-picker-sheet { animation: none; } }
    body.theme-night .variant-picker-sheet { background: #161B36; }
    body.theme-low-lume .variant-picker-sheet { background: #12151C; }
    .variant-picker-title {
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 1.05rem;
      margin: 0 0 2px; color: var(--ink);
    }
    .variant-picker-sub { font-size: 0.78rem; color: var(--ink-soft); margin: 0 0 14px; font-weight: 600; }
    .variant-picker-option {
      display: flex; align-items: center; gap: 12px;
      background: var(--bg); border-radius: 12px; padding: 11px 13px; margin-bottom: 8px;
      cursor: pointer; border: 2px solid transparent; -webkit-tap-highlight-color: transparent;
      transition: border-color 0.12s ease, transform 0.1s ease;
    }
    body.theme-night .variant-picker-option { background: #1B2140; }
    .variant-picker-option:active { border-color: var(--sprout-green); transform: scale(0.99); }
    .variant-picker-option-badge {
      flex: 0 0 auto; width: 50px; height: 50px; border-radius: 14px;
      background: var(--sprout-green); color: #fff; position: relative; overflow: hidden;
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      font-family: 'Baloo 2', sans-serif; line-height: 1;
    }
    .variant-picker-option-badge .emoji-watermark {
      position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
      font-size: 34px; opacity: 0.28; filter: grayscale(0.3); line-height: 1;
    }
    .variant-picker-option-badge .num,
    .variant-picker-option-badge .unit { position: relative; z-index: 1; }
    .variant-picker-option-badge .num { font-size: 0.95rem; font-weight: 800; white-space: nowrap; }
    .variant-picker-option-badge .unit { font-size: 0.48rem; font-weight: 700; opacity: 0.9; }
    .variant-picker-option-info { flex: 1; min-width: 0; }
    .variant-picker-option-name { font-size: 0.9rem; font-weight: 800; color: var(--ink); }
    .variant-picker-option-carbs100 { font-size: 0.76rem; font-weight: 700; color: var(--ink-soft); margin-top: 2px; }
    .variant-picker-option-missing { font-size: 0.74rem; font-weight: 800; color: #B45309; margin-top: 3px; }
    body.theme-night .variant-picker-option-missing { color: #FBBF6B; }
    body.theme-low-lume .variant-picker-option-missing { color: #E0AD5C; }
    .variant-picker-option-delete {
      flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; border: none;
      background: var(--bg2); color: var(--ink-soft); font-size: 12px; font-weight: 900;
      cursor: pointer; display: flex; align-items: center; justify-content: center;
      -webkit-tap-highlight-color: transparent;
      transition: background-color 0.12s ease, width 0.12s ease;
    }
    .variant-picker-option-delete.confirming {
      width: 32px; background: #E4573D; color: #fff; font-size: 14px;
    }
    .variant-picker-cancel-btn {
      width: 100%; border: none; background: var(--bg2); color: var(--ink);
      border-radius: 100px; padding: 11px; font-family: 'Baloo 2', sans-serif;
      font-weight: 800; font-size: 0.88rem; cursor: pointer; margin-top: 4px;
      -webkit-tap-highlight-color: transparent;
    }
    body.theme-night .variant-picker-cancel-btn { background: #262C52; color: #fff; }

    /* ---- Sprout eats: flying food emoji ---- */
    .flying-food {
      position: absolute; z-index: 40; font-size: 30px; pointer-events: none;
      left: 50%; top: -6px; transform: translate(-50%, -120%) scale(0.7);
      animation: flyIntoMouth 0.62s cubic-bezier(0.5,0,0.75,0.2) forwards;
    }
    @keyframes flyIntoMouth {
      0%   { transform: translate(-50%, -120%) scale(0.7) rotate(-12deg); opacity: 0; }
      25%  { opacity: 1; }
      75%  { transform: translate(-50%, 10%) scale(1.1) rotate(8deg); opacity: 1; }
      100% { transform: translate(-50%, 40%) scale(0.15) rotate(0deg); opacity: 0; }
    }
    .greeting-mascot.sprout-chomp-action { animation: sproutChomp 0.36s ease-in-out 2; transform-origin: center bottom; }
    @keyframes sproutChomp {
      0%,100% { transform: scale(1,1); }
      50% { transform: scale(1.06, 0.9); }
    }
    @media (prefers-reduced-motion: reduce) {
      .flying-food { animation-duration: 0.01s; }
      .greeting-mascot.sprout-chomp-action { animation: none; }
      .greeting-mascot.sprout-dance-action { animation: none; }
    }

    /* ---- Growing garden ---- */
    /* Grass tuft colours, themed via variables so the blades recolour with
       the rest of the app. Defaults here are the light-theme greens; each
       theme below overrides --grass-front/-back. Deliberately true greens
       (not the app's teal --sprout-green) so the lawn reads as grass rather
       than cyan, especially on the dark night card. */
    .garden-plot {
      --grass-front: #0E8C7A;
      --grass-back: #00594E;
      --grass-sky-top: #fbfdfc;
      --grass-sky-bot: #eef8f5;
      --grass-border: rgba(14,140,122,0.18);
      position: relative;
      display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; align-items: flex-end;
      padding: 16px 14px 26px; margin-top: 8px;
      background: linear-gradient(180deg, var(--grass-sky-top) 0%, var(--grass-sky-bot) 72%);
      border-radius: var(--radius-md);
      border: 2px solid var(--grass-border);
      min-height: 64px;
      overflow: hidden;
    }
    /* Back tuft layer — taller thin blades, darker, sits behind (offset from
       the front layer so the two interleave into a fuller lawn). */
    .garden-plot::before {
      content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 26px;
      z-index: 1; pointer-events: none; opacity: 0.6;
      background-color: var(--grass-back);
      -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='26' viewBox='0 0 40 26'%3E%3Cg fill='black'%3E%3Cpath d='M2 26 L4 4 L6 26 Z'/%3E%3Cpath d='M9 26 L11 9 L13 26 Z'/%3E%3Cpath d='M16 26 L18 2 L20 26 Z'/%3E%3Cpath d='M23 26 L25 10 L27 26 Z'/%3E%3Cpath d='M30 26 L32 5 L34 26 Z'/%3E%3Cpath d='M36 26 L38 11 L40 26 Z'/%3E%3C/g%3E%3C/svg%3E");
      mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='26' viewBox='0 0 40 26'%3E%3Cg fill='black'%3E%3Cpath d='M2 26 L4 4 L6 26 Z'/%3E%3Cpath d='M9 26 L11 9 L13 26 Z'/%3E%3Cpath d='M16 26 L18 2 L20 26 Z'/%3E%3Cpath d='M23 26 L25 10 L27 26 Z'/%3E%3Cpath d='M30 26 L32 5 L34 26 Z'/%3E%3Cpath d='M36 26 L38 11 L40 26 Z'/%3E%3C/g%3E%3C/svg%3E");
      -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
      -webkit-mask-size: 40px 26px; mask-size: 40px 26px;
      -webkit-mask-position: bottom left; mask-position: bottom left;
    }
    /* Front tuft layer — shorter fuller blades in the main grass colour. */
    .garden-plot::after {
      content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 20px;
      z-index: 1; pointer-events: none;
      background-color: var(--grass-front);
      -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='20' viewBox='0 0 36 20'%3E%3Cg fill='black'%3E%3Cpath d='M0 20 L3 6 L6 20 Z'/%3E%3Cpath d='M6 20 L9 10 L12 20 Z'/%3E%3Cpath d='M12 20 L15 4 L18 20 Z'/%3E%3Cpath d='M18 20 L21 11 L24 20 Z'/%3E%3Cpath d='M24 20 L27 7 L30 20 Z'/%3E%3Cpath d='M30 20 L33 12 L36 20 Z'/%3E%3C/g%3E%3C/svg%3E");
      mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='20' viewBox='0 0 36 20'%3E%3Cg fill='black'%3E%3Cpath d='M0 20 L3 6 L6 20 Z'/%3E%3Cpath d='M6 20 L9 10 L12 20 Z'/%3E%3Cpath d='M12 20 L15 4 L18 20 Z'/%3E%3Cpath d='M18 20 L21 11 L24 20 Z'/%3E%3Cpath d='M24 20 L27 7 L30 20 Z'/%3E%3Cpath d='M30 20 L33 12 L36 20 Z'/%3E%3C/g%3E%3C/svg%3E");
      -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
      -webkit-mask-size: 36px 20px; mask-size: 36px 20px;
      -webkit-mask-position: bottom left; mask-position: bottom left;
    }
    /* Night: warm true-greens, not the cyan mint, so the lawn stays "grass"
       against the dark navy card. */
    body.theme-night .garden-plot {
      --grass-front: #3DBB8E; --grass-back: #1E7A5A;
      --grass-sky-top: #1f2647; --grass-sky-bot: #171d3a;
      --grass-border: rgba(61,187,142,0.22);
    }
    /* Low Luminescence: same "warm true-green, not cyan mint" reasoning as
       theme-night above, but the sky gradient is tinted toward low-lume's
       own near-black bg (#0D0F14/#161A23) rather than reusing theme-night's
       navy — copying theme-night's sky values here would reintroduce a
       hint of navy blue into a theme whose whole point is a flatter,
       darker, lower-contrast palette. */
    body.theme-low-lume .garden-plot {
      --grass-front: #359977; --grass-back: #1A5C46;
      --grass-sky-top: #14171E; --grass-sky-bot: #0F1116;
      --grass-border: rgba(53,153,119,0.20);
    }
    body.theme-contrast .garden-plot {
      --grass-front: #00FF00; --grass-back: #00b800;
      --grass-sky-top: #000000; --grass-sky-bot: #0a0a0a;
      --grass-border: #FFFFFF;
    }
    body.theme-contrast-light .garden-plot {
      --grass-front: #1B5E20; --grass-back: #0d3d13;
      --grass-sky-top: #FFFFFF; --grass-sky-bot: #f4f4f4;
      --grass-border: #000000;
    }
    .garden-plant {
      position: relative;
      z-index: 2;
      font-size: 26px; line-height: 1;
      background: none; border: none; padding: 0; cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      animation: plantPop 0.5s cubic-bezier(0.175,0.885,0.32,1.4) both;
    }
    /* Watering only ever adds: a damp patch and a bead of dew that stay for
       good. A plant that has never been watered still looks perfectly happy. */
    .garden-plant.is-watered { filter: saturate(1.12); }
    .garden-plant.is-watered::after {
      content: ''; position: absolute; left: 50%; bottom: -4px;
      transform: translateX(-50%); width: 22px; height: 5px; border-radius: 50%;
      background: rgba(14,140,122,0.3); pointer-events: none;
    }
    .garden-plant.is-watered::before {
      content: ''; position: absolute; top: -1px; right: -2px;
      width: 7px; height: 7px; border-radius: 50% 50% 50% 0;
      transform: rotate(-45deg); pointer-events: none;
      background: linear-gradient(160deg, #DCF3FF, #5AB6E8);
      box-shadow: 0 0 0 1px rgba(255,255,255,0.55);
    }
    .garden-plant.drink { animation: plantDrink 0.6s ease; }
    @keyframes plantDrink {
      0%,100% { transform: translateY(0) scale(1,1); }
      35% { transform: translateY(2px) scale(1.12,0.88); }
      70% { transform: translateY(-3px) scale(0.94,1.1); }
    }
    .garden-drop, .garden-splash { position: absolute; pointer-events: none; }
    .garden-drop {
      top: -12px; width: 6px; height: 9px; border-radius: 50% 50% 50% 0;
      background: linear-gradient(160deg, #DCF3FF, #4FA8DC);
      animation: dropFall 0.6s ease-in forwards;
    }
    @keyframes dropFall {
      0%   { opacity: 0; transform: translateY(-6px) rotate(-45deg) scale(0.6); }
      25%  { opacity: 1; }
      100% { opacity: 0; transform: translateY(24px) rotate(-45deg) scale(1); }
    }
    .garden-splash {
      left: 50%; bottom: -3px; width: 10px; height: 10px; border-radius: 50%;
      border: 2px solid rgba(90,182,232,0.85);
      animation: splashRing 0.5s ease-out forwards;
    }
    @keyframes splashRing {
      0%   { opacity: 0.9; transform: translateX(-50%) scale(0.3); }
      100% { opacity: 0; transform: translateX(-50%) scale(2.4); }
    }
    .garden-plant:focus-visible { outline: 3px solid var(--sky); outline-offset: 3px; border-radius: 6px; }
    .garden-plant.wiggle { animation: plantWiggle 0.5s ease; }
    @keyframes plantWiggle {
      0%,100% { transform: rotate(0) scale(1); }
      30% { transform: rotate(-12deg) scale(1.25); }
      65% { transform: rotate(10deg) scale(1.15); }
    }
    /* Golden plants: ~1 in 50, purely decorative — a soft pulsing glow plus
       a tiny twinkle riding alongside the plant (a sibling span, not a
       pseudo-element, since ::before/::after on .garden-plant are already
       spoken for by the watered-state dew drop and can co-occur). */
    .garden-plant.is-golden {
      animation: plantPop 0.5s cubic-bezier(0.175,0.885,0.32,1.4) both, goldenPulse 2.2s ease-in-out infinite;
    }
    .garden-plant.is-golden.is-watered { filter: saturate(1.12); }
    @keyframes goldenPulse {
      0%,100% { filter: drop-shadow(0 0 3px rgba(255,196,40,0.65)); }
      50% { filter: drop-shadow(0 0 8px rgba(255,210,80,0.95)); }
    }
    .golden-sparkle {
      position: absolute; top: -9px; right: -7px; font-size: 11px;
      pointer-events: none; animation: goldenTwinkle 1.7s ease-in-out infinite;
    }
    @keyframes goldenTwinkle {
      0%,100% { opacity: 0.35; transform: scale(0.8) rotate(0deg); }
      50% { opacity: 1; transform: scale(1.15) rotate(14deg); }
    }
    /* v1.31.0: marks the single most recently grown plant (see
       js/garden/garden-ui.js's renderGarden — only ever applied to the
       last item of the last bed, since the log is strictly
       append-ordered) so the garden reads as "still growing" rather
       than a static wall of near-identical emoji. Sibling <span>, same
       pattern as .golden-sparkle above — .garden-plant's ::before/
       ::after are already used by the is-watered dew-drop marker, so a
       pseudo-element here would collide with that on a plant that's
       both watered and newest. Uses var(--sun), the app's plain UI gold
       accent (matching the favourites-shortcut badge), deliberately
       different from goldenPulse's hardcoded amber glow above — that
       one means "rare cosmetic event", this one means "ordinary but
       recent", and reusing the same visual would blur the two. */
    .newest-plant-ring {
      position: absolute; inset: -6px; border-radius: 50%;
      border: 1.5px solid var(--sun); pointer-events: none;
      animation: newestPlantPulse 1.6s ease-in-out infinite;
    }
    @keyframes newestPlantPulse {
      0%,100% { transform: scale(1); opacity: 0.55; }
      50% { transform: scale(1.12); opacity: 1; }
    }
    @media (prefers-reduced-motion: reduce) {
      .newest-plant-ring { animation: none; opacity: 0.85; }
    }
    /* A rare cameo drifting across whichever bed is on screen — not part of
       the flower-count critter system, never saved, gone in a few seconds. */
    .garden-rare-visitor {
      position: absolute; top: 8px; left: -32px; font-size: 24px;
      pointer-events: none; z-index: 3;
      animation: rareVisitorDrift 5s linear forwards;
    }
    @keyframes rareVisitorDrift {
      0%   { left: -32px; opacity: 0; transform: translateY(0) rotate(0deg); }
      10%  { opacity: 1; }
      50%  { transform: translateY(-16px) rotate(8deg); }
      90%  { opacity: 1; }
      100% { left: 105%; opacity: 0; transform: translateY(8px) rotate(-8deg); }
    }
    /* Visitors drift rather than pop — they arrived, they weren't planted. */
    .garden-critter {
      position: relative;
      font-size: 22px; line-height: 1; align-self: flex-end;
      background: none; border: none; padding: 0; cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      animation: critterFloat 3.2s ease-in-out infinite;
    }
    .garden-critter:focus-visible { outline: 3px solid var(--sky); outline-offset: 3px; border-radius: 6px; }
    /* Fed visitors keep a faint golden shimmer. Nothing here ever gets hungry. */
    .garden-critter.is-fed { filter: drop-shadow(0 0 3px rgba(255,190,60,0.95)); }
    .garden-critter.happy { animation: critterHappy 0.85s ease; }
    @keyframes critterHappy {
      0%   { transform: translateY(0) rotate(0); }
      45%  { transform: translateY(-12px) rotate(180deg); }
      100% { transform: translateY(0) rotate(360deg); }
    }
    .garden-nectar, .garden-pollen { position: absolute; pointer-events: none; }
    .garden-nectar {
      left: 50%; bottom: -7px; width: 9px; height: 9px; border-radius: 50%;
      background: radial-gradient(circle at 32% 30%, #FFEEB4, #F2A93B);
      animation: nectarSip 0.75s ease-in forwards;
    }
    @keyframes nectarSip {
      0%   { opacity: 0; transform: translate(-50%, 8px) scale(0.4); }
      30%  { opacity: 1; transform: translate(-50%, 0) scale(1); }
      100% { opacity: 0; transform: translate(-50%, -3px) scale(0.25); }
    }
    .garden-pollen {
      top: 4px; width: 5px; height: 5px; border-radius: 50%;
      background: #FFD25A; box-shadow: 0 0 4px rgba(255,196,60,0.9);
      animation: pollenRise 0.8s ease-out forwards;
    }
    @keyframes pollenRise {
      0%   { opacity: 0; transform: translateY(0) scale(0.5); }
      30%  { opacity: 1; }
      100% { opacity: 0; transform: translateY(-17px) scale(1); }
    }
    @keyframes critterFloat {
      0%,100% { transform: translateY(0) rotate(-4deg); }
      50% { transform: translateY(-7px) rotate(4deg); }
    }
    .garden-nav {
      display: flex; align-items: center; justify-content: center; gap: 12px;
      margin-top: 10px;
    }
    .garden-nav[hidden] { display: none; }
    .garden-nav button {
      width: 34px; height: 34px; border-radius: 50%; cursor: pointer;
      border: 2px solid rgba(14,140,122,0.3); background: var(--card);
      color: var(--sprout-green-deep); font-size: 0.8rem; line-height: 1;
      -webkit-tap-highlight-color: transparent;
    }
    .garden-nav button:disabled { opacity: 0.35; cursor: default; }
    .garden-nav button:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    body.theme-night .garden-nav button { background: #1B2140; }
    #gardenBedLabel {
      font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 0.8rem;
      color: var(--ink-soft); min-width: 92px; text-align: center;
    }
    .garden-note {
      font-family: 'Nunito', sans-serif; font-weight: 700; font-size: 0.76rem;
      color: var(--ink-soft); text-align: center; margin-top: 4px; line-height: 1.4;
    }
    .garden-note[hidden] { display: none; }
    .garden-caption.is-memory { color: var(--ink); line-height: 1.4; }
    .garden-memory-name { font-weight: 900; }
    /* display scoped so the `hidden` attribute still works — see
       .plate-attention-flag in main.css for why. */
    .garden-actions:not([hidden]) { display: flex; }
    .garden-actions {
       justify-content: center; align-items: center;
      gap: 8px; flex-wrap: wrap; margin-top: 8px;
    }
    .garden-actions[hidden] { display: none; }
    .garden-action {
      font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 0.78rem;
      color: var(--ink); background: var(--card); cursor: pointer;
      border: 2px solid rgba(14,140,122,0.35); border-radius: 999px;
      padding: 6px 13px; -webkit-tap-highlight-color: transparent;
    }
    .garden-action:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    .garden-action.is-done { border-style: dotted; color: var(--ink-soft); }
    body.theme-night .garden-action { background: #1B2140; }
    .garden-name-input {
      font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 0.82rem;
      color: var(--ink); background: var(--card); text-align: center;
      border: 2px solid rgba(14,140,122,0.45); border-radius: 999px;
      padding: 6px 12px; width: 148px; max-width: 55%;
    }
    .garden-name-input:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    body.theme-night .garden-name-input { background: #1B2140; }
    .garden-memory-quip {
      display: block; font-family: 'Nunito', sans-serif; font-weight: 700;
      font-size: 0.76rem; color: var(--ink-soft); margin-top: 3px;
    }
    @keyframes plantPop { 0% { transform: scale(0) translateY(8px); } 100% { transform: scale(1) translateY(0); } }
    .garden-empty { position: relative; z-index: 2; font-family:'Nunito',sans-serif; font-weight:700; color:var(--ink-soft); font-size:0.85rem; align-self:center; }
    .garden-caption { font-family:'Nunito',sans-serif; font-weight:800; color:var(--ink-soft); font-size:0.82rem; text-align:center; margin-top:10px; min-height: 2.6em; }
    @media (prefers-reduced-motion: reduce) {
      .garden-plant, .garden-plant.wiggle, .garden-plant.drink, .garden-plant.is-golden,
      .golden-sparkle, .garden-rare-visitor,
      .garden-critter, .garden-critter.happy { animation: none; }
    }

    /* ---- Full-screen level-up takeover ---- */
    .levelup-overlay {
      position: fixed; inset: 0; z-index: 9999;
      display: none; align-items: center; justify-content: center;
      background: radial-gradient(circle at center, rgba(14,140,122,0.55), rgba(11,18,40,0.92));
      backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
      text-align: center; padding: 24px;
    }
    .levelup-overlay.show { display: flex; animation: luFade 0.3s ease; }
    @keyframes luFade { from { opacity: 0; } to { opacity: 1; } }
    .levelup-inner { position: relative; z-index: 2; }
    .levelup-icon { font-size: 5rem; line-height: 1; animation: luPop 0.6s cubic-bezier(0.175,0.885,0.32,1.4) both; }
    @keyframes luPop { 0% { transform: scale(0) rotate(-25deg); } 70% { transform: scale(1.25) rotate(8deg); } 100% { transform: scale(1) rotate(0); } }
    .levelup-kicker {
      font-family: 'Baloo 2', sans-serif; font-weight: 800; letter-spacing: 0.12em;
      text-transform: uppercase; font-size: 0.95rem; color: #FFE194; margin-top: 10px;
    }
    .levelup-title {
      font-family: 'Baloo 2', sans-serif; font-weight: 800; color: #FFFFFF;
      font-size: 2.1rem; line-height: 1.1; margin: 4px 0 6px;
      text-shadow: 0 3px 0 rgba(0,0,0,0.25);
    }
    .levelup-sub { font-family:'Nunito',sans-serif; font-weight:800; color:#DFF7EF; font-size:1rem; margin-bottom:22px; }
    .levelup-btn {
      border: none; padding: 14px 34px; border-radius: 999px;
      background: var(--sun); color: #4A3B00; font-family:'Baloo 2',sans-serif;
      font-weight: 800; font-size: 1.1rem; cursor: pointer;
      box-shadow: 0 6px 0 rgba(0,0,0,0.2);
    }
    .levelup-btn:active { transform: translateY(3px); box-shadow: none; }
    @media (prefers-reduced-motion: reduce) {
      .levelup-icon, .levelup-overlay.show { animation: none; }
    }

    .shortcut-share-container {
      margin-top: 14px;
      display: none;
      animation: pop 0.4s ease;
    }
    .shortcut-share-btn {
      width: 100%; border: 3px solid var(--sky); padding: 14px;
      background: var(--card); color: var(--sky);
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 1.05rem;
      border-radius: 18px; cursor: pointer;
      display: flex; align-items: center; justify-content: center; gap: 8px;
      box-shadow: 0 4px 12px var(--shadow);
      transition: background 0.2s, transform 0.1s;
    }
    .shortcut-share-btn:active { transform: scale(0.98); }

    .confirm-share-btn {
      width: 100%; border: none; padding: 12px; background: var(--sky); color: white;
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 1rem; border-radius: 12px;
      cursor: pointer; box-shadow: 0 4px 10px rgba(63,169,245,0.25);
      transition: all 0.2s ease, transform 0.1s ease;
    }
    .confirm-share-btn:disabled { background: #B0BEC5 !important; color: #FFFFFF !important; opacity: 0.6; cursor: not-allowed; box-shadow: none; }

    .badges-current-rank {
      display: flex; align-items: center; gap: 9px;
      background: #FFF4D6; border: 1.5px solid #FBBF24; border-radius: 12px;
      padding: 9px 11px; margin-bottom: 10px;
    }
    body.theme-night .badges-current-rank { background: rgba(255,193,7,0.14); border-color: rgba(255,193,7,0.4); }
    /* v1.36.1: same gap as .fav-food-chip-pinned above — theme-low-lume
       had no override here, so it fell through to the bright #FFF4D6
       base background. Mirrors theme-night's own approach (a translucent
       gold overlay rather than an opaque colour, so the card's own dark
       background still shows through) rather than inventing a new
       pattern; opacity trimmed slightly below theme-night's (0.10 vs
       0.14) to stay a touch dimmer, consistent with low-lume's own
       --sun already being a dimmer gold than the default theme's. */
    body.theme-low-lume .badges-current-rank { background: rgba(246,224,94,0.10); border-color: rgba(246,224,94,0.35); }
    .badges-current-rank-icon { font-size: 1.3rem; flex: 0 0 auto; }
    .badges-current-rank-text {
      font-family: 'Nunito', sans-serif; font-weight: 700; font-size: 0.76rem;
      color: #8A5A00; line-height: 1.35;
    }
    body.theme-night .badges-current-rank-text { color: #FFD98A; }
    body.theme-low-lume .badges-current-rank-text { color: #E8D584; }
    .badges-current-rank-text b { font-family: 'Baloo 2', sans-serif; font-weight: 800; }
    .badges-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 12px 6px;
      margin-top: 4px;
    }
    .badge-item {
      background: var(--bg);
      border: 2px solid var(--bg2);
      border-radius: 14px;
      padding: 10px 2px;
      text-align: center;
      font-size: 0.72rem;
      font-weight: 800;
      opacity: 0.35;
      filter: grayscale(100%);
      position: relative;
      transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    }
    body.theme-night .badge-item { background: #1B2140; border-color: #262C52; }
    .badge-item.unlocked {
      opacity: 1;
      filter: grayscale(0%);
      border-color: var(--mint);
      background: rgba(46, 196, 182, 0.08);
      cursor: help;
    }
    .badge-item:not(.unlocked):hover {
      opacity: 0.85;
      transform: translateY(-2px);
    }
    .badge-icon {
      font-size: 1.75rem;
      margin-bottom: 3px;
      display: block;
    }
    .badge-title {
      color: var(--ink);
      line-height: 1.2;
      display: block;
    }

    .badge-item.unlocked::after {
      content: "Earned: " attr(data-hint);
      position: absolute;
      bottom: 105%;
      left: 50%;
      transform: translateX(-50%) translateY(4px);
      background: #1F2A44;
      color: #FFFFFF;
      padding: 6px 10px;
      border-radius: 8px;
      font-size: 0.72rem;
      font-weight: 700;
      line-height: 1.3;
      white-space: normal;
      width: 100px;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s ease, transform 0.2s ease;
      box-shadow: 0 4px 12px rgba(0,0,0,0.35);
      border: 1px solid rgba(255,255,255,0.15);
      z-index: 1000;
    }
    .badge-item.unlocked:hover::after, .badge-item.unlocked:focus::after {
      opacity: 1;
      transform: translateX(-50%) translateY(0);
    }

    @keyframes pop {
      0% { transform: scale(0.85); opacity: 0; }
      100% { transform: scale(1); opacity: 1; }
    }
    
    .result-card {
      background: linear-gradient(135deg, var(--mint), #004D40);
      border-radius: 18px; padding: 20px 18px; color: white; text-align: center;
      box-shadow: 0 10px 28px var(--shadow); margin-bottom: 12px; display: none;
    }
    .result-card.show { display: block; animation: pop .5s ease; }
    .result-num { font-family: 'Baloo 2', sans-serif; font-size: 2.6rem; font-weight: 800; line-height: 1; }
    .result-label { font-weight: 700; opacity: 0.9; margin-top: 2px; }
    .sprout-says { margin-top: 12px; background: rgba(255,255,255,0.2); border-radius: 14px; padding: 10px 12px; font-weight: 700; font-size: 0.9rem; }

    .meal-log-card {
      background: var(--card); border-radius: var(--radius-lg); padding: 20px 18px;
      box-shadow: 0 10px 28px var(--shadow); margin-bottom: 18px; display: none;
    }
    .garden-card {
      position: relative;
      background: var(--card); border-radius: var(--radius-lg); padding: 20px 18px;
      box-shadow: 0 10px 28px var(--shadow); margin-bottom: 18px;
    }
    /* Decorative rainbow band removed by request — the plot keeps its soft
       green tint (from .garden-plot's own background) and dashed border, so
       plants still sit on a subtle backdrop rather than a bare card. */
    .meal-log-card.show { display: block; animation: pop .5s ease; }
    .meal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; border-bottom: 2px dashed var(--bg2); padding-bottom: 8px; }
    .meal-header h2 { font-size: 1.15rem; }
    .meal-header-total {
      /* v1.37.0: this is the running plate total — arguably the single
         highest-stakes number in the entire app, and it had zero
         font-scale support before this, unaffected by "Large"/"Larger". */
      font-size: calc(0.85rem * var(--font-scale, 1));
      font-weight: 800; color: var(--sky); margin-top: 2px;
    }

    /* Plate needs-attention flag (5 Sep 2026). Replaces the removed
       header-button badge (see index.html's own comment on
       #openFavDrawerBtn) — hidden unless something on the plate
       genuinely needs fixing, worded explicitly (js/meals/meals-render.js
       sets the text) rather than showing a bare number, since a digit
       with no label was the entire problem being fixed here. Amber
       matches this app's existing warning convention (guess/estimate
       indicators elsewhere use the same colour), not a new colour
       invented for this. */
    /* display is scoped to :not([hidden]) deliberately (6 Sep 2026). The
       `hidden` attribute works by applying display:none from the browser's
       DEFAULT stylesheet — the weakest source in the cascade — so a plain
       `display: flex` here silently overrode it and the flag could never
       hide, regardless of what js/meals/meals-render.js decided. It sat
       permanently visible showing index.html's hardcoded placeholder text
       ("needs a number"), which is why it appeared on items that were
       perfectly fine. The JS was correct throughout: it set .hidden = true
       properly, and jsdom tests confirmed that property — jsdom just
       doesn't apply the CSS cascade, so only a real browser showed it. */
    .plate-attention-flag:not([hidden]) {
      display: flex;
    }
    .plate-attention-flag {
      flex: 0 0 auto;
      align-items: center; gap: 4px;
      background: var(--sun); color: #5C4400;
      border: none; border-radius: 999px;
      padding: 6px 12px;
      font-family: inherit; font-weight: 800; font-size: 0.76rem;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .plate-attention-flag:active { transform: scale(0.96); }

    /* Brief highlight on the plate item the flag scrolled to, so it's
       unmistakable which one it meant rather than just "the page moved
       somewhere." Removed by JS after ~1.6s (window.scrollToPlate
       AttentionItem in js/meals/meals-render.js), not left as a
       permanent state. */
    .meal-item.flash-attention {
      animation: mealItemFlash 1.6s ease-out;
    }
    @keyframes mealItemFlash {
      0%   { background: rgba(255, 201, 60, 0.35); }
      100% { background: transparent; }
    }
    @media (prefers-reduced-motion: reduce) {
      .meal-item.flash-attention { animation: none; background: rgba(255, 201, 60, 0.35); }
    }

    .clear-btn { background: none; border: none; color: var(--mint); font-weight: 800; font-size: 0.85rem; cursor: pointer; }

    /* Promoted "Log This Meal" CTA (3 Sep 2026). Deliberately matches
       .go-btn's visual weight — it IS the same class of action (a primary
       commit), and previously it was a 0.85rem text link while the
       intermediate "add food" step got the hero treatment. That
       hierarchy was backwards. */
    .log-meal-btn {
      width: 100%;
      margin-top: 14px;
      border: none;
      padding: 14px 16px;
      background: linear-gradient(135deg, var(--sprout-green), var(--sprout-green-deep));
      color: #fff;
      border-radius: 18px;
      cursor: pointer;
      box-shadow: 0 8px 18px rgba(0,89,78,0.32);
      display: flex; flex-direction: column; align-items: center; gap: 3px;
      -webkit-tap-highlight-color: transparent;
    }
    .log-meal-btn:active { transform: scale(0.985); }
    .log-meal-btn-main {
      font-family: 'Baloo 2', sans-serif; font-weight: 700; font-size: 1.15rem;
    }
    .log-meal-btn-sub {
      display: flex; align-items: center; gap: 6px;
      font-size: 0.76rem; font-weight: 700;
      color: rgba(255,255,255,0.92);
    }
    .log-meal-btn-stickers { display: inline-flex; align-items: center; gap: 2px; }
    .log-meal-btn-stickers img { width: 20px; height: 20px; display: block; }

    /* Post-log reward panel. Appears where the meal card was, instantly.
       See index.html's own comment on #mealLoggedPanel for why this is a
       panel rather than a toast.

       BUG (found 5 Sep 2026, reported as "the sticker panel never
       appears"): the animation was declared here, on the base selector,
       not on .show below. CSS animations start the moment an element
       MATCHES their selector — independent of display:none — so this ran
       to completion while the panel was still hidden, before JS ever
       added .show. By the time .show flipped display to block, the
       animation declaration hadn't changed, so it never re-triggered.
       Net effect: window.showMealLoggedPanel() was executing correctly
       (confirmed by tracing call order, DOM position, and findEntry
       matching — all fine) and the panel was structurally present, but
       visually never appeared. Moved the animation onto .show, which is
       the actual state transition that should trigger it. */
    .meal-logged-panel {
      display: none;
      background: var(--card);
      border-radius: var(--radius-lg);
      box-shadow: 0 8px 24px var(--shadow);
      padding: 16px;
      margin-bottom: 18px;
    }
    .meal-logged-panel.show {
      display: block;
      animation: mealLoggedPanelIn 0.25s ease-out;
    }
    @keyframes mealLoggedPanelIn {
      from { opacity: 0; transform: translateY(8px); }
      to { opacity: 1; transform: translateY(0); }
    }
    @media (prefers-reduced-motion: reduce) {
      .meal-logged-panel.show { animation: none; }
    }
    .meal-logged-panel-head { display: flex; align-items: center; gap: 10px; }
    .meal-logged-panel-tick { font-size: 1.5rem; line-height: 1; }
    .meal-logged-panel-title {
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 1.15rem; color: var(--ink);
    }
    .meal-logged-panel-total { font-size: 0.82rem; font-weight: 700; color: var(--ink-soft); }
    .meal-logged-panel-close {
      margin-left: auto; background: none; border: none; cursor: pointer;
      color: var(--ink-soft); font-size: 1rem;
      width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
    }
    .meal-logged-panel-ask {
      margin: 12px 0 8px; text-align: center;
      font-weight: 800; font-size: 0.9rem; color: var(--ink);
    }

    .meal-item {
      display: flex; justify-content: space-between; align-items: center;
      padding: 10px 12px; background: var(--bg); border-radius: 12px; margin-bottom: 8px;
    }
    body.theme-night .meal-item { background: #1B2140; }
    .theme-contrast .meal-item, body.theme-contrast-light .meal-item { background: var(--bg2); border: 1px solid var(--ink); }
    .meal-item-name-wrap { flex: 1; cursor: pointer; }
    .meal-item-emoji { font-size: 22px; line-height: 1; margin-right: 10px; flex: 0 0 auto; }
    .meal-item-title {
      /* v1.37.0: added explicit font-scale support — this had no
         font-size of its own before, inheriting all the way up with
         nothing in that chain responding to --font-scale either, so the
         item's own NAME never grew under the "Large"/"Larger" text-size
         setting. calc(1em * var(--font-scale, 1)) rather than a fixed px
         value: 1em preserves whatever this already renders at today when
         the scale is 1 (no visual change at the default setting), and
         only the multiplier is new. */
      font-size: calc(1em * var(--font-scale, 1));
      font-weight: 800; color: var(--ink); text-transform: capitalize;
    }
    .rename-hint { font-size: 0.7rem; color: var(--ink-soft); font-weight: 600; margin-left: 6px; }
    .meal-item-details { font-size: calc(0.75rem * var(--font-scale, 1)); color: var(--ink-soft); font-weight: 600; margin-top: 2px; }
    .meal-item-val {
      /* v1.37.0: same reasoning as .meal-item-title above — this is the
         actual carb amount for the item (e.g. "45g"), arguably the single
         most important number in this row, and it had no font-scale
         support at all before this. */
      font-size: calc(1em * var(--font-scale, 1));
      font-weight: 800; color: var(--ink); margin: 0 10px; font-family: 'Baloo 2', sans-serif;
    }
    .del-btn { background: none; border: none; color: var(--ink-soft); font-size: 20px; cursor: pointer; padding: 0 4px; }
    /* Sits between the value and the bin. Deliberately quieter than the carb
       figure next to it — a repeat is a convenience, not the thing being read. */
    /* Border and glyph follow --ink so the ring reads as a clear outline in
       every theme: near-white on the dark themes, near-black on the light
       ones. var(--bg2) was a background tint in several palettes, which left
       the circle almost invisible against the row it sits on. */
    .repeat-btn { background: var(--bg); border: 1.5px solid var(--ink); color: var(--ink);
      font-family: 'Nunito', sans-serif; font-weight: 800;
      font-size: calc(12px * var(--font-scale, 1)); line-height: 1;
      border-radius: 999px; padding: 0 10px; margin-right: 2px; cursor: pointer;
      min-height: 34px; min-width: 40px; }
    .repeat-btn:hover, .repeat-btn:focus-visible { border-color: var(--sprout-green); color: var(--sprout-green); }
    .repeat-btn:active { transform: scale(0.94); }
    @media (prefers-reduced-motion: reduce) { .repeat-btn:active { transform: none; } }
    
    /* v1.37.0: this is the persistent safety disclaimer shown on every
       screen ("Sprout only works out carbs — nothing else...") — added
       font-scale support since it's meaningfully safety-relevant text,
       not a cosmetic label. */
    .disclaimer { text-align: center; font-size: calc(0.75rem * var(--font-scale, 1)); color: var(--ink-soft); margin-top: 24px; font-weight: 700; line-height: 1.4; padding: 0 12px; }
    
    .overlay { position: fixed; inset: 0; background: rgba(31,42,68,0.5); backdrop-filter: blur(4px); z-index: 10; display: none; opacity: 0; transition: opacity 0.3s; }
    .overlay.show { display: block; opacity: 1; }
    
    .drawer {
      position: fixed; top: 0; bottom: 0; right: 0; width: 295px; background: var(--card); z-index: 20;
      padding: 24px 16px 30px; transform: translateX(100%); transition: transform .35s ease;
      box-shadow: -10px 0 30px rgba(0,0,0,0.2); overflow-y: auto;
    }
    .drawer.show { transform: translateX(0); }
    .drawer h2 { font-size: 1.2rem; margin-bottom: 14px; }
    .drawer .close-btn { position: absolute; top: 16px; right: 16px; border: none; background: none; font-size: 22px; cursor: pointer; color: var(--ink-soft); }

    /* .drawer-left removed 4 Sep 2026 — it was only ever used by
       #favDrawer (My Meals), which became a full-width bottom sheet.
       The 295px side-drawer width was the root cause of the truncation
       and icon-wrap problems patched in v1.16.0; widening the container
       removed the constraint instead of managing it. TO REVERT: restore
       `.drawer-left { left:0; right:auto; transform:translateX(-100%);
       box-shadow:10px 0 30px rgba(0,0,0,0.2); }` plus its `.show` rule,
       and put `class="drawer drawer-left"` back on #favDrawer. */

    /* My Meals bottom sheet. Structure mirrors the existing
       .sprout-score-sheet (handle, slide-up, pinned to the bottom of a
       flex-end overlay) rather than inventing a second sheet idiom. */
    .meals-sheet {
      position: fixed; inset: 0; z-index: 30;
      display: none; align-items: flex-end; justify-content: center;
      background: rgba(0,0,0,0.5);
    }
    .meals-sheet.show { display: flex; }
    .meals-sheet-inner {
      width: 100%; max-width: 440px;
      max-height: 85vh; overflow-y: auto;
      background: var(--bg);
      border-radius: 26px 26px 0 0;
      padding: 10px 16px 26px;
      animation: mealsSheetUp 0.28s ease-out;
    }
    @keyframes mealsSheetUp {
      from { transform: translateY(100%); }
      to { transform: translateY(0); }
    }
    @media (prefers-reduced-motion: reduce) {
      .meals-sheet-inner { animation: none; }
    }
    .meals-sheet-inner .sheet-handle {
      width: 40px; height: 5px; border-radius: 999px;
      background: var(--bg2);
      margin: 0 auto 12px;
    }
    .meals-sheet-head {
      display: flex; align-items: center; justify-content: space-between;
      gap: 10px; margin-bottom: 6px;
    }
    .meals-sheet-head h2 { font-size: 1.15rem; margin: 0; }
    .meals-sheet-close {
      flex: 0 0 auto; width: 44px; height: 44px;
      background: var(--card); border: none; border-radius: 12px;
      color: var(--ink-soft); font-size: 1rem; cursor: pointer;
      display: flex; align-items: center; justify-content: center;
      -webkit-tap-highlight-color: transparent;
    }
    .meals-sheet-close:active { transform: scale(0.92); }
    /* The shared .fav-search-wrap is --bg, which is also the sheet's own
       surface — invisible. Scoped override rather than changing the
       shared rule, which the food search and favourite-foods panels
       also use on --card surfaces where --bg is correct. */
    .meals-sheet .fav-search-wrap { background: var(--card); }
    .fav-drawer-hint { font-size: 0.8rem; font-weight: 600; color: var(--ink-soft); line-height: 1.4; margin-bottom: 14px; }

    .fav-search-wrap {
      display: none; align-items: center; gap: 8px; background: var(--bg); border: 2px solid var(--bg2);
      border-radius: 12px; padding: 9px 12px; margin-bottom: 14px;
    }
    .fav-search-wrap.show { display: flex; }
    .fav-search-icon { font-size: 0.9rem; flex-shrink: 0; opacity: 0.7; }
    .fav-search-input {
      border: none; background: transparent; outline: none; color: var(--ink); font-weight: 700;
      /* Was 0.9rem (14.4px) — under Safari's 16px auto-zoom-on-focus
         threshold, so focusing this field zoomed the whole viewport in on
         iOS. TO REVERT: back to 0.9rem. */
      font-size: 1rem; width: 100%; font-family: inherit; min-width: 0;
    }
    .fav-search-input::placeholder { color: var(--ink-soft); font-weight: 600; }
    body.theme-night .fav-search-wrap { background: #1B2140; border-color: #262C52; }

    .fav-empty-state { display: flex !important; flex-direction: column; align-items: center; gap: 6px; padding: 26px 8px !important; text-align: center; }
    .fav-empty-icon { font-size: 2.2rem; margin-bottom: 2px; }

    /* ---- Favourite MEALS as a compact list (matches fav-list-row foods view) ---- */
    .fav-mrow {
      border-bottom: 1px solid var(--bg2); animation: pop 0.3s ease;
    }
    .fav-mrow:last-child { border-bottom: none; }
    /* ============================================================
       FAVOURITE MEAL ROW — REDESIGNED. A previous fix stacked name
       above meta so the meta text stopped stealing the name's width —
       real improvement, but it only changed the RATIO of space, and the
       underlying assumption was still wrong: #saveFavInput allows up to
       40 characters (maxlength="40", confirmed in index.html), so a
       favourite meal's name isn't really a short label like "Pizza" —
       it's closer to a short SENTENCE a caregiver deliberately typed
       (e.g. "Wednesday After School Snack With Extra Biscuits").
       Single-line-plus-ellipsis is the wrong
       treatment for that: it silently throws away exactly the
       information the caregiver bothered to write, right when they
       need to tell several saved meals apart in a scannable list.
       Redesign: the name now wraps to up to two lines instead of
       truncating to one (line-clamp), and meta/chevron move to their
       own row underneath so the row's height flexes naturally per item
       rather than every row being forced to one fixed height regardless
       of name length. Padding increased so two-line rows don't feel
       cramped against their neighbours in a list.
       ============================================================ */
    .fav-mrow-main {
      display: flex; align-items: flex-start; gap: 10px; padding: 13px 4px;
      cursor: pointer; user-select: none; -webkit-user-select: none;
      -webkit-tap-highlight-color: transparent;
    }
    .fav-mrow-main:active { background: var(--bg); }
    body.theme-night .fav-mrow-main:active { background: #131737; }
    .fav-mrow-main:focus-visible { outline: 2px solid var(--sky); outline-offset: 1px; }
    .fav-mrow-avatar { font-size: 18px; width: 26px; text-align: center; flex-shrink: 0; margin-top: 1px; }
    .fav-mrow-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
    .fav-mrow-name {
      font-size: 0.9rem; font-weight: 800; color: var(--ink); line-height: 1.28;
      font-family: 'Baloo 2', sans-serif;
      /* Wraps up to 2 lines instead of truncating to 1 (see the block
         comment above) — a genuinely long name (up to 40 chars) stays
         readable rather than being cut down to its first ~20
         characters. A -webkit-line-clamp value still needs a fallback
         for the rare non-WebKit engine; the fallback simply lets a
         very long single word overflow rather than break mid-word,
         which is an acceptable rare-case trade-off. */
      display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
      overflow: hidden; word-break: break-word;
    }
    .fav-mrow-meta-row {
      display: flex; align-items: center; justify-content: space-between; gap: 8px;
    }
    .fav-mrow-meta {
      font-size: 0.72rem; color: var(--ink-soft); font-weight: 700;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
    }
    .fav-mrow-chevron {
      font-size: 1rem; color: var(--ink-soft); flex-shrink: 0;
      transition: transform 0.2s ease; width: 20px; text-align: center;
    }
    .fav-mrow-chevron.open { transform: rotate(180deg); }
    .fav-mrow-details {
      display: none; flex-direction: column; gap: 5px; padding: 0 4px 12px 36px;
    }
    .fav-mrow-details.open { display: flex; animation: pop 0.2s ease; }
    .fav-mrow-detail-item {
      display: flex; justify-content: space-between; gap: 8px;
      font-size: 0.75rem; font-weight: 600; color: var(--ink-soft);
    }
    .fav-mrow-details-footer {
      display: flex; align-items: center; justify-content: space-between; gap: 8px;
      margin-top: 6px; padding-top: 8px; border-top: 1px dashed var(--bg2);
    }

    .fav-meal-quick-add {
      background: none; border: none; color: var(--mint); font-weight: 800; font-size: 0.75rem;
      cursor: pointer; padding: 4px 6px; font-family: 'Baloo 2', sans-serif;
      -webkit-tap-highlight-color: transparent;
    }
    .fav-meal-quick-add:active { opacity: 0.7; }

    .fav-meal-edit-btn {
      background: none; border: none; color: var(--sky); font-weight: 800; font-size: 0.75rem;
      cursor: pointer; padding: 4px 6px; font-family: 'Baloo 2', sans-serif; -webkit-tap-highlight-color: transparent;
    }
    .fav-meal-edit-btn:active { opacity: 0.7; }

    #favMealsList.history-container { gap: 0; max-height: none; overflow-y: visible; }

    .topbar-actions { display: flex; align-items: center; gap: 8px; }
    .fav-meals-btn { position: relative; }
    .fav-meals-btn-icon { font-size: 1.35rem; line-height: 1; }

    #openSettings { position: relative; }

    .save-fav-row { text-align: center; margin-top: 12px; }
    .save-fav-btn {
      background: rgba(126,87,194,0.10); border: 2px solid var(--grape); color: var(--grape); border-radius: 12px;
      padding: 12px 16px; font-weight: 800; font-size: 0.92rem; cursor: pointer; width: 100%;
      font-family: 'Baloo 2', sans-serif;
      transition: background 0.2s, transform 0.1s;
    }
    .save-fav-btn:hover { background: rgba(126,87,194,0.16); }
    .save-fav-btn:active { transform: scale(0.98); }
    .settings-group { margin-bottom: 20px; width: 100%; display: block; }
    .settings-group h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-soft); margin-bottom: 10px; }
    
    .drawer-accordion-header {
      position: relative;
      background: var(--bg); border-radius: 14px; padding: 12px; font-weight: 800; font-size: 0.95rem;
      color: var(--ink); display: flex; justify-content: space-between; align-items: center; cursor: pointer;
      user-select: none; -webkit-user-select: none; font-family: 'Baloo 2', sans-serif;
    }
    body.theme-night .drawer-accordion-header { background: #1B2140; }
    .drawer-accordion-content {
      display: none; padding: 12px 4px 4px 4px; animation: pop 0.25s ease;
    }
    .drawer-accordion-content.open { display: block; }
    .drawer-section-label {
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 0.7rem;
      text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-soft);
      margin: 24px 2px 12px; padding-bottom: 6px; border-bottom: 2px solid var(--bg2);
    }
    body.theme-night .drawer-section-label { border-bottom-color: #262C52; }

    /* Dismissible install banner — sits above the tabs, replacing the old
       permanent settings-group card. Dismissing it only hides it for the
       current session (sessionStorage), so it can still resurface next
       time rather than being gone for good on a stray tap. */
    .install-banner-drawer {
      display: flex; align-items: center; gap: 10px;
      background: linear-gradient(135deg, rgba(0,137,123,0.12), rgba(21,101,192,0.10));
      border: 1px solid rgba(0,137,123,0.28);
      border-radius: 14px;
      padding: 10px 10px 10px 14px;
      margin-bottom: 14px;
    }
    body.theme-night .install-banner-drawer {
      background: linear-gradient(135deg, rgba(0,137,123,0.22), rgba(21,101,192,0.18));
    }
    /* Low Luminescence: these teal/blue RGB values aren't sourced from a
       theme variable in either the base rule or theme-night's override —
       they're fixed brand-ish colours for this specific banner regardless
       of theme — so there's no low-lume-specific colour to substitute
       the way the --sky/--mint components above needed. Reuses the same
       opacity bump theme-night already validated for "visible against a
       dark background" rather than inventing new numbers with nothing to
       ground them in. */
    body.theme-low-lume .install-banner-drawer {
      background: linear-gradient(135deg, rgba(0,137,123,0.22), rgba(21,101,192,0.18));
    }
    .install-banner-drawer p {
      flex: 1 1 auto; min-width: 0; margin: 0; font-size: 0.72rem; font-weight: 700; color: var(--ink-soft); line-height: 1.35;
    }
    .install-banner-dismiss {
      flex: 0 0 auto; background: none; border: none; color: var(--ink-soft);
      font-size: 1.1rem; line-height: 1; cursor: pointer; padding: 4px;
    }
    .install-banner-cta {
      flex: 0 0 auto; width: auto; white-space: nowrap;
      border: 2px solid var(--sky); border-radius: 100px;
      padding: 7px 14px; font-size: 0.78rem;
      background: var(--card); color: var(--sky);
      font-family: 'Baloo 2', sans-serif; font-weight: 800; cursor: pointer;
      box-shadow: none;
    }
    .install-banner-cta:active { transform: scale(0.96); }

    /* Drawer tabs: Sprout / Data / Help. Only the active tab's panel
       renders, which is the main lever for making the drawer feel less
       busy — instead of every accordion stacked in one long scroll, you
       only ever see the handful belonging to the current tab. */
    .drawer-tab-row {
      display: flex; gap: 6px; margin-bottom: 16px;
    }
    .drawer-tab-btn {
      flex: 1 1 0; background: var(--bg); border: 1.5px solid transparent; border-radius: 100px;
      padding: 8px 4px; font-family: 'Baloo 2', sans-serif; font-weight: 700; font-size: 0.74rem;
      color: var(--ink-soft); cursor: pointer; display: flex; align-items: center; justify-content: center;
      gap: 5px; transition: all 0.15s ease;
    }
    body.theme-night .drawer-tab-btn { background: #1B2140; }
    .drawer-tab-btn[aria-selected="true"] {
      background: var(--card); color: var(--sky); border-color: var(--sky);
      box-shadow: 0 3px 8px var(--shadow);
    }
    body.theme-night .drawer-tab-btn[aria-selected="true"] { background: #262C52; }
    .drawer-tab-panel[hidden] { display: none; }

    /* Merged accordions (Sprout's Look; Get in touch) hold more than one
       labeled sub-section in a single body rather than nesting another
       collapsible toggle inside — keeps the merge from just re-adding the
       complexity it was meant to remove. */
    /* Live "how Sprout looks" preview shown at the top of the Look accordion,
       above the wardrobe chips, so costume/colour changes are visible right
       where they're picked instead of only on the header mascot above the
       fold. Deliberately static (no bounce/hover animation, no tap handler)
       since it's a preview, not the interactive mascot. */
    .look-preview-wrap {
      display: flex; justify-content: center; padding: 4px 0 16px;
    }
    .look-preview-mascot {
      width: 84px; height: 84px; flex: 0 0 auto; pointer-events: none;
    }
    .drawer-accordion-content .sub-section-label {
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 0.8rem;
      color: var(--ink-soft); margin: 0 0 8px;
    }
    .drawer-accordion-content .sub-section + .sub-section { margin-top: 18px; }

    .drawer-text-input {
      width: 100%; padding: 12px; border-radius: 12px; border: 2px solid var(--bg2); 
      background: var(--bg); color: var(--ink); font-weight: 700; font-size: 1rem; outline: none;
    }
    body.theme-night .drawer-text-input { background: #1B2140; border-color: #262C52; }

    .history-container {
      display: flex; flex-direction: column; gap: 12px; max-height: 250px; overflow-y: auto; width: 100%; padding-right: 4px;
    }
    .history-day-group { display: flex; flex-direction: column; gap: 4px; width: 100%; }
    .history-day-header { 
      font-size: 0.85rem; font-weight: 800; color: var(--sky); cursor: pointer; 
      padding: 10px 12px; background: var(--bg); border-radius: 12px; 
      display: flex; justify-content: space-between; align-items: center;
      font-family: 'Baloo 2', sans-serif; text-transform: capitalize;
      user-select: none; -webkit-user-select: none;
    }
    body.theme-night .history-day-header { background: #1B2140; }
    .history-day-header:active { opacity: 0.85; }
    .history-log-list { display: none; flex-direction: column; gap: 6px; margin-top: 4px; padding-left: 2px; }
    .history-log-list.open { display: flex; }
    
    .history-card-item {
      background: var(--bg); border-radius: 12px; padding: 8px 10px; font-size: 0.8rem; border-left: 4px solid var(--mint); width: 100%; box-sizing: border-box;
      display: flex; align-items: center; justify-content: space-between; gap: 8px;
    }
    body.theme-night .history-card-item { background: #1B2140; border-color: var(--mint); }
    .history-card-content-wrap { flex: 1; min-width: 0; }
    .history-card-time { font-weight: 800; color: var(--ink); margin-bottom: 2px; display: flex; justify-content: space-between; align-items: center; }
    .history-card-total { font-family: 'Baloo 2', sans-serif; font-weight: 800; color: var(--berry); font-size: 0.9rem; }
    .history-card-details { font-weight: 600; color: var(--ink-soft); line-height: 1.3; word-break: break-word; }
    .history-empty-state { font-size: 0.8rem; font-weight: 700; color: var(--ink-soft); text-align: center; padding: 12px 0; width: 100%; }
    .history-del-btn { background: none; border: none; color: var(--ink-soft); font-size: 16px; cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center; }
    .history-del-btn:active { transform: scale(0.9); }

    /* ---- Unified "My Meals" drawer (3 Sep 2026) --------------------
       Replaces the split between a 1-tap favourites drawer and a 5-tap
       History accordion buried in settings — both answered the same
       question ("the thing I ate before") with different mental models
       and different capabilities. See index.html's #favDrawer. */
    /* Sticky within .meals-sheet-inner (the scroll container since My
       Meals became a bottom sheet — was .drawer before that). An earlier
       negative-margin approach to pull this flush against the container's
       own padding was tried and discarded: overflow-y:auto implies
       overflow-x:auto, which clips negative side margins rather than
       letting them extend into the padding area. This version stays
       inside its normal content box and relies on an opaque background
       plus sticky's own top offset instead. */
    .meals-range-row {
      display: flex; gap: 6px;
      position: sticky; top: 0; z-index: 5;
      margin: 0 0 12px; padding: 8px 0;
      background: var(--bg);
    }
    .meals-range-btn {
      flex: 1; min-height: 44px;
      border: 2px solid var(--bg2); background: var(--bg);
      color: var(--ink-soft);
      border-radius: 12px;
      font-family: inherit; font-weight: 800; font-size: 0.8rem;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .meals-range-btn.is-active {
      background: var(--sprout-green); border-color: var(--sprout-green); color: #fff;
    }
    .meals-section-head {
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 0.82rem;
      color: var(--ink-soft);
      text-transform: uppercase; letter-spacing: 0.04em;
      margin: 14px 0 6px;
    }
    .meals-section-head:first-child { margin-top: 0; }

    .meals-row {
      /* --card (white) on the sheet's --bg surface. Inverted from the old
         side drawer, where the drawer itself was --card and rows were
         --bg; the bottom sheet's surface is --bg, so rows keep their
         contrast by taking the lighter colour instead. */
      background: var(--card); border-radius: 14px;
      padding: 10px 12px; margin-bottom: 8px;
      /* position: relative + the extra top margin below are both for
         .meals-row-sticker-badge — the badge is positioned absolutely
         against THIS element and pokes up above its top edge, so this
         needs to be the containing block, and rows need enough of a gap
         above them that the badge doesn't overlap the row (or section
         header) sitting above it. 22px chosen to clear the badge's own
         ~30px upward poke (see .meals-row-sticker-badge's top offset)
         with a small margin of its own — .meals-section-head's 14px
         top margin plus this row's 8px isn't enough on its own. */
      position: relative;
      margin-top: 22px;
    }
    /* First row in each section (right under a "TODAY"/pinned heading, or
       the very first row in the sheet) doesn't need the extra clearance —
       nothing above it for the badge to collide with. */
    .meals-section-head + .meals-row,
    .history-container > .meals-row:first-child { margin-top: 8px; }
    /* Full-width title row. This is what makes zero-truncation possible —
       nothing else shares this row's horizontal space, so a long meal
       name (e.g. "Chicken Nuggets - Sweet Chilli") wraps onto as many
       lines as it needs rather than being squeezed by neighbouring
       icons and clipped with an ellipsis. */
    .meals-row-title {
      display: block; width: 100%; box-sizing: border-box;
      appearance: none; -webkit-appearance: none;
      background: none; border: none; padding: 0; margin: 0; outline: none;
      text-align: left; cursor: pointer;
      font-family: 'Baloo 2', sans-serif; font-weight: 800; font-size: 0.94rem;
      line-height: 1.3; color: var(--ink);
      white-space: normal; word-break: normal; overflow-wrap: break-word;
      -webkit-tap-highlight-color: transparent;
    }
    .meals-row-title:active { opacity: 0.75; }
    /* Meta text + icon row. Deliberately its OWN row below the title
       (not sharing space with it) so its height — and therefore every
       icon's tap target — stays fixed and predictable no matter how many
       lines the title above wraps to. */
    .meals-row-footer {
      display: flex; align-items: center; justify-content: space-between;
      gap: 8px; margin-top: 8px;
    }
    /* Secondary info (time/grams/item count) — genuinely low-stakes text,
       unlike the meal title above. Truncates if the icon group needs the
       room; the zero-truncation requirement was specifically about the
       meal NAME, which this isn't. */
    .meals-row-meta {
      font-weight: 700; font-size: 0.76rem; color: var(--ink-soft);
      min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .meals-row-icons {
      display: flex; align-items: center; gap: 6px;
      flex: 0 0 auto; flex-shrink: 0; flex-wrap: nowrap;
    }
    /* Every icon in this row is visually 36px (matches the mockup and the
       sticker slot's own row-context size below) but gets a 44px
       INVISIBLE tap area via ::before — same trick already used for the
       Reset button's touch-target fix earlier this session. Shrinking an
       icon visually should never shrink what a thumb can actually hit. */
    /* Repeat + pin, deliberately smaller and muted (5 Sep 2026, was 48px
       matching the sticker). Jon: the sticker is what makes a kid finish
       logging a meal rather than just tapping "add to plate" and moving
       on — it's the actual incentive, not a status indicator alongside
       two equally-weighted controls. These two are useful but adult-
       facing and used far less often (pin especially — once per meal, if
       ever), so they've been demoted to secondary size and muted colour,
       leaving the sticker as the one thing the eye lands on in this row.
       32px is below the 44px minimum touch target, so restored the same
       invisible ::before hitbox trick removed when these were at 48px
       (see the "No ::before hitbox here" comment history below) — the
       visible size shrank but the tappable area still has to meet the
       minimum. */
    .meals-row-add, .meals-row-pin, .meals-row-delete {
      position: relative;
      width: 32px; height: 32px;
      background: var(--bg); border: none; border-radius: 9px;
      color: var(--ink-soft); cursor: pointer; font-size: 0.85rem;
      opacity: 0.65;
      display: flex; align-items: center; justify-content: center;
      -webkit-tap-highlight-color: transparent;
    }
    .meals-row-add::before, .meals-row-pin::before, .meals-row-delete::before {
      content: ''; position: absolute; top: 50%; left: 50%;
      width: 44px; height: 44px; transform: translate(-50%, -50%);
    }
    .meals-row-add {
      font-weight: 800;
    }
    .meals-row-add:active, .meals-row-pin:active { transform: scale(0.9); opacity: 0.9; }
    /* Delete gets its own active-state colour (red-tinted) rather than
       repeat/pin's neutral scale-down — a mistaken tap here is genuinely
       destructive (backed by deleteHistoryEntry's existing confirm
       dialog, so nothing is lost without a second step, but the visual
       signal on press should still differ from a harmless action like
       repeat or pin). */
    .meals-row-delete:active { transform: scale(0.9); opacity: 0.9; background: var(--bg2); color: var(--berry); }
    /* Sticker corner badge (6 Sep 2026 — replaces the old inline-in-
       icon-row placement; see loggedRowHtml's comment in
       favourites-ui.js for the full history). Absolutely positioned
       against .meals-row (position: relative, above), poking up and
       right off the card's corner — the classic notification-badge
       treatment — rather than sitting flush inside the row like another
       icon. 64px, up from the old inline 48px: Jon's ask was for
       something people can actually see at a glance, not a same-sized
       icon in a more prominent spot.
       top/right offsets were tuned directly against Jon's mockup
       feedback ("higher and to the right") — these are deliberately NOT
       derived from a formula, just nudged until it read right, so if a
       future pass wants it different, the numbers below are recording
       taste, not a distinct calculation.
       .meal-sticker-slot's own 48px width/height (button + img, set in
       its base rule further down) is overridden here since the badge
       wrapper controls the visible circle size, not the button inside
       it — the button just needs to fill its wrapper. */
    .meals-row-sticker-badge {
      position: absolute;
      top: -30px; right: -14px;
      width: 64px; height: 64px;
      border-radius: 50%;
      background: var(--card);
      border: 3px solid var(--bg);
      box-shadow: 0 3px 8px var(--shadow);
      display: flex; align-items: center; justify-content: center;
      z-index: 1;
    }
    body.theme-night .meals-row-sticker-badge { background: #1B2140; }
    .meals-row-sticker-badge .meal-sticker-slot,
    .meals-row-sticker-badge .meal-sticker-slot img { width: 100%; height: 100%; }


    /* Meal-reaction sticker slot on each history card (added 3 Sep 2026).
       48px confirmed against a real mockup — see docs/STICKER-PIPELINE.md.
       See js/meals/meal-stickers.js. */
    .meal-sticker-slot {
      flex: 0 0 auto;
      width: 48px; height: 48px;
      border: none; background: none; padding: 0;
      border-radius: 10px;
      cursor: pointer;
      display: flex; align-items: center; justify-content: center;
      -webkit-tap-highlight-color: transparent;
    }
    .meal-sticker-slot img { width: 48px; height: 48px; display: block; }
    .meal-sticker-slot.empty {
      /* Same full size as a filled slot (see .meals-row-icons
         .meal-sticker-slot above) — deliberately NOT shrunk to match
         repeat/pin now that those are secondary-sized. An empty slot is
         still an invitation to react, which is the actual reason a kid
         finishes logging a meal rather than just adding food and moving
         on — it should look inviting, not like a leftover control. */
      border: 1.5px dashed var(--bg2);
      color: var(--ink-soft);
      font-size: 1.2rem;
      font-weight: 800;
    }
    .meal-sticker-slot.empty:active { background: var(--bg2); }
    .meal-sticker-slot.has-sticker:active { transform: scale(0.94); }

    /* The sticker picker toast itself. A second element alongside
       #undoToast (see index.html's own comment there for why) rather than
       overloading it — #undoToast's base .undo-toast rules assume one
       line of text plus one button (white-space:nowrap, ellipsis,
       border-radius:100px pill), none of which suit a row of 48px
       sticker images, hence the overrides below. */
    .undo-toast.meal-sticker-toast {
      flex-direction: column;
      align-items: stretch;
      gap: 8px;
      border-radius: 20px;
      padding: 12px 16px 14px;
      max-width: min(calc(100vw - 32px), 340px);
    }
    .meal-sticker-toast .undo-toast-msg {
      white-space: normal;
      text-align: center;
      font-size: 0.85rem;
    }
    .meal-sticker-picker {
      /* Wrapping flex row rather than a fixed one. The sticker set is
         expected to grow well beyond the 5 it launched with (4 Sep 2026),
         so this reflows to whatever fits the container instead of needing
         a layout change per sticker added. Works unchanged in both
         contexts it renders in: the wide My Meals sheet and the narrower
         post-log panel.
         Flex rather than CSS Grid deliberately: grid would express this
         slightly more directly, but wkhtmltoimage (the only renderer
         available for verifying layout in this project's tooling) has no
         grid support, so grid CSS here would be unverifiable before
         shipping. Flex gives the same reflow behaviour and can actually
         be checked.
         64px (up from 48px) is the size at which wordmark text baked into
         the sticker art becomes properly legible — see the legibility
         notes in docs/STICKER-PIPELINE.md. */
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      align-items: center;
      gap: 8px;
      /* Caps at roughly three rows, then scrolls, so a large set can't
         push the rest of the panel off screen. */
      max-height: 240px;
      overflow-y: auto;
    }
    .meal-sticker-option {
      border: 2px solid transparent;
      background: none;
      padding: 2px;
      border-radius: 10px;
      cursor: pointer;
      display: flex;
      -webkit-tap-highlight-color: transparent;
    }
    .meal-sticker-option img { width: 64px; height: 64px; display: block; }
    .meal-sticker-option:active { transform: scale(0.94); background: rgba(255,255,255,0.12); }


    .swatches { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
    .swatch-wrapper { position: relative; display: inline-block; }
    .swatch { width: 38px; height: 38px; border-radius: 50%; border: 3px solid transparent; cursor: pointer; transition: transform 0.15s ease; }
    .swatch:hover { transform: scale(1.1); }
    .swatch.active { border-color: var(--ink); }

    .swatch-wrapper::after {
      content: attr(data-color-name); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%) translateY(-6px);
      background: #1F2A44; color: #FFFFFF; padding: 6px 10px; border-radius: 8px; font-size: 0.75rem; font-weight: 800;
      white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.15s ease, transform 0.15s ease;
      box-shadow: 0 4px 14px rgba(0,0,0,0.4); z-index: 999;
      border: 1px solid rgba(255,255,255,0.2);
    }
    .swatch-wrapper:hover::after { opacity: 1; transform: translateX(-50%) translateY(-2px); }

    .theme-opts { display: flex; gap: 8px; flex-wrap: wrap; }
    .theme-chip {
      background: var(--bg); border: 2px solid var(--bg2); color: var(--ink);
      padding: 8px 12px; border-radius: 12px; font-weight: 700; cursor: pointer; flex: 1; text-align: center;
      min-width: 120px; margin-bottom: 4px;
    }
    body.theme-night .theme-chip { background: #1B2140; border-color: #262C52; }
    .theme-chip.active { border-color: var(--sky); background: var(--sky); color: white; }

    .wardrobe-chip {
      background: var(--bg); border: 2px solid var(--bg2); color: var(--ink);
      padding: 8px 12px; border-radius: 12px; font-weight: 700; cursor: pointer; flex: 1; text-align: center;
      min-width: 120px; margin-bottom: 4px;
    }
    body.theme-night .wardrobe-chip { background: #1B2140; border-color: #262C52; }
    /* Seasonal chip shown all year but locked until its dates; still hoverable/tappable for the nudge */
    .wardrobe-chip.locked {
      opacity: 0.5;
      cursor: not-allowed;
    }
    .wardrobe-chip.locked::after {
      content: " 🔒";
      font-size: 0.85em;
    }
    .wardrobe-chip.active { 
      border: 2px solid var(--ink) !important; 
      background: var(--mint); 
      color: white;
      font-weight: 900;
    }

    /* Resource chips: external links to trusted diabetes info (NHS, charities) */
    .resource-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
    .resource-chip {
      background: var(--bg); border: 2px solid var(--bg2); color: var(--ink);
      padding: 9px 8px; border-radius: 12px; font-weight: 700; cursor: pointer;
      text-align: center; font-size: 0.78rem;
      display: inline-flex; align-items: center; justify-content: center; gap: 5px;
      text-decoration: none; line-height: 1.2;
    }
    body.theme-night .resource-chip { background: #1B2140; border-color: #262C52; }
    .resource-chip:hover { border-color: var(--sky); }
    .resource-chip:focus-visible { outline: 3px solid var(--sky); outline-offset: 2px; }
    .resource-chip .resource-chip-ext { font-size: 0.85em; opacity: 0.55; }
    .resource-chip.emergency {
      grid-column: 1 / -1;
      border-color: var(--berry);
      color: var(--berry);
      background: rgba(216,27,96,0.08);
      font-weight: 900;
    }
    body.theme-night .resource-chip.emergency { background: rgba(255,93,143,0.12); }
    /* Low Luminescence: same fix as theme-night — the base rule's
       rgba(216,27,96,...) is the DEFAULT theme's --berry hardcoded
       rather than referencing var(--berry), so every other theme needs
       its own correction back to its actual --berry value. Confirmed
       theme-night's override RGB (255,93,143) is an exact match for its
       own --berry (#FF5D8F) — same approach applied here with
       low-lume's own --berry (#F687B3 = 246,135,179). */
    body.theme-low-lume .resource-chip.emergency { background: rgba(246,135,179,0.12); }
    .resource-chip.emergency:hover { border-color: var(--berry); background: rgba(216,27,96,0.16); }

    .switch { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; font-weight: 700; color: var(--ink); }
