/**
 * Relynked design tokens, from relynked-concept.html.
 *
 * TWO DELIBERATE DEPARTURES FROM THE PROTOTYPE, both invisible at the
 * default setting and both worth the trade:
 *
 * 1. SIZES ARE rem, NOT calc(Npx * var(--ts)).
 *    The prototype hardcodes a 16px base and multiplies it. That works,
 *    but it overrides whatever the person has set their phone's text
 *    size to, which is the one setting an older owner is most likely to
 *    have already changed. Here the root is `calc(100% * var(--ts))`,
 *    and 100% IS the device setting, so the app opens correct before we
 *    ask anything. Every prototype value converts exactly: N px becomes
 *    (N / 16) rem, so at the default setting the rendering is identical
 *    to the prototype, pixel for pixel.
 *
 * 2. --forest-2 in the dark block reads `#14miss` in the prototype,
 *    which is not a colour. It silently falls back, so dark mode was
 *    running without it. Set to #14miss's evident intent, a step above
 *    the dark --forest.
 *
 * Colour names are the prototype's own. Do not rename them; the screens
 * are ported against these.
 */

:root {
  /* The one scaling variable. Multiplies the device setting, never
     replaces it. See textScale.ts. */
  --ts: 1;

  /* THE DESIGN SCALE, which is not the same thing as --ts.
     --ts is the PERSON's text-size choice and must keep working. --ui is
     the design's own density, set once here: the whole interface was
     drawn 33% larger than it needed to be, so every rem-based size
     (type, icons, tiles, padding, radii, gaps) comes down together
     rather than by editing 148 separate declarations and getting the
     proportions wrong.

     TWO REGIONS ARE PINNED and must not follow it: the dashboard card
     and the tab bar. They read their lengths through --fix instead,
     which divides --ui straight back out, so 1 unit of --fix is what
     1rem was before this token existed. If --ui changes, they stay put.

     Anything that has to CLEAR the pinned tab bar is pinned too, or the
     content slides under glass that did not move: see .scroll, .fab and
     the sticky bars. */
  /* 0.75 was a quarter off, which read slightly too tight on a real
     handset; 0.825 gives 10% of it back. The pinned regions below do not
     move either way. */
  --ui: 0.825;
  --fix: calc(1rem / var(--ui));

  font-size: calc(100% * var(--ts) * var(--ui));

  /* ── brand ─────────────────────────────────────────────────────── */
  --forest: #0b3d2e;
  --forest-2: #0f4a38;
  --lime: #dcef4e;
  --lime-br: #e7fe25;

  /* ── surfaces ──────────────────────────────────────────────────── */
  --bg: #f6f7f7;
  --card: #ffffff;
  --tint-row: #f2f8e4;

  /* ── ink and lines ─────────────────────────────────────────────── */
  --t1: #0f1412;
  --t2: #6c7478;
  --t3: #a3aaae;
  --line: #edefef;
  --fill: #f0f2f2;

  /* ── icon tile tints ───────────────────────────────────────────── */
  --lime-pale: #eef7c9;
  --mint: #ddf2e3;
  --amber: #fce6c4;
  --blue: #d8e7fb;
  --purple: #e4defa;
  --grey: #eceeee;
  --rose: #fbdddd;

  /* ── status ────────────────────────────────────────────────────── */
  --green: #12a150;
  --red: #e5484d;
  --orange: #e08600;

  /* The dashboard artwork. Swap this one line for a season and the
     card changes everywhere it appears. */
  --dash-art: url("/dash-art.webp");

  /* ── chrome ────────────────────────────────────────────────────── */
  --glass: rgba(255, 255, 255, 0.82);
  --gline: rgba(0, 0, 0, 0.06);
  --bub-in: #eff1f1;
  --sh: 0 1px 2px rgba(15, 20, 18, 0.04), 0 8px 24px rgba(15, 20, 18, 0.05);

  /* Accents used as INK rather than as a surface. Forest is a dark
     colour; on a dark background a dark accent is simply not there, so
     these flip in dark mode and every component reads them instead of
     reaching for --forest directly. */
  --accent-ink: var(--forest);
  --on-accent: #ffffff;
  /* Ink that sits on a LIME fill, and the one accent token that must NOT
     flip in dark mode. Lime is a light colour in both themes, so its ink
     is dark in both. The selected filter chip's count read
     `color: var(--accent-ink)` on a lime pill, which is forest in light
     and LIME IN DARK, so in dark mode the number was lime on lime and
     the badge looked like an empty green blob. */
  --on-lime: var(--forest);

  /* ── type ──────────────────────────────────────────────────────── */
  /* "Noto Sans" is loaded subset to U+20A6 only (see next.html), so it
     supplies the naira sign Inter lacks and is never reached for
     anything else. Order matters: Inter must come first. */
  --font: "Inter", "Noto Sans", -apple-system, system-ui, "Segoe UI", sans-serif;

  /* The prototype's px ladder, converted. Comments give the original. */
  --t-d1: 1.9375rem; /* 31px */
  --t-d2: 1.4375rem; /* 23px */
  --t-h1: 1.0625rem; /* 17px */
  --t-bd: 0.96875rem; /* 15.5px */
  --t-sm: 0.84375rem; /* 13.5px */
  --t-xs: 0.75rem; /* 12px */
  --t-cap: 0.71875rem; /* 11.5px */
  --t-13: 0.8125rem; /* 13px */
  --t-14: 0.875rem; /* 14px */
  --t-19: 1.1875rem; /* 19px */
  --t-nm: 2rem; /* 32px, the greeting name */
  --t-big: 2.625rem; /* 42px, the money figure */

  /* ── radii ─────────────────────────────────────────────────────── */
  --r-sm: 0.6875rem; /* 11px */
  --r-md: 0.9375rem; /* 15px */
  --r-lg: 1.125rem; /* 18px */
  --r-xl: 1.25rem; /* 20px */
  --r-2xl: 1.375rem; /* 22px */
  --r-pill: 62.4375rem;

  /* Hairlines stay in px on purpose: a 1px rule is a rendering detail,
     not a reading size, and should be 1px at every text scale. */
  --hair: 1px;
}

/* ── dark ──────────────────────────────────────────────────────────
   Three states, not two. An explicit choice stamps data-theme; the
   default "system" stamps nothing, so prefers-color-scheme is the only
   signal and the media query must not beat an explicit light choice. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --forest: #0f4a38;
    /* The prototype has "#14miss" here, which is not a colour, so dark
       mode was running with --forest-2 unset. This is the step above
       --forest it was clearly meant to be. */
    --forest-2: #1a6049;
    --bg: #0b0c0d;
    --card: #17191a;
    --tint-row: #1b2416;
    --t1: #f2f4f4;
    --t2: #9ca3a6;
    --t3: #5e6669;
    --line: #242728;
    --fill: #212425;
    --lime-pale: #2a3118;
    --mint: #12291b;
    --amber: #2e2210;
    --blue: #13202e;
    --purple: #201c33;
    --grey: #232626;
    --rose: #2e1616;
    --green: #30d158;
    --red: #ff453a;
    --orange: #ff9f0a;
    --glass: rgba(23, 25, 26, 0.8);
    --gline: rgba(255, 255, 255, 0.07);
    --bub-in: #212425;
    --sh: none;
    --accent-ink: var(--lime);
    --on-accent: var(--forest);
  }
}

:root[data-theme="dark"] {
  --forest: #0f4a38;
  --forest-2: #1a6049;
  --bg: #0b0c0d;
  --card: #17191a;
  --tint-row: #1b2416;
  --t1: #f2f4f4;
  --t2: #9ca3a6;
  --t3: #5e6669;
  --line: #242728;
  --fill: #212425;
  --lime-pale: #2a3118;
  --mint: #12291b;
  --amber: #2e2210;
  --blue: #13202e;
  --purple: #201c33;
  --grey: #232626;
  --rose: #2e1616;
  --green: #30d158;
  --red: #ff453a;
  --orange: #ff9f0a;
  --glass: rgba(23, 25, 26, 0.8);
  --gline: rgba(255, 255, 255, 0.07);
  --bub-in: #212425;
  --sh: none;
  --accent-ink: var(--lime);
  --on-accent: var(--forest);
}
/**
 * The opening.
 *
 * Choreography, in one sentence: the forest tile appears, the two bars
 * fall into it with a bounce, the word unfurls to the right which pushes
 * the tile left until the pair sits centred, then the whole mark shrinks
 * into its resting place on the next screen.
 *
 * Two decisions worth stating.
 *
 * THE TILE IS NEVER TOLD TO MOVE LEFT. The lockup is a centred flex row;
 * the word grows from zero width, and the tile is displaced by it. So
 * the two movements are one movement, and they cannot drift out of sync
 * or disagree about where the centre is. Animating both independently to
 * meet in the middle is the version that looks almost right and is
 * impossible to tune.
 *
 * THE LAST STEP IS A VIEW TRANSITION, NOT A KEYFRAME. The mark carries
 * `view-transition-name`, and so does the logo on Welcome and on Sign in,
 * so the browser tweens between wherever the mark actually is and
 * wherever it actually needs to be. Hand-computed positions would drift
 * the moment the text-size setting changes the target's size, or the
 * phone is rotated, or a screen is added. Where view transitions are
 * unsupported the app simply cuts, which is honest; see motion.ts.
 *
 * Load after tokens.css, before app.css.
 */

:root {
  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  /* Overshoot, for things that land rather than arrive. */
  --ease-bounce: cubic-bezier(0.3, 1.5, 0.5, 1);
}

/* ── the opening sequence ───────────────────────────────────────────
   Timings are absolute rather than relative so the whole thing can be
   read down the page:
     0        tile scales up
     300      short bar drops
     400      long bar drops
     950      word unfurls, tile is pushed left
     1750     App hands over to the next screen (see SPLASH_MS)
*/

.splash .lockup {
  display: flex;
  align-items: center;
  justify-content: center;
}

.splash .mk {
  width: 5.5rem;
  height: 5.5rem;
  flex: none;
}

/* SVG children need an explicit transform box, or the origin is the
   whole SVG canvas and a scale sends the part flying off. */
.splash .lg-tile,
.splash .lg-bar {
  transform-box: fill-box;
  transform-origin: center;
}

.splash .lg-tile {
  animation: tile-in 460ms var(--ease-bounce) both;
}

/* Lengths here are SVG user units (the viewBox is 0 0 100 100), so the
   drop distance scales with the mark at any rendered size. */
.splash .lg-bar-1 {
  animation: bar-drop 560ms var(--ease-bounce) 300ms both;
}
.splash .lg-bar-2 {
  animation: bar-drop 560ms var(--ease-bounce) 400ms both;
}

.splash .wd {
  font-size: 2.125rem;
  font-weight: 800;
  letter-spacing: -0.04em;
  color: var(--paper, #fff);
  white-space: nowrap;
  overflow: hidden;
  max-width: 0;
  opacity: 0;
  animation: word-unfurl 700ms var(--ease) 950ms both;
}

@keyframes tile-in {
  from {
    transform: scale(0.35);
    opacity: 0;
  }
}

@keyframes bar-drop {
  from {
    transform: translateY(-62px);
    opacity: 0;
  }
}

@keyframes word-unfurl {
  from {
    max-width: 0;
    margin-left: 0;
    opacity: 0;
  }
  40% {
    opacity: 1;
  }
  to {
    /* Only a little wider than the word actually is (about 8.6rem at
       scale 1). A cap far above the real width means the animation
       spends most of its duration growing a box that is already bigger
       than its contents, so the reveal appears to finish early and then
       nothing happens. In rem, so the headroom holds at every text
       size. */
    max-width: 10rem;
    margin-left: 0.75rem;
    opacity: 1;
  }
}

/* ── the handover ───────────────────────────────────────────────────
   The mark is lifted out of the page snapshot and tweened on its own
   path, so it survives the change of screen instead of being redrawn
   there. The word has no counterpart on the next screen, so it leaves
   on its own rather than trying to morph into nothing. */

.mk {
  view-transition-name: relynked-mark;
}
.splash .wd {
  view-transition-name: relynked-word;
}

::view-transition-group(relynked-mark) {
  animation-duration: 620ms;
  animation-timing-function: var(--ease-bounce);
}
/* The two snapshots are the same artwork at different sizes, so the
   browser's own plus-lighter cross-fade has nothing to reveal. Forcing
   `animation: none` instead would stack two opaque tiles and swap the
   colour in one frame partway through the travel. */
::view-transition-old(relynked-mark),
::view-transition-new(relynked-mark) {
  animation-duration: 620ms;
  animation-timing-function: var(--ease-in-out);
}

::view-transition-old(relynked-word) {
  animation: word-away 300ms var(--ease-in-out) both;
}
::view-transition-new(relynked-word) {
  animation: none;
}

@keyframes word-away {
  to {
    opacity: 0;
    transform: translateX(0.75rem);
  }
}

/* The screen underneath fades up. It must NOT also travel: the mark is
   already moving, and a second thing moving on a small screen at a
   different speed is what reads as a tear. */
html[data-nav="splash"]::view-transition-old(root) {
  animation: fade-out 320ms var(--ease-in-out) both;
}
html[data-nav="splash"]::view-transition-new(root) {
  animation: fade-in 620ms var(--ease-in-out) both;
}

/* ── ordinary screen changes ────────────────────────────────────────
   A push rises slightly so it has a direction; back reverses it. Tabs
   only cross-fade, and faster: a tab is a place you were already, and
   380ms of travel four times a minute stops being delightful long
   before it stops being noticeable. */
::view-transition-old(root) {
  animation: fade-out 200ms var(--ease-in-out) both;
}
::view-transition-new(root) {
  animation: page-in 300ms var(--ease) both;
}
html[data-nav="back"]::view-transition-new(root) {
  animation: page-in-back 300ms var(--ease) both;
}
html[data-nav="tab"]::view-transition-old(root),
html[data-nav="tab"]::view-transition-new(root) {
  animation-duration: 160ms;
}
html[data-nav="tab"]::view-transition-new(root) {
  animation-name: fade-in;
}

/* The tab bar is lifted out of the page snapshot so it stays nailed
   down instead of riding the page animation on every push. Its two
   snapshots are identical, so the cross-fade is invisible. */
.tabs {
  view-transition-name: relynked-tabs;
}

@keyframes fade-out {
  to {
    opacity: 0;
  }
}
@keyframes fade-in {
  from {
    opacity: 0;
  }
}
@keyframes page-in {
  from {
    opacity: 0;
    transform: translateY(0.625rem);
  }
}
@keyframes page-in-back {
  from {
    opacity: 0;
    transform: translateX(-0.625rem);
  }
}

/* ── reduced motion is law ──────────────────────────────────────────
   Not a softening: switched off. Someone who asked their phone for less
   movement usually did so because movement makes them ill, and a gentler
   bounce is still a bounce. Nothing above carries meaning that is not
   also written on the screen, so the app stays complete without it. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .splash .wd {
    max-width: 10rem;
    margin-left: 0.75rem;
    opacity: 1;
  }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
/**
 * Relynked component layer, ported from relynked-concept.html.
 *
 * Class names are the prototype's own, so a screen can be read against
 * it side by side. Every size is the prototype's value converted from
 * `calc(Npx * var(--ts))` to `(N/16)rem`, which renders identically at
 * the default text size and additionally inherits the device setting.
 * See tokens.css for why.
 *
 * Relynked is the app. Remy is the assistant inside it.
 */

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  -webkit-tap-highlight-color: transparent;
}

html,
body,
#root {
  height: 100%;
}

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--t1);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}

button {
  font: inherit;
  color: inherit;
  border: 0;
  background: none;
  cursor: pointer;
  text-align: left;
}

input,
textarea {
  font: inherit;
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
  border-radius: 0.5rem;
}

/* ── the phone frame ───────────────────────────────────────────────
   On a real device this is the whole viewport. Capped and centred so
   the same build is usable in a desktop browser while it is being
   reviewed, which is how the Meta screencast gets recorded. */
/* The phone frame. NOT `.ph`: that was a two-letter class a modifier
   could collide with, and twice it did. A `val ph` placeholder and an
   `askbar > .ph` both matched this rule and inherited height: 100dvh,
   rendering a form row and then a search bar at the height of the
   screen. A name nothing will reach for by accident. */
.frame {
  position: relative;
  width: 100%;
  /* Pinned: this is the device's own width, not a type size. Left to
     follow --ui it would cap a 390px phone at 330px and leave dead
     gutters down both sides. */
  max-width: calc(27.5 * var(--fix));
  height: 100dvh;
  margin: 0 auto;
  background: var(--bg);
  color: var(--t1);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  scrollbar-width: none;
  /* Clears the floating tab bar: its 1.625rem bottom offset plus its
     3.75rem height, and the rest is breathing room so the last card does
     not sit tight under the glass. Follows the bar's height. */
  /* Pinned with the bar it clears. The bar did not shrink, so neither
     can the room left for it, or the last card slides under glass. */
  padding-bottom: calc(7.5 * var(--fix));
}
.scroll::-webkit-scrollbar {
  display: none;
}
.pad {
  padding: 0 1.125rem;
}

/* ── type ──────────────────────────────────────────────────────────── */
.d1 {
  font-size: var(--t-d1);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.12;
}
.d2 {
  font-size: var(--t-d2);
  font-weight: 700;
  letter-spacing: -0.02em;
}
.h1 {
  font-size: var(--t-h1);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.bd {
  font-size: var(--t-bd);
  font-weight: 500;
}
.sm {
  font-size: var(--t-sm);
  font-weight: 500;
}
.xs {
  font-size: var(--t-xs);
  font-weight: 600;
}
.sec {
  color: var(--t2);
}
.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}
.cap {
  font-size: var(--t-cap);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--t2);
}

/* ── top bar ───────────────────────────────────────────────────────── */
.tbar {
  display: flex;
  align-items: center;
  gap: 0.6875rem;
  padding: 0.375rem 1.125rem 0.875rem;
}
.tbar .logo {
  width: 2.75rem;
  height: 2.75rem;
  flex: none;
  display: block;
}
.tbar .logo svg {
  width: 100%;
  height: 100%;
  display: block;
}
.tbar .who {
  flex: 1;
  min-width: 0;
}
.tbar .who b {
  font-size: var(--t-h1);
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 0.3125rem;
  letter-spacing: -0.01em;
}
.tbar .who b svg {
  width: 0.9375rem;
  height: 0.9375rem;
  color: var(--t2);
  flex: none;
}
.tbar .who span {
  font-size: calc(0.8125 * var(--fix));
  color: var(--t2);
  font-weight: 500;
}
.tbar .ib {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex: none;
  position: relative;
  color: var(--t1);
}
.tbar .ib svg {
  width: 1.3125rem;
  height: 1.3125rem;
}
.tbar .ib .dot {
  position: absolute;
  top: 0.375rem;
  right: 0.4375rem;
  width: 0.5625rem;
  height: 0.5625rem;
  border-radius: 50%;
  background: var(--lime-br);
  border: 2px solid var(--bg);
}
.tbar .pf {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--forest);
  color: var(--lime);
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: 0.9375rem;
  flex: none;
}

/* ── greeting ──────────────────────────────────────────────────────── */
/* The top padding drops the greeting away from the business header, so
   the name reads as the start of the page rather than as another line of
   chrome hanging off the bar above it. */
.greet {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.6875rem 1.125rem 1rem;
}
.greet .tx {
  flex: 1;
}
/* Deliberately a step below the name's own size: this is the throwaway
   half of the greeting and the name is the half worth reading. */
.greet .hi {
  color: var(--t2);
  font-size: var(--t-bd);
  font-weight: 500;
}
/* No bottom margin: it used to buy space from the summary line that sat
   under the name, and the gap down to the card is .greet's own padding. */
.greet .nm {
  font-size: var(--t-nm);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
  margin: 0.0625rem 0 0;
}
.trend {
  background: var(--lime-pale);
  border-radius: var(--r-sm);
  padding: 0.6875rem 0.875rem;
  flex: none;
  text-align: left;
  color: var(--t1);
}
.trend .ic {
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 50%;
  background: var(--card);
  display: grid;
  place-items: center;
  margin-bottom: 0.3125rem;
  color: var(--accent-ink);
}
.trend .ic svg {
  width: 0.875rem;
  height: 0.875rem;
}
.trend b {
  font-size: var(--t-h1);
  font-weight: 700;
  display: block;
  letter-spacing: -0.01em;
}
.trend span {
  display: block;
  font-size: var(--t-cap);
  color: var(--t2);
  font-weight: 500;
}

/* ── sales card ────────────────────────────────────────────────────── */
/* The dashboard card carries artwork, not a flat fill.
   --dash-art is a variable on purpose: the whole point is to swap it
   with the seasons, which is then one line rather than a code change.
   The forest underneath is not a fallback nobody sees, it is what shows
   while the image loads and if it ever fails, so the white text on top
   is always legible. */
.sales {
  margin: 0 calc(1.125 * var(--fix));
  background-color: var(--forest);
  background-image: var(--dash-art);
  background-size: cover;
  background-position: center;
  border-radius: var(--r-2xl);
  padding: calc(0.75 * var(--fix)) calc(1 * var(--fix)) calc(0.875 * var(--fix));
  color: #fff;
  position: relative;
  overflow: hidden;
}
.sales .hd {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
/* Not bold. It is the caption on the number, not a heading competing
   with it. */
.sales .hd .ttl {
  font-size: calc(0.8125 * var(--fix));
  font-weight: 500;
  color: rgba(255, 255, 255, 0.88);
}
.sales .rep {
  display: flex;
  align-items: center;
  gap: calc(0.3125 * var(--fix));
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: var(--r-pill);
  padding: calc(0.25 * var(--fix)) calc(0.5625 * var(--fix));
  font-size: calc(0.71875 * var(--fix));
  font-weight: 500;
  color: #fff;
}
.sales .rep svg {
  width: calc(0.8125 * var(--fix));
  height: calc(0.8125 * var(--fix));
}
/* THE ONLY BOLD THING ON THIS CARD. */
.sales .big {
  /* The token is rem and would follow --ui; the card is pinned, so
     this is the token's own value held through --fix. */
  font-size: calc(2.625 * var(--fix));
  font-weight: 800;
  /* No negative tracking on the money figure: the naira sign comes from
     a different face to the digits, so tightening on top of that runs
     the sign into the first digit. */
  letter-spacing: 0;
  line-height: 1.05;
}
.sales .big .cur {
  margin-right: 0.08em;
}
/* Money on the left, the week on the right, on ONE row. Stacking them
   was most of the card's old height. */
.sales .bot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: calc(0.75 * var(--fix));
  margin-top: calc(0.4375 * var(--fix));
}
.sales .fig {
  min-width: 0;
}
.sales .wk {
  flex: none;
}
.sales .meta {
  display: flex;
  align-items: center;
  gap: calc(0.5 * var(--fix));
  font-size: var(--t-13);
  color: rgba(255, 255, 255, 0.82);
  font-weight: 500;
  white-space: nowrap;
  flex: none;
  margin-top: calc(0.25 * var(--fix));
}
/* Count on top, comparison beneath. */
.sales .meta .lines {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}
/* Was a <b> at 700. The money is the only bold thing on this card, so
   this is a plain span carrying nothing but full-strength white, which
   is enough to separate it from the line beneath. */
.sales .meta .lines .n1 {
  font-size: calc(0.8125 * var(--fix));
  font-weight: 500;
  color: #fff;
  line-height: 1.2;
}
.sales .meta .lines span {
  font-size: calc(0.6875 * var(--fix));
  color: rgba(255, 255, 255, 0.72);
  line-height: 1.2;
}
/* Nothing has happened yet: grey, and pointing along rather than up. */
.sales .meta .up.flat {
  background: rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.72);
}
.sales .meta .up {
  width: calc(1.375 * var(--fix));
  height: calc(1.375 * var(--fix));
  border-radius: 50%;
  background: var(--lime);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--forest);
}
.sales .meta .up svg {
  width: calc(0.8125 * var(--fix));
  height: calc(0.8125 * var(--fix));
}
/* Seven bars on a seven-column grid, and the axis uses the SAME grid, so
   a label sits under its own bar instead of being spaced independently
   and drifting out of line. */
/* Slender bars, close together. The first pass stretched seven bars
   across 55% of the card, which made each one a fat slab. The chart is a
   glance, not a read: it needs to say "the week has a shape" in a
   quarter of a second, and thin marks with tight spacing do that while
   thick ones just take up room.

   Fixed bar width rather than 1fr columns, so the bars stay slim and the
   group sits together instead of spreading to fill whatever width is
   left over. */
/* Slender bars, grouped tight.
   Width and gap are fixed rather than fractions of the card: 1fr columns
   stretched seven bars into fat slabs, and shrinking them to 8px with a
   5px gap turned them into dots with the weekday labels colliding.
   7px bars on a 19px pitch is thin enough to read as a chart and wide
   enough that the labels under every other bar clear each other. */
.chart {
  --bar: calc(0.4375 * var(--fix));
  --pitch: calc(0.75 * var(--fix));
  display: grid;
  grid-template-columns: repeat(7, var(--bar));
  gap: var(--pitch);
  justify-content: end;
  margin-left: auto;
}
.chart {
  align-items: end;
  /* Halved with the rest of the card. The chart is a glance at the
     week's shape, and the shape survives at this height. */
  height: calc(1.75 * var(--fix));
}
.chart i {
  /* Rounded, not a pill: at 7px wide a full pill radius turns a short
     bar into a dot, which is what a quiet week looked like. */
  border-radius: calc(0.21875 * var(--fix));
  background: rgba(255, 255, 255, 0.24);
  display: block;
  width: 100%;
  min-height: calc(0.75 * var(--fix));
}
.chart i.on {
  background: var(--lime);
}
/* Days that have not happened yet. Present, so the week keeps its
   shape, but clearly not a day that traded and made nothing. */
.chart i.future {
  background: rgba(255, 255, 255, 0.1);
}
/* Only four labels, spread across exactly the chart's own width. A
   7-column grid cannot hold four words at this pitch without them
   touching, but bars 1, 3, 5 and 7 are evenly spaced, so space-between
   over the same width lands each label on its own bar. */
.axis {
  display: flex;
  justify-content: space-between;
  width: calc(7 * calc(0.4375 * var(--fix)) + 6 * calc(0.75 * var(--fix)));
  margin-left: auto;
  font-size: calc(0.625 * var(--fix));
  color: rgba(255, 255, 255, 0.55);
  margin-top: calc(0.25 * var(--fix));
  font-weight: 500;
  white-space: nowrap;
}

/* ── stat trio ─────────────────────────────────────────────────────── */
.trio {
  display: flex;
  gap: 0.5rem;
  padding: 0.75rem 1.125rem 0;
  align-items: stretch;
}
.trio .c {
  flex: 1;
  min-width: 0;
  background: var(--card);
  border-radius: var(--r-lg);
  padding: 0.5rem 0.5625rem;
  box-shadow: var(--sh);
  display: flex;
  flex-direction: column;
}
.trio .hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.1875rem;
}
.trio .ic {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 0.4375rem;
  display: grid;
  place-items: center;
  color: var(--accent-ink);
}
.trio .ic svg {
  width: 0.8125rem;
  height: 0.8125rem;
}
.trio .k {
  font-size: 0.71875rem;
  color: var(--t2);
  font-weight: 500;
  line-height: 1.2;
}
/* PUSHED TO THE BOTTOM OF THE CARD, which is what keeps the three
   figures on one line. "Need a person" is the long label: it fits on one
   line at the current width but wraps as soon as the phone's text size
   goes up or the screen gets narrower, and a wrapped label used to shove
   its own number a line below its neighbours'.

   This was a reserved two-line height on .k, which aligned them but cost
   an empty line on every card whenever nothing wrapped. Anchoring the
   value to the bottom instead costs nothing and holds at every text
   size: the row stretches all three cards to match the tallest, and the
   numbers meet at the bottom however many lines each label takes. */
.trio .v {
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin-top: auto;
  padding-top: 0.1875rem;
}
/* The way to everything else: its own row under the trio, pushed to the
   right. A pill rather than a card, because it is a control and not
   another thing to read. 2.25rem keeps it a comfortable target without
   letting it compete with the three cards above it. */
/* Text, not a button, and ABOVE the three it acts on. As a pill below
   them it read as a fourth action in the row; as a quiet link above it
   reads as what it is, a way to change what the row shows. */
.morerow {
  display: flex;
  justify-content: flex-end;
  padding: 0.75rem 1.125rem 0;
}
.morebtn {
  padding: 0.125rem 0.125rem;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--accent-ink);
}
.morebtn:active {
  transform: scale(0.97);
}
.morebtn {
  transition: transform 140ms cubic-bezier(0.32, 0.72, 0, 1);
}

/* ── section head ──────────────────────────────────────────────────── */
.shead {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 1.5rem 1.125rem 0.625rem;
}
.shead b {
  font-size: var(--t-19);
  font-weight: 700;
  letter-spacing: -0.02em;
}
.shead span {
  font-size: var(--t-13);
  color: var(--t2);
  font-weight: 500;
}
.ghead {
  padding: 1.5rem 1.125rem 0.5625rem;
}

/* ── list card ─────────────────────────────────────────────────────── */
.lc {
  margin: 0 1.125rem;
  background: var(--card);
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--sh);
}
.li {
  display: flex;
  align-items: center;
  gap: 0.8125rem;
  padding: 0.8125rem 0.9375rem;
  position: relative;
  min-height: 3.5rem;
  width: 100%;
  text-align: left;
  color: inherit;
}
/* The divider starts after the icon, not at the card edge, so the icons
   read as a column rather than the rules cutting across them. */
.li + .li::before {
  content: "";
  position: absolute;
  top: 0;
  left: 4.375rem;
  right: 0.9375rem;
  height: 1px;
  background: var(--line);
}
.li.noic + .li.noic::before {
  left: 0.9375rem;
}
.li .ic {
  width: 2.625rem;
  height: 2.625rem;
  border-radius: 0.8125rem;
  display: grid;
  place-items: center;
  flex: none;
}
.li .ic svg {
  width: 1.25rem;
  height: 1.25rem;
}
/* The item's own photo in place of the icon tile. `overflow: hidden` with
   the tile's radius is what makes a square photo take the rounded shape
   the icons have, so a list of some-photos-some-icons still reads as one
   column. A tinted fill sits underneath as the colour shown while the
   image is still loading. */
.li .ic.shotic {
  overflow: hidden;
  background: var(--fill);
}
.li .ic.shotic img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The neutral icon: forest tile, white glyph. In dark mode the tile
   stays forest and the glyph turns lime, because white on a dark green
   tile against a near-black page has too little separation from the
   page itself, while lime reads instantly. */
.ic.neutral {
  background: var(--forest);
  color: #fff;
}
:root[data-theme="dark"] .ic.neutral {
  color: var(--lime);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ic.neutral {
    color: var(--lime);
  }
}
.li .tx {
  flex: 1;
  min-width: 0;
}
.li .tt {
  display: block;
  font-size: var(--t-bd);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.li.danger .tt {
  color: var(--red);
}
.li .ds {
  display: block;
  font-size: var(--t-13);
  color: var(--t2);
  font-weight: 500;
  margin-top: 0.125rem;
  line-height: 1.35;
}
.li .rt {
  text-align: right;
  flex: none;
}
.li .rt .v {
  display: block;
  font-size: var(--t-14);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.li .rt .s {
  display: block;
  font-size: var(--t-xs);
  color: var(--t2);
  font-weight: 500;
  margin-top: 0.0625rem;
}
.li .ago {
  font-size: 0.78125rem;
  color: var(--t2);
  font-weight: 500;
  flex: none;
}
.cv {
  width: 0.5rem;
  height: 0.875rem;
  color: var(--t3);
  flex: none;
}
.act {
  background: var(--mint);
  color: var(--accent-ink);
  border-radius: var(--r-lg);
  padding: 0.5625rem 0.9375rem;
  font-size: var(--t-sm);
  font-weight: 700;
  flex: none;
}

/* ── pills ─────────────────────────────────────────────────────────── */
.pill {
  border-radius: 1rem;
  padding: 0.3125rem 0.6875rem;
  font-size: var(--t-xs);
  font-weight: 700;
  flex: none;
  white-space: nowrap;
}
.pill.red {
  background: var(--rose);
  color: var(--red);
}
.pill.mint {
  background: var(--mint);
  color: var(--green);
}
.pill.amb {
  background: var(--amber);
  color: var(--orange);
}
.pill.dark {
  background: var(--forest);
  color: #fff;
}

/* ── insight ───────────────────────────────────────────────────────── */
.insight {
  margin: 1.125rem 1.125rem 0;
  background: var(--forest);
  border-radius: var(--r-2xl);
  padding: 1.125rem;
  color: #fff;
  display: flex;
  align-items: center;
  gap: 0.875rem;
  position: relative;
  overflow: hidden;
}
.insight .tx {
  flex: 1;
  position: relative;
  z-index: 1;
}
.insight .k {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
}
.insight .t {
  font-size: var(--t-h1);
  font-weight: 700;
  line-height: 1.3;
  margin: 0.4375rem 0 0.3125rem;
  letter-spacing: -0.01em;
}
.insight .d {
  font-size: var(--t-13);
  color: rgba(255, 255, 255, 0.72);
  font-weight: 500;
}
.insight .go {
  width: 2.875rem;
  height: 2.875rem;
  border-radius: 50%;
  background: var(--lime);
  display: grid;
  place-items: center;
  flex: none;
  position: relative;
  z-index: 1;
  color: var(--forest);
}
.insight .go svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* ── big title header ──────────────────────────────────────────────── */
.bigt {
  padding: 0.5rem 1.125rem 0;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}
.bigt .tx {
  flex: 1;
}
.bigt .ds {
  color: var(--t2);
  font-size: var(--t-14);
  font-weight: 500;
  margin-top: 0.1875rem;
}
.bigt .btns {
  display: flex;
  gap: 0.5rem;
  flex: none;
  padding-top: 0.25rem;
}
.bigt .cb {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--fill);
  display: grid;
  place-items: center;
  color: var(--t1);
}
.bigt .cb.lime {
  background: var(--lime);
  color: var(--forest);
}
.bigt .cb svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* ── page header with back ─────────────────────────────────────────── */
.pageh {
  display: flex;
  align-items: center;
  gap: 0.8125rem;
  padding: 0.5rem 1.125rem 0;
}
.pageh .tx {
  flex: 1;
}
.pageh .ds {
  color: var(--t2);
  font-size: var(--t-14);
  font-weight: 500;
  margin-top: 0.125rem;
}
.bk2 {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--fill);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--t1);
}
.bk2 svg {
  width: 0.6875rem;
  height: 1.1875rem;
}
.helpb {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  background: var(--mint);
  border-radius: var(--r-pill);
  padding: 0.6875rem 0.9375rem;
  color: var(--accent-ink);
  font-weight: 700;
  font-size: var(--t-14);
  flex: none;
}
.helpb svg {
  width: 1.0625rem;
  height: 1.0625rem;
}

/* ── search field ──────────────────────────────────────────────────── */
.sf {
  margin: 0.875rem 1.125rem 0;
  background: var(--fill);
  border-radius: var(--r-md);
  min-height: 3rem;
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  padding: 0 0.875rem;
  color: var(--t2);
  width: calc(100% - 2.25rem);
}
.sf svg {
  width: 1.1875rem;
  height: 1.1875rem;
  flex: none;
}
.sf .q {
  flex: 1;
  font-size: 0.90625rem;
  font-weight: 500;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── chips ─────────────────────────────────────────────────────────── */
.chiprow {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  padding: 0.875rem 1.125rem 0;
  scrollbar-width: none;
}
.chiprow::-webkit-scrollbar {
  display: none;
}
.chip {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.625rem 0.9375rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--card);
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--t1);
}
.chip.on {
  background: var(--forest);
  color: #fff;
  /* Transparent, not an accent colour. This was --accent-ink, which is
     lime in dark mode, so the selected chip wore a bright lime ring the
     rest of the app has nothing like. The fill already says which chip
     is chosen; a ring on top of it is saying it twice. Kept as a
     transparent border rather than removed so the chip stays exactly the
     same size as the unselected ones beside it. */
  border-color: transparent;
}
.chip b {
  border-radius: 0.625rem;
  padding: 0.125rem 0.4375rem;
  font-size: var(--t-cap);
  font-weight: 700;
}
.chip.on b {
  background: var(--lime);
  /* --on-lime, never --accent-ink: that token is lime in dark mode, so
     the count was lime on lime and the badge read as an empty green
     blob. Lime does not flip between themes, so neither does its ink. */
  color: var(--on-lime);
}
.chip .r {
  background: var(--rose);
  color: var(--red);
}
.chip .g {
  background: var(--mint);
  color: var(--green);
}

/* ── avatar ────────────────────────────────────────────────────────── */
.av {
  width: 2.875rem;
  height: 2.875rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: var(--t-14);
  flex: none;
  position: relative;
  background: var(--grey);
  color: var(--t2);
}
.av .nd {
  position: absolute;
  top: 0;
  right: 0;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: var(--red);
  border: 2.5px solid var(--card);
}
.needs {
  background: var(--tint-row) !important;
}
.needs .li + .li::before {
  background: rgba(11, 61, 46, 0.1);
}
.needs .av .nd {
  border-color: var(--tint-row);
}

/* ── business card row ─────────────────────────────────────────────── */
.bizcard {
  margin: 0.875rem 1.125rem 0;
  background: var(--tint-row);
  border-radius: var(--r-xl);
  padding: 0.9375rem;
  display: flex;
  align-items: center;
  gap: 0.8125rem;
  width: calc(100% - 2.25rem);
  text-align: left;
  color: inherit;
}
.bizcard .ic {
  width: 3.125rem;
  height: 3.125rem;
  border-radius: var(--r-md);
  background: var(--forest);
  display: grid;
  place-items: center;
  flex: none;
  color: #fff;
}
.bizcard .ic svg {
  width: 1.5rem;
  height: 1.5rem;
}
.bizcard .tx {
  flex: 1;
  min-width: 0;
}
.bizcard b {
  color: var(--t1);
  font-size: var(--t-h1);
  font-weight: 700;
  display: block;
  letter-spacing: -0.01em;
}
.bizcard .tx span {
  display: block;
  font-size: var(--t-13);
  color: var(--t2);
  font-weight: 500;
}

/* ── thread ────────────────────────────────────────────────────────── */
.thd {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}
.thnav {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.25rem 0.875rem 0.75rem;
  flex: none;
}
.thnav .bk {
  display: flex;
  align-items: center;
  gap: 0.0625rem;
  color: var(--accent-ink);
  font-weight: 600;
  font-size: 1rem;
  flex: none;
}
.thnav .bk svg {
  width: 0.6875rem;
  height: 1.1875rem;
}
.thnav .who {
  flex: 1;
  min-width: 0;
}
.thnav .who b {
  font-size: 1.03125rem;
  font-weight: 700;
  display: block;
  letter-spacing: -0.01em;
}
.thnav .who span {
  font-size: 0.78125rem;
  color: var(--green);
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 0.3125rem;
}
.thnav .who span i {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: 50%;
  background: var(--green);
}
.thnav .ib {
  width: 2.625rem;
  height: 2.625rem;
  border-radius: 50%;
  background: var(--fill);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--t1);
}
.thnav .ib.green {
  color: var(--green);
}
.thnav .ib svg {
  width: 1.25rem;
  height: 1.25rem;
}
.banner {
  margin: 0 1rem 0.625rem;
  background: var(--fill);
  border-radius: var(--r-md);
  padding: 0.75rem 0.875rem;
  display: flex;
  align-items: center;
  gap: 0.6875rem;
  flex: none;
}
.banner .ic {
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 50%;
  background: var(--card);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--accent-ink);
}
.banner .ic svg {
  width: 1.0625rem;
  height: 1.0625rem;
}
.banner .tx {
  flex: 1;
  min-width: 0;
}
.banner b {
  color: var(--t1);
  font-size: var(--t-14);
  font-weight: 700;
  display: block;
}
.banner .tx span {
  display: block;
  font-size: 0.78125rem;
  color: var(--t2);
  font-weight: 500;
}
.banner .go {
  display: flex;
  align-items: center;
  gap: 0.1875rem;
  color: var(--accent-ink);
  font-weight: 700;
  font-size: var(--t-sm);
  flex: none;
}
.banner .go svg {
  width: 0.5rem;
  height: 0.8125rem;
}
.msgs {
  flex: 1;
  overflow-y: auto;
  scrollbar-width: none;
  padding: 0 1rem 1.25rem;
  display: flex;
  flex-direction: column;
}
.msgs::-webkit-scrollbar {
  display: none;
}
.datepill {
  align-self: center;
  background: var(--fill);
  color: var(--t2);
  border-radius: 0.875rem;
  padding: 0.375rem 0.875rem;
  font-size: 0.78125rem;
  font-weight: 600;
  margin: 0.375rem 0 0.875rem;
}
.b {
  /* WhatsApp message bodies carry real newlines: a menu, an itemised
     read-back, an address on its own line. Collapsing them turns a
     readable list into a paragraph and stops the transcript matching
     what the customer actually saw on their phone. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-width: 80%;
  padding: 0.75rem 0.9375rem;
  border-radius: var(--r-xl);
  font-size: var(--t-bd);
  line-height: 1.42;
  font-weight: 500;
}
.b.in {
  align-self: flex-start;
  background: var(--bub-in);
  color: var(--t1);
  border-bottom-left-radius: 0.4375rem;
}
.b.out {
  align-self: flex-end;
  background: var(--forest);
  color: #fff;
  border-bottom-right-radius: 0.4375rem;
}
/* A reply that is on screen before the server has confirmed it: the real
   bubble, ghosted. Opacity only, no shimmer and no pulse, so it says
   "not yet" without drawing the eye to itself every second and without
   needing a reduced-motion exception. The transition is on the way OUT
   as well, so when the confirmed message replaces it the change reads as
   the same bubble settling rather than one swapping for another. */
.b.out.ghost {
  opacity: 0.45;
  transition: opacity 220ms cubic-bezier(0.32, 0.72, 0, 1);
}
.stamp {
  font-size: var(--t-cap);
  color: var(--t2);
  font-weight: 500;
  margin: 0.3125rem 0.125rem 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
.stamp.r {
  align-self: flex-end;
}
.stamp svg {
  width: 0.9375rem;
  height: 0.6875rem;
  color: var(--green);
}
.sysp {
  align-self: center;
  background: var(--fill);
  border-radius: 1rem;
  padding: 0.5625rem 0.875rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.78125rem;
  color: var(--t2);
  font-weight: 500;
  margin: 0.25rem 0 0.875rem;
  max-width: 88%;
}
.sysp svg {
  width: 0.9375rem;
  height: 0.9375rem;
  flex: none;
}

/* ── composer ──────────────────────────────────────────────────────── */
.comp {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  padding: 0.625rem 0.875rem 2.125rem;
  padding-bottom: max(2.125rem, env(safe-area-inset-bottom));
  background: var(--card);
  border-top: 1px solid var(--line);
}
/* A composer on a screen that KEEPS the tab bar has to clear it. Thread
   gets away with the plain padding because it hides the bar; the Ask tab
   cannot, and its send button sat underneath the glass where a tap
   reached the nav instead. Pinned through --fix like everything else
   measured against that bar: 1.625rem of bottom offset plus its 3.75rem
   height, plus room to breathe. */
.comp.overtabs {
  padding-bottom: calc(6.25 * var(--fix));
}
.comp .plus {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: var(--fill);
  display: grid;
  place-items: center;
  color: var(--t1);
  flex: none;
}
.comp .plus svg {
  width: 1.25rem;
  height: 1.25rem;
}
.comp .fld {
  flex: 1;
  min-width: 0;
  min-height: 2.875rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--bg);
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0 0.875rem;
  color: var(--t2);
  font-size: 0.9375rem;
  font-weight: 500;
}
.comp .fld input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  outline: none;
  color: var(--t1);
  font-size: 0.9375rem;
  font-weight: 500;
  padding: 0.75rem 0;
}
.comp .fld input::placeholder {
  color: var(--t2);
}
.comp .fld .ib {
  margin-left: auto;
  display: flex;
  gap: 0.75rem;
  flex: none;
  color: var(--t2);
}
.comp .fld svg {
  width: 1.1875rem;
  height: 1.1875rem;
}
.comp .mic {
  width: 2.875rem;
  height: 2.875rem;
  border-radius: 50%;
  background: var(--forest);
  display: grid;
  place-items: center;
  flex: none;
  color: #fff;
}
.comp .mic svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* The recording pulse. Red, moving, and beside a running timer: three
   signals, because "am I still recording?" is the question a voice note
   UI most often fails to answer. */
.recdot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--red);
  flex: none;
  animation: recpulse 1.1s ease-in-out infinite;
}
@keyframes recpulse {
  50% {
    opacity: 0.25;
    transform: scale(0.8);
  }
}
.comp .fld .ib button {
  display: grid;
  place-items: center;
  color: var(--t2);
}
.comp .plus:disabled,
.comp .mic:disabled {
  opacity: 0.45;
  pointer-events: none;
}

/* ── stock: view toggle, floating add, product form ────────────────── */
.viewtoggle {
  display: flex;
  gap: 0.125rem;
  background: var(--fill);
  border-radius: var(--r-pill);
  padding: 0.1875rem;
  flex: none;
}
.viewtoggle button {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--t2);
}
.viewtoggle button.on {
  background: var(--forest);
  color: #fff;
}
.viewtoggle svg {
  width: 1.0625rem;
  height: 1.0625rem;
}

/* Bottom LEFT, clear of the tab bar. The right side is where the thumb
   rests and where the tab targets already are. */
.fab {
  /* Right, not left: a phone is held in the right hand far more often
     than the left, and every other primary action sits on that side.

     ABSOLUTE, AND PORTALLED INTO `.frame` (see ui/FabDial.tsx).
     `position: fixed` on a button inside the scrolling element measured
     correctly in a desktop browser and came apart on a real Android
     phone: the button and its options landed at the top of the screen
     over the header, in different places from each other, and moved as
     the page scrolled. A fixed element inside a transformed or contained
     subtree anchors to that subtree rather than the viewport, and
     browsers disagree about exactly when that applies.

     `.frame` is position: relative, so absolute anchors to the phone
     frame. That is also what puts it inside the frame on a wide desktop
     window, with no 50vw arithmetic to get wrong. */
  position: absolute;
  right: calc(0.875 * var(--fix));
  /* Sits above the pinned tab bar, so the offset is pinned too. */
  bottom: calc(6.5 * var(--fix));
  bottom: calc(6.5 * var(--fix) + env(safe-area-inset-bottom));
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: var(--forest);
  color: #fff;
  display: grid;
  place-items: center;
  box-shadow: 0 6px 20px rgba(11, 61, 46, 0.35);
  z-index: 60;
  transition: transform 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.fab:active {
  transform: scale(0.93);
}
.fab svg {
  width: 1.5rem;
  height: 1.5rem;
}

.flabel {
  display: block;
  font-size: var(--t-14);
  font-weight: 700;
  margin: 1.25rem 0 0.5rem;
}
.flabel em {
  font-style: normal;
  font-weight: 500;
  color: var(--t2);
}
.two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
}
.stepper {
  display: flex;
  align-items: center;
  background: var(--fill);
  border-radius: var(--r-md);
  min-height: 3.375rem;
  padding: 0 0.5rem;
}
.stepper button {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--card);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--t1);
}
.stepper button:disabled {
  opacity: 0.4;
  pointer-events: none;
}
.stepper input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  outline: none;
  text-align: center;
  font-size: 1.25rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.moreopts {
  display: flex;
  align-items: center;
  gap: 0.8125rem;
  width: 100%;
  background: var(--tint-row);
  border-radius: var(--r-md);
  padding: 0.8125rem 0.875rem;
  margin-top: 1.5rem;
  color: inherit;
}
.moreopts .tx {
  flex: 1;
  min-width: 0;
}
.moreopts .tt {
  display: block;
  font-size: var(--t-bd);
  font-weight: 700;
}
.moreopts .ds {
  display: block;
  font-size: var(--t-13);
  color: var(--t2);
  font-weight: 500;
}
.moreopts svg {
  flex: none;
  color: var(--t2);
  width: 1.125rem;
  height: 1.125rem;
}
/* The finish button never scrolls out of reach on a long form. */
/* max-width is the FRAME's width, so it is pinned through --fix like
   .frame is. Left as a bare rem it shrank with the design scale while the
   frame it is supposed to span did not, so the bar stopped reaching the
   edges. */
.stickybar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-width: calc(27.5 * var(--fix));
  margin: 0 auto;
  padding: 0.75rem 1.125rem;
  padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
  background: linear-gradient(to top, var(--bg) 62%, transparent);
  z-index: 70;
}
.remybtn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--mint);
  border-radius: var(--r-md);
  padding: 0.5rem 0.75rem;
  color: var(--accent-ink);
  flex: none;
}
.remybtn svg {
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
}
.remybtn b {
  display: block;
  font-size: var(--t-13);
  font-weight: 700;
  white-space: nowrap;
}
.remybtn i {
  display: block;
  font-style: normal;
  font-size: 0.6875rem;
  opacity: 0.75;
  white-space: nowrap;
}

/* ── hint ──────────────────────────────────────────────────────────
   A quiet mark that opens an explainer. Small and low contrast on
   purpose: it must be findable when wanted and invisible when not. */
.hint {
  padding: 0.5rem 1.125rem 0;
}
.hintbtn {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--t3);
  background: var(--fill);
  flex: none;
}
.hintbtn.on {
  background: var(--forest);
  color: #fff;
}
.hintbtn svg {
  width: 0.9375rem;
  height: 0.9375rem;
}
.hintbtn.inline {
  display: inline-grid;
  vertical-align: middle;
  margin-left: 0.375rem;
  width: 1.5rem;
  height: 1.5rem;
}
.hintbtn.inline svg {
  width: 0.8125rem;
  height: 0.8125rem;
}
.hintbody {
  margin-top: 0.5rem;
  background: var(--fill);
  border-radius: var(--r-md);
  padding: 0.8125rem 0.875rem;
  font-size: var(--t-sm);
  color: var(--t2);
  font-weight: 500;
  line-height: 1.45;
}
.hintbody.standalone {
  margin: 0 1.125rem 0.5rem;
}

/* ── ask remy ──────────────────────────────────────────────────────── */
.asknav {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.25rem 1.125rem 0.875rem;
  flex: none;
}
.asknav .lg {
  width: 2.875rem;
  height: 2.875rem;
  border-radius: 50%;
  background: var(--forest);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--lime);
}
.asknav .lg svg {
  width: 1.375rem;
  height: 1.375rem;
}
.asknav .tx {
  flex: 1;
  min-width: 0;
}
.asknav b {
  color: var(--t1);
  font-size: var(--t-19);
  font-weight: 700;
  display: block;
  letter-spacing: -0.02em;
}
.asknav .tx span {
  display: block;
  font-size: var(--t-13);
  color: var(--t2);
  font-weight: 500;
}
.asknav .ib {
  width: 2.625rem;
  height: 2.625rem;
  border-radius: 50%;
  background: var(--fill);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--t1);
}
.asknav .ib svg {
  width: 1.1875rem;
  height: 1.1875rem;
}
.tip {
  margin: 0 1.125rem 0.875rem;
  background: var(--mint);
  border-radius: 1rem;
  padding: 0.8125rem 0.875rem;
  display: flex;
  align-items: flex-start;
  gap: 0.6875rem;
  flex: none;
}
.tip .ic {
  width: 1.875rem;
  height: 1.875rem;
  border-radius: 50%;
  background: var(--lime);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--forest);
}
.tip .ic svg {
  width: 0.9375rem;
  height: 0.9375rem;
}
.tip p {
  flex: 1;
  font-size: var(--t-sm);
  color: var(--accent-ink);
  font-weight: 500;
  line-height: 1.4;
}
.tip .x {
  color: var(--accent-ink);
  opacity: 0.55;
  flex: none;
}
.tip .x svg {
  width: 1rem;
  height: 1rem;
}
.arow {
  display: flex;
  gap: 0.625rem;
  align-items: flex-start;
  margin-bottom: 0.375rem;
}
.arow .ra {
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 50%;
  background: var(--mint);
  display: grid;
  place-items: center;
  flex: none;
  margin-top: 1.125rem;
  color: var(--accent-ink);
}
.arow .ra svg {
  width: 1rem;
  height: 1rem;
}
.arow .col {
  flex: 1;
  min-width: 0;
}
.rlab {
  font-size: 0.78125rem;
  color: var(--t2);
  font-weight: 600;
  margin-bottom: 0.3125rem;
}
.rbub {
  background: var(--fill);
  border-radius: var(--r-lg);
  padding: 0.875rem;
  font-size: 0.9375rem;
  line-height: 1.45;
  font-weight: 500;
}
.rbub.ok {
  background: var(--mint);
}
.rline {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  margin: 0.4375rem 0;
}
.rline svg {
  width: 1.0625rem;
  height: 1.0625rem;
  color: var(--t2);
  flex: none;
}
.rbtns {
  display: flex;
  gap: 0.5625rem;
  margin-top: 0.8125rem;
  flex-wrap: wrap;
}
.rbtns button {
  padding: 0.6875rem 1.25rem;
  border-radius: 0.8125rem;
  font-size: 0.90625rem;
  font-weight: 700;
  background: var(--forest);
  color: #fff;
}
.rbtns button.w {
  background: var(--card);
  color: var(--t1);
  border: 1px solid var(--line);
}
.okline {
  display: flex;
  align-items: center;
  gap: 0.6875rem;
}
.okline .ck {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--green);
  display: grid;
  place-items: center;
  flex: none;
  color: #fff;
}
.okline .ck svg {
  width: 1rem;
  height: 1rem;
}
.okline b {
  color: var(--green);
  font-weight: 700;
}
.okline .m {
  font-size: 0.78125rem;
  color: var(--t2);
  font-weight: 500;
  margin-top: 0.125rem;
}
.ts {
  align-self: flex-end;
  font-size: var(--t-xs);
  color: var(--t2);
  font-weight: 500;
  margin: 0.625rem 0.125rem 0.375rem;
  text-align: right;
}
.qrow {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  padding: 0.25rem 1.125rem 0.75rem;
  scrollbar-width: none;
  flex: none;
}
.qrow::-webkit-scrollbar {
  display: none;
}
.qrow button {
  flex: none;
  padding: 0.6875rem 1.0625rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--card);
  font-size: var(--t-14);
  font-weight: 600;
  white-space: nowrap;
  color: var(--t1);
}
.qrow button.on {
  background: var(--forest);
  color: #fff;
  border-color: var(--accent-ink);
}

/* ── tab bar ───────────────────────────────────────────────────────
   Four tabs, forever. A new service adds a Menu tile, never a tab. */
.tabs {
  position: absolute;
  left: calc(0.875 * var(--fix));
  right: calc(0.875 * var(--fix));
  bottom: calc(1.625 * var(--fix));
  bottom: max(calc(1.625 * var(--fix)), env(safe-area-inset-bottom));
  /* calc(3.75 * var(--fix)) is the floor, and the padding below is set to match it, not
     to taste. The button has to stay at 48px, the Android minimum touch
     target, on the four controls the whole app is navigated with.
     Measured, because the arithmetic is easy to get wrong: the 1px
     border counts twice, so the button is height - 2px border - 2x
     padding = 60 - 2 - 10 = 48. Shrink the height further and the
     padding has nothing left to give. */
  height: calc(3.75 * var(--fix));
  border-radius: var(--r-pill);
  background: var(--glass);
  backdrop-filter: blur(22px) saturate(180%);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid var(--gline);
  box-shadow: 0 8px 32px rgba(15, 20, 18, 0.12);
  display: flex;
  padding: calc(0.3125 * var(--fix));
  z-index: 85;
}
/* A full-screen page. The bar SLIDES OUT rather than being unmounted,
   because an element carrying a view-transition-name that disappears
   leaves its old snapshot fading over the new page for the length of the
   transition, which read as a flicker of the bar in its old position
   every time a settings or product screen opened.

   Far enough to clear the safe-area inset as well as the bar itself, so
   nothing peeks back on a phone with a home indicator. */
.tabs.away {
  transform: translateY(calc(100% + 3 * var(--fix)));
  opacity: 0;
  pointer-events: none;
}
.tabs button {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(0.125 * var(--fix));
  border-radius: var(--r-pill);
  position: relative;
  color: var(--t2);
}
.tabs button.on {
  background: var(--lime);
  color: var(--forest);
}
.tabs svg {
  width: calc(1.375 * var(--fix));
  height: calc(1.375 * var(--fix));
}

/* ── the Remy mark, sized ───────────────────────────────────────────
   Every other glyph here is a near-square 24-grid icon that fills about
   0.705 of its box: measured at a 22px box, Home inks 16.15px, Menu
   15.58, Chats 14.95. The Remy mark is a MARK, 1.59 times wider than it
   is tall, with its viewBox cropped to its ink, so a square width and
   height letterboxes it: it came out 9.39px tall, 58% of Home, and
   looked like an error.

   So it is given a HEIGHT only and its width follows. Each height below
   is 0.705 of the square box its neighbours use in that context, which
   puts its ink on the same line as theirs. If an icon size changes, the
   matching mark height has to change with it.

   These selectors are two classes deep on purpose: `.tabs svg` above
   would otherwise win on specificity and force it square again. */
.rmk {
  width: auto;
  flex: none;
}
/* 22px box -> 15.5px of ink */
/* The tab bar is pinned, so its mark is measured in --fix; the other
   two follow the design scale with the icons beside them. */
.tabs .rmk {
  width: auto;
  height: calc(0.96875 * var(--fix));
}
.gi .ic .rmk,
.asknav .lg .rmk {
  width: auto;
  height: 0.96875rem;
}
/* 20px box -> 14px */
.li .ic .rmk {
  width: auto;
  height: 0.875rem;
}
/* 18px box -> 12.5px */
.remybtn .rmk {
  width: auto;
  height: 0.78125rem;
}
.tabs span {
  font-size: calc(0.6875 * var(--fix));
  font-weight: 600;
  line-height: 1.1;
}
/* Offsets track the icon: the badge's right edge sits about 0.625rem past
   the icon's, which is what gives the overlapping-corner look. Change the
   icon size above and this has to move with it. */
.tabs .bg {
  position: absolute;
  top: calc(0.3125 * var(--fix));
  right: calc(50% - calc(1.3125 * var(--fix)));
  min-width: calc(1.0625 * var(--fix));
  height: calc(1.0625 * var(--fix));
  border-radius: calc(0.53125 * var(--fix));
  background: var(--red);
  color: #fff;
  font-size: calc(0.65625 * var(--fix));
  font-weight: 700;
  display: grid;
  place-items: center;
  padding: 0 calc(0.28125 * var(--fix));
  border: 2px solid var(--glass);
}

/* ── hero (plan) ───────────────────────────────────────────────────── */
.hero {
  margin: 0 1.125rem;
  border-radius: var(--r-2xl);
  overflow: hidden;
  position: relative;
  background: var(--forest);
  color: #fff;
}
.hero .in {
  position: relative;
  padding: 1.25rem;
}
.hero .tagp {
  display: inline-block;
  background: rgba(255, 255, 255, 0.16);
  border-radius: 0.75rem;
  padding: 0.3125rem 0.75rem;
  font-size: 0.78125rem;
  font-weight: 600;
  color: #fff;
}
.hero .nm {
  font-size: 1.8125rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  margin-top: 0.8125rem;
  line-height: 1.1;
}
.hero .pr {
  font-size: var(--t-d1);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.08;
}
.hero .sb {
  font-size: var(--t-sm);
  color: rgba(255, 255, 255, 0.7);
  font-weight: 500;
  margin-top: 0.3125rem;
}
.hero .crown {
  position: absolute;
  top: 1.125rem;
  right: 1.125rem;
  width: 2.875rem;
  height: 2.875rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.14);
  display: grid;
  place-items: center;
  color: #fff;
}
.hero .crown svg {
  width: 1.375rem;
  height: 1.375rem;
}
.hero .usek {
  font-size: var(--t-sm);
  color: rgba(255, 255, 255, 0.82);
  font-weight: 500;
  margin-top: 1.5rem;
}
.hero .bar {
  height: 0.5rem;
  border-radius: 0.25rem;
  background: rgba(255, 255, 255, 0.2);
  overflow: hidden;
  margin: 0.5625rem 0 0.5rem;
}
.hero .bar i {
  display: block;
  height: 100%;
  border-radius: 0.25rem;
  background: var(--lime);
}
.hero .ends {
  display: flex;
  justify-content: space-between;
  font-size: var(--t-13);
  font-weight: 600;
}
.hero .ends span:last-child {
  color: rgba(255, 255, 255, 0.7);
}

/* ── appearance card ───────────────────────────────────────────────── */
.appear {
  margin: 0.75rem 1.125rem 0;
  border-radius: var(--r-2xl);
  overflow: hidden;
  position: relative;
  background: var(--forest);
  color: #fff;
}
.appear .in {
  position: relative;
  padding: 1.0625rem;
}
.appear .hd {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.appear .ic {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.14);
  display: grid;
  place-items: center;
  flex: none;
  color: #fff;
}
.appear .ic svg {
  width: 1.3125rem;
  height: 1.3125rem;
}
.appear b {
  font-size: 1.09375rem;
  font-weight: 700;
  display: block;
  letter-spacing: -0.01em;
}
.appear .ds {
  font-size: var(--t-13);
  color: rgba(255, 255, 255, 0.7);
  font-weight: 500;
  margin-top: 0.125rem;
}
.appear .lbl {
  font-size: var(--t-13);
  color: rgba(255, 255, 255, 0.78);
  font-weight: 500;
  margin: 0.9375rem 0 0.5rem;
}
.appear .segs {
  display: flex;
  gap: 0.5625rem;
}
.appear .segs button {
  flex: 1;
  padding: 0.75rem 0.375rem;
  border-radius: 0.875rem;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: transparent;
  color: #fff;
  font-size: 0.90625rem;
  font-weight: 700;
  text-align: center;
}
.appear .segs button.on {
  background: var(--lime);
  color: var(--forest);
  border-color: var(--lime);
}

/* ── promise card ──────────────────────────────────────────────────── */
.promise {
  margin: 0.875rem 1.125rem 0;
  background: var(--mint);
  border-radius: var(--r-xl);
  padding: 1rem;
  display: flex;
  gap: 0.8125rem;
  align-items: flex-start;
  position: relative;
  overflow: hidden;
}
.promise .ic {
  width: 2.625rem;
  height: 2.625rem;
  border-radius: 50%;
  background: var(--card);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--accent-ink);
}
.promise .ic svg {
  width: 1.25rem;
  height: 1.25rem;
}
.promise b {
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent-ink);
  line-height: 1.35;
  display: block;
}
.promise span {
  font-size: var(--t-13);
  color: var(--accent-ink);
  opacity: 0.72;
  font-weight: 500;
  display: block;
  margin-top: 0.3125rem;
}

/* ── toggle ────────────────────────────────────────────────────────── */
.tog2 {
  width: 3.25rem;
  height: 1.9375rem;
  border-radius: 1rem;
  background: var(--line);
  position: relative;
  flex: none;
  transition: background 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.tog2 i {
  position: absolute;
  top: 0.15625rem;
  left: 0.15625rem;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  transition: left 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.tog2.on {
  background: var(--forest);
}
.tog2.on i {
  left: 1.46875rem;
}

/* ── first open: splash, welcome, sign in ──────────────────────────── */
.splash {
  position: absolute;
  inset: 0;
  /* Deliberately NOT --forest: the tile is forest, and a forest tile on
     a forest field is invisible until the bars land, which throws away
     the first beat of the animation. A near-black ground lets the tile
     arrive as an object. */
  background: #071a14;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  z-index: 200;
}
/* Sizing and the animation itself live in motion.css. */

/* A full-screen page with no frame: the splash, sign in, and the crash
   screen.

   NOT `.open`, WHICH IS WHAT IT USED TO BE CALLED. "open" is the natural
   word for "this thing is expanded", so it was also the state modifier on
   the sheet, the plus button and the plus button's option stack. A bare
   `.open` rule setting `position: absolute; inset: 0` therefore captured
   `.fab.open` and pinned the button to the top-left corner of the frame
   the moment it was tapped, which is what put it over the header.

   This is the THIRD time a short generic class in this shared stylesheet
   has done this: `.ph` caught a `val ph` placeholder and an `askbar .ph`
   and gave both height: 100dvh, and it is why the phone frame is now
   `.frame`. The lesson is the same each time, so the names here are
   deliberately ones nothing will reach for by accident. */
.fullpage {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  padding: 0 1.125rem;
  padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
  overflow-y: auto;
  scrollbar-width: none;
}
.fullpage::-webkit-scrollbar {
  display: none;
}
.fullpage .mk {
  width: 3.25rem;
  height: 3.25rem;
  color: var(--accent-ink);
  flex: none;
}
.fullpage h1 {
  font-size: var(--t-d1);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.14;
  text-wrap: balance;
}
.fullpage p {
  font-size: 1rem;
  color: var(--t2);
  font-weight: 500;
  line-height: 1.5;
  margin-top: 0.75rem;
}
.spacer {
  flex: 1;
  min-height: 1.25rem;
}
.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  min-height: 3.375rem;
  border-radius: var(--r-md);
  font-size: 1rem;
  font-weight: 700;
  transition: transform 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.btn:active {
  transform: scale(0.985);
}
.btn.dark {
  background: var(--forest);
  color: #fff;
}
.btn.lime {
  background: var(--lime);
  color: var(--forest);
}
.btn.ghost {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--t1);
}
.btn:disabled {
  opacity: 0.42;
  pointer-events: none;
}
.btn svg {
  width: 1.125rem;
  height: 1.125rem;
}

/* The Aa control, offered before anything is asked and never a gate. */
.aa {
  position: absolute;
  top: 1rem;
  right: 1.125rem;
  display: flex;
  align-items: center;
  gap: 0.125rem;
  background: var(--fill);
  border-radius: var(--r-pill);
  padding: 0.25rem;
  z-index: 3;
}
.aa button {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  font-weight: 700;
  display: grid;
  place-items: center;
  color: var(--t2);
}
.aa button.on {
  background: var(--forest);
  color: #fff;
}
.aa .s1 {
  font-size: 0.8125rem;
}
.aa .s2 {
  font-size: 0.9375rem;
}
.aa .s3 {
  font-size: 1.0625rem;
}

/* phone number field */
.phonef {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--fill);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 0 0.875rem;
  min-height: 3.375rem;
  transition: border-color 140ms;
}
.phonef:focus-within {
  border-color: var(--accent-ink);
  background: var(--card);
}
.phonef .cc {
  font-weight: 700;
  color: var(--t2);
  flex: none;
}
/* The show-password button. A full tap target, negative margin so it
   does not make the field taller than the others. */
.phonef .peek {
  flex: none;
  width: 2.75rem;
  height: 2.75rem;
  margin-right: -0.5rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--t2);
}
.phonef .peek[aria-pressed="true"] {
  color: var(--accent-ink);
}
.phonef .peek svg {
  width: 1.25rem;
  height: 1.25rem;
}
.phonef input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: none;
  padding: 0.75rem 0;
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

/* PIN */
.pindots {
  display: flex;
  justify-content: center;
  gap: 0.875rem;
  margin: 0 0 1.5rem;
}
.pindots i {
  width: 0.875rem;
  height: 0.875rem;
  border-radius: 50%;
  border: 2px solid var(--t3);
  transition:
    background 100ms,
    border-color 100ms,
    transform 160ms cubic-bezier(0.34, 1.4, 0.64, 1);
}
.pindots i.on {
  background: var(--forest);
  border-color: var(--accent-ink);
  transform: scale(1.18);
}
.pindots.shake {
  animation: shake 0.4s cubic-bezier(0.4, 0, 0.2, 1) both;
}
.pindots.shake i {
  border-color: var(--red);
}
@keyframes shake {
  0%,
  100% {
    transform: translateX(0);
  }
  20% {
    transform: translateX(-0.5rem);
  }
  40% {
    transform: translateX(0.45rem);
  }
  60% {
    transform: translateX(-0.3rem);
  }
  80% {
    transform: translateX(0.15rem);
  }
}
.keys {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.625rem;
}
.keys button {
  min-height: 3.75rem;
  border-radius: var(--r-md);
  background: var(--fill);
  font-weight: 700;
  font-size: 1.375rem;
  display: grid;
  place-items: center;
  transition: transform 100ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* Scale, not colour: on a cheap LCD in daylight a 100ms background
   change is frequently not seen, and a key that gives no answer gets
   pressed twice. */
.keys button:active {
  transform: scale(0.93);
  background: var(--line);
}
.keys button.del {
  background: none;
  color: var(--t2);
}
.keys button.del svg {
  width: 1.375rem;
  height: 1.375rem;
}
.pinerr {
  display: block;
  color: var(--red);
  font-size: var(--t-sm);
  font-weight: 600;
  text-align: center;
  margin: -0.75rem 0 0.75rem;
}

/* ── misc ──────────────────────────────────────────────────────────── */
.thinking {
  display: inline-flex;
  gap: 0.25rem;
  align-items: center;
  padding: 0.25rem 0;
}
.thinking i {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.35;
  animation: blink 1.1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
.thinking i:nth-child(2) {
  animation-delay: 0.16s;
}
.thinking i:nth-child(3) {
  animation-delay: 0.32s;
}
@keyframes blink {
  30% {
    opacity: 1;
    transform: translateY(-0.12rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ── sheet ─────────────────────────────────────────────────────────
   Anything that rises from the bottom edge. Scrim and panel animate
   separately and at different speeds because they say different things:
   the scrim says the screen behind is paused, the panel says this came
   from down there and that is where it goes back to.

   The scrim is neutral ink, not forest: a green scrim tints everything
   behind it green and turns the lime button olive. A scrim should
   darken, not colour. */
.sheetwrap {
  position: absolute;
  inset: 0;
  z-index: 120;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
/* Dimmed AND blurred. The dim says the screen behind is paused; the blur
   says it is still there, which is what keeps a sheet feeling like a
   layer over your business rather than a new page. Neutral ink, not
   forest: a green scrim tints everything behind it and turns the lime
   button olive. A scrim should darken, not colour. */
.scrim {
  position: absolute;
  inset: 0;
  background: rgba(12, 18, 16, 0.32);
  backdrop-filter: blur(18px) saturate(115%);
  -webkit-backdrop-filter: blur(18px) saturate(115%);
  opacity: 0;
  transition: opacity 260ms cubic-bezier(0.4, 0, 0.2, 1);
  /* The scrim swallows touch-scroll so the screen behind cannot be
     dragged around under the sheet. */
  touch-action: none;
}
.sheetwrap.in .scrim {
  opacity: 1;
}
/* 0.32, 0.72, 0, 1 is the iOS sheet curve: almost no ease-in, so the
   panel leaves the bottom edge immediately, and a long tail so it
   settles instead of stopping. The scrim runs on its own, shorter and
   softer, because the two are saying different things. */
.sheet {
  position: relative;
  background: var(--card);
  border-radius: var(--r-2xl) var(--r-2xl) 0 0;
  padding: 0 1.125rem 1.5rem;
  padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
  max-height: 88dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  box-shadow: 0 -8px 40px rgba(15, 20, 18, 0.18);
  transform: translateY(100%);
  transition: transform 420ms cubic-bezier(0.32, 0.72, 0, 1);
  /* Vertical gestures are handled in JS, so the browser must not also
     claim them; without this a drag scrolls the panel and moves it. */
  touch-action: pan-y;
}
.sheet.tall {
  height: 88dvh;
}
.sheetwrap.in .sheet {
  transform: translateY(0);
}
.sheet:focus {
  outline: none;
}
.sheetwrap.leaving .sheet {
  transform: translateY(100%);
  transition-duration: 280ms;
}
/* While the finger is down the panel is positioned from JS, frame by
   frame, so any transition would lag behind the hand. */
.sheetwrap.dragging .sheet {
  transition: none;
}
/* Sticky so the handle stays grabbable once a long list has scrolled,
   and opaque so rows do not show through underneath it. */
.sheethead {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--card);
  padding-bottom: 0.5rem;
  cursor: grab;
}
.sheetwrap.dragging .sheethead {
  cursor: grabbing;
}
/* The universal sign that a thing can be pushed back down, and now it
   really can be. */
.grabber {
  display: block;
  width: 2.5rem;
  height: 0.25rem;
  border-radius: 0.125rem;
  background: var(--line);
  margin: 0.5rem auto 0.75rem;
}
.sheettitle {
  font-size: var(--t-19);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: 0.5rem;
}
.sheet p {
  font-size: var(--t-bd);
  color: var(--t2);
  font-weight: 500;
  line-height: 1.5;
}
/* ── shortcut grid ─────────────────────────────────────────────────
   Four across, which is the most that fits a readable label on the
   narrowest phone this app targets. Rows wrap and the sheet scrolls. */
.grid4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.125rem 0.5rem;
  padding: 0.375rem 0 0.5rem;
}
.gi {
  display: grid;
  justify-items: center;
  gap: 0.4375rem;
  min-width: 0;
}
.gi .ic {
  position: relative;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: var(--r-md);
  display: grid;
  place-items: center;
  color: var(--accent-ink);
}
.gi .ic svg {
  width: 1.375rem;
  height: 1.375rem;
}
/* The same count as the tab badge, in the same place, so the number
   means the same thing wherever it appears. */
.gi .ct {
  position: absolute;
  top: -0.25rem;
  right: -0.25rem;
  min-width: 1.125rem;
  height: 1.125rem;
  padding: 0 0.3125rem;
  border-radius: 0.5625rem;
  background: var(--red);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  display: grid;
  place-items: center;
  border: 2px solid var(--card);
}
.gi .lb {
  font-size: 0.6875rem;
  font-weight: 500;
  color: var(--t2);
  line-height: 1.25;
  text-align: center;
}
.gi:active .ic {
  transform: scale(0.94);
}
.gi .ic {
  transition: transform 140ms cubic-bezier(0.32, 0.72, 0, 1);
}

.sheet .acts {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  margin-top: 1.25rem;
}
.btn.danger {
  background: var(--red);
  color: #fff;
}

/* ── signed out ────────────────────────────────────────────────────
   The remembered-account card on the sign-in screen. */
.acct {
  display: flex;
  align-items: center;
  gap: 0.8125rem;
  background: var(--fill);
  border-radius: var(--r-md);
  padding: 0.875rem;
}
.acct .av {
  background: var(--forest);
  color: var(--lime);
}
.acct .tx {
  flex: 1;
  min-width: 0;
}
.acct b {
  display: block;
  font-size: var(--t-h1);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.acct .tx span {
  display: block;
  font-size: var(--t-13);
  color: var(--t2);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.textlink {
  display: block;
  width: 100%;
  text-align: center;
  padding: 0.875rem;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--t2);
}

/* ── add product: media card, select row, description ───────────────
   Built to the supplied design. The media card is the one piece here
   that cannot do its job yet: there is no bucket and no media table, so
   its buttons are inert and it says why rather than taking a photo and
   dropping it. Keeping it in place, at the top where it was designed,
   means the screen is not quietly redesigned around a missing feature. */
.mediacard {
  background: var(--card);
  border: var(--hair) solid var(--line);
  border-radius: var(--r-lg);
  padding: 0.875rem;
  margin-bottom: 1.125rem;
}
.mediacard .hd {
  display: grid;
  gap: 0.125rem;
  margin-bottom: 0.75rem;
}
.mediacard .hd b {
  font-size: var(--t-bd);
  font-weight: 700;
}
.mediacard .hd span {
  font-size: var(--t-sm);
  color: var(--t2);
  font-weight: 500;
}
.mediacard .slots {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  scrollbar-width: none;
}
.mediacard .slots::-webkit-scrollbar {
  display: none;
}
.mediacard .slot {
  flex: none;
  width: 4.625rem;
  height: 4.625rem;
  border-radius: var(--r-md);
  background: var(--fill);
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0.3125rem;
  color: var(--t2);
}
.mediacard .slot svg {
  width: 1.25rem;
  height: 1.25rem;
}
.mediacard .slot span {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--t2);
}
.mediacard .slot:disabled {
  opacity: 0.55;
}
/* The camera leads, so it is the one tile that looks like an action
   rather than a slot. WhatsApp's order: shutter, then the gallery, then
   what is already attached. */
.mediacard .slot.shoot {
  background: var(--forest);
  color: #fff;
}
.mediacard .slot.shoot span {
  color: rgba(255, 255, 255, 0.86);
}
/* The "add more" tile from the design: dashed, and inert because there
   is nothing to add to yet. */
.mediacard .ghostslot {
  background: none;
  border: var(--hair) dashed var(--line);
  opacity: 0.55;
}
.mediacard .why {
  font-size: var(--t-xs);
  color: var(--t2);
  font-weight: 500;
  line-height: 1.45;
  margin: 0.75rem 0 0;
}

/* A field that opens a picker rather than taking typing. Shaped like the
   other inputs so the form still reads as one column of fields. */
/* Padding and min-height copied from .phonef, not invented, so this row
   sits on the same rhythm as the inputs above and below it. Left to its
   own padding it came out 34px against their 44.5px and read as a
   different kind of thing. */
.selectrow {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  background: var(--fill);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 0 0.875rem;
  min-height: 3.375rem;
  color: var(--t1);
  text-align: left;
}
/* The tile styles are scoped to .li .ic, so an .ic here has no size of
   its own and collapsed to nothing. Only the glyph is wanted in a field
   row anyway, at the same size the fields' own leading icons use. */
.selectrow .ic {
  flex: none;
  width: auto;
  height: auto;
  background: none;
  color: var(--t2);
}
.selectrow .ic svg {
  width: 1.125rem;
  height: 1.125rem;
}
.selectrow .val {
  flex: 1;
  min-width: 0;
  font-size: var(--t-bd);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Renamed off the frame class. See the note on .frame. */
.selectrow .val.empty {
  color: var(--t3);
  font-weight: 500;
}
/* Sized, not just coloured. `chev` has an 8x14 viewBox and no intrinsic
   pixel size, so an unsized one in a flex row takes the full width and
   1.75x that in height: this row rendered 862px tall, nineteen times the
   field above it. The Chev component carries .cv for exactly this
   reason; this is the belt to its braces. */
.selectrow > svg:last-child {
  width: 0.5rem;
  height: 0.875rem;
  color: var(--t3);
  flex: none;
}

/* The description box. The counter sits inside the field, bottom right,
   as designed, so it never pushes the layout around as it changes. */
.areaf {
  position: relative;
  background: var(--fill);
  border-radius: var(--r-md);
  padding: 0.6875rem 0.75rem 1.5rem;
}
.areaf textarea {
  width: 100%;
  border: 0;
  background: none;
  outline: none;
  resize: none;
  font: inherit;
  font-size: var(--t-bd);
  font-weight: 500;
  color: var(--t1);
  line-height: 1.5;
}
.areaf textarea::placeholder {
  color: var(--t3);
}
.areaf .count {
  position: absolute;
  right: 0.75rem;
  bottom: 0.5rem;
  font-size: var(--t-cap);
  color: var(--t3);
  font-weight: 600;
}

/* ── picker sheet ──────────────────────────────────────────────────── */
.picklist {
  display: grid;
  gap: 0.25rem;
  padding-bottom: 0.25rem;
}
.pickrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  padding: 0.75rem 0.75rem;
  border-radius: var(--r-md);
  background: var(--fill);
  font-size: var(--t-bd);
  font-weight: 600;
  color: var(--t1);
  text-align: left;
}
.pickrow.on {
  background: var(--tint-row);
  color: var(--accent-ink);
}
.pickrow svg {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
}
.pickrow.on svg {
  color: var(--accent-ink);
}
/* Creating a category is a deliberate act, so it looks like one. */
.pickrow.new {
  background: none;
  border: var(--hair) dashed var(--line);
  color: var(--accent-ink);
}
.pickempty {
  font-size: var(--t-sm);
  color: var(--t2);
  font-weight: 500;
  padding: 0.75rem 0.25rem;
}

/* ── media thumbnails ───────────────────────────────────────────────
   The tiles are LABELS wrapping hidden file inputs, because only a file
   input can open a camera; they still have to look and size like the
   buttons they replaced. */
.mediacard .slot {
  cursor: pointer;
}
.mediacard label.slot input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.mediacard label.slot:active {
  transform: scale(0.96);
}
.mediacard label.slot {
  transition: transform 140ms cubic-bezier(0.32, 0.72, 0, 1);
}
/* A taken photo, same footprint as the tiles so the row stays even. */
.mediacard .shot {
  position: relative;
  flex: none;
  width: 4.625rem;
  height: 4.625rem;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--fill);
}
.mediacard .shot img,
.mediacard .shot video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The remove control sits ON the thumbnail, as designed. Given its own
   hit area rather than relying on the 16px glyph: a thumbnail is small
   and a mis-tap deletes the photo. */
.mediacard .shot .rm {
  position: absolute;
  top: 0.1875rem;
  right: 0.1875rem;
  width: 1.375rem;
  height: 1.375rem;
  border-radius: 50%;
  background: rgba(12, 18, 16, 0.72);
  color: #fff;
  display: grid;
  place-items: center;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.mediacard .shot .rm svg {
  width: 0.75rem;
  height: 0.75rem;
}
/* Marks a video apart from a photo at a glance: the poster frame of a
   video and a still photo look identical at this size. */
.mediacard .shot .vtag {
  position: absolute;
  left: 0.1875rem;
  bottom: 0.1875rem;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: rgba(12, 18, 16, 0.72);
  color: #fff;
  display: grid;
  place-items: center;
}
.mediacard .shot .vtag svg {
  width: 0.6875rem;
  height: 0.6875rem;
}

/* The draft notice. Quiet: it is good news, not a warning, and it sits
   above a form the owner is about to carry on filling. */
.draftbar {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  background: var(--tint-row);
  border-radius: var(--r-md);
  padding: 0.625rem 0.75rem;
  margin-bottom: 1rem;
}
.draftbar .tx {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: 0.0625rem;
}
.draftbar .tx b {
  font-size: var(--t-sm);
  font-weight: 700;
}
.draftbar .tx span {
  font-size: var(--t-xs);
  color: var(--t2);
  font-weight: 500;
}
.draftbar > button {
  flex: none;
  font-size: var(--t-xs);
  font-weight: 700;
  color: var(--accent-ink);
  padding: 0.25rem 0.125rem;
}
.draftbar .ic {
  flex: none;
  width: auto;
  height: auto;
  background: none;
  color: var(--accent-ink);
}
.draftbar .ic svg {
  width: 1.125rem;
  height: 1.125rem;
}

/* ── Ask Remy proposal cards ────────────────────────────────────────
   A draft, shown so it can be CHECKED rather than merely agreed with.
   Every row carries before and after: "1,200 to 1,500" is a claim you
   can catch, "1,500" is a number you nod at. */
.pcard {
  background: var(--card);
  border: var(--hair) solid var(--line);
  border-radius: var(--r-lg);
  padding: 0.75rem 0.875rem;
  margin-top: 0.5rem;
  box-shadow: var(--sh);
}
.pcard .ptitle {
  font-size: var(--t-bd);
  font-weight: 700;
  margin-bottom: 0.5rem;
}
.pcard .pline {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.25rem 0;
  font-size: var(--t-sm);
}
.pcard .pline .lb {
  color: var(--t2);
  font-weight: 500;
  flex: none;
}
.pcard .pline .ba {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  min-width: 0;
  text-align: right;
}
/* The old value, struck through, so the change is the thing you read. */
.pcard .pline .ba s {
  color: var(--t3);
  font-weight: 500;
}
.pcard .pline .ba b {
  font-weight: 700;
}
.pcard .pwarn {
  display: flex;
  align-items: flex-start;
  gap: 0.4375rem;
  margin-top: 0.5rem;
  padding: 0.4375rem 0.5rem;
  border-radius: var(--r-sm);
  background: var(--amber);
  color: var(--t1);
  font-size: var(--t-xs);
  font-weight: 500;
  line-height: 1.4;
}
.pcard .pwarn svg {
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  color: var(--orange);
  margin-top: 0.0625rem;
}
/* The PIN sits ON the card being confirmed, not in a modal over it, so
   the owner can still read what they are agreeing to while they type. */
.pcard .ppin {
  margin-top: 0.625rem;
  background: var(--fill);
  border-radius: var(--r-sm);
  padding: 0 0.75rem;
  min-height: 2.75rem;
  display: flex;
  align-items: center;
}
.pcard .ppin input {
  width: 100%;
  border: 0;
  background: none;
  outline: none;
  font: inherit;
  font-size: var(--t-bd);
  font-weight: 600;
  letter-spacing: 0.24em;
  color: var(--t1);
}
.pcard .perr {
  margin-top: 0.5rem;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--red);
}
/* A decided card keeps its detail but stops inviting a tap. */
.pcard.done {
  background: none;
  box-shadow: none;
  opacity: 0.72;
}
.pcard .pdone {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: var(--hair) solid var(--line);
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--accent-ink);
}
.pcard .pdone svg {
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
}

/* ── Ask Remy search bar ────────────────────────────────────────────
   Ask used to be a quarter of the tab bar. It is a search field now, at
   the top where a phone puts search, and the freed slot went to Menu.
   Shaped like a search pill rather than a button because that is the
   thing every owner already knows how to use. */
.askbar {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: calc(100% - 2.25rem);
  margin: 0 1.125rem 0.875rem;
  /* A quarter taller than it first was: at 3.125rem it read as a row
     of chrome rather than the thing you tap to talk to Remy. */
  min-height: 3.90625rem;
  /* Room above it. It follows the shortcut row directly, and at the old
     margin the two read as one block instead of two separate things. */
  margin-top: 1.375rem;
  padding: 0 1rem;
  border-radius: var(--r-pill);
  background: var(--card);
  border: var(--hair) solid var(--line);
  box-shadow: var(--sh);
  text-align: left;
}
.askbar .mk {
  flex: none;
  display: grid;
  place-items: center;
  color: var(--accent-ink);
}
/* The mark is wider than it is tall, so it is sized by height like
   everywhere else it appears. */
.askbar .mk .rmk {
  width: auto;
  height: 1rem;
}
.askbar .hint {
  flex: 1;
  min-width: 0;
  font-size: var(--t-bd);
  font-weight: 500;
  color: var(--t3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.askbar .mic {
  flex: none;
  display: grid;
  place-items: center;
  color: var(--t2);
}
.askbar .mic svg {
  width: 1.0625rem;
  height: 1.0625rem;
}
.askbar:active {
  transform: scale(0.99);
}
.askbar {
  transition: transform 140ms cubic-bezier(0.32, 0.72, 0, 1);
}

/* A pick row that carries an icon tile as well as a label. */
.pickrow .pkl {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
}
.pickrow .pkl .ic {
  flex: none;
  width: 1.875rem;
  height: 1.875rem;
  border-radius: 0.5rem;
  display: grid;
  place-items: center;
  color: var(--accent-ink);
}
.pickrow .pkl .ic svg {
  width: 0.9375rem;
  height: 0.9375rem;
}
/* Three are pinned and the rest go quiet, so the cap explains itself
   without a sentence about it. */
.pickrow:disabled {
  opacity: 0.45;
}

/* ── swipe a row to reveal Delete ───────────────────────────────────
   The action sits UNDERNEATH and the row slides off it, rather than the
   button sliding in from the side. It means the reveal is exactly the
   distance the finger travelled, with nothing to keep in sync. */
.swipe {
  position: relative;
  overflow: hidden;
}
.swipeact {
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: flex-end;
  background: var(--red);
}
.swipeact .del {
  width: 5.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.1875rem;
  color: #fff;
  background: none;
  border: 0;
  font-size: var(--t-cap);
  font-weight: 700;
}
.swipeact .del svg {
  width: 1.125rem;
  height: 1.125rem;
}
.swipeface {
  position: relative;
  background: var(--card);
  /* The row is dragged sideways, so the browser must not also try to
     pan the page with the same finger. */
  touch-action: pan-y;
}

/* The teaching nudge on a long press: twice to the left and back, far
   enough to show the red behind it without opening. */
.swipeface.nudge {
  animation: nudge-left 700ms cubic-bezier(0.3, 0.7, 0.3, 1);
}
@keyframes nudge-left {
  0%, 100% { transform: translateX(0); }
  18% { transform: translateX(-22px); }
  36% { transform: translateX(0); }
  56% { transform: translateX(-22px); }
  78% { transform: translateX(0); }
}

/* ── the bar that says what happened, and offers it back ────────────── */
.toast {
  position: absolute;
  left: calc(0.875 * var(--fix));
  right: calc(0.875 * var(--fix));
  bottom: calc(5.75 * var(--fix));
  bottom: calc(5.75 * var(--fix) + env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.75rem 0.875rem;
  border-radius: var(--r-md);
  background: var(--forest);
  color: #fff;
  box-shadow: 0 10px 30px rgba(11, 61, 46, 0.35);
  /* Above the tab bar's 85: an undo you cannot reach is not an undo. */
  z-index: 95;
  overflow: hidden;
  animation: toast-in 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.toast.out {
  animation: toast-out 240ms cubic-bezier(0.4, 0, 1, 1) forwards;
}
.toast .ti {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--lime);
}
.toast .ti svg {
  width: 1.125rem;
  height: 1.125rem;
}
.toast .tt {
  flex: 1;
  font-size: var(--t-sm);
  font-weight: 600;
  line-height: 1.3;
}
.toast .ta {
  flex: none;
  padding: 0.375rem 0.75rem;
  border-radius: var(--r-pill);
  background: var(--lime);
  color: var(--on-lime);
  border: 0;
  font-size: var(--t-cap);
  font-weight: 800;
}
/* The visible clock, so the undo window is something you can see rather
   than something you find out about. */
.toast .tbarfill {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  background: var(--lime);
  animation: toast-drain linear forwards;
}
@keyframes toast-drain {
  from { width: 100%; }
  to { width: 0%; }
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(0.75rem); }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(0.75rem); }
}

@media (prefers-reduced-motion: reduce) {
  .swipeface.nudge,
  .toast,
  .toast.out,
  .toast .tbarfill {
    animation-duration: 1ms;
  }
}

/* ── the stock counter, greyed out when nothing is counted ──────────── */
.stepper.off {
  opacity: 0.45;
}
.stepper.off input {
  /* A dash, centred, rather than a stale number nobody can act on. */
  color: var(--t3);
}

/* A `moreopts` row carrying a switch instead of a chevron: the toggle is
   the control, so the row itself must not also be a button. */
.moreopts.asrow {
  cursor: default;
}

/* ── the plus button, and what it spins out ─────────────────────────── */
.fab.open {
  transform: rotate(45deg);
}
/* Tapping anywhere else shuts it, which is how every menu should close.
   Transparent rather than dimmed: the list behind is still the context
   for the choice being made. */
.fabveil {
  position: absolute;
  inset: 0;
  background: none;
  border: 0;
  z-index: 59;
}
.fabstack {
  position: absolute;
  right: calc(0.875 * var(--fix));
  bottom: calc(10.5 * var(--fix));
  bottom: calc(10.5 * var(--fix) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.5rem;
  z-index: 61;
  /* Shut: gone, and not tabbable. `visibility` rather than `display` so
     the entrance can animate. */
  visibility: hidden;
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity 160ms ease, transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1), visibility 160ms;
}
.fabstack.open {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
}
.fabopt {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.5rem 0.5rem 0.9375rem;
  border-radius: var(--r-pill);
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: var(--sh);
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--t1);
  white-space: nowrap;
}
.fabopt .ic {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--forest);
  color: var(--lime);
  flex: none;
}
.fabopt .ic svg {
  width: 1.0625rem;
  height: 1.0625rem;
}
/* The second option arrives just after the first, so the stack reads as
   unfolding rather than appearing. */
.fabstack.open .fabopt:nth-child(1) {
  animation: fab-rise 220ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
.fabstack.open .fabopt:nth-child(2) {
  animation: fab-rise 220ms cubic-bezier(0.2, 0.8, 0.2, 1) 45ms both;
}
@keyframes fab-rise {
  from { opacity: 0; transform: translateY(0.625rem) scale(0.96); }
}

@media (prefers-reduced-motion: reduce) {
  .fabstack, .fabstack.open .fabopt { transition-duration: 1ms; animation-duration: 1ms; }
}

/* The vertical "more" dots in a page header. No pill: it sits alone, so
   the touch target is padding rather than a visible circle, which keeps
   it 44px without drawing a second button next to the back arrow. */
.dotsbtn {
  background: none;
  border: 0;
  padding: 0.625rem 0.375rem;
  margin-right: -0.375rem;
  color: var(--t1);
  display: grid;
  place-items: center;
  flex: none;
}
.dotsbtn svg {
  width: 1.375rem;
  height: 1.375rem;
}
.dotsbtn:active {
  opacity: 0.55;
}
