/* ═══ THE PALETTE ═══════════════════════════════════════════════════════════
   Lifted verbatim out of the reference build so that the terminal, the
   onboarding panel and the guest portals are painted from ONE set of tokens.
   Two copies of a palette is two palettes: the moment one is edited the other
   is wrong, and nobody finds out until a chip is unreadable in light mode.

   A theme here is a full palette swap, not a filter. Every value was tuned
   against a real screen at a real width — do not round them, rename them, or
   substitute a scale you prefer.
   ═══════════════════════════════════════════════════════════════════════ */

/* Palette as the reference defined it — :root custom properties, dark by
   default. ThemeMode / CustomColors on the Users table select the variant. */
:root{
  /* MOTION, ONE SET. Short enough to feel like the till answering, never long enough to wait for.
     Exits are shorter than entries; the curve decelerates (fast start, soft landing). */
  --kp-dur-1:120ms; --kp-dur-2:220ms; --kp-dur-3:320ms;
  --kp-ease:cubic-bezier(.05,.7,.1,1);
  /* Guest-app palette, night side. The ramp is near-neutral with only a
     whisper of the brick #8e3226 the guest phone sits on — 2-3% chroma, enough
     to relate the two surfaces, little enough that the coral accent is the only
     saturated thing on screen. A fully warm ramp gave the accent nothing to
     pop against; every surface was already the brand hue. */
  --bg:#111214; --bg-0:#161719; --bg-1:#1b1c1f; --bg-2:#212327; --bg-3:#2c2e33;
  --line-soft:#212327; --line:#2c2e33; --line-2:#3a3d43; --line-3:#4e525a;
  /* The neutral status fill. It has to read on --bg-1 AND on --bg-3, because a
     selected tile is --bg-3 — one step past both. */
  --mute-dim:#3a3d43;
  /* Every tier clears 7:1 against bg…bg-2. Subdued here means smaller and
     lighter in weight, never lower in contrast — a till is read off-axis,
     across a counter, under downlights. Low-contrast grey is not an option. */
  --text:#f5f6f7; --text-dim:#d3d5d9; --text-muted:#bcbfc5; --text-faint:#a6aab1;
  --code-text:#d0d3d8;
  /* --amber-* is the BRAND ACCENT and is rewritten inline on <html> by
     applyTheme() from the user's CustomColors. It must therefore never carry
     meaning. --warn-* below is the fixed warning tier: it is defined only here
     and in the light block, and applyTheme() never touches it. A user picking
     the crimson accent previously repainted every warning into the destructive
     red — an open table then signalled in the same hue as Void. */
  --amber:#f0f0f0; --amber-bright:#ffffff; --amber-dim:#232323; --amber-line:#3a3a3a; --amber-deep:#cfcfcf;
  --warn:#f2a43a; --warn-bright:#ffc14a; --warn-dim:rgba(242,164,58,.14); --warn-line:#6b4a1f; --on-warn:#1a0f00;
  /* Tint tokens for the four STATUS families. Hard-coded rgba() tints cannot
     follow the theme, so in light mode a tint kept its dark-theme hue while its
     paired border and label resolved to the light ramp — tint and text drifting
     apart on the same chip. Every status tint now resolves per theme. */
  --ok-dim:#12281d; --bad-dim:#301218;
  --info-dim:#171c33; --violet-dim:#221a30;
  --brand-dim:#232323;
  --brand-red:#8e3226; --brand-red-bright:#f4553c; --brand-olive:#8a9440; --brand-olive-bright:#a8b354;
  --brand-cream:#f0e2c0; --brand-grey:#a4a8af;
  --brand-plate:#ffffff; --brand-plate-line:#3d2c28;
  --on-amber:#111111;
  /* Semantic. Colour states intent and never decorates: green commits money,
     blue advances the order, red destroys, amber warns. Nothing else is
     allowed a filled background, so a fill always means "this does something". */
  --go:#34c77b; --go-bright:#5fe09a; --go-dim:#12281d; --go-line:#256b47; --on-go:#04180d;
  --fwd:#7c92f5; --fwd-bright:#a3b3fa; --fwd-dim:#171c33; --fwd-line:#3a4a9c; --on-fwd:#060c26;
  --danger:#f2445e; --danger-bright:#ff7b8e; --danger-dim:#301218; --danger-line:#7d2231; --on-danger:#fff2f4;
  /* Categorical. Hue carries the menu section so a cashier lands on it without
     reading the label. Swatches, dots and rails only — never a button fill —
     so a category colour can never be misread as an action. */
  --cat-starters:#5f9d92; --cat-mains:#7b90c4; --cat-grill:#c08f68; --cat-rice:#b0a06a;
  --cat-sides:#8fa86a; --cat-desserts:#bd83a6; --cat-drinks:#9689bf; --cat-all:#878b92;
  /* IDENTITY — who someone is, not what something does. Seven fixed hues at one
     lightness, spaced so no two are confusable: every pair is ≥40 apart in RGB
     and every one clears 7:1 on --bg-3. Never sourced from --amber-* (a user can
     repaint that, and identity is meaning), never from --cat-* (muted by design,
     tops out near 5:1 as text). Add a role, add a hue here — do not borrow one. */
  --id-violet:#ddb5ff; --id-gold:#ecc663; --id-green:#90e19c; --id-blue:#97ccff;
  --id-orange:#ffab9a; --id-teal:#4be3eb; --id-pink:#ffa8d6;
  /* --green is the INK role (chip labels, table cells, KPI figures), the peer
     of --red-bright / --warn-bright / --blue-bright. The FILL is --go. Green was
     the one family missing an ink token, so call sites reached for the fill and
     every green chip landed under the contrast floor. */
  --green:#5fe09a; --green-bright:#5fe09a; --green-line:#256b47; --green-soft:#93dcb2;
  --red:#f2607a; --red-bright:#ff8ba0; --red-line:#6b2a38; --red-line-2:#3a222a;
  --red-dim:#7a4050; --red-dim-2:#c06880; --red-soft:#f0a8b8;
  --blue:#8ba0f7; --blue-bright:#adbaf9; --violet:#a88ad9; --violet-bright:#c3aae6;
  --paper:#fdfbf6; --paper-ink:#1a1a1a; --qr-ink:#0e0f11;
  --focus:var(--fwd);
  --scrim:rgba(8,9,11,.76); --shadow-lg:0 2px 4px rgba(0,0,0,.4), 0 24px 64px rgba(0,0,0,.55);
}
[data-theme="light"]{
  /* ── LIGHT THEME ──────────────────────────────────────────────────────────
     Rebuilt in OKLCH, not picked by eye, so lightness and chroma are even
     across every family and no one hue shouts louder than another.

     Neutrals are TRUE grey — chroma 0, no warm or cool bias — matching the
     reference screens, where an off-white page (#efefef) carries pure-white
     cards and the only chromatic pixel is a notification dot. The earlier warm
     hue-75 ramp was derived from a coral accent; with the accent collapsed to
     ink (see --amber below), a tinted neutral has nothing to agree with and
     just reads as beige. Elevation is now carried by white against grey and by
     shadow, not by hue.

     Elevation means what it means in the dark block: --bg is the page, --bg-1
     the card, --bg-2 a raised row, --bg-3 hover/pressed. Light mode cannot go
     brighter than white, so --bg-3 steps DOWN in lightness where dark steps up.
     Same role, opposite direction — do not "correct" it to be monotonic.
     ───────────────────────────────────────────────────────────────────────── */
  --bg:#efefef; --bg-0:#e9e9e9; --bg-1:#ffffff; --bg-2:#f8f8f8; --bg-3:#e7e7e7;
  --line-soft:#f2f2f2; --line:#e6e6e6; --line-2:#d6d6d6; --line-3:#b4b4b4;
  /* Same role. A translucent white here vanished on #ffffff, which is why a
     Free table read as having no status at all in this theme. */
  --mute-dim:#d6d6d6;
  /* All four ink tiers clear 7:1 on --bg-3, the worst surface in this theme —
     14.92 / 11.27 / 8.87 / 7.03:1, and 18.36 / 13.86 / 10.91 / 8.65:1 on white. Subdued means
     smaller and lighter in weight, never lower in contrast: a till is read
     off-axis, across a counter, under downlights. */
  --text:#131313; --text-dim:#2b2b2b; --text-muted:#3d3d3d; --text-faint:#4d4d4d;
  --code-text:#2b2b2b;
  /* Three roles per family, and they are NOT interchangeable:
       base    the FILL, carrying white ink   (white-on-fill ≥ 7:1)
       bright  INK on a tint or on the page   (darker than base in light mode)
       dim     the tint; line its border
     ONE RULE, NO EXCEPTIONS: text and icons take --*-bright; fills, bars, dots
     and chart series take the bare name. Every family now has a -bright alias,
     including green, whose missing one is exactly why call sites reached for
     the fill and landed under the contrast floor.
     Swapping base and bright is the classic light-theme bug — a fill colour
     used as 12px text, or an ink used as a button. */
  /* The reference profile makes the PRIMARY ACTION near-black, not a hue: the
     only saturated pixel on those screens is a 6px notification dot. So the
     accent family collapses to ink — fill #1c1c1c carrying white, a neutral
     #f1f1f1 tint, and a grey line. Semantic families keep their hues below;
     with the accent neutral, a coloured fill now means ONLY go/warn/danger. */
  --amber:#1c1c1c; --amber-bright:#111111; --amber-dim:#f1f1f1; --amber-line:#d8d8d8; --amber-deep:#000000;
  /* Warn holds hue 70: 34° off brand coral, 55° off danger, so "needs
     attention" can never be read as the brand or as a destructive act. */
  --warn:#804c00; --warn-bright:#663700; --warn-dim:#fcf4ec; --warn-line:#ecd6bf; --on-warn:#ffffff;
  /* Aliases, deliberately equal: ok=go, bad=danger, brand=amber. Any change to
     one must be made to its pair. */
  --ok-dim:#eef9f2; --bad-dim:#fff1f1;
  --info-dim:#eff6ff; --violet-dim:#f7f3ff;
  --brand-dim:#f1f1f1;
  --brand-red:#8e3226; --brand-red-bright:#6f2418; --brand-olive:#5f6a2c; --brand-olive-bright:#4a5320;
  --brand-cream:#ededed; --brand-grey:#6a6a6a;
  --brand-plate:#ffffff; --brand-plate-line:#dcdcdc;
  --on-amber:#ffffff;
  /* Brand coral hue 36, danger crimson hue 15 — the same separation the dark
     block uses (#f4553c against #f2445e), so both themes agree on what red
     means. Hue is not the safeguard on its own: only a semantic tier may take a
     filled background, so a fill always means "this does something".
     Green sits a step darker than the rest (L .435) because white ink on green
     needs it to clear the same 7:1 floor. */
  --go:#006337; --go-bright:#004c24; --go-dim:#eef9f2; --go-line:#c4e3d0; --on-go:#ffffff;
  --fwd:#2754ac; --fwd-bright:#143d8e; --fwd-dim:#eff6ff; --fwd-line:#c7dbff; --on-fwd:#ffffff;
  --danger:#9b283d; --danger-bright:#7e0f2a; --danger-dim:#fff1f1; --danger-line:#fccccf; --on-danger:#ffffff;
  /* Categorical: ONE lightness (.50) and ONE chroma (.072) across all eight, so
     hue is the only variable and no menu section outranks another. Well below
     the semantic families in chroma — a category can never read as an action. */
  --cat-starters:#26706e; --cat-mains:#50628d; --cat-grill:#85563e; --cat-rice:#706330;
  --cat-sides:#4d6d46; --cat-desserts:#80536f; --cat-drinks:#685a87; --cat-all:#6a6a6a;
  /* IDENTITY — who someone is, not what something does. Seven fixed hues at one
     lightness, spaced so no two are confusable: every pair is ≥40 apart in RGB
     and every one clears 7:1 on --bg-3. Never sourced from --amber-* (a user can
     repaint that, and identity is meaning), never from --cat-* (muted by design,
     tops out near 5:1 as text). Add a role, add a hue here — do not borrow one. */
  --id-violet:#522977; --id-gold:#5d3900; --id-green:#00520f; --id-blue:#083d84;
  --id-orange:#771910; --id-teal:#00535c; --id-pink:#6f194b;
  /* --green is the INK role — peer of --red-bright / --warn-bright. --go is the
     FILL. 9.45:1 on --ok-dim; the fill it replaced measured 6.86:1 there. */
  --green:#004c24; --green-bright:#004c24; --green-line:#c4e3d0; --green-soft:#004c24;
  --red:#9b283d; --red-bright:#7e0f2a; --red-line:#fccccf; --red-line-2:#ffdfe1;
  --red-dim:#ab575f; --red-dim-2:#993041; --red-soft:#67051f;
  --blue:#2754ac; --blue-bright:#143d8e; --violet:#67419d; --violet-bright:#502b81;
  /* Shadow is warm-neutral, never black: black over a warm surface greys it.
     Two layers — a 1px contact edge and a wide, soft ambient. */
  /* Neutral shadow now that the surfaces are neutral — a warm shadow over a
     grey desk was the tint that read as "beige" in the reference comparison. */
  --scrim:rgba(20,20,20,.34); --shadow-lg:0 1px 2px rgba(0,0,0,.06), 0 16px 40px rgba(0,0,0,.10);
}
/* ═══ THE LAGOON LOOK ══════════════════════════════════════════════════════
   One layout, two looks (PRODUCT.md). `data-look` on <html> is the store's
   choice and `data-theme` is this device's day or night, so these two blocks
   re-map the SAME token names the whole app already reads — no screen is
   edited to change look. Night is guarded with :not(light) because
   [data-theme="light"] and [data-look="lagoon"] have equal specificity and
   source order would otherwise decide.

   Deep-sea neutrals, lagoon turquoise as the accent (the next action), and
   the semantic families left alone: green commits money, red destroys, amber
   warns, whatever the look. Ink tiers hold the 7:1 floor on bg…bg-2 like the
   blocks above. */
[data-look="lagoon"]:not([data-theme="light"]){
  --bg:#03131b; --bg-0:#051a24; --bg-1:#071f2b; --bg-2:#0b2837; --bg-3:#11394a;
  --line-soft:#0b2837; --line:#11394a; --line-2:#1a4a5e; --line-3:#2a6178;
  --mute-dim:#1a4a5e;
  --text:#e5f4f6; --text-dim:#c6dde3; --text-muted:#b0ccd4; --text-faint:#9dbec8;
  --code-text:#c6dde3;
  --amber:#35e4f2; --amber-bright:#7defff; --amber-dim:#073545; --amber-line:#0f5566; --amber-deep:#22b8c6; --on-amber:#021a22;
  --brand-dim:#073545;
  --scrim:rgba(1,12,18,.78); --shadow-lg:0 2px 4px rgba(0,0,0,.45), 0 24px 64px rgba(0,0,0,.6);
  --font-display:'Bricolage Grotesque','Instrument Sans',system-ui,sans-serif;
}
/* KASHIKEYO — the brand's own look, worn where the business meets the product
   rather than where staff serve: the account and onboarding pages. Greys,
   crimson for the one action, the kashikeyo mark as the only colour. */
[data-look="kashikeyo"]:not([data-theme="light"]){
  --bg:#0C0B0A; --bg-0:#121110; --bg-1:#1A1816; --bg-2:#1F1D1B; --bg-3:#2A2724;
  --line-soft:#1A1816; --line:#2C2926; --line-2:#3A3633; --line-3:#57524D;
  --mute-dim:#3A3633;
  --text:#F5F3EF; --text-dim:#E1DCD5; --text-muted:#C4BDB5; --text-faint:#B3ADA6;
  --code-text:#E1DCD5;
  --amber:#C23145; --amber-bright:#F2A0AB; --amber-dim:#2E1519; --amber-line:#5A2530; --amber-deep:#982030; --on-amber:#FFFFFF;
  --brand-dim:#2E1519; --brand-word:#F2A0AB;
  --scrim:rgba(8,6,5,.78); --shadow-lg:0 2px 4px rgba(0,0,0,.45), 0 24px 64px rgba(0,0,0,.6);
}
[data-look="kashikeyo"][data-theme="light"]{
  --bg:#FAFAF8; --bg-0:#F4F3F0; --bg-1:#FFFFFF; --bg-2:#F4F3F0; --bg-3:#ECEAE5;
  --line-soft:#F1F0EC; --line:#E3E0DA; --line-2:#D2CEC7; --line-3:#A8A29C;
  --mute-dim:#D2CEC7;
  --text:#16120F; --text-dim:#2E2925; --text-muted:#4A4541; --text-faint:#5E5853;
  --code-text:#2E2925;
  --amber:#982030; --amber-bright:#7E1A28; --amber-dim:#F6E6E8; --amber-line:#E7C3C8; --amber-deep:#7E1A28; --on-amber:#FFFFFF;
  --brand-dim:#F6E6E8; --brand-word:#982030;
  --scrim:rgba(22,18,15,.42); --shadow-lg:0 1px 2px rgba(22,18,15,.08), 0 16px 40px rgba(22,18,15,.14);
}
[data-look="lagoon"][data-theme="light"]{
  --bg:#edf4f5; --bg-0:#e4eef0; --bg-1:#ffffff; --bg-2:#f6fafb; --bg-3:#ddebee;
  --line-soft:#eef5f6; --line:#d5e3e7; --line-2:#bed2d8; --line-3:#93aeb7;
  --mute-dim:#bed2d8;
  --text:#06222e; --text-dim:#1b3a47; --text-muted:#2c4b57; --text-faint:#33515d;
  --code-text:#1b3a47;
  --amber:#19c6d6; --amber-bright:#08535e; --amber-dim:#ddf6f9; --amber-line:#a8e3ea; --amber-deep:#0e8f9c; --on-amber:#04202b;
  --brand-dim:#ddf6f9;
  --scrim:rgba(4,24,33,.42); --shadow-lg:0 1px 2px rgba(6,34,46,.08), 0 16px 40px rgba(6,34,46,.14);
  --font-display:'Bricolage Grotesque','Instrument Sans',system-ui,sans-serif;
}
/* Money and table numbers set in the look's display face. Standard has none,
   so it falls back to the body face and nothing changes there. */
.disp{font-family:var(--font-display,inherit);font-stretch:88%;letter-spacing:-.02em}

*{box-sizing:border-box}
/* Tabular figures everywhere, inherited, not opt-in. A total that reflows its
   own width as digits change reads as a glitch and pulls the eye off the line
   being rung. Instrument Sans's tnum keeps every numeric column static. */
html,body{margin:0;padding:0;background:var(--bg);color:var(--text);font-family:'Instrument Sans',system-ui,sans-serif;height:100%;overflow:hidden;font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;-webkit-font-smoothing:antialiased}
input,select,textarea,button{font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit;text-align:left;
  -webkit-tap-highlight-color:transparent;touch-action:manipulation;
  transition:background-color .14s cubic-bezier(.2,.7,.3,1),border-color .14s cubic-bezier(.2,.7,.3,1),color .14s,box-shadow .14s,transform .09s cubic-bezier(.2,.7,.3,1)}
/* Every button answers the finger the same way: one pixel down, shadow pulled
   in. On a till the operator is looking at the guest, not the screen — the
   press has to be felt in peripheral vision, which rules out a colour-only
   response. It is deliberately not a bounce: this is a cash register, and a
   button that springs reads as unserious next to a total. */
button:active:not(:disabled){transform:translateY(1px)}
/* A FINGER IS 48 PIXELS, WHATEVER THE INLINE STYLE SAYS (PRODUCT.md). Controls here are
   styled one by one — 30, 34, 36, 38px — which a mouse forgives and a cashier
   with wet fingers mid-service does not: a mis-tap lands on a guest's bill.
   On a touch screen every button is at least 48×48; a mouse keeps the dense
   layout. `!important` because the sizes it corrects are inline declarations.
   any-pointer too: a counter PC with a touchscreen AND a mouse reports its
   primary pointer as fine, and the cashier still taps it with a finger. */
@media (pointer:coarse),(any-pointer:coarse){
  button,[role="button"]{min-height:48px!important;min-width:48px!important}
  /* And a field is 16px: iOS zooms the whole page into any field smaller than
     that the moment it takes focus, iPad included. The six code boxes on
     /account are larger on purpose and keep their size. */
  input,select,textarea{font-size:16px!important}
  .otp input{font-size:22px!important}
}
/* Keyboard focus only. A till is driven by touch and the ring would otherwise
   sit under every finger; :focus-visible keeps it for the people who need it. */
button:focus-visible,[role="button"]:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:inherit}
/* A BUTTON was all that rule ever covered. Every input, select and textarea in
   the terminal carries `outline:none` in its inline style — a layout decision
   each one has its own reason for — and an inline declaration beats a plain
   stylesheet rule, so the fields drew nothing at all: tabbing into the top-bar
   search landed a keyboard user on a control they could not see. `!important`
   is the only thing that reaches past 22 inline styles without editing 22
   inline styles. The ring draws INSIDE a field, because a field sits in a
   bordered pill with no room outside it for one.

   AND ON A TEXT FIELD `:focus-visible` MATCHES A MOUSE CLICK TOO. That is the
   browser's own rule and not a bug — a text field always shows where the
   caret went — so the comment above is true of a button and never of these.
   Which means the ring is seen constantly, and its SHAPE is the whole of
   whether it reads as a field lighting up or as a stray rectangle: an input
   carries no border-radius of its own, so a bare input inside a rounded pill
   drew a square 2px box floating in the middle of it. Reported. The answer is
   never to soften the ring — it is that an input must BE its field, filling
   the pill and inheriting its radius, which is how the till's menu search was
   always built. See `findInputStyle` in app/index.html. */
input:focus-visible,textarea:focus-visible,select:focus-visible,
a:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--fwd) !important;outline-offset:-2px}
button:disabled{cursor:not-allowed}
@media (prefers-reduced-motion:reduce){button{transition:none}button:active:not(:disabled){transform:none}[style*="kp-rise"],[style*="kp-grow"]{animation-name:kfade!important}}
input,select,textarea{font-family:inherit}
a{color:var(--fwd-bright);text-decoration:none}
a:hover{color:var(--fwd);text-decoration:underline}
::selection{background:var(--fwd);color:var(--on-fwd)}
::-webkit-scrollbar{width:9px;height:9px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--line);border-radius:5px}
::-webkit-scrollbar-thumb:hover{background:var(--line-3)}
/* A single-row chip rail (menu sections) scrolls sideways rather than wrapping:
   a wrapping row changes height with the section count, and every dish below it
   moves. The bar is slimmed rather than hidden — it is the only honest signal
   that there are more sections off the right edge, and it shows up exactly when
   there are. */
.krail{scrollbar-width:thin;scrollbar-color:var(--line-3) transparent}
.krail::-webkit-scrollbar{height:4px}
.krail::-webkit-scrollbar-track{background:transparent}
.krail::-webkit-scrollbar-thumb{background:var(--line-3);border-radius:3px}
@keyframes kp-grow{from{transform:scaleY(.04);opacity:.4}to{transform:none;opacity:1}}
@keyframes kp-rise{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@keyframes kfade{from{opacity:0}to{opacity:1}}
@keyframes kmodal{from{opacity:0;transform:translateY(14px) scale(.985)}to{opacity:1;transform:none}}
@keyframes ktoast{from{opacity:0;transform:translate(-50%,18px)}to{opacity:1;transform:translate(-50%,0)}}
@keyframes kpulse{0%,100%{opacity:1}50%{opacity:.35}}
@keyframes kspin{to{transform:rotate(360deg)}}
@keyframes kslide{from{transform:translateX(-100%)}to{transform:none}}
@keyframes ksheet{from{transform:translateY(100%)}to{transform:none}}
@keyframes kshake{0%,100%{transform:translateX(0)}18%{transform:translateX(-6px)}36%{transform:translateX(5px)}54%{transform:translateX(-3px)}72%{transform:translateX(2px)}}
@keyframes ktoastshake{0%,100%{transform:translate(-50%,0)}18%{transform:translate(calc(-50% - 6px),0)}36%{transform:translate(calc(-50% + 5px),0)}54%{transform:translate(calc(-50% - 3px),0)}72%{transform:translate(calc(-50% + 2px),0)}}
@keyframes kaddline{from{opacity:0;transform:translateX(14px);background:var(--warn-dim)}to{opacity:1;transform:none}}
@keyframes kcheck{from{stroke-dashoffset:26}to{stroke-dashoffset:0}}
@keyframes kpop{from{transform:scale(.6);opacity:0}to{transform:scale(1);opacity:1}}
/* Content entrance. Two identical names because an animation only restarts when
   its NAME changes — switching menu section alternates between them, so the
   dish grid re-enters on every section instead of sitting there silently. */
@keyframes krise{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:none}}
@keyframes kriseb{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:none}}
/* Skeletons, not spinners, for content about to arrive: the operator sees the
   shape of the answer and can aim their hand at it before it lands. */
@keyframes kshimmer{from{background-position:-160% 0}to{background-position:260% 0}}
/* A till is tapped thousands of times a shift. The press must answer instantly
   or the operator taps again and rings the item twice — so this is a duplicate
   charge control, not decoration. Kept off inputs and selects, which have
   their own native affordance. */
button{transition:transform .09s cubic-bezier(.2,.8,.3,1),background .08s ease-out,border-color .08s ease-out,color .08s ease-out}
button:active:not(:disabled){transform:scale(.97)}
/* Disabled is a visible state, not an absent one: it must still read as a
   button so the operator knows where the control is and why it is refusing. */
button:disabled{opacity:.42;cursor:not-allowed;transform:none;filter:saturate(.35)}
/* Reduced motion: swap MOVEMENT for a FADE, never strip the feedback.
   The blunt "animation-duration:.01ms !important" this replaces was worse than
   nothing: it freezes the sync spinner mid-rotation (a stuck icon reads as a
   crashed till), kills the offline pulse that is the only signal the terminal
   has stopped talking to the server, and flattens the just-added-line tint that
   tells the operator which of twenty rows they touched. All of that is meaning.
   Large positional movement is the trigger; a quick opacity change almost never
   is. So each keyframe is REDEFINED here under the same name — every inline
   "animation:kmodal .2s" keeps working, and timing, easing and staging survive.
   What dies: slide-ins, the sheet rising, the shake, the spin, the shimmer
   sweep, the scale-up pop. What lives: opacity, colour, and the 3% button
   squish, which is user-initiated, sub-pixel at a fingertip, and the one cue
   that stops a cashier tapping twice and ringing an item twice. */
@media (prefers-reduced-motion:reduce){
  @keyframes kmodal{from{opacity:0}to{opacity:1}}
  @keyframes ksheet{from{opacity:0}to{opacity:1}}
  @keyframes kslide{from{opacity:0}to{opacity:1}}
  @keyframes kpop{from{opacity:0}to{opacity:1}}
  @keyframes krise{from{opacity:0}to{opacity:1}}
  @keyframes kriseb{from{opacity:0}to{opacity:1}}
  @keyframes kaddline{from{opacity:0;background:var(--warn-dim)}to{opacity:1}}
  /* translate(-50%) is the toast's centring, not its animation — it must stay. */
  @keyframes ktoast{from{opacity:0;transform:translate(-50%,0)}to{opacity:1;transform:translate(-50%,0)}}
  /* The shake meant "rejected". It becomes a double blink so the message still
     announces itself when it replaces an identical one already on screen. */
  @keyframes kshake{0%,100%{opacity:1}25%,75%{opacity:.45}}
  @keyframes ktoastshake{0%,100%{opacity:1;transform:translate(-50%,0)}25%,75%{opacity:.45;transform:translate(-50%,0)}}
  /* Rotation out, "still working" kept. */
  @keyframes kspin{0%,100%{opacity:1}50%{opacity:.4}}
  @keyframes kshimmer{0%,100%{opacity:1}50%{opacity:.55}}
  @keyframes kcheck{from{stroke-dashoffset:0}to{stroke-dashoffset:0}}
  /* Skeleton gradients stop travelling; the block still breathes. */
  [style*="kshimmer"]{background-image:none !important;background:var(--bg-2) !important}
}


/* ═══ THE WATERLINE ══════════════════════════════════════════════════════
   A seated table fills as it waits against the store's table-turn target
   (--fill, set per tile) and turns coral past it. It sits under the tile's
   content (the tile is its own stacking context) and never takes a tap.
   Lagoon draws water with a wave on its surface; Standard draws the same
   measure as a thin bar along the foot, because a filled tile is Lagoon's
   signature and not the category standard. */
.water{position:absolute;inset:0;background:var(--wl-tint,transparent);z-index:-1;pointer-events:none;transform:translateY(calc(100% - var(--fill,0%)));transition:transform 1.2s ease}
[data-look="lagoon"] .water::before{content:"";position:absolute;left:0;right:0;top:-6px;height:7px;background:inherit;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 8' preserveAspectRatio='none'%3E%3Cpath d='M0 8V4Q15 0 30 4T60 4T90 4T120 4V8Z'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 8' preserveAspectRatio='none'%3E%3Cpath d='M0 8V4Q15 0 30 4T60 4T90 4T120 4V8Z'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
/* By day the tint tokens are near-white on a white tile (about 1.1:1), so the
   signature vanished in the theme a café runs all day. The day water is mixed
   from the tile's own state colour instead, and its surface is a stronger band
   of it, so the level reads across a counter. Night keeps its own tints. */
[data-look="lagoon"][data-theme="light"] .water{background:color-mix(in srgb,var(--wl-bar,transparent) 22%,transparent)}
[data-look="lagoon"][data-theme="light"] .water::before{background:color-mix(in srgb,var(--wl-bar,transparent) 55%,transparent)}
[data-look="standard"] .water{top:auto;height:3px;background:var(--wl-bar,transparent);transform:translateX(calc(var(--fill,0%) - 100%))}
@media (prefers-reduced-motion:reduce){.water{transition:none}}

/* An overdue KDS ticket pulses to draw the eye without flashing (WCAG 2.3.1):
   a slow opacity breathe, at most once every two seconds, applied inline only
   on the ticket card that is past its target. Colour, the thicker border and
   the words "Over Nm" already say it, so the animation is decoration here —
   unlike the offline kpulse above, which is the only signal it carries and so
   stays alive under reduced motion. This one does not. */
@keyframes kdspulse{0%,100%{opacity:1}50%{opacity:.6}}
@media (prefers-reduced-motion:reduce){@keyframes kdspulse{from{opacity:1}to{opacity:1}}}

/* ═══ THE CHROME (the new layout) ═════════════════════════════════════════
   The frame of the till — the nav rail, the top bar, the Now column and the
   ticket beside the menu — is deep sea in the Lagoon look, day and night,
   while the working surface stays light by day. It is done by re-scoping the
   same token names inside `.chrome`, so everything drawn in the frame
   (buttons, chips, cards, text) takes the sea without being restyled, and
   the status families switch to the ramps that read on a dark ground.
   Standard defines no chrome, so its frame is its page. */
[data-look="lagoon"] .chrome{
  --bg:#062a3a; --bg-0:#05202c; --bg-1:#0c3a4e; --bg-2:#0c3a4e; --bg-3:#124a61;
  --line-soft:#0c3a4e; --line:#124a61; --line-2:#1a5a74; --line-3:#2a6f8a; --mute-dim:#124a61;
  --text:#e9f7f9; --text-dim:#cfe3e8; --text-muted:#bfd6dd; --text-faint:#b2ccd4; --code-text:#cfe3e8;
  --amber:#35e4f2; --amber-bright:#7defff; --amber-dim:#073545; --amber-line:#0f5566; --amber-deep:#22b8c6; --on-amber:#021a22;
  --warn:#f2a43a; --warn-bright:#ffc14a; --warn-dim:rgba(242,164,58,.16); --warn-line:#6b4a1f; --on-warn:#1a0f00;
  --ok-dim:#12281d; --bad-dim:#301218; --info-dim:#171c33; --violet-dim:#221a30;
  --go:#34c77b; --go-bright:#5fe09a; --go-dim:#12281d; --go-line:#256b47; --on-go:#04180d;
  --fwd:#7c92f5; --fwd-bright:#a3b3fa; --fwd-dim:#171c33; --fwd-line:#3a4a9c; --on-fwd:#060c26;
  --danger:#f2445e; --danger-bright:#ff7b8e; --danger-dim:#301218; --danger-line:#7d2231; --on-danger:#fff2f4;
  --green:#5fe09a; --green-bright:#5fe09a; --green-line:#256b47;
  --red:#f2607a; --red-bright:#ff8ba0; --red-line:#6b2a38;
  --blue:#8ba0f7; --blue-bright:#adbaf9; --violet:#a88ad9; --violet-bright:#c3aae6;
  color:var(--text);
}
[data-look="lagoon"]:not([data-theme="light"]) .chrome{
  --bg:#010c12; --bg-0:#03131b; --bg-1:#05202c; --bg-2:#05202c; --bg-3:#0a2e3e;
  --line-soft:#05202c; --line:#0a2e3e; --line-2:#11394a; --line-3:#1a4a5e; --mute-dim:#0a2e3e;
}

/* ═══ PAY / PAID (redesign, 2026-09) ═══════════════════════════════════════
   Sizing helpers only — every figure they draw is already bound from
   modalVals()/receiptVals(). The due figure repeats (a sticky strip on
   narrow/short viewports, this big one otherwise), so only one shows at a
   time; the breakpoint mirrors bpOf()'s own 760px line. */
.pay-due-big{display:block;font-size:clamp(40px,4.6vw,88px)}
@media (max-width:759px){.pay-due-big{display:none}}
.pay-giv-chg{font-size:clamp(20px,2.6vw,30px)}
/* The change figure is sized to its own panel, so a four-digit change cannot spill out of it. */
.paid-panel{container-type:inline-size}
.paid-change-big{font-size:clamp(40px,6vw,96px);font-size:min(16cqi,188px);white-space:nowrap}
.pay-tenders{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
@media (min-width:760px){.pay-tenders{grid-template-columns:repeat(3,1fr)}}

/* A sheet's action rows are chosen with a thumb. Every caller brings its own
   style, so the floor is set here once: 48px, the till's own minimum. */
.ksheet-act{min-height:48px!important}
