/* ---------------------------------------------------------------
   Rasoi landing page.
   The only styles here are its own — /css and /js stay behind the
   auth gate, so this page can't reach for them. Photos live in
   /welcome/img (public alongside this file); palette and type
   mirror web/css/rasoi-theme.css.
   It is a file rather than a block in the page because this app's
   Content-Security-Policy allows no inline style (app/security.py) —
   and it can live here, beside the page, because /welcome/ is the
   public folder both of them were always entitled to.
   --------------------------------------------------------------- */
:root {
  --bg:        #f7f1e6;
  --card:      #fffdf8;
  --ink:       #2a2520;
  --muted:     #686052;
  --line:      #e7ddca;
  --brand:     #3f7d52;
  --brand-ink: #2f6840;
  --surface-2: #f3ecde;
  --herb-soft: #e6efe2;
  --clay:      #ae5727;
  /* The terracotta you can *read*, as `--brand-ink` is to `--brand`. `--clay` is
     tuned to be a fill and comes out near 3.4:1 at badge size on its own tint,
     which is fine for a drawing and short of what a word needs. The same pair the
     app carries — see `--clay-ink` in css/rasoi-theme.css. */
  --clay-ink:  #8f4520;
  /* The tint the pair sits on: the assistant's own ground, worn by the "Rasoi
     AI" pill, by the ask panel, and by the AI seat in the tab bar. */
  --clay-soft: #f6e6da;
  /* Low stock, which is the one mark on a pantry tile that is neither of the
     two above — `--saffron-soft` / `--warn-ink` in css/rasoi-theme.css. */
  --saffron-soft: #f7ecd0;
  --warn-ink:  #855a0c;
  /* "Out", on a tile's own action row: a chili word on the card's surface. */
  --danger-ink: #a5342b;
  /* The same colours as channels, for the handful of places that need one
     at an alpha, and the blends that used to be written inline. Both are
     here rather than `color-mix()` because this page is the one a crawler
     and a television both land on — see the head of css/rasoi-theme.css. */
  --bg-rgb:    247, 241, 230;
  --card-rgb:  255, 253, 248;
  --line-rgb:  231, 221, 202;
  --brand-rgb: 63, 125, 82;
  --clay-rgb:  174, 87, 39;
  --edge-yes:   #9cb192;  /* brand 45% on --line */
  --edge-never: #d9a686;  /* clay 45% on --line */
  --edge-beat:  #aebc9f;  /* brand 34% on --line */
  --edge-card:  #b2bea2;  /* brand 32% on --line */
  --edge-hover: #b5c0a4;  /* brand 30% on --line */
  --serif: "Newsreader", Georgia, serif;
  --sans:  "Hanken Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --shadow-sm: 0 1px 2px rgba(63,49,30,.05), 0 6px 18px rgba(63,49,30,.06);
  --shadow-lg: 0 2px 6px rgba(63,49,30,.06), 0 24px 60px rgba(63,49,30,.14);
}

/* The same palette after dark. This page is outside the auth gate and can't
   reach /css or ask the server anything, so it carries its own copy of the
   tokens, kept in step with web/css/theme-dark.css — which is the original,
   and where the reasoning for each value lives. The attribute is set by the
   inline script in <head> from the same localStorage key the app writes, so
   someone who chose dark inside Rasoi gets a dark landing page too, and a
   first-time visitor gets whatever their device says. With JavaScript off
   the attribute is never set and this stays the light page it has always
   been. */
:root[data-theme="dark"] {
  --bg:        #15120f;
  --card:      #211d1a;
  --ink:       #edebe7;
  --muted:     #a39e96;
  --line:      #33302b;
  --brand:     #3b834e;
  --brand-ink: #95d7a2;
  --surface-2: #1b1815;
  --herb-soft: #1c3422;
  --clay:      #ce7443;
  --clay-ink:  #ffbd9b;
  --clay-soft: #482b1b;
  --saffron-soft: #403012;
  --warn-ink:  #f4c677;
  --danger-ink: #ffaba0;
  --bg-rgb:    21, 18, 15;
  --card-rgb:  33, 29, 26;
  --line-rgb:  51, 48, 43;
  --brand-rgb: 59, 131, 78;
  --clay-rgb:  206, 116, 67;
  --edge-yes:   #3c553c;
  --edge-never: #764e38;
  --edge-beat:  #3a4c38;
  --edge-card:  #3a4a37;
  --edge-hover: #3a4936;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 2px 6px rgba(0, 0, 0, 0.45), 0 24px 60px rgba(0, 0, 0, 0.6);
  color-scheme: dark;
}
/* The washes behind the page: mid-lightness colours laid over paper, which
   need a little more of themselves to register over near-black. */
[data-theme="dark"] .glow {
  background:
    radial-gradient(58% 46% at 84% -6%, rgba(173, 86, 34, 0.22), transparent 62%),
    radial-gradient(50% 44% at 4% 2%,   rgba(51, 115, 68, 0.2), transparent 60%);
}
/* Google's own dark treatment for the sign-in button. Its light one is
   specified down to the hex and so is this, which is why these three don't
   come from the palette — and a white button would be the single brightest
   thing on the page by a distance. */
[data-theme="dark"] .gbtn { background: #131314; color: #e3e3e3; border-color: #8e918f; }
/* A chat line inside the green band, which is painted near-white and takes
   its type from `--ink` — and `--ink` has just changed ends. */
[data-theme="dark"] .band-card .line { color: #1b1814; }

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0; font-family: var(--sans); color: var(--ink);
  background: var(--bg); line-height: 1.55; overflow-x: hidden;
}
a { color: inherit; }
/* `height: auto` belongs with `max-width: 100%`, always. Without it the
   width/height attributes — which are there so the browser reserves the
   right box before the bytes land — keep dictating the height after the
   width has been overridden, and an image scaled down arrives stretched.
   Every other picture here sets its own height and is unaffected. */
img { max-width: 100%; height: auto; display: block; }

.wrap { width: min(1140px, 100% - 40px); margin-inline: auto; }
.glow {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(58% 46% at 84% -6%, rgba(192,97,47,.17), transparent 62%),
    radial-gradient(50% 44% at 4% 2%,   rgba(63,125,82,.16), transparent 60%);
}

/* ---------------- nav ---------------- */
header.nav {
  position: sticky; top: 0; z-index: 30;
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  backdrop-filter: saturate(140%) blur(10px);
  background: rgba(var(--bg-rgb), 0.8);
  border-bottom: 1px solid transparent;
  /* The border eases; the ground does not. A background that fades is a
     background that is briefly half-there, and "briefly" is not a promise a
     paused tab keeps — a throttled or hidden tab stops running transitions
     altogether and leaves the property at its *start* value, which here is the
     translucent one this rule exists to get away from. The border can afford
     that failure and the ground cannot, so only one of them is animated. */
  transition: border-color .25s ease;
}
/* **Solid once it is stuck.** At the top of the page the bar sits over the hero,
   which is the page's own ground, so eight-tenths of it plus a blur is a nice
   piece of glass over nothing. Further down it crosses the dark sections — the
   Telegram panel above all — and a translucent bar over a dark ground is not
   glass, it is mud: the green washes through, the nav labels lose the ground
   they are read against, and whatever is passing underneath (the "cooked it"
   chip, at the wrong moment) shows through the bar it is sliding beneath.

   So the ground goes solid at exactly the moment there might be something dark
   behind it. `.stuck` is already computed for the border — see `onScroll` in
   landing.js — so this costs nothing new to know, and it is the property that
   fixes all three complaints rather than one of them.

   The blur comes off with it: a blur behind an opaque background does nothing
   but cost, and on the low-powered TV browsers this estate is read on that is a
   cost worth not paying. */
header.nav.stuck {
  border-bottom-color: var(--line);
  background: var(--bg);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.nav-in { display: flex; align-items: center; gap: 18px; height: 66px; }
.brand {
  font-family: var(--serif); font-size: 1.4rem; font-weight: 600;
  letter-spacing: -.015em; text-decoration: none;
  display: inline-flex; align-items: center; gap: 9px; min-height: 44px;
}
.brand .pan {
  width: 32px; height: 32px; border-radius: 10px; background: var(--brand);
  color: #fff; display: grid; place-items: center;
  box-shadow: 0 3px 10px rgba(63,125,82,.35);
}
.pan svg { display: block; }
.nav-links { margin-left: auto; display: flex; align-items: center; gap: 26px; }
.nav-links a.link { text-decoration: none; color: var(--muted); font-size: .93rem; font-weight: 500; }
.nav-links a.link:hover { color: var(--ink); }

/* ---------------- dark mode, in the nav ----------------
   Rasoi starts light and there is one press to say otherwise, on the pages a
   stranger reads as well as inside the app. These pages are outside the gate —
   they cannot ask the server anything — but they are on the same origin, so
   they write the same `rasoi.theme` mirror the app reads, and somebody who
   turns the pitch dark finds the app dark when they sign in.

   Both drawings ship and CSS shows one. /js/theme-boot.js paints the attribute
   before the first frame and wires the button at DOMContentLoaded, so an icon
   chosen in script would visibly correct itself on every load for anyone who
   reads dark. Every rule names both classes: `.theme-btn svg` would be a class
   plus an element, which out-specifies a bare `.theme-ico-sun` and puts the
   moon and the sun on screen together. */
.theme-btn {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); color: var(--muted); cursor: pointer;
  transition: color .18s ease, border-color .18s ease;
}
.theme-btn:hover { color: var(--ink); border-color: var(--brand); }
.theme-btn .theme-ico { display: none; width: 18px; height: 18px; }
.theme-btn .theme-ico-moon { display: block; }
[data-theme="dark"] .theme-btn .theme-ico-moon { display: none; }
[data-theme="dark"] .theme-btn .theme-ico-sun { display: block; }

@media (max-width: 820px) { .nav-links a.link { display: none; } }

/* ---------------- buttons ---------------- */
/* **The one thing this page is asking for, drawn at the size of the ask.** It
   was a 46px button in 1rem type, which is the size of a form's Submit — and
   this page's whole job is that somebody presses it. It is 54px now, a step up
   in type, and it carries a herb ring: the Google surface itself may not be
   recoloured (their light button is specified down to the hex, and the dark one
   below is theirs too), so the emphasis is drawn *around* it rather than on it.
   That ring is also what tells it apart from the ghost beside it at a glance,
   on a page where both are pale rectangles.

   Bigger at both widths, and for different reasons. On a laptop it is competing
   with a photograph the size of a window; on a phone it is a thumb target at the
   end of a scroll, which is why `.cta-row` stretches it to the full column below
   and why the dock at the foot of this file exists at all. */
.gbtn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 11px;
  background: #fff; color: #3c4043; border: 1px solid #dadce0; border-radius: 12px;
  padding: 15px 26px; min-height: 54px;
  font-family: var(--sans); font-size: 1.06rem; font-weight: 700;
  text-decoration: none;
  box-shadow: 0 1px 2px rgba(60,64,67,.1), 0 0 0 3px rgba(var(--brand-rgb), .14);
  transition: box-shadow .18s ease, transform .18s ease; white-space: nowrap;
  /* The label is allowed to be longer than the phone is wide. `nowrap` above
     is what kept "Set up your kitchen with Google" on one line and pushed it
     through the edge of a 360px screen — the button drew wider than its own
     column, took the padding with it and sat off-centre under the heading.
     Wrapping is the fix rather than a smaller font: the words are the point,
     and a two-line button on a narrow phone is a perfectly ordinary thing. */
  max-width: 100%; text-align: center;
}
@media (max-width: 560px) { .gbtn { white-space: normal; } }
.gbtn:hover {
  box-shadow: 0 6px 20px rgba(60,64,67,.18), 0 0 0 4px rgba(var(--brand-rgb), .22);
  transform: translateY(-2px);
}
.gbtn:active { transform: translateY(0); }
/* a light sweep across the button on hover */
.gbtn::after {
  content: ""; position: absolute; inset: 0 auto 0 -60%; width: 45%;
  background: linear-gradient(100deg, transparent, rgba(63,125,82,.16), transparent);
  transform: skewX(-18deg); transition: left .55s ease;
}
.gbtn:hover::after { left: 115%; }
/* The small variant is the one in the sticky bar and in the dock at the foot of
   a phone. It was 42px tall — the nearest miss on the page, on the button the
   whole page is pointing at — and then 44px, which cleared the floor and nothing
   more. 46px and a heavier label is what a bar with a wordmark, two icon
   buttons and this in it can spare, and the ring above comes with it: in a
   header of pale controls, an unringed white button reads as a third icon. */
.gbtn.sm {
  padding: 10px 18px; min-height: 46px; font-size: .95rem; font-weight: 700;
  border-radius: 11px;
}
.gbtn svg { flex: none; }
/* On a phone the header holds a wordmark, two icon buttons and "Sign in", and
   the 26px gaps drawn for a laptop's text links left the button too little room:
   the phone rule above lets a .gbtn wrap, so "Sign in" broke over two lines and
   the button grew taller than the bar. Two words never need to wrap — the long
   hero label is what that rule is for — so the header's button stays on one
   line and the seats close up to 8px, which is what the row can spare. */
@media (max-width: 560px) {
  .nav-in { gap: 10px; }
  .nav-links { gap: 8px; }
  .nav-links .gbtn.sm { white-space: nowrap; padding: 10px 12px; gap: 8px; }
}
/* A 320px phone is still 6px short at those gaps; 6px each gives it back. */
@media (max-width: 360px) {
  .nav-in, .nav-links { gap: 6px; }
}
.ghost {
  display: inline-flex; align-items: center; gap: 8px; padding: 13px 20px;
  border-radius: 12px; border: 1px solid var(--line); background: transparent;
  color: var(--ink); font-size: 1rem; font-weight: 600; text-decoration: none;
  transition: border-color .18s ease, background .18s ease, gap .18s ease;
}
.ghost:hover { border-color: var(--brand); background: var(--herb-soft); gap: 12px; }
/* The same control over the final section's photograph, which is dark at every
   palette — so its ink and its hairline come from white rather than from the
   page tokens, which are drawn for a light card. Plain rgba over white: this
   page is read on a TV browser and a system WebView years behind color-mix(). */
.ghost-on-dark {
  color: #fff; border-color: rgba(255,255,255,.42); background: rgba(255,255,255,.08);
  min-height: 48px; cursor: pointer; font-family: inherit;
}
.ghost-on-dark:hover { border-color: #fff; background: rgba(255,255,255,.18); }
.ghost-on-dark svg { flex: none; }
@media (max-width: 560px) { .cta-row .ghost-on-dark { justify-content: center; } }

/* ---------------- hero ---------------- */
.hero { padding: 54px 0 30px; }
.hero-grid { display: grid; grid-template-columns: 1.02fr .98fr; gap: 56px; align-items: center; }
@media (max-width: 940px) { .hero { padding: 30px 0 10px; } .hero-grid { grid-template-columns: 1fr; gap: 44px; } }

.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--herb-soft); color: var(--brand-ink);
  border: 1px solid rgba(var(--brand-rgb), 0.22);
  border-radius: 999px; padding: 6px 14px;
  font-size: .78rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
}
h1 {
  font-family: var(--serif); font-weight: 600;
  font-size: clamp(2.7rem, 6.2vw, 4.2rem);
  line-height: 1.02; letter-spacing: -.03em; margin: 20px 0 0; text-wrap: balance;
}
h1 em { font-style: italic; color: var(--brand); }
.sub { font-size: clamp(1.05rem, 2vw, 1.2rem); color: var(--muted); margin: 18px 0 0; max-width: 27em; }
.cta-row { display: flex; flex-wrap: wrap; gap: 13px; margin-top: 28px; }
.fineprint {
  margin-top: 14px; font-size: .87rem; color: var(--muted);
  display: flex; flex-wrap: wrap; gap: 4px 18px; align-items: center;
}
/* `flex-start`, not `center`: when one of these wraps to two lines — which
   the final CTA's does on a phone — a vertically centred tick floats in the
   gutter between them instead of sitting beside the first word. */
.fineprint span { display: inline-flex; align-items: flex-start; gap: 6px; }
.tick { color: var(--brand); font-weight: 800; font-style: normal; }
@media (max-width: 560px) {
  .wrap { width: min(1140px, 100% - 32px); }
  .cta-row { flex-direction: column; align-items: stretch; }
  /* Full width and a little taller again: on a phone this is the widest,
     highest-contrast thing on the screen, which is what "press this" looks like
     where there is no room for anything to be beside it. */
  .cta-row .gbtn, .cta-row .ghost { justify-content: center; }
  .cta-row .gbtn { min-height: 58px; font-size: 1.1rem; }
}

/* hero photo + the cards floating over it */
.shot { position: relative; width: 100%; max-width: 440px; justify-self: center; }
.shot .frame {
  border-radius: 26px; overflow: hidden; box-shadow: var(--shadow-lg);
  border: 1px solid var(--line); background: var(--surface-2);
  aspect-ratio: 4 / 5;
}
.shot .frame img { width: 100%; height: 100%; object-fit: cover; }
.float {
  position: absolute; background: var(--card); border: 1px solid var(--line);
  border-radius: 18px; box-shadow: var(--shadow-lg); padding: 13px 15px;
}
.float.chat { left: -34px; bottom: 44px; width: min(300px, 84%); display: grid; gap: 8px; }
.float.stat {
  right: -22px; top: 34px; display: flex; align-items: center; gap: 10px;
  font-size: .88rem; font-weight: 600; padding: 11px 15px;
}
.float.stat .dot {
  width: 26px; height: 26px; border-radius: 9px; background: var(--herb-soft);
  color: var(--brand-ink); display: grid; place-items: center; flex: none;
}
.float.stat small { display: block; color: var(--muted); font-weight: 500; font-size: .78rem; }
@media (max-width: 1040px) { .float.chat { left: 0; } .float.stat { right: 0; } }
/* On a phone the chat card would cover the photo it's meant to sit beside,
   so it tucks under it instead. The stat pill used to be dropped here
   outright — but it is the half of the hero that shows the *consequence*,
   and a phone is where most people read this. It stays, inside the frame's
   top-right corner and a size smaller, where there is nothing under it but
   cloth. */
@media (max-width: 560px) {
  .float.chat {
    position: static; width: auto; margin: -30px 14px 0; animation-name: rise;
  }
  .float.stat {
    right: 10px; top: 12px; padding: 8px 11px; gap: 8px; font-size: .78rem;
  }
  .float.stat .dot { width: 22px; height: 22px; border-radius: 8px; }
  .float.stat small { font-size: .68rem; }
}

/* ---------------- a turn of chat, as the app draws it ----------------
   The hero card and the walkthrough below both show the assistant talking,
   and until now they showed it in shapes this page invented: two coloured
   rectangles with no avatar, no byline and no accent. Somebody who signs in
   after seeing that meets a different-looking app on the other side.

   These are the app's own: avatar, byline, bubble — see `.msg` in
   web/css/app.css, which is the original and where the reasoning lives. The
   values are copied rather than imported because /css is behind the auth
   gate and this page can't reach it; if you change one, change both. */
.msg { display: flex; align-items: flex-start; gap: 8px; max-width: 92%; }
.msg.mine { align-self: flex-end; flex-direction: row-reverse; margin-left: auto; }
.msg.theirs { align-self: flex-start; }
.msg-av {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
}
/* The assistant wears the accent; the household is the neutral one. */
.msg-av.av-ai {
  background: var(--herb-soft); color: var(--brand-ink);
  border-color: rgba(var(--brand-rgb), 0.35);
}
.msg-av svg { display: block; }
.msg-main { min-width: 0; display: flex; flex-direction: column; }
.msg.mine .msg-main { align-items: flex-end; }
.msg-meta { font-size: .7rem; color: var(--muted); margin: 2px 0 3px; padding: 0 2px; }
.msg-bubble {
  padding: 9px 13px; border-radius: 16px;
  border: 1px solid var(--line); background: var(--card);
  font-size: .88rem; line-height: 1.45;
}
.msg.theirs .msg-bubble { border-top-left-radius: 6px; }
.msg.mine .msg-bubble {
  background: var(--brand); border-color: var(--brand); color: #fff;
  border-top-right-radius: 6px;
}
.msg-bubble b { color: var(--brand-ink); }
.msg-bubble p { margin: 0 0 .45em; }
.msg-bubble p:last-child { margin-bottom: 0; }
/* The assistant answers in lists more often than in sentences, and a list
   drawn as a paragraph with dots in it is the fastest way to look unlike
   the product. Same rule as .msg-body in app.css. */
.msg-bubble ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 5px; }
.msg-bubble li { display: flex; gap: 7px; align-items: flex-start; }
.msg-bubble li svg { flex: none; color: var(--brand); margin-top: .18em; }
/* Three dots standing in for the answer while it's being written. */
.msg.typing .msg-bubble { display: flex; gap: 5px; align-items: center; padding: 13px; }
.typing-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--muted);
  animation: typing-bounce 1.2s infinite ease-in-out;
}
.typing-dot:nth-child(2) { animation-delay: .15s; }
.typing-dot:nth-child(3) { animation-delay: .3s; }
@keyframes typing-bounce {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) { .typing-dot { animation: none; opacity: .5; } }

/* ---------------- the walkthrough ----------------
   Four screens of the app, rebuilt in the page's own markup rather than
   muxed into an animated WebP. What that buys, in order:

   - it is sharp on a 3x phone, where the 560px-wide recording was upscaled
     and text is what compresses worst;
   - it comes in both palettes, because it is made of the same tokens as
     everything else here, instead of being a dark screenshot on a light page;
   - each beat is a *control*, so the thing that moves can be stopped,
     stepped and re-read (WCAG 2.2.2) rather than only paused;
   - and it cannot quietly go stale in the one way the recording did: no
     household's real names, no wording from a prompt we have since changed.

   What it does *not* buy is the recording's one irreplaceable virtue — being
   the app rather than an argument about it. So the note under it says plainly
   that these are the app's screens redrawn, and the roadmap entry for
   re-cutting the real thing stands.

   **Every measurement below is copied from a real rule, and the comment
   says which.** A drawing of your own app that is subtly not your own app is
   worse than no drawing: somebody signs in expecting this and meets
   something else. If a screen moves, this moves. */
.demo { display: grid; grid-template-columns: minmax(0, 300px) 1fr; gap: 54px; align-items: center; }
@media (max-width: 860px) { .demo { grid-template-columns: 1fr; gap: 32px; justify-items: center; } }

/* The device frame. It is doing real work: dropped straight onto the page a
   screen reads as a panel rather than a phone. */
.phone {
  position: relative; width: min(300px, 100%); justify-self: center;
  border-radius: 34px; padding: 9px;
  background: linear-gradient(160deg, #2c2a27, #14120f);
  box-shadow: var(--shadow-lg), inset 0 0 0 1px rgba(255,255,255,.08);
}
/* A fixed box, so nothing on the page reflows as the scenes swap under it.
   9:19 is a modern handset, and each scene now carries enough to fill one —
   six pantry tiles, two exchanges of chat, two meals. An earlier cut was
   shorter because the screens were emptier, which is fixing the frame to
   suit the content rather than the other way round. */
.screen {
  position: relative;
  border-radius: 26px; overflow: hidden; background: var(--bg);
  aspect-ratio: 9 / 19; display: flex; flex-direction: column;
}

/* The top bar on a phone: the mark, the screen's name, the search and the
   account — and *nothing else*. The tabs are not up here; `nav.tabs` in
   web/css/rasoi-theme.css is `position: fixed` at the bottom under 640px, and
   drawing them in the header was the single biggest way this demo lied about
   the app.

   The word beside the mark went with the app's own on 30 August 2026, and the
   seat it left is `#topbar-title` — which is why the title below is the only
   thing in this bar that changes as the scenes do. */
.app-top {
  display: flex; align-items: center; gap: 6px; padding: 9px 11px;
  background: var(--card); border-bottom: 1px solid var(--line);
}
.app-brand { display: inline-flex; align-items: center; flex: none; }
.app-brand img { display: block; width: 15px; height: 15px; }
/* The way back, in the mark's seat, on the one scene that is a screen you
   drilled into. The mark steps aside rather than sitting beside it: two marks
   of where you are, in one corner, is what `js/back.js` exists to avoid. */
.app-back {
  display: none; align-items: center; gap: 2px; flex: none;
  padding: 2px 6px 2px 4px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--card); color: var(--ink);
  font-size: .58rem; font-weight: 600; line-height: 1.6;
}
.app-back svg { display: block; }
.app-top.drilled .app-brand { display: none; }
.app-top.drilled .app-back { display: inline-flex; }
/* Which screen this is, or the headline fact it swaps in after its own paint.
   One per scene, and only the lit one is drawn — `show()` in landing.js. The
   qualifier's separator is drawn here rather than typed into the words, exactly
   as `.ttl-note::before` does it in web/css/app.css. */
.app-title { display: none; min-width: 0; align-items: baseline; gap: 4px; }
.app-title.on { display: flex; }
.app-title b {
  font-size: .68rem; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-title i {
  font-style: normal; font-size: .6rem; font-weight: 400; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-title i::before { content: "·"; margin-right: 3px; }
.app-search { flex: none; margin-left: auto; color: var(--muted); }
.app-search svg { display: block; }
.app-av {
  width: 21px; height: 21px; border-radius: 50%; flex: none;
  background: var(--clay); color: #fff; display: grid; place-items: center;
  font-size: .62rem; font-weight: 700;
}
/* With no script the four scenes are a storyboard rather than one screen at a
   time, so a bar naming one of them would be naming the wrong three. Same
   answer the tab bar gets below. */
html:not(.js) .app-title,
html:not(.js) .app-back { display: none; }

/* ---- the bottom tab bar ----
   Copied from `nav.tabs` under 640px, and **flush to the edge since 30 August
   2026**: full width, square, one hairline along the top and a light shadow
   cast upwards — it used to float 10px off each edge with a 22px radius, which
   cost about 14px of every phone screen. Icon over a label, and the tab you are
   on in `--brand-ink` with a herb pill on its top edge — not a filled one,
   which is what the *desktop* bar does. The screen's own 26px corners clip it,
   the way a phone's do. */
.app-nav {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  background: rgba(var(--card-rgb), 0.92);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  backdrop-filter: blur(16px) saturate(1.2);
  border: 0; border-top: 1px solid var(--line); border-radius: 0;
  box-shadow: 0 -2px 14px rgba(50,38,20,.07); padding: 3px 4px 5px;
}
.app-nav .t {
  position: relative; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 2px; padding: 6px 0 5px; border-radius: 10px;
  color: var(--muted); font-size: 7.5px; font-weight: 500; line-height: 1;
  transition: color .35s ease;
}
.app-nav .t svg { display: block; }
.app-nav .t.on { color: var(--brand-ink); }
.app-nav .t.on::before {
  content: ""; position: absolute; top: -1px; left: 50%; transform: translateX(-50%);
  width: 16px; height: 2.5px; border-radius: 3px; background: var(--brand);
}
/* The AI seat, which is a switch rather than a place and says so at rest: the
   assistant's own terracotta, worn from the moment the bar is drawn. Inset as a
   pill rather than filled edge to edge, because a filled cell in a grid with no
   gap reads as a block parked beside the tabs rather than as one of them — and
   it is a pseudo-element rather than padding so it cannot change the row's
   height. `z-index: -1` puts it behind the icon and the label and in front of
   the bar, which the bar's own stacking context guarantees. */
.app-nav .t.ai { color: var(--clay-ink); }
.app-nav .t.ai::after {
  content: ""; position: absolute; z-index: -1; inset: 1px 5px;
  border-radius: 999px; background: var(--clay-soft);
  box-shadow: inset 0 0 0 1px rgba(var(--clay-rgb), .5);
}
/* On the chat screen itself the green marker above the icon is the answer, and
   a second mark under it would be the bar saying two things about one seat. */
.app-nav .t.ai.on { color: var(--brand-ink); }
.app-nav .t.ai.on::after { display: none; }

/* The scenes are stacked and cross-faded, so the phone never changes height.
   The bottom padding is the strip the tab bar owns — `main` does the same with
   `padding-bottom: calc(var(--bottom-clear) + 16px)`, and `--bottom-clear` is
   a constant there for the reason it is a number here: CSS cannot ask a box how
   tall it is. 46px is what this bar measures at this scale. */
.stages { position: relative; flex: 1; min-height: 0; }
.stage {
  position: absolute; inset: 0; padding: 9px 9px 46px; overflow: hidden;
  display: flex; flex-direction: column; gap: 6px;
  opacity: 0; transform: translateY(10px); pointer-events: none;
  transition: opacity .5s ease, transform .5s ease;
}
.stage.on { opacity: 1; transform: none; }
/* Nothing in a scene shrinks. The stage is a flex column, so once a screen
   held more than fitted — six pantry tiles, three meal cards — the browser
   started taking the height back out of whatever was most compressible, and
   the segmented control at the top of Meals collapsed to a hairline. A real
   phone doesn't do that; it scrolls, and what runs off the bottom is simply
   off the bottom, which is what `overflow: hidden` on the stage already
   gives us. The deck is the exception: it is explicitly the thing that takes
   whatever height is left over (`.deck` in web/css/app.css). */
.stage > * { flex: 0 0 auto; }
.stage > .deck { flex: 1 1 auto; }
/* This phone is drawn at about 0.62 of life size, hence the ratios throughout.

   No screen in here has a heading of its own: `.scr-head` went with the app's
   own on 13 September 2026 — "Stock" over a lit Pantry tab was the same word
   twice, and the figure worth having ("38 items · 1 to buy") is in the bar. */

/* The Upcoming / History switch: one pill, split, the active half filled with the
   accent — `.seg` / `.seg-btn` in web/css/app.css. It was a rounded-rectangle
   segmented control here, which is a different component. */
.seg {
  display: flex; width: fit-content; border: 1px solid var(--line);
  border-radius: 999px; overflow: hidden; margin-bottom: 2px;
}
.seg span {
  padding: 3px 9px; font-size: .62rem; font-weight: 600;
  background: var(--card); color: var(--muted);
  display: inline-flex; align-items: center; gap: 4px;
}
.seg span.on { background: var(--brand); color: #fff; }
.seg .pill {
  background: rgba(255,255,255,.28); border-radius: 999px;
  padding: 0 4px; font-size: .55rem; font-weight: 700;
}

/* ---- the Pantry toolbar ----
   `.list-bar` in web/css/app.css: what adds at the left-hand end, and at the
   right the four icon buttons a phone gets — the bill scanner, the magnifier,
   the shape the list is in, and the funnel. There is nothing above it at either
   width; the screen's two figures are in the top bar. */
.list-bar { display: flex; align-items: center; gap: 5px; }
.list-bar .add {
  padding: 3px 8px; border-radius: 999px; background: var(--brand); color: #fff;
  font-size: .58rem; font-weight: 700;
}
.list-bar .tools { display: flex; gap: 4px; margin-left: auto; }
.tbtn {
  display: inline-flex; align-items: center; justify-content: center; gap: 2px;
  width: 19px; height: 19px; border-radius: 7px;
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
}
.tbtn svg { display: block; }
.tbtn b { font-size: .5rem; font-weight: 700; color: var(--brand-ink); }

/* ---- the Pantry tile ----
   `.card.item.stock-card` in web/css/app.css: two columns on a phone, a square
   photograph as wide as the card, the caption laid over the foot of it rather
   than in a band under it — a band of text here is a band on *every* card in
   the grid — and the two writes at the foot. The mark sits in the picture's
   far corner from the caption so the two never meet on a narrow card.

   It was a 58px row here until 13 September 2026, which is the shape the list
   takes in one of its three states and not the one anybody meets: the Pantry
   opens on tiles.

   The pictures are the pantry's own: `app/data/item_icons/*.webp`, the same
   files the app draws these tiles with. They are served from `/item-img/`,
   which is *not* in PUBLIC_PREFIXES (see app/auth.py) — a signed-out browser
   cannot fetch them — so the six this scene needs are copied into
   `welcome/img/items/`, which is public. 44 KB for all six, and they show a
   kilo of rajma rather than a plate of it. If a copy here ever disagrees
   with the icon it came from, the icon wins. */
.item-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.itile {
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
}
.itile .pic { position: relative; aspect-ratio: 1; background: var(--surface-2); }
.itile .pic img { width: 100%; height: 100%; object-fit: cover; }
/* An empty shelf is drawn as one: the photograph desaturates and the card takes
   the page's second surface, so a grid reads out-of-stock before a word on it
   is read. Desaturation carries on both palettes where alpha does not, which is
   why the fade is only a nudge. */
.itile.is-out { background: var(--surface-2); }
.itile.is-out .pic img { filter: grayscale(1); opacity: .8; }
/* White on a scrim that fades upward, in both palettes: what is underneath is a
   photograph, and a photograph has no palette. Flat behind every pixel of text
   and eased through the padding above it, so how much scrim a word gets doesn't
   depend on how tall the box happens to be. */
.itile .cap {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  display: flex; align-items: baseline; gap: 5px;
  padding: 22px 6px 4px; color: #fff;
  background: linear-gradient(to top,
    rgba(18, 15, 12, .80) 0%,
    rgba(18, 15, 12, .76) calc(100% - 22px),
    rgba(18, 15, 12, .40) calc(100% - 14px),
    rgba(18, 15, 12, .13) calc(100% - 6px),
    rgba(18, 15, 12, 0) 100%);
}
.itile .cap b {
  font-size: .58rem; font-weight: 700; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-shadow: 0 1px 2px rgba(0,0,0,.5);
}
.itile .cap i {
  margin-left: auto; font-style: normal; font-size: .55rem; font-weight: 600;
  opacity: .92; text-shadow: 0 1px 2px rgba(0,0,0,.5);
}
/* The one mark the tile wears, drawn as the badge family is — a tint and the
   ink that can be read on it, in both palettes — rather than as glass: it is a
   *word about the shelf*, over whatever photograph happens to be under it. */
.itile .mark {
  position: absolute; top: 4px; right: 4px; z-index: 1;
  padding: 0 5px; border-radius: 999px;
  font-size: .46rem; font-weight: 700; line-height: 1.9;
  text-transform: uppercase; letter-spacing: .04em;
}
.itile .mark.low { background: var(--saffron-soft); color: var(--warn-ink); }
.itile .mark.out { background: var(--line); color: var(--muted); }
/* The two writes. Not symmetrical, on purpose: running out is the loud one, and
   going back to stocked is the button on a tile that is already greyed out — so
   it is filled herb and takes a little more of the row than the Qty beside it. */
.itile .iacts { display: flex; gap: 4px; margin-top: auto; padding: 5px 6px 6px; }
.iact {
  flex: 1 1 0; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  padding: 2px 4px; border-radius: 7px;
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--danger-ink); font-size: .5rem; font-weight: 700;
}
.iact svg { display: block; }
.iact.qty { color: var(--muted); }
.iact.back {
  flex: 1.25 1 0; color: var(--brand-ink);
  background: var(--herb-soft); border-color: var(--brand);
}


/* ---- a meal card ----
   The log card is a square picture and the body under it, the same shape every
   other card in the app draws — see "(B2) SQUARE HERO" in web/css/rasoi-theme.css.
   A suggestion is told apart by its *ground*: a clay tint, dashed, no shadow, its
   name a step lighter, with the word in a badge above the dish (see "three
   surfaces" in that file). It wore a full-width strip across the top here and in
   the app until 13 September 2026, which said in two lines what the badge says in
   one pill. */
.mcard {
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  overflow: hidden; box-shadow: var(--shadow-sm); flex: none;
}
/* The undecided row's ground, mixed here from the same pair the app's
   `--suggest-wash` is: this sheet has its own tokens and no access to that one. */
.mcard.sug {
  background: rgba(var(--clay-rgb), 0.12);
  border: 1px dashed rgba(var(--clay-rgb), 0.45);
  box-shadow: none;
}
.mcard.sug .mpic img { opacity: .8; }
.mcard.sug .mname { font-weight: 400; }
.mmeta .badge.sug {
  display: inline-flex; align-items: center; gap: 3px;
  background: rgba(var(--clay-rgb), 0.18); color: var(--clay-ink);
  letter-spacing: 0; text-transform: none;
}
.mmeta .badge.sug svg { flex: none; }
/* Square, like the picture on the card it is a drawing of. */
.mcard .mpic { aspect-ratio: 1 / 1; height: auto; }
.mcard .mpic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mcard .mbody { padding: 6px 8px 7px; display: flex; flex-direction: column; gap: 4px; }
.mcard .mname { font-size: .74rem; font-weight: 600; }
.mmeta { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
/* The time is a fact about the card rather than a label on it, so it is the
   muted ink with a clock beside it — not one more pill in the row. */
.mmeta .when { display: inline-flex; align-items: center; gap: 3px; font-size: .53rem; font-weight: 600; color: var(--muted); }
.mmeta .badge {
  font-size: .48rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 1px 5px; border-radius: 999px; background: var(--line); color: var(--muted);
}
.mcard .why { margin: 0; font-size: .55rem; color: var(--muted); line-height: 1.35; }
.acts { display: flex; gap: 4px; }
.acts .act {
  flex: 1 1 0; min-width: 0; display: inline-flex; align-items: center; justify-content: center;
  gap: 3px; padding: 4px 0; border: 1px solid var(--line); border-radius: 8px;
  font-size: .53rem; font-weight: 700; color: var(--muted);
}
.acts .act.primary { background: var(--brand); border-color: var(--brand); color: #fff; }
/* The two answers that carry no word take no width — the glyph is the whole
   control, as on the card this is a drawing of. */
.acts .act.ico { flex: none; padding: 0; width: 16px; justify-content: center; }
.acts .act svg { flex: none; }
/* The rule above one day's dishes. */
.day-head {
  display: flex; align-items: baseline; gap: 6px; margin: 4px 0 1px; padding-bottom: 3px;
  border-bottom: 1px solid rgba(var(--line-rgb), 0.7);
}
.day-head .dname { font-family: var(--serif); font-size: .7rem; font-weight: 500; }
.day-head .ddate { font-size: .53rem; color: var(--muted); }
.day-head .dcount { margin-left: auto; font-size: .5rem; font-weight: 600; color: var(--muted); }

/* ---- the Discover deck ----
   A viewport-tall stack: the slot pill, the cards taking whatever is left,
   and the three verdict buttons pinned under them (`.deck-screen`). The back
   cards shrink by 4% and drop 10px per depth, which is the transform in
   `.deck-card`. There is no "rest of the week" on this screen — that was
   invented, and it has gone. */
/* The one row above the cards: the mealtimes as chips you change while looking
   at what they changed, and the funnel that holds every filter that isn't one —
   wearing a count, which is the part of the old summary pill worth keeping.
   `slotBar` in web/js/views/discover.js. It scrolls sideways there and is
   simply cut here, which is what the same row does on a narrow phone. */
.deck-filters {
  display: flex; align-items: center; gap: 4px; overflow: hidden;
}
.deck-filters .chip {
  flex: none; padding: 2px 7px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--card);
  color: var(--muted); font-size: .55rem; font-weight: 600;
}
.deck-filters .chip.on {
  background: var(--brand); border-color: var(--brand); color: #fff;
}
.deck-filters .tbtn { margin-left: auto; width: auto; padding: 0 4px; }
.deck { position: relative; flex: 1 1 auto; min-height: 0; margin-bottom: 4px; }
.dcard {
  position: absolute; inset: 0; display: flex; flex-direction: column; overflow: hidden;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,.10);
  transform: scale(calc(1 - var(--depth, 0) * .04)) translateY(calc(var(--depth, 0) * 8px));
  transform-origin: bottom center;
  /* `.deck-card.flying` in app.css — the curve a decided card leaves on. */
  transition: transform .45s cubic-bezier(.3,.1,.5,1);
}
/* Which card of the three this is, counting down from the one on top. A class
   rather than the `style="--depth:1"` it used to be, for the same reason as the
   delays above — and a worse failure if it were dropped: all three cards would
   land at the same scale and the same offset, which is not a deck, it is one
   card. */
.depth-0 { --depth: 0; }
.depth-1 { --depth: 1; }
.depth-2 { --depth: 2; }
.dcard .pic { flex: 1 1 auto; min-height: 0; }
.dcard .pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dcard .bd { flex: none; padding: 7px 9px 8px; background: var(--card); }
.dcard h5 { margin: 0 0 4px; font-size: .82rem; font-weight: 700; }
.badges { display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: 4px; }
.badges span {
  font-size: .48rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 1px 5px; border-radius: 999px; background: var(--herb-soft); color: var(--brand-ink);
}
.badges span.plain { background: var(--line); color: var(--muted); }
.blurb { margin: 0 0 3px; font-size: .6rem; line-height: 1.35; }
.needs { margin: 0; font-size: .53rem; color: var(--muted); }
.open-link { margin-top: 4px; font-size: .53rem; font-weight: 600; color: var(--brand-ink); }
/* The card mid-swipe, with the verdict stamped on it — the thing this screen
   is actually *for*, and the one interaction the recording never showed. */
/* Parked at a lean, not at a full fly-off: the card is *held* here rather
   than passing through, so anything the frame clips reads as a layout bug
   instead of as motion. Far enough that the card behind shows and the stamp
   has landed; near enough that the dish's own words stay on the screen. */
.dcard.swiping { transform: translateX(7%) rotate(5deg); }
.stamp {
  position: absolute; top: 50%; left: 50%; width: 54px; height: 54px; margin: -27px 0 0 -27px;
  display: grid; place-items: center; border-radius: 50%; border: 3px solid currentColor;
  background: rgba(var(--card-rgb), 0.82);
  color: var(--brand); opacity: 0; transition: opacity .4s ease;
}
.dcard.swiping .stamp { opacity: 1; }
/* What the lean means, said once, in the words the toast for that direction
   uses — `.deck-demo`, filled like a toast because it is saying what the
   toast would have said. */
.swipe-say {
  position: absolute; z-index: 3; top: 8px; left: 50%; transform: translateX(-50%);
  width: max-content; max-width: calc(100% - 20px); padding: 4px 9px;
  border-radius: 999px; background: var(--ink); color: var(--card);
  font-size: .55rem; font-weight: 600; text-align: center;
}
.deck-controls { display: flex; justify-content: center; align-items: center; gap: 12px; padding-top: 2px; }
.deck-btn {
  width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center;
  background: var(--card); border: 1px solid var(--line);
  box-shadow: 0 2px 6px rgba(0,0,0,.06), 0 8px 18px rgba(0,0,0,.10);
}
.deck-btn svg { display: block; }
.deck-btn.never { color: var(--clay); border-color: var(--edge-never); }
.deck-btn.skip { width: 28px; height: 28px; color: var(--muted); }
.deck-btn.yes { color: var(--brand); border-color: var(--edge-yes); }

/* ---- the composer ----
   A textarea that grows, and a round accent send button — `.chat-input` /
   `.chat-send`, which is 42px and a circle. */
.composer {
  margin-top: auto; display: flex; gap: 5px; align-items: center;
  padding-top: 6px; border-top: 1px solid var(--line);
}
.composer .box {
  flex: 1; border: 1px solid var(--line); background: var(--card);
  border-radius: 14px; padding: 6px 9px; font-size: .62rem; color: var(--muted);
}
.composer .snd {
  width: 26px; height: 26px; border-radius: 50%; flex: none; background: var(--brand);
  color: #fff; display: grid; place-items: center;
}
.composer .snd svg { display: block; }
/* A chat thread grows from the bottom of the screen, not the top. */
.stage[data-scene="1"] .msg:first-of-type { margin-top: auto; }
/* The bubbles are small in here; the app's own type scale would overflow a
   phone drawn at 62%. */
.stage .msg-bubble { font-size: .68rem; }
.stage .msg-meta { font-size: .55rem; }
.stage .msg-av { width: 20px; height: 20px; }

/* ---- the beats, which are also the controls ----
   Each one selects its scene. A list of four buttons is a better pause than
   a pause button: it stops the loop, it says which screen you are looking
   at, and it can be tabbed and arrowed. */
.demo-media { display: flex; flex-direction: column; align-items: center; gap: 14px; width: 100%; }
.beats { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; max-width: 30rem; }
.beat { margin: 0; }
.beat button {
  display: flex; gap: 14px; align-items: flex-start; width: 100%; text-align: left;
  background: transparent; border: 1px solid transparent; border-radius: 14px;
  padding: 11px 13px; cursor: pointer; font-family: var(--sans); color: inherit;
  transition: background .25s ease, border-color .25s ease;
}
.beat button:hover { border-color: var(--line); background: var(--card); }
.beat[aria-current="true"] button { background: var(--card); border-color: var(--edge-beat); box-shadow: var(--shadow-sm); }
.beat .no {
  width: 30px; height: 30px; border-radius: 10px; flex: none; margin-top: 1px;
  background: var(--surface-2); color: var(--muted);
  display: grid; place-items: center; font-size: .86rem; font-weight: 800;
  transition: background .25s ease, color .25s ease;
}
.beat[aria-current="true"] .no { background: var(--brand); color: #fff; }
.beat h3 { margin: 0 0 3px; font-size: 1.02rem; font-weight: 700; }
.beat p { margin: 0; color: var(--muted); font-size: .95rem; }
.beat q { font-style: italic; quotes: "“" "”"; }
.demo-play {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; min-width: 44px; padding: 0 18px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--card);
  color: var(--ink); font-family: var(--sans); font-size: .88rem; font-weight: 600;
  cursor: pointer; transition: border-color .18s ease, background .18s ease;
}
.demo-play:hover { border-color: var(--brand); background: var(--herb-soft); }
.demo-play[hidden] { display: none; }
.demo-note { margin: 26px 0 0; font-size: .87rem; color: var(--muted); }

/* With no script the scenes can't take turns — so they stop being stacked
   and become four screens in a row, which is a storyboard and still tells
   the story. */
html:not(.js) .stage { position: static; opacity: 1; transform: none; }
html:not(.js) .screen { aspect-ratio: auto; }
html:not(.js) .app-nav { display: none; }

@media (prefers-reduced-motion: reduce) {
  .stage, .dcard, .stamp { transition: none; }
}

/* ---------------- sections ---------------- */
section { padding: 74px 0; }
@media (max-width: 720px) { section { padding: 52px 0; } }
.sec-head { max-width: 34rem; margin-bottom: 36px; }
.kicker { font-size: .78rem; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: var(--clay); }
h2 {
  font-family: var(--serif); font-weight: 600; font-size: clamp(1.85rem, 3.4vw, 2.5rem);
  line-height: 1.1; letter-spacing: -.022em; margin: 10px 0 0; text-wrap: balance;
}

/* ---- what it solves ----
   Four questions a kitchen asks itself every week, in the words they get
   asked in. The page used to open with what Rasoi *is* and work towards
   what it's for; this is the other way round, and it is the section a
   stranger should be able to stop reading after. */
.pains { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; }
@media (max-width: 820px) { .pains { grid-template-columns: 1fr; } }
.pain {
  background: var(--card); border: 1px solid var(--line); border-radius: 20px;
  padding: 24px 24px 26px; box-shadow: var(--shadow-sm);
  transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.pain:hover {
  transform: translateY(-3px); box-shadow: var(--shadow-lg);
  border-color: var(--edge-hover);
}
.pain .ico {
  width: 42px; height: 42px; border-radius: 13px; background: var(--herb-soft);
  color: var(--brand-ink); display: grid; place-items: center; margin-bottom: 14px;
}
/* The question is set in the serif and in quotes: it is somebody talking,
   not a feature name. The answer under it is the product. */
.pain .ask {
  font-family: var(--serif); font-size: 1.28rem; font-weight: 600; font-style: italic;
  letter-spacing: -.015em; margin: 0 0 8px; color: var(--ink);
}
.pain h3 { margin: 0 0 6px; font-size: 1.02rem; font-weight: 700; color: var(--brand-ink); }
.pain p { margin: 0; color: var(--muted); font-size: .96rem; }
/* The sentence somebody would actually type. Same italic as everywhere else
   on the page that quotes a person, and the last thing left standing from
   the "how it works" steps this section replaced. */
.pain .say {
  display: block; margin-top: 12px; padding: 9px 12px; background: var(--surface-2);
  border-radius: 11px; font-size: .87rem; font-style: italic;
}

/* how it works — three lean steps */
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
@media (max-width: 860px) { .steps { grid-template-columns: 1fr; } }
.step {
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  padding: 22px 22px 24px; box-shadow: var(--shadow-sm);
  transition: transform .25s ease, box-shadow .25s ease;
}
.step:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.step .ico {
  width: 40px; height: 40px; border-radius: 12px; background: var(--herb-soft);
  color: var(--brand-ink); display: grid; place-items: center; margin-bottom: 13px;
}
.step h3 { font-size: 1.06rem; margin: 0 0 5px; font-weight: 700; }
.step p { margin: 0; color: var(--muted); font-size: .94rem; }
.step .say {
  display: block; margin-top: 13px; padding: 9px 12px; background: var(--surface-2);
  border-radius: 11px; font-size: .89rem; font-style: italic;
}

/* features — photo cards */
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 980px) { .cards { grid-template-columns: repeat(2, 1fr); } }
/* Stays two-up on a phone rather than dropping to one. Six of these
   stacked full-width is 2,400px — the single largest block on the page,
   and they are six short labels with a photo each, not six things anyone
   reads in a column. Two-up they're 700px and still legible. */
/* Two-up on a phone, each card about 165px wide — and at 16:10 that left a
   photograph 103px tall carrying a caption that ran to four lines under it.
   The picture was the smallest thing on a card that exists to show a
   picture. Square is the fix, and it is also what the app does: every
   picture in Rasoi is square (`.card .thumb-box.hero`), so this is the page
   agreeing with the product rather than a one-off. The captions lose a line
   each to pay for it. */
@media (max-width: 620px) {
  .cards { gap: 12px; }
  .card .pic { aspect-ratio: 1; }
  .card .body { padding: 12px 13px 15px; }
  .card h3 { font-size: .98rem; margin-bottom: 3px; }
  .card p { font-size: .88rem; line-height: 1.4; }
}
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: 20px;
  overflow: hidden; box-shadow: var(--shadow-sm);
  transition: transform .28s ease, box-shadow .28s ease, border-color .28s ease;
}
.card:hover {
  transform: translateY(-5px); box-shadow: var(--shadow-lg);
  border-color: var(--edge-card);
}
.card .pic { aspect-ratio: 16 / 10; overflow: hidden; background: var(--surface-2); }
.card .pic img { width: 100%; height: 100%; object-fit: cover; transition: transform .6s cubic-bezier(.2,.7,.3,1); }
.card:hover .pic img { transform: scale(1.07); }
.card .body { padding: 18px 20px 22px; }
.card h3 { font-size: 1.06rem; margin: 0 0 5px; font-weight: 700; }
.card p { margin: 0; color: var(--muted); font-size: .93rem; }

/* telegram band — photo with a herb wash over it */
.band {
  position: relative; overflow: hidden; border-radius: 26px; color: #fff;
  padding: 54px 46px; box-shadow: var(--shadow-lg);
  background: #2f6840;
}
.band .bg { position: absolute; inset: 0; }
/* the photo is texture here, not subject — desaturated under a herb wash so
   the type stays the loudest thing in the block */
.band .bg img { width: 100%; height: 100%; object-fit: cover; filter: saturate(.6); }
.band .bg::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(105deg, rgba(28,70,43,.97) 0%, rgba(40,92,57,.94) 52%, rgba(47,104,64,.86) 100%);
}
.band-in { position: relative; display: grid; grid-template-columns: 1.1fr .9fr; gap: 40px; align-items: center; }
@media (max-width: 860px) { .band { padding: 34px 24px; } .band-in { grid-template-columns: 1fr; gap: 26px; } }
.band h2 { color: #fff; margin: 0 0 12px; }
.band p { color: rgba(255,255,255,.88); margin: 0; font-size: 1.02rem; }
.band ul { list-style: none; margin: 18px 0 0; padding: 0; display: grid; gap: 9px; }
.band li { display: flex; gap: 10px; align-items: center; color: rgba(255,255,255,.95); font-size: .96rem; }
.band li svg { flex: none; }
.band-card {
  background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.24);
  border-radius: 18px; padding: 16px; display: grid; gap: 8px; backdrop-filter: blur(6px);
}
.band-card .line { background: rgba(255,255,255,.95); color: var(--ink); border-radius: 12px; padding: 10px 13px; font-size: .89rem; }
.band-card .line.you { background: rgba(0,0,0,.24); color: #fff; justify-self: end; }
.band-card .line b { color: var(--brand-ink); }

/* privacy — one tight row */
.trio { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 720px) { .trio { grid-template-columns: 1fr; } }
.mini { display: flex; gap: 13px; align-items: flex-start; }
.mini .ico {
  width: 38px; height: 38px; border-radius: 12px; flex: none;
  background: var(--herb-soft); color: var(--brand-ink); display: grid; place-items: center;
}
.mini h3 { margin: 2px 0 3px; font-size: 1rem; font-weight: 700; }
.mini p { margin: 0; color: var(--muted); font-size: .91rem; }

/* ---- bring your own AI ----
   The MCP server, which this page did not mention for as long as it existed.
   Two columns: the argument and the address on the left, what it looks like
   from inside somebody else's assistant on the right — the same split as the
   hero, so the page has one idea of where the words go and where the picture
   does.

   The chat rows are the app's own `.msg` classes, unscoped, which is why they
   come out at the hero's size rather than the walkthrough's: `.stage .msg-*`
   is what shrinks them inside the phone, and this is not inside the phone. */
.mcp { display: grid; grid-template-columns: 1.04fr .96fr; gap: 48px; align-items: center; }
@media (max-width: 900px) { .mcp { grid-template-columns: 1fr; gap: 32px; } }

/* The address, set to be read across a room. `break-all` rather than a
   smaller font: on a 320px phone the URL is wider than the column, and a URL
   that has been shrunk to fit is a URL nobody can check they typed right. */
.addr {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  padding: 16px 18px; box-shadow: var(--shadow-sm);
}
.addr-who {
  flex-basis: 100%; display: inline-flex; align-items: center; gap: 8px;
  color: var(--brand-ink); font-size: .74rem; font-weight: 800;
  letter-spacing: .11em; text-transform: uppercase;
}
.addr-who svg { flex: none; }
.addr code {
  flex: 1 1 auto; min-width: 0; word-break: break-all;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.02rem; font-weight: 600; color: var(--ink); letter-spacing: -.01em;
}
/* Box, hover and 44px target come from the share row's rule above; only the
   type size differs, because this one sits beside a monospaced address rather
   than in a row of five siblings. */
.copy-url { font-size: .85rem; }

/* Three points, stacked. `.mini` is the privacy row's — same glyph box, same
   two lines — because "a short claim with an icon" is one component on this
   page and not four. */
.mcp-list { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 18px; }
.mcp-list .mini p { margin-top: 2px; }
.mcp-say .cta-row { margin-top: 24px; }

/* The assistant's window. Not anybody's actual chrome — no vendor mark, no
   colours borrowed from a product we don't ship — just enough of a frame to
   say "this is happening somewhere else". */
.chat-frame {
  background: var(--card); border: 1px solid var(--line); border-radius: 22px;
  box-shadow: var(--shadow-lg); overflow: hidden;
}
.chat-top {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 16px; border-bottom: 1px solid var(--line); background: var(--surface-2);
}
.chat-who { font-family: var(--serif); font-size: .95rem; font-weight: 600; }
.chat-pill {
  margin-left: auto; display: inline-flex; align-items: center; gap: 5px;
  background: var(--herb-soft); color: var(--brand-ink);
  border: 1px solid rgba(var(--brand-rgb), 0.28); border-radius: 999px;
  padding: 3px 10px; font-size: .72rem; font-weight: 700;
}
.chat-pill svg { flex: none; }
.chat-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }

/* The tool call. Deliberately the quietest thing in the frame — it is not
   what anybody came for — but present, because it is the only place the
   read/write line above becomes something you can see rather than a promise.
   Indented to the assistant's side, since that is who is making the call. */
.toolrow {
  align-self: flex-start; max-width: 92%;
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin-left: 34px; padding: 5px 11px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px;
  font-size: .74rem; color: var(--muted);
}
.toolrow b { color: var(--ink); font-weight: 700; }
.tflag {
  border-radius: 999px; padding: 1px 8px; font-size: .66rem; font-weight: 700;
  letter-spacing: .03em;
}
.tflag.read { background: var(--herb-soft); color: var(--brand-ink); }
/* Clay, the page's other accent, and the one it already uses for "this is a
   change" — the expiring-soon tag on a pantry tile is the same colour. The
   fill and the hairline are the clay channel at two alphas, which is what
   `--clay-rgb` is there for: `color-mix()` is what a television can't parse.

   **The word itself is `--ink`, not `--clay`, and the hairline is why it can
   be.** `--clay` on this tint is 4.2:1 in both palettes and this label is
   10.5px — under the 4.5:1 small text owes, in a pill whose only job is to be
   read. So the colour is carried by the fill and the border, which owe
   nothing, and the text takes the ink: 10.9:1 light, 12.4:1 dark. The `read`
   pill beside it keeps `--brand-ink`, which is already 5.6:1 and is the same
   green the eyebrow and `.chat-pill` wear. */
.tflag.write {
  background: rgba(var(--clay-rgb), 0.14); color: var(--ink);
  box-shadow: inset 0 0 0 1px rgba(var(--clay-rgb), 0.45);
}

/* The six clients, and how much each one costs to set up. A row of names at
   two weights rather than six cards: this is a list of facts, and drawing it
   as cards would give it the same weight as the six features above, which it
   does not have. */
.clients {
  margin-top: 34px; padding: 26px 26px 28px;
  background: var(--card); border: 1px solid var(--line); border-radius: 22px;
  box-shadow: var(--shadow-sm);
}
.clients h3 {
  margin: 0; font-family: var(--serif); font-weight: 600; font-size: 1.4rem;
  letter-spacing: -.02em;
}
.clients ul {
  list-style: none; margin: 18px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
}
@media (max-width: 860px) { .clients ul { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .clients ul { grid-template-columns: 1fr; } }
.clients li {
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px 14px; border-radius: 14px;
  background: var(--surface-2); border: 1px solid var(--line);
}
.clients li b { font-size: 1rem; font-weight: 700; }
.clients li span { font-size: .85rem; color: var(--muted); }
/* The two the vendor actually ships an install link for. Nothing else on this
   page may wear it — see the note in the markup, and app/connect_page.py. */
.clients li.one { background: var(--herb-soft); border-color: rgba(var(--brand-rgb), 0.28); }
.clients li.one span { color: var(--brand-ink); font-weight: 600; }
.clients-note { margin: 18px 0 0; color: var(--muted); font-size: .93rem; max-width: 44rem; }
.clients-note a { color: var(--brand-ink); font-weight: 600; }

/* prose — the one place on the page that just talks. It reads as a
   column now rather than a spread: the figure that used to sit beside it
   moved out when the walkthrough above took over the job of showing. */
.prose { max-width: 34rem; }
.prose p { margin: 0 0 1.05em; font-size: 1.02rem; color: var(--muted); }
.prose p:last-child { margin-bottom: 0; }
.prose strong { color: var(--ink); font-weight: 600; }
/* The sentences someone would actually type. Same italic as .step .say, so
   "this is a thing you'd say out loud" looks the same everywhere. */
.prose q { font-style: italic; quotes: "\201C" "\201D"; }

/* questions — visible, and the same six the FAQPage block marks up.
   Icons for the same reason the steps have them: six identical text blocks
   are a page you scroll past, and the glyph is what makes one of them the
   one you were looking for. */
/* Six questions with six full answers is about 2,000px of phone — a third
   of the page, spent on the part most people skim for the one line that
   worries them. As disclosures the answers are still in the HTML, still
   marked up in the FAQPage block, and still found by ctrl-F and by a
   crawler: <details> hides nothing from anything but the eye. */
.faq { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px 20px; align-items: start; }
@media (max-width: 800px) { .faq { grid-template-columns: 1fr; } }
.qa {
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  box-shadow: var(--shadow-sm);
  transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.qa:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.qa[open] { border-color: var(--edge-hover); }
.qa summary {
  display: flex; gap: 14px; align-items: center; cursor: pointer;
  padding: 17px 20px; list-style: none; min-height: 44px;
}
/* Safari still draws its own triangle without this. */
.qa summary::-webkit-details-marker { display: none; }
.qa .ico {
  width: 38px; height: 38px; border-radius: 12px; flex: none;
  background: var(--herb-soft); color: var(--brand-ink); display: grid; place-items: center;
}
.qa h3 { margin: 0; font-size: 1.02rem; font-weight: 700; }
.qa .chev { margin-left: auto; flex: none; color: var(--muted); transition: transform .22s ease; }
.qa[open] .chev { transform: rotate(180deg); }
.qa p { margin: 0; padding: 0 20px 19px 72px; color: var(--muted); font-size: .95rem; }
@media (max-width: 560px) { .qa p { padding-left: 20px; } }
.qa a { color: var(--brand-ink); font-weight: 600; }

/* share — the page has no analytics and no widget; these are five links.
   It used to be a row of buttons behind the words "Know a kitchen that needs
   this?", which asks the reader to do us a favour before they've had one.
   As a panel it asks the other way round: if this was any use, say so. */
.shout {
  background: var(--card); border: 1px solid var(--line); border-radius: 22px;
  padding: 26px 26px 28px; margin-top: 34px; box-shadow: var(--shadow-sm);
}
.shout h3 { margin: 0; font-family: var(--serif); font-weight: 600; font-size: 1.5rem; letter-spacing: -.02em; }
.shout p { margin: 8px 0 0; color: var(--muted); font-size: .96rem; max-width: 40rem; }
.share { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 18px; }
.share .label { font-size: .9rem; color: var(--muted); font-weight: 600; margin-right: 4px; }
/* On a phone the label sat on the first row and pushed one button onto its
   own line, leaving the five ragged. Its own row costs 20px and buys a grid. */
@media (max-width: 560px) { .share .label { flex-basis: 100%; margin: 0; } }
/* 44px minimum, and it has to be the *box* rather than the font: these were
   42px tall and a couple of them barely 60px wide, which is inside the
   margin where a thumb misses. `min-height` over more padding so the label
   stays optically centred at any text size. */
.share a, .share button, .copy-url {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; min-width: 44px;
  padding: 10px 16px; border-radius: 12px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  font-family: var(--sans); font-size: .88rem; font-weight: 600; text-decoration: none;
  transition: border-color .18s ease, background .18s ease, transform .18s ease;
}
.share a:hover, .share button:hover, .copy-url:hover {
  border-color: var(--brand); background: var(--herb-soft); transform: translateY(-2px);
}
.share svg, .copy-url svg { flex: none; }

/* final CTA over the spice photo */
.final {
  position: relative; overflow: hidden; border-radius: 26px;
  padding: 74px 32px; text-align: center; color: #fff; box-shadow: var(--shadow-lg);
}
.final .bg { position: absolute; inset: 0; }
.final .bg img { width: 100%; height: 100%; object-fit: cover; filter: saturate(.85); }
.final .bg::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(26,40,30,.7), rgba(26,40,30,.84));
}
.final .in { position: relative; }
.final h2 { color: #fff; }
.final p { color: rgba(255,255,255,.86); max-width: 34rem; margin: 14px auto 0; }
.final .cta-row { justify-content: center; margin-top: 26px; }
.final .fineprint { justify-content: center; color: rgba(255,255,255,.82); }
.final .tick { color: #a9d6b5; }

footer {
  border-top: 1px solid var(--line); margin-top: 20px; padding: 24px 0 36px;
  color: var(--muted); font-size: .86rem;
}
.foot-in { display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center; }
.foot-in .brand { font-size: 1.12rem; }
.foot-links { display: flex; flex-wrap: wrap; gap: 2px 12px; }
/* Privacy and Contact were 21px tall — half a touch target, and they're the
   two links a stranger reaches for before signing in. The padding buys the
   height; the negative inline margin keeps the row looking as it did, since
   the box is now wider than the word. */
.foot-in a.flink {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 10px; margin-inline: -10px;
  text-decoration: none; font-weight: 500;
}
.foot-in a.flink:hover { color: var(--ink); text-decoration: underline; }
/* A middot between items. Five short labels all set at the same weight read as
   one run, and the row says where each one starts but not where the last one
   ended.

   **The two sides of the dot are built out of different pieces, and they have
   to be added up rather than assumed.** Each link carries `padding: 0 10px`
   with `margin-inline: -10px` — negative on *both* sides — to buy a 44px touch
   target without moving the words. So:

     left  = this rule's 12px margin
     right = 10px (own right padding) − 10px (own right margin)
             + 12px (the row's column gap)
             − 10px (next item's left margin) + 10px (its left padding)  = 12px

   which is why the column gap above is 12px and not the 2px it was. Read the
   right-hand side as "the gap, and everything else cancels". At 2px the dot sat
   12px after one word and 2px before the next, which reads as `Privacy ·Terms`
   — correct in the box model and wrong on the page. Change one number here and
   the other one moves.

   `inline-block` stops the hover underline from running through it, and it is
   never on the last item, so no wrapped row ends in a dangling dot. */
.foot-in .foot-links > *:not(:last-child)::after,
.foot-in .right > *:not(:last-child)::after {
  content: "·";
  display: inline-block;
  margin-left: 12px;
  text-decoration: none;
  opacity: .55;
}
/* The page you're standing on, in the row above: text rather than an anchor,
   so five public pages don't each carry a self-link. Same box as .flink so the
   row's rhythm and its 44px targets are unchanged — opacity rather than a
   fainter token because this file carries its own palette twice and a fifth
   colour would be a fifth thing to keep in step. */
.foot-in .fhere {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 10px; margin-inline: -10px;
  opacity: .62;
}
/* The sign-off. Serif italic and a clay heart — the one line on the page
   allowed to sound like a person rather than a product. */
.foot-love {
  margin: 18px 0 0; font-family: var(--serif); font-style: italic;
  font-size: .95rem; color: var(--muted);
  display: flex; align-items: flex-start; gap: 7px;
}
/* the heart sits on the first line, not adrift between two when it wraps */
.foot-love .heart { color: var(--clay); flex: none; margin-top: .32em; }

:focus-visible { outline: 3px solid var(--brand); outline-offset: 3px; border-radius: 8px; }

/* ---------------- small type, on a phone ----------------
   `--muted` on `--bg` is 4.6:1 — over the AA line for body text, but only
   just, and several of these run at 12–14px where that margin stops being
   comfortable. The colour is the app's token and shared with
   web/css/rasoi-theme.css, so it isn't ours to darken here; size is the
   lever that's actually ours. Nothing supporting drops below 14px on a
   phone, which is also where the reading distance is longest. */
@media (max-width: 720px) {
  .float.stat small,
  .share .label,
  .demo-note,
  footer { font-size: .9rem; }
  .card p, .step p, .qa p, .beat p { font-size: .96rem; }
  .toolrow, .clients li span, .clients-note { font-size: .9rem; }
}

/* ---------------- motion ----------------
   Entrances are opt-in: JS adds .anim to <html> only when motion is
   welcome, and a timer reveals anything the observer never reached, so
   content is never left invisible. */
@keyframes rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes floaty { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }
@keyframes kenburns { from { transform: scale(1); } to { transform: scale(1.07); } }

.anim .r { opacity: 0; transform: translateY(18px); }
.anim .r.in {
  opacity: 1; transform: none;
  transition: opacity .7s cubic-bezier(.2,.7,.3,1), transform .7s cubic-bezier(.2,.7,.3,1);
  transition-delay: var(--d, 0s);
}
/* How far behind its neighbour each one arrives. These were `style="--d:.06s"`
   attributes in the markup until this app grew a Content-Security-Policy
   (app/security.py) — an inline style attribute is inline style, and
   `style-src 'self'` drops it silently, which here reads as four things
   appearing at once instead of in order.
   Named in hundredths of a second, always two digits, because `.d-1` and
   `.d-12` next to each other are a bug waiting to be read wrong. Not one ladder
   with one step: a row of three cards is staggered at .06s and six questions at
   .05s, and both of those are somebody's judgement about the rhythm rather than
   an arithmetic sequence to derive. */
.d-00 { --d: 0s; }
.d-05 { --d: .05s; }
.d-06 { --d: .06s; }
.d-08 { --d: .08s; }
.d-10 { --d: .1s; }
.d-12 { --d: .12s; }
.d-15 { --d: .15s; }
.d-18 { --d: .18s; }
.d-20 { --d: .2s; }
.d-25 { --d: .25s; }
.d-30 { --d: .3s; }
.anim .hero .r { animation: rise .7s cubic-bezier(.2,.7,.3,1) backwards; opacity: 1; transform: none; }
.anim .hero .r:nth-child(1) { animation-delay: .04s; }
.anim .hero .r:nth-child(2) { animation-delay: .12s; }
.anim .hero .r:nth-child(3) { animation-delay: .20s; }
.anim .hero .r:nth-child(4) { animation-delay: .28s; }
.anim .hero .r:nth-child(5) { animation-delay: .36s; }
.anim .shot .frame img { animation: kenburns 26s ease-in-out infinite alternate; }
.anim .float.chat { animation: rise .7s .5s cubic-bezier(.2,.7,.3,1) backwards, floaty 7s 1.4s ease-in-out infinite; }
.anim .float.stat { animation: rise .7s .7s cubic-bezier(.2,.7,.3,1) backwards, floaty 6s 1.9s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .anim .r, .anim .float, .anim .shot .frame img { animation: none !important; opacity: 1 !important; transform: none !important; }
}

/* ---------------- the phone's own CTA ----------------
   A bar at the foot of the window holding the one thing this page is for,
   below 820px and only once the hero's own button has gone past. The reasoning
   is beside the markup in index.html; what is here is the three things the
   drawing has to get right.

   **It slides rather than appears**, because a bar that materialises over the
   page under a scrolling thumb reads as something that was tapped. Which is why
   it is `visibility` and a transform rather than `hidden`: `display: none` has
   no transition to run from, and `visibility` still takes it out of the
   accessibility tree while it is down, so a reader tabbing the page before it is
   up never meets a fourth sign-in link.

   **It owns the home indicator's strip**, inside its own padding rather than
   under it — the same rule the app's tab bar follows in
   web/css/rasoi-theme.css.

   **Nothing reserves room for it.** It is up only between the hero and the
   closing section, and the closing section is what puts it away, so it can never
   be the thing covering the footer. */
.cta-dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  background: var(--card); border-top: 1px solid var(--line);
  box-shadow: 0 -2px 18px rgba(63,49,30,.10);
  padding: 9px 0 calc(9px + env(safe-area-inset-bottom));
  visibility: hidden; transform: translateY(110%);
  transition: transform .28s ease, visibility .28s;
}
.cta-dock.up { visibility: visible; transform: none; }
/* Past this width the sticky header carries the sign-in at every scroll
   position, which is what this bar is standing in for. The number is written
   down twice — here and as DOCK in /welcome/landing.js — because a media query
   cannot be read from script and matchMedia cannot be read from CSS. Change
   one, change the other. */
@media (min-width: 821px) { .cta-dock { display: none; } }
@media (prefers-reduced-motion: reduce) { .cta-dock { transition: none; } }
.cta-dock-in { display: flex; align-items: center; gap: 14px; }
/* What the button costs you, beside the button — the two facts the header has
   never had room for, and the reason this bar is worth more than the shortcut
   it replaces. */
.cta-dock-say { margin: 0; min-width: 0; font-size: .78rem; line-height: 1.35; }
.cta-dock-say b { display: block; font-size: .88rem; font-weight: 700; }
.cta-dock-say span { color: var(--muted); }
.cta-dock .gbtn { margin-left: auto; flex: none; }
/* On a phone the button takes the whole width and the two facts go above it on
   one line. Beside it they would have about 170px, which is where "Set up in
   one sentence" becomes three lines and the bar becomes 90px of the screen — and
   the button is the thing this bar is for, so it is the button that gets the
   room. Not dropped: a bare Google button at the foot of a page, with nothing
   saying what pressing it costs, is the one shape that reads as an advert. */
@media (max-width: 480px) {
  .cta-dock-in { flex-wrap: wrap; gap: 5px 8px; justify-content: center; }
  .cta-dock-say { width: 100%; text-align: center; font-size: .74rem; }
  .cta-dock-say b { display: inline; font-size: .74rem; }
  .cta-dock-say b::after { content: " · "; font-weight: 400; color: var(--muted); }
  .cta-dock .gbtn { margin-left: 0; flex: 1 1 auto; }
}
/* One offer on one screen: while the dock is up the header's own button stands
   down, so the page is not asking the same question twice fourteen pixels
   apart. It comes back the moment the dock goes, which is the hero and the
   closing section — both of which have a full-size button of their own on
   screen at that moment. */
@media (max-width: 820px) {
  body.cta-docked .nav-links .gbtn.sm { display: none; }
}
