/**
 * 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;

  /* ── 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. */
.ph {
  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. */
.morerow {
  display: flex;
  justify-content: flex-end;
  padding: 0.5rem 1.125rem 0;
}
.morebtn {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 2.25rem;
  padding: 0 0.875rem;
  background: var(--card);
  border-radius: var(--r-pill);
  box-shadow: var(--sh);
  font-size: 0.78125rem;
  font-weight: 600;
  color: var(--accent-ink);
}
.morebtn svg {
  width: 1rem;
  height: 1rem;
}
.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 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;
  border-color: var(--accent-ink);
}
.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);
  color: var(--accent-ink);
}
.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);
}
.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 {
  position: fixed;
  left: 1.125rem;
  /* 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
   .ph 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;
}
.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. */

.open {
  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;
}
.open::-webkit-scrollbar {
  display: none;
}
.open .mk {
  width: 3.25rem;
  height: 3.25rem;
  color: var(--accent-ink);
  flex: none;
}
.open h1 {
  font-size: var(--t-d1);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.14;
  text-wrap: balance;
}
.open 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 `.ph`, which is the phone frame. See AddProduct. */
.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.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;
}
