/* ============================================================
   09-glass.css — Liquid Glass, the material of everything you touch
   ============================================================

   The owner asked for the whole app in Apple's Liquid Glass, and chose
   Apple's own rule for it: glass is the layer you work the app with —
   the tab bar and the mic beside it, the buttons, chips and toggles,
   the sheets — floating over a calm content layer. Cards that hold
   meals, numbers and words are solid surfaces again, because glass over
   text is glass you have to read through.

   The material, as iOS draws it and as a browser can:
     · a body that blurs and brightens what is behind it,
     · an edge that catches the light at the top and darkens away from it,
     · a lens of light gathered in the upper part of the shape,
     · a soft shadow that says how far it floats.
   Never coloured for decoration: tint is for what carries meaning — the
   primary action, the selected tab, the thing that is on.

   Motion follows the same physics (liquid-glass-motion): selection is a
   lens that travels on a spring, not a highlight that blinks; glass
   materializes — its blur and lens ramp up — rather than fading; a
   pressed control swells and brightens. Every movement has a Reduce
   Motion path at the bottom of this file.

   Last in the cascade on purpose: this file is the material layer, and
   it wins.
   ============================================================ */

:root {
  /* Apple's spring presets, sampled (spring(duration: .5, bounce: …)),
     because CSS has no springs and linear() is exact enough to feel
     the same. snappy for selection and morphs, bouncy for a celebration
     and a press, smooth for appear and disappear. */
  --spring-snappy: linear(0, .036, .121, .231, .349, .464, .57, .663, .742, .807, .86, .901, .933, .957, .974, .986, .995, 1, 1.004, 1.005, 1.006, 1.006, 1.006, 1.005, 1.004, 1.004, 1.003, 1.002, 1);
  --spring-snappy-d: 650ms;
  --spring-bouncy: linear(0, .053, .177, .332, .492, .639, .764, .863, .936, .988, 1.02, 1.038, 1.045, 1.045, 1.041, 1.034, 1.027, 1.02, 1.013, 1.008, 1.004, 1.002, 1, .999, .998, .998, .998, .998, 1);
  --spring-bouncy-d: 790ms;
  --spring-smooth: linear(0, .026, .088, .17, .259, .35, .436, .515, .587, .65, .706, .754, .795, .83, .859, .884, .905, .922, .936, .948, .957, .965, .972, .977, .981, .985, .988, .99, 1);
  --spring-smooth-d: 550ms;

  /* The glass, as iOS 27 draws it: clear. What passes under it shows
     through, diffused and lifted by the blur — brightness() — so the
     words on it stay readable without fogging it. How tinted it is runs
     from 0, clear, to 1, fully tinted: iOS 27's slider, Settings →
     Appearance (S.glass, applyGlass()). */
  --lg-k:           0;
  /* Increase Contrast sets a floor under it (below). */
  --lg-k-floor:     0;
  --lg-t:           max(var(--lg-k), var(--lg-k-floor));
  --lg-fill:        rgba(255, 255, 255, calc(.12 + .68 * var(--lg-t)));
  /* Sheets hold sentences, over a dimmed page: thicker, and fixed. */
  --lg-fill-strong: rgba(250, 252, 251, .84);
  /* The selection lens carries a trace of the tint: selection is meaning. */
  --lg-lens:        color-mix(in srgb, var(--accent) 10%, rgba(255, 255, 255, .86));
  --lg-blur:        blur(8px) saturate(180%) brightness(1.06);
  --lg-blur-deep:   blur(30px) saturate(190%) brightness(1.08);
  /* Words and symbols on clear glass: stronger than the grey they would
     be on a card, as iOS's vibrant labels are — 5.5:1 or more over every
     colour of the backdrop, a light day's dusk included. */
  --lg-ink:         #26302d;
  /* iOS 27: a brighter specular highlight, and a darkened edge that sets
     clear glass off from what is behind it. */
  --lg-hi:          rgba(255, 255, 255, 1);
  --lg-rim:         rgba(255, 255, 255, .55);
  --lg-lo:          rgba(15, 26, 23, .09);
  --lg-edge:        rgba(15, 26, 23, .18);
  /* Controls cast no shadow; only what floats over the page does — the
     tab bar, the mic, her bubble (--lg-shadow-float, set on them). */
  --lg-shadow:      none;
  --lg-shadow-float: 0 10px 28px -12px rgba(15, 26, 23, .32), 0 2px 6px rgba(15, 26, 23, .06);
  --lg-shadow-deep: 0 30px 70px -24px rgba(15, 26, 23, .42), 0 4px 14px rgba(15, 26, 23, .08);
  /* A control on a card or in a sheet floats a little over it, not as
     high over it as the tab bar floats over the page. */
  --lg-shadow-sm:   none;
  --lg-sheen:       linear-gradient(180deg, rgba(255, 255, 255, .5) 0%, rgba(255, 255, 255, 0) 34%);
  /* Knock-out ink for the details inside a filled tab symbol. */
  --tab-knock:      #f7faf9;

  /* The floating sheet's corners: the sheet, its foot, and the sheet
     with focus all draw them from here. */
  --r-sheet:  34px;
  /* Shapes the tab bar and the voice capsule share: the capsule grows
     out of the mic, so it has to be exactly as wide as bar, gap and mic. */
  --bar-max:  316px;
  --bar-gap:  10px;
  --mic:      58px;

  /* The content layer: solid, calm, readable. */
  --surface:       #fbfdfc;
  --surface-line:  rgba(15, 26, 23, .06);
  --surface-shadow: none;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --lg-fill:        rgba(46, 58, 54, calc(.3 + .55 * var(--lg-t)));
    --lg-fill-strong: rgba(30, 40, 37, .84);
    --lg-lens:        rgba(255, 255, 255, .16);
    --lg-blur:        blur(8px) saturate(160%) brightness(.8);
    --lg-blur-deep:   blur(36px) saturate(170%) brightness(.8);
    --lg-ink:         #e3ebe8;
    --lg-hi:          rgba(255, 255, 255, .45);
    --lg-rim:         rgba(255, 255, 255, .12);
    --lg-lo:          rgba(0, 0, 0, .4);
    --lg-edge:        rgba(0, 0, 0, .55);
    --lg-shadow:      none;
    --lg-shadow-float: 0 12px 30px -12px rgba(0, 0, 0, .7), 0 2px 6px rgba(0, 0, 0, .3);
    --lg-shadow-deep: 0 30px 70px -24px rgba(0, 0, 0, .8), 0 4px 14px rgba(0, 0, 0, .35);
    --lg-shadow-sm:   none;
    --lg-sheen:       linear-gradient(180deg, rgba(255, 255, 255, .14) 0%, rgba(255, 255, 255, 0) 46%);
    --tab-knock:      #1d2724;
    --surface:        #151d1b;
    --surface-line:   rgba(255, 255, 255, .06);
    --surface-shadow: none;
  }
}
:root[data-theme="dark"] {
  --lg-fill:        rgba(46, 58, 54, calc(.3 + .55 * var(--lg-t)));
  --lg-fill-strong: rgba(30, 40, 37, .84);
  --lg-lens:        rgba(255, 255, 255, .16);
  --lg-blur:        blur(8px) saturate(160%) brightness(.8);
  --lg-blur-deep:   blur(36px) saturate(170%) brightness(.8);
  --lg-ink:         #e3ebe8;
  --lg-hi:          rgba(255, 255, 255, .45);
  --lg-rim:         rgba(255, 255, 255, .12);
  --lg-lo:          rgba(0, 0, 0, .4);
  --lg-edge:        rgba(0, 0, 0, .55);
  --lg-shadow:      none;
  --lg-shadow-float: 0 12px 30px -12px rgba(0, 0, 0, .7), 0 2px 6px rgba(0, 0, 0, .3);
  --lg-shadow-deep: 0 30px 70px -24px rgba(0, 0, 0, .8), 0 4px 14px rgba(0, 0, 0, .35);
  --lg-shadow-sm:   none;
  --lg-sheen:       linear-gradient(180deg, rgba(255, 255, 255, .14) 0%, rgba(255, 255, 255, 0) 46%);
  --tab-knock:      #1d2724;
  --surface:        #151d1b;
  --surface-line:   rgba(255, 255, 255, .06);
  --surface-shadow: none;
}

/* ---------- the material ----------
   One recipe, for every control made of glass. The edge is drawn with
   inset shadows rather than a border, so it can be bright on top and
   dark underneath the way a real edge catches light. */
.tabbar .inner,
.mic-fab,
.btn.ghost,
.btn.danger,
.chip-btn,
.seg button,
.langtoggle,
.icon-btn,
.voice-pill,
.back-btn,
.icon-corner,
.lb,
.chat-composer {
  background: var(--lg-sheen), var(--lg-fill);
  -webkit-backdrop-filter: var(--lg-blur);
  backdrop-filter: var(--lg-blur);
  border: none;
  box-shadow:
    inset 0 1px 0 var(--lg-hi),
    inset 0 -1px 0 var(--lg-lo),
    inset 0 0 0 .5px var(--lg-rim),
    0 0 0 .5px var(--lg-edge),
    var(--lg-shadow);
}
/* Glass is never laid on glass, and over a solid card a blur has nothing
   to refract: inside a sheet or on a card a control keeps the fill, the
   edge and the lens, without a second blur pass — one for each chip on
   every frame of a scroll, in the Android WebView — and floats lower. */
:is(.card, .sheet) :is(.btn.ghost, .btn.danger, .chip-btn, .seg button, .langtoggle, .icon-btn, .back-btn, .icon-corner, .lb) {
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
:is(.card, .sheet) { --lg-shadow: var(--lg-shadow-sm); }
/* With no shadow, a glass control on a card was the card's own white and
   all but vanished. It is told apart the way a flat button is: a fill
   a step darker than the card, and a 1px edge. Pressed or chosen states
   keep their own colours. */
:is(.card, .sheet) :is(.btn.ghost, .chip-btn:not(.on), .icon-btn, .lb, .icon-corner, .back-btn) {
  background: var(--sunk-ink);
  box-shadow: inset 0 0 0 1px var(--edge);
}
/* Floating over the page, so lifted off it. */
.tabbar, .pal-say { --lg-shadow: var(--lg-shadow-float); }

/* ---------- the scroll edge ----------
   iOS 26 has no bar at the top: content scrolls up under a soft blur
   that fades out downward. At rest it blurs only the backdrop, which
   looks the same as the backdrop. */
body::before {
  content: ''; position: fixed; inset: 0 0 auto 0; z-index: 25; pointer-events: none;
  block-size: calc(env(safe-area-inset-top) + 30px);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  -webkit-mask-image: linear-gradient(#000 35%, transparent);
  mask-image: linear-gradient(#000 35%, transparent);
}
html.onboarding body::before { display: none; }

/* ---------- the tab bar, and the mic beside it ----------
   A floating capsule with a lens that travels to the selected tab (the
   tab bar's own morph), and — like iOS 26's search button — a separate
   round glass button for the one action that belongs on every screen:
   saying what you ate. */
.tabbar { gap: var(--bar-gap); align-items: center; }
/* The bar gives up the width the mic needs; it never squeezes the mic.
   Both are clear glass, and a tab's name is the glass's own ink: on the
   thin fill, the grey it was measured 3.6:1 over a light day's dusk. */
.tabbar .inner {
  --n: 4;
  position: relative; isolation: isolate;
  flex: 1 1 auto; min-width: 0; max-width: var(--bar-max);
  padding: 5px; gap: 0;
}
.tabbar .inner button {
  position: relative; z-index: 1; background: none; color: var(--lg-ink);
  min-block-size: 50px; padding: 6px 2px 5px;
  transition: color .2s var(--ease), transform var(--spring-bouncy-d) var(--spring-bouncy);
}
.tabbar .inner button.on { color: var(--accent-ink); background: none; }
.tabbar .inner button:active { transform: scale(1.08); transition-duration: 160ms; }
.tabbar .inner button svg { width: 23px; height: 23px; }
.tab-lens {
  --i: 0;
  position: absolute; z-index: 0; inset-block: 5px; inset-inline-start: 5px;
  inline-size: calc((100% - 10px) / var(--n, 4));
  border-radius: 999px; pointer-events: none;
  background: var(--lg-sheen), var(--lg-lens);
  box-shadow:
    inset 0 1px 0 var(--lg-hi),
    inset 0 0 0 .5px var(--lg-rim),
    0 4px 14px -6px rgba(15, 26, 23, .3);
  transform: translateX(calc(var(--i, 0) * 100%));
  transition: transform var(--spring-snappy-d) var(--spring-snappy);
}
[dir="rtl"] .tab-lens { transform: translateX(calc(var(--i, 0) * -100%)); }

/* Scoped under .tabbar: the bar's own button rule (stretch to share the
   width, no background) is more specific than a bare class, and it
   flattened the mic into a sliver. */
.tabbar .mic-fab {
  flex: 0 0 auto; pointer-events: auto; position: relative;
  inline-size: var(--mic); block-size: var(--mic); min-block-size: 0; padding: 0;
  border-radius: 50%; display: grid; place-items: center; cursor: pointer;
  color: var(--accent-ink);
  transition: transform var(--spring-bouncy-d) var(--spring-bouncy);
}
.tabbar .mic-fab svg { width: 25px; height: 25px; }
.tabbar .mic-fab:active { transform: scale(1.08); transition-duration: 160ms; }
.tabbar .mic-fab:focus-visible { border-radius: 50%; }
.tabbar .mic-fab[aria-expanded="true"] { visibility: hidden; }
/* While the mic is a capsule, the capsule is the bar. */
.tabbar:has(.mic-fab[aria-expanded="true"]) .inner { visibility: hidden; }

/* What passes under the bar. iOS glass adapts to it: over a dark card,
   clear light glass turns dark and its words light — dark words on it
   measured 1.9:1 there — and in the dark theme, over something light,
   the other way. chrome.js, glassUnder(), as the page scrolls. */
.tabbar.under-dark {
  --lg-fill: rgba(30, 40, 37, calc(.34 + .5 * var(--lg-t)));
  --lg-blur: blur(8px) saturate(160%) brightness(.8);
  --lg-ink: #e3ebe8;
  --lg-lens: rgba(255, 255, 255, .16);
  --lg-hi: rgba(255, 255, 255, .45);
  --lg-rim: rgba(255, 255, 255, .12);
  --lg-lo: rgba(0, 0, 0, .4);
  --lg-edge: rgba(0, 0, 0, .55);
  --lg-sheen: linear-gradient(180deg, rgba(255, 255, 255, .14) 0%, rgba(255, 255, 255, 0) 46%);
  --tab-knock: #1d2724;
  --accent-ink: #9df3cf;
}
.tabbar.under-light {
  --lg-fill: rgba(255, 255, 255, calc(.3 + .5 * var(--lg-t)));
  --lg-blur: blur(8px) saturate(180%) brightness(1.06);
  --lg-ink: #26302d;
  --lg-lens: rgba(255, 255, 255, .86);
  --lg-hi: rgba(255, 255, 255, 1);
  --lg-rim: rgba(255, 255, 255, .55);
  --lg-lo: rgba(15, 26, 23, .09);
  --lg-edge: rgba(15, 26, 23, .18);
  --lg-sheen: linear-gradient(180deg, rgba(255, 255, 255, .5) 0%, rgba(255, 255, 255, 0) 34%);
  --tab-knock: #f7faf9;
  --accent-ink: #0b6b62;
}

/* ---------- the rest of what is pressed ----------
   Back is a glass capsule, as it is on iOS 26; the corner plus is a
   glass circle; the Progress tools are glass tiles; the coach's message
   field is a glass capsule — and the bar behind it is clear, because
   glass is never laid on glass. */
.back-btn {
  padding: 6px 14px; padding-inline-start: 8px; border-radius: 999px;
  margin-bottom: 10px; color: var(--accent-ink);
  transition: transform var(--spring-bouncy-d) var(--spring-bouncy);
}
.back-btn::after { inset: -4px; }
.back-btn:active { transform: scale(1.05); transition-duration: 160ms; }
.icon-corner { color: var(--text-dim); transition: transform var(--spring-bouncy-d) var(--spring-bouncy); }
.icon-corner:active { transform: scale(1.1); transition-duration: 160ms; }
.lb { color: var(--text-dim); transition: transform var(--spring-bouncy-d) var(--spring-bouncy); }
button.lb:active { transform: scale(1.04); transition-duration: 160ms; }
.chat-foot { background: none; -webkit-backdrop-filter: none; backdrop-filter: none; border-top: none; }

/* ---------- the language switch ---------- */
.langtoggle { padding: 3px; }
.langtoggle button.on {
  background: var(--lg-sheen), var(--lg-lens); color: var(--text);
  box-shadow: inset 0 1px 0 var(--lg-hi), inset 0 0 0 .5px var(--lg-rim), 0 2px 8px -3px rgba(15, 26, 23, .3);
}

/* ---------- buttons ----------
   Capsules. The primary is tinted glass — Apple's glassProminent: the
   brand teal with the material's edge and lens over it. The rest are
   clear glass. Pressed, glass swells a little and brightens; it does
   not shrink. */
.btn {
  border-radius: 999px; min-block-size: 52px; padding: 14px 20px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, 0) 32%),
    linear-gradient(180deg, var(--btn), var(--btn-2));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .42),
    inset 0 -1px 0 rgba(0, 0, 0, .2),
    inset 0 0 0 .5px rgba(255, 255, 255, .22);   /* no glow under it: no shadows on buttons */
  transition: transform var(--spring-bouncy-d) var(--spring-bouncy), filter .2s var(--ease);
}
.btn:active { transform: scale(1.025); filter: brightness(1.07); transition-duration: 160ms; }
.btn.ghost { color: var(--text); }
.btn.danger { color: var(--danger-ink); }
.btn.sm { border-radius: 999px; min-block-size: 44px; padding: 10px 16px; }
.btn:focus-visible, .btn.sm:focus-visible { border-radius: 999px; }
.btn[disabled], .btn:disabled { filter: saturate(.4); }

.icon-btn { color: var(--text-dim); transition: transform var(--spring-bouncy-d) var(--spring-bouncy); }
.icon-btn:active { transform: scale(1.08); transition-duration: 160ms; }

/* ---------- chips and segments ----------
   Clear glass when off; on, the glass takes the tint — the one thing in
   the group that carries meaning — and a tick, because a tint alone is
   colour alone: the label barely changes in lightness. A chip is a
   full-size target; the glass took away the border that made it 33px. */
.chip-btn {
  min-block-size: 44px; min-inline-size: 44px;
  color: var(--text-dim); transition: transform var(--spring-bouncy-d) var(--spring-bouncy);
}
.chip-btn:active { transform: scale(1.06); transition-duration: 160ms; }
.chip-btn.on {
  color: var(--accent-ink);
  background: var(--lg-sheen), color-mix(in srgb, var(--accent) 18%, var(--lg-fill));
  box-shadow:
    inset 0 1px 0 var(--lg-hi),
    inset 0 0 0 1px color-mix(in srgb, var(--accent) 38%, transparent),
    var(--lg-shadow);
}
/* Seen, not read: a screen reader has aria-pressed or aria-checked. */
.chip-btn.on::before { content: "✓ "; content: "✓ " / ""; }
.seg button { border-radius: 999px; color: var(--text); transition: transform var(--spring-bouncy-d) var(--spring-bouncy); }
.seg button:active { transform: scale(1.04); transition-duration: 160ms; }
.seg button.on {
  color: var(--on-accent);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, 0) 34%),
    linear-gradient(180deg, var(--btn), var(--btn-2));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4), inset 0 0 0 .5px rgba(255, 255, 255, .22);
}
.seg.stack button { border-radius: var(--r-md); }

/* ---------- sheets ----------
   Floating glass, inset from the edges with its corners all the way
   round, rising on a spring and materializing — its blur coming up
   with it — rather than sliding in as a flat panel. It does not scale
   on the way: a sheet is measured the moment it opens (44 px targets),
   and a scaled one measures short. */
.sheet {
  inline-size: calc(100% - 16px);
  /* Over the home indicator — but above a keyboard there is no home
     indicator, and the sheet sits 8px over the keys. */
  margin-block-end: max(8px, calc(8px + env(safe-area-inset-bottom) - var(--kb, 0px)));
  max-block-size: min(86vh, calc(100vh - var(--kb, 0px) - 32px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
  border-radius: var(--r-sheet);
  padding-block-end: 22px;
  background: var(--lg-sheen), var(--lg-fill-strong);
  -webkit-backdrop-filter: var(--lg-blur-deep);
  backdrop-filter: var(--lg-blur-deep);
  border: none;
  box-shadow: inset 0 1px 0 var(--lg-hi), inset 0 0 0 .5px var(--lg-rim), var(--lg-shadow-deep);
  /* Appearing is the smooth spring — no overshoot; snappy is for morphs and controls. */
  animation: lg-rise var(--spring-smooth-d) var(--spring-smooth);
}
@keyframes lg-rise {
  from { transform: translateY(42px); -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0); }
}
.sheet .lg-foot {
  inset-block-end: -22px; padding-block-end: 22px;
  background: var(--lg-fill-strong);
  -webkit-backdrop-filter: var(--lg-blur); backdrop-filter: var(--lg-blur);
  border-radius: 0 0 var(--r-sheet) var(--r-sheet);
}
/* The dim behind a sheet. Frosted, or faded in with opacity, it became
   the only thing the sheet's own glass could see — in Chromium either
   one makes it the sheet's backdrop root — and the fade took the glass
   sheet with it. It dims, by its colour. */
.sheet-bg {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  animation: lg-scrim .2s var(--ease);
}
@keyframes lg-scrim { from { background-color: transparent; } }

/* ---------- the content layer ----------
   Solid and calm, so what is written on it reads first. Grouped like
   iOS's inset lists: one surface, a hairline, a shadow that says it
   lies on the page rather than floats over it. */
.card {
  background: var(--surface);
  -webkit-backdrop-filter: none; backdrop-filter: none;
  border: 1px solid var(--surface-line);
  box-shadow: var(--surface-shadow);
}

/* ---------- the voice capsule (voice.js) ----------
   The mic, grown: it opens out of the mic's own circle across the bar,
   the glass getting thicker — a deeper shadow — as it gets bigger, and
   shows the words as they are said.

   Exactly as wide as the bar, the gap and the mic, so it starts on the
   mic at any width: at 520px it started 11–68px beside the mic on a
   wide phone or a tablet. The reveal is a clip, and a clip that ends at
   the capsule's edge hides its own shadow until the last frame, when
   the shadow popped in — so it ends outside the shadow instead, and the
   shadow is never animated (it could not interpolate, and repainted
   every frame). */
.voice-wrap {
  position: fixed; inset-inline: 0; bottom: calc(10px + env(safe-area-inset-bottom)); z-index: 45;
  display: flex; justify-content: center; padding: 0 12px; pointer-events: none;
}
.voice-pill {
  pointer-events: auto; position: relative;
  display: flex; align-items: center; gap: 10px;
  inline-size: min(100%, calc(var(--bar-max) + var(--bar-gap) + var(--mic)));
  min-block-size: 60px; padding: 7px; padding-inline-start: 18px;
  border-radius: 999px;
  background: var(--lg-sheen), var(--lg-fill);
  -webkit-backdrop-filter: var(--lg-blur-deep); backdrop-filter: var(--lg-blur-deep);
  box-shadow: inset 0 1px 0 var(--lg-hi), inset 0 0 0 .5px var(--lg-rim), 0 0 0 .5px var(--lg-edge), var(--lg-shadow-deep);
  animation: lg-grow var(--spring-snappy-d) var(--spring-snappy);
}
@keyframes lg-grow {
  from { clip-path: inset(0 0 0 calc(100% - var(--mic)) round 999px); }
  to   { clip-path: inset(-20px -48px -80px -48px round 999px); }
}
[dir="rtl"] .voice-pill { animation-name: lg-grow-rtl; }
@keyframes lg-grow-rtl {
  from { clip-path: inset(0 calc(100% - var(--mic)) 0 0 round 999px); }
  to   { clip-path: inset(-20px -48px -80px -48px round 999px); }
}
.voice-wave { flex: none; display: inline-flex; align-items: center; gap: 3px; block-size: 22px; color: var(--accent); }
.voice-wave i { inline-size: 3px; block-size: 100%; border-radius: 2px; background: currentColor; transform-origin: center; animation: lg-wave 1s var(--ease) infinite; }
.voice-wave i:nth-child(2) { animation-delay: .15s; }
.voice-wave i:nth-child(3) { animation-delay: .3s; }
.voice-wave i:nth-child(4) { animation-delay: .45s; }
@keyframes lg-wave { 0%, 100% { transform: scaleY(.3); } 50% { transform: scaleY(1); } }
/* Recording for the voice service (hear.js), the bars follow the voice
   itself — --lv, 0 to 1 — so the capsule shows it is hearing you, and
   each bar answers a little differently. Being written down, they
   breathe on their own again, and ✓ has nothing left to do. */
.voice-wave { --lv: 0; }
.voice-wave.live i { animation: none; transform: scaleY(calc(.22 + var(--lv) * var(--k))); transition: transform 90ms linear; }
.voice-wave.live i:nth-child(1) { --k: .55; }
.voice-wave.live i:nth-child(2) { --k: .95; }
.voice-wave.live i:nth-child(3) { --k: .78; }
.voice-wave.live i:nth-child(4) { --k: .5; }
.voice-pill.working .voice-wave i { animation: lg-wave 1s var(--ease) infinite; }
.voice-pill.working .voice-done { visibility: hidden; }
/* The glass's own ink, as on the bar: the capsule is clear glass too. */
.voice-text {
  flex: 1; min-inline-size: 0; margin: 0; font-size: .95rem; line-height: 1.35; color: var(--lg-ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.voice-text.heard { color: var(--text); font-weight: 500; }
.voice-btn {
  flex: none; inline-size: 46px; block-size: 46px; border-radius: 50%; border: none; cursor: pointer;
  display: grid; place-items: center; font: inherit;
  transition: transform var(--spring-bouncy-d) var(--spring-bouncy);
}
.voice-btn:active { transform: scale(1.08); transition-duration: 160ms; }
.voice-cancel {
  color: var(--text-dim);
  background: var(--lg-sheen), var(--lg-fill);
  box-shadow: inset 0 1px 0 var(--lg-hi), inset 0 0 0 .5px var(--lg-rim);
}
.voice-done {
  color: var(--on-accent);
  background: linear-gradient(180deg, rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, 0) 34%), linear-gradient(180deg, var(--btn), var(--btn-2));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4);
}
.voice-type {
  inline-size: auto; padding: 0 18px; border-radius: 999px; font-weight: 600; font-size: .9rem;
  color: var(--on-accent);
  background: linear-gradient(180deg, var(--btn), var(--btn-2));
}
.voice-pill.failed .voice-text { color: var(--text); }

/* ---------- Basbousa, the companion on Today (companion.js) ----------
   The cat alone, on nothing — no glass, no circle (the owner). Each mood
   is its own picture, so it is never a colour alone. She breathes from
   her feet, and tapped she bobs on the bouncy spring and says a line in
   a glass bubble. A soft shadow sets her on the page in both themes. */
.pal-wrap { position: relative; flex: none; }
.pal {
  --pal-t: 0ms; --pal-lean: 0deg;
  position: relative; display: block; inline-size: 72px; block-size: 72px; padding: 0; margin: 0;
  border: none; border-radius: 16px; background: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.pal-pic {
  display: block; inline-size: 100%; block-size: 100%; object-fit: contain;
  transform-origin: 50% 100%;
  filter: drop-shadow(0 3px 4px rgba(15, 26, 23, .18));
  -webkit-user-select: none; user-select: none; pointer-events: none;
  animation: pal-breathe 4.8s ease-in-out infinite;
}
@keyframes pal-breathe { 0%, 100% { transform: scale(1, 1); } 50% { transform: scale(1.015, 1.035); } }
/* Toward the finger (companion.js sets --pal-lean), from her feet. */
.pal-lean {
  display: block; inline-size: 100%; block-size: 100%;
  transform-origin: 50% 100%; rotate: var(--pal-lean, 0deg);
  transition: rotate .45s var(--spring-smooth);
}
/* Each mood idles its own way, on top of breathing. */
.pal-calm .pal-pic { animation: pal-breathe 4.8s ease-in-out infinite, pal-sway 7s ease-in-out infinite; }
.pal-happy .pal-pic { animation: pal-breathe 4.8s ease-in-out infinite, pal-bounce 3.2s ease-in-out infinite; }
.pal-proud .pal-pic { animation: pal-breathe 4.8s ease-in-out infinite, pal-puff 5s ease-in-out infinite; }
.pal-sleepy .pal-pic { animation: pal-breathe 6.4s ease-in-out infinite, pal-nod 6.4s ease-in-out infinite; }
.pal-gentle .pal-pic { animation: pal-breathe 5.6s ease-in-out infinite, pal-sway 9s ease-in-out infinite; }
@keyframes pal-sway   { 0%, 100% { rotate: -1.5deg; } 50% { rotate: 1.5deg; } }
@keyframes pal-bounce { 0%, 70%, 100% { translate: 0 0; } 78% { translate: 0 -7px; } 86% { translate: 0 0; } 92% { translate: 0 -3px; } }
@keyframes pal-puff   { 0%, 80%, 100% { scale: 1; } 88% { scale: 1.06; } }
@keyframes pal-nod    { 0%, 100% { rotate: 0deg; } 40% { rotate: 4deg; } 60% { rotate: 4deg; } }
/* Tapped: a hop, squashed on landing. Logged something: a bigger jump
   and a burst of sparks. translate is its own property, so it rides on
   top of the hello's scale and rotate. */
.pal.hop { animation: pal-hop 620ms var(--spring-bouncy) both; }
.pal.pal-cheer { animation: pal-cheer 1100ms var(--spring-bouncy) both; }
@keyframes pal-hop {
  0% { translate: 0 0; scale: 1 1; } 18% { translate: 0 0; scale: 1.08 .9; }
  45% { translate: 0 -14px; scale: .95 1.06; } 75% { translate: 0 0; scale: 1.06 .94; } 100% { translate: 0 0; scale: 1 1; }
}
@keyframes pal-cheer {
  0% { translate: 0 0; rotate: 0deg; } 15% { translate: 0 0; scale: 1.1 .88; }
  40% { translate: 0 -22px; rotate: -8deg; scale: .94 1.08; } 60% { translate: 0 -18px; rotate: 8deg; }
  82% { translate: 0 0; rotate: 0deg; scale: 1.06 .94; } 100% { translate: 0 0; scale: 1 1; }
}
.pal-burst { position: absolute; inset: 0; pointer-events: none; }
.pal-burst i {
  position: absolute; inset-block-start: 30%; inset-inline-start: 50%; inline-size: 8px; block-size: 8px;
  background: #f5c84c; clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
  animation: pal-burst 900ms ease-out 150ms both;   /* starts unseen: 'both' holds the first frame */
}
.pal-burst i:nth-child(1) { --bx: -34px; --by: -26px; }
.pal-burst i:nth-child(2) { --bx: 30px;  --by: -32px; background: var(--accent); }
.pal-burst i:nth-child(3) { --bx: -40px; --by: 4px; }
.pal-burst i:nth-child(4) { --bx: 38px;  --by: 2px; background: #f9a8d4; }
.pal-burst i:nth-child(5) { --bx: 2px;   --by: -44px; }
@keyframes pal-burst {
  0% { opacity: 0; transform: translate(0, 0) scale(.4); } 25% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--bx), var(--by)) scale(1.2) rotate(40deg); }
}
/* Proud, a glint; sleepy, a z drifting up. */
.pal-spark {
  position: absolute; inset-block-start: -2px; inset-inline-end: -2px; inline-size: 17px; block-size: 17px;
  background: #f5c84c;
  clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
  animation: pal-twinkle 2.4s ease-in-out infinite;
}
.pal-z {
  position: absolute; inset-block-start: -8px; inset-inline-end: 2px;
  font: 700 14px/1 var(--font); color: var(--text-dim);
  animation: pal-z 3.2s ease-in-out infinite;
}
@keyframes pal-twinkle { 0%, 100% { transform: scale(.7) rotate(0); } 50% { transform: scale(1.1) rotate(20deg); } }
@keyframes pal-z { 0% { transform: translate(0, 6px) scale(.8); } 100% { transform: translate(4px, -6px) scale(1.1); } }
/* Arriving (companion.js): she springs up from small and waves. scale
   and rotate are their own properties, so they ride on top of her
   breathing. */
.pal.pal-hello {
  transform-origin: 50% 100%;
  animation:
    pal-in 900ms var(--spring-bouncy) var(--pal-t) both,
    pal-wave 900ms var(--spring-smooth) calc(var(--pal-t) + 760ms) both;
}
@keyframes pal-in { from { scale: .35; } to { scale: 1; } }
@keyframes pal-wave { 0% { rotate: 0deg; } 30% { rotate: -9deg; } 65% { rotate: 7deg; } 100% { rotate: 0deg; } }
.pal:focus-visible { border-radius: 16px; }
/* What she says: a glass bubble under her, at the inline end. */
.pal-say {
  position: absolute; z-index: 20; inset-block-start: calc(100% + 8px); inset-inline-end: 0;
  inline-size: max-content; max-inline-size: min(240px, 70vw); margin: 0; padding: 10px 14px;
  border-radius: 18px; font-size: .86rem; line-height: 1.4; color: var(--text);
  background: var(--lg-sheen), var(--lg-fill-strong);
  -webkit-backdrop-filter: var(--lg-blur); backdrop-filter: var(--lg-blur);
  box-shadow: inset 0 1px 0 var(--lg-hi), inset 0 0 0 .5px var(--lg-rim), 0 0 0 .5px var(--lg-edge), var(--lg-shadow);
  transform-origin: top right;
  animation: pal-pop var(--spring-snappy-d) var(--spring-snappy);
}
[dir="rtl"] .pal-say { transform-origin: top left; }
.pal-say[hidden] { display: none; }
@keyframes pal-pop { from { transform: translateY(-6px) scale(.94); -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0); } }

/* ---------- Reduce Motion ----------
   No travel and no bounce: the lens is simply on the new tab, sheets
   cross-fade, the capsule appears in place. Motion is reduced, not
   feedback: a pressed control does not swell, but it still brightens. */
@media (prefers-reduced-motion: reduce) {
  .tab-lens { transition: none; }
  .voice-pill, [dir="rtl"] .voice-pill { animation: none; }
  .voice-wave i, .voice-pill.working .voice-wave i { animation: none; transform: scaleY(.6); }
  .voice-wave.live i { transition: none; }
  .voice-btn { transition: none; }
  .voice-btn:active { transform: none; }
  .sheet { animation: fade .2s linear; }
  .btn, .chip-btn, .seg button, .tabbar .mic-fab, .tabbar .inner button, .icon-btn, .back-btn, .icon-corner, .lb { transition: none; }
  .btn:active, .chip-btn:active, .seg button:active, .tabbar .mic-fab:active, .tabbar .inner button:active, .icon-btn:active, .back-btn:active, .icon-corner:active, button.lb:active { transform: none; }
  :is(.chip-btn, .seg button, .tabbar .mic-fab, .tabbar .inner button, .icon-btn, .back-btn, .icon-corner, button.lb, .voice-btn):active { filter: brightness(1.06); }
  .pal-pic, .pal-spark, .pal-z, .pal-say, .pal-burst, .pal-burst i { animation: none !important; }
  .pal.pal-hello, .pal.hop, .pal.pal-cheer { animation: none; }
  .pal-burst { display: none; }
  .pal-lean { rotate: none; transition: none; }
}

/* ---------- when the system asks for more contrast ----------
   As iOS does: the glass frosts and its edge darkens. */
@media (prefers-contrast: more) {
  :root { --lg-k-floor: 1; --lg-edge: rgba(15, 26, 23, .5); }
  .tabbar.under-dark { --lg-edge: rgba(0, 0, 0, .8); }
}

/* ---------- when the system asks for less transparency ----------
   Frosted, not see-through: the same shapes on a solid fill. The top
   edge fades from the page's own colour instead of blurring. */
@media (prefers-reduced-transparency: reduce) {
  .tabbar .inner, .tabbar .mic-fab, .btn.ghost, .btn.danger, .chip-btn, .seg button, .langtoggle, .icon-btn, .voice-pill, .sheet, .sheet .lg-foot, .back-btn, .icon-corner, .lb, .chat-composer, .pal-say {
    -webkit-backdrop-filter: none; backdrop-filter: none;
    background: var(--surface);
  }
  body::before { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--page); }
}

/* ---------- the glass setting (Settings → Appearance) ---------- */
.glass-amt { display: flex; align-items: center; gap: 12px; min-block-size: 44px; font-size: .82rem; color: var(--text-dim); }
.glass-amt input {
  --pct: 0%;
  flex: 1; min-inline-size: 0; block-size: 32px; margin: 0;
  -webkit-appearance: none; appearance: none; background: transparent; cursor: pointer;
}
/* As iOS draws a slider: a thin track, the tint up to the knob, and a
   white knob that floats. The fill runs from the start edge either way. */
.glass-amt input::-webkit-slider-runnable-track {
  block-size: 4px; border-radius: 2px;
  background: linear-gradient(to right, var(--accent) var(--pct), var(--sunk-ink) var(--pct));
}
[dir="rtl"] .glass-amt input::-webkit-slider-runnable-track {
  background: linear-gradient(to left, var(--accent) var(--pct), var(--sunk-ink) var(--pct));
}
.glass-amt input::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  inline-size: 28px; block-size: 28px; margin-top: -12px; border-radius: 50%; background: #fff;
  box-shadow: 0 0 0 .5px rgba(15, 26, 23, .08), 0 3px 8px rgba(15, 26, 23, .18), 0 1px 1px rgba(15, 26, 23, .1);
}
.glass-amt input:focus-visible { outline-offset: 4px; border-radius: 999px; }
