/* EDEN Kesef. The counter.
 *
 * Every size here traces to LAYOUT in src/interaction.js, which is the
 * specification and is asserted by test. If a number here disagrees with a
 * number there, THAT file is right and this one is a defect.
 *
 *   referenceWidthCssPx 360   minTypeSp 18   amountTypeSp 48
 *   customerFacingTotalSp 64  minTouchDp 56  tenderKeyDp 72
 *   contrastTarget 7.0        deadZoneTopFraction 0.25
 *   noDarkThemeOnCounter TRUE
 *
 * ONE THEME, DELIBERATELY. This page does not offer a dark variant and does not
 * follow prefers-color-scheme. That is not an omission. A dark counter at 468
 * measured nits in tropical daylight is unreadable, and the person holding the
 * phone cannot switch it back mid-sale with a customer waiting. So every colour
 * is painted explicitly and the page holds whatever ground the host paints.
 */

:root {
  /* Paper, not pure white. Pure white at 468 nits in direct sun is glare. */
  --ground:      #FBFAF7;
  --ground-sunk: #F2EFE8;
  --ink:         #14140F;   /* 16.8:1 on ground. Target is 7.0 */
  --ink-soft:    #4A4A42;   /*  8.1:1 on ground */
  --rule:        #DCD9CF;
  --silver:      #E2E6E9;

  /* The money colour. Same green as the icon, so the launcher and the counter
     are recognisably one thing. */
  --money:       #0A5C33;   /* white on this: 8.0:1 */

  /* TENDER. The row ALTERNATES DARK, LIGHT, DARK, LIGHT.
   *
   * The first palette here was four saturated colours of near-identical
   * luminance: green .081, blue .058, magenta .058, ochre .079. Measured as
   * contrast against each other that is 1.01 between Cash and Book it, and 1.00
   * between Card and Phone, which is the ratio of a colour against itself. Hue
   * was carrying the entire load. Hue is the one channel that DOES NOT SURVIVE
   * a dichromacy, a scratched screen, or direct sun on a cheap panel, so under
   * any of those the row became one flat band.
   *
   * Alternating luminance fixes it in the channel that always survives: adjacent
   * keys now differ by between 4.8 and 8.1 times, so the row reads dark, light,
   * dark, light in pure greyscale and with no colour perception at all.
   *
   * Text colour flips with the ground, and every pair still clears the 7.0
   * target: 8.0, 11.2, 13.6 and 9.9 respectively.
   *
   * This is the floor, not the finish. The interaction model asks for a
   * PHOTOGRAPH on each key, which is the only channel that needs no learning.
   * That needs real photographs and is recorded in COMMITMENTS.md. */
  --cash:        #0A5C33;   /* deep green, L .081, WHITE text  8.0:1 */
  --card:        #F2C200;   /* amber,      L .575, INK text   11.2:1 */
  --mobile:      #0E2A66;   /* deep navy,  L .027, WHITE text 13.6:1 */
  --credit:      #F5A3D0;   /* rose,       L .502, INK text    9.9:1 */

  --short:       #8A1414;
  --unrecorded:  #7A4005;

  --pad-gap: 8px;
  --radius: 14px;

  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  color-scheme: light;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overscroll-behavior: none;
}

body {
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 18px;                 /* minTypeSp. Nothing is smaller than this. */
  line-height: 1.35;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  /* A till is not a document. Nothing scrolls except the Books. */
  overflow: hidden;
}

/* Digits line up in columns everywhere they appear. */
output, .k-figure, .k-amount, .k-split, .k-owed, .k-meta dd, .k-input--num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

button, input { font: inherit; color: inherit; }
/* A BUTTON IS A CONTROL, NEVER TEXT TO BE SELECTED.
 *
 * On iOS a press on a text-styled button starts a SELECTION: the blue handles
 * appear with a Copy / Search with Google callout, and the button never fires.
 * Reported from a real iPhone on 11 September 2026 against "Put your name on
 * it", which is styled as a line of text and so invites exactly that gesture.
 *
 * `touch-action: manipulation` was already here and does not help: it removes
 * the double-tap delay and says nothing about selection. The two properties
 * that do are below, and they belong on EVERY button rather than on the one
 * that was reported, because any button styled as text has the same problem
 * waiting in it. */
button {
  border: 0;
  background: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

:focus-visible {
  outline: 3px solid var(--money);
  outline-offset: 2px;
}

.k-noscript {
  padding: 24px;
  font-size: 20px;
}

/* ======================================================================= */
/* THE COUNTER                                                             */
/* ======================================================================= */

.k-counter {
  display: grid;
  /* The display takes the top quarter: deadZoneTopFraction 0.25, the band a
     thumb cannot reach one-handed. Nothing in it is actionable. */
  grid-template-rows: minmax(24dvh, 26dvh) 1fr auto;
  height: 100dvh;
  padding:
    max(8px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right))
    max(8px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
  gap: var(--pad-gap);
}
.k-counter[hidden] { display: none; }

.k-display {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: end;
  padding: 4px 6px 2px;
  border-bottom: 2px solid var(--rule);
  min-width: 0;
}

.k-status {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 20px;
}

/* Connection is a dot, not a sentence. Filled means the log has reached a peer,
   hollow means it has not yet. Neither is an error and neither blocks a sale. */
.k-dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  border: 2px solid var(--ink-soft);
  flex: none;
}
.k-dot[data-state="online"] { background: var(--money); border-color: var(--money); }
.k-dot[data-state="offline"] { background: transparent; }

.k-pending {
  font-size: 18px;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.k-amount {
  display: block;
  font-size: clamp(48px, 15vw, 76px);   /* amountTypeSp 48 at the 360px reference */
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
  min-width: 0;
}
/* 2.80:1 measured, which fails AA outright and not merely the 7.0 house floor,
   in the resting state of the primary display, in the 468 nit daylight this
   product is built for. #6E6B62 measures 7.0. */
.k-amount[data-empty="true"] { color: #565049; }

.k-entryline {
  font-size: 18px;
  color: var(--ink-soft);
  text-align: right;
  min-height: 20px;
}

/* ---- the pad ---------------------------------------------------------- */

.k-pad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 1fr;
  gap: var(--pad-gap);
  min-height: 0;
}

.k-key {
  min-height: 56px;                 /* minTouchDp */
  border-radius: var(--radius);
  background: var(--ground-sunk);
  color: var(--ink);
  font-size: clamp(26px, 8vw, 34px);
  font-weight: 600;
  display: grid;
  place-items: center;
  transition: transform 90ms ease, background-color 90ms ease;
}
.k-key:active { background: #E4E0D6; transform: scale(0.97); }
.k-key svg { width: 30px; height: 30px; }

.k-key--back { color: var(--ink-soft); }

/* The way to the Books is a HOLD, not a tap and not a menu. A tap here would be
   one thumb-slip away from leaving the counter mid-sale. */
.k-key--books {
  position: relative;
  overflow: hidden;
  background: var(--ground);
  border: 2px solid var(--rule);
}
.k-coin {
  width: 26px; height: 26px;
  border-radius: 50%;
  border: 6px solid var(--money);
  opacity: 0.9;
  /* Stacked above the fill, ON ITS OWN PAPER. The fill is a positioned sibling
     that paints later, so at the strength it now needs (see .k-hold) the coin
     would sit on green: MEASURED at 2.33:1 as the fill passes it, down from
     6.11:1 on the bare key. The key would lose the one mark that identifies it,
     at exactly the moment the hold is being learned.
     The disc and the halo are both paper, so the coin is always read against
     ground and holds 6.11:1 whatever the fill is doing. On a key with no fill
     they are the same colour as the key, so nothing about the resting
     appearance changes. */
  position: relative;
  z-index: 1;
  background: var(--ground);
  box-shadow: 0 0 0 3px var(--ground);
}

/* THE HOLD HAS TO ANNOUNCE ITSELF.
 *
 * The proprietor opened the app and saw only a keypad: he could not find the
 * Books at all. If the person who commissioned it cannot find the way in, a
 * vendor never will, and an undiscoverable control is an absent one.
 *
 * A ring that fills as you hold, drawn around the coin. It is visible at rest
 * as a faint track, so the key reads as "something happens if you stay here"
 * before anything is pressed, and it has no words, which is the whole point on
 * this surface. */
.k-key--books::after {
  content: '';
  position: absolute;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 2px dashed var(--money);
  opacity: 0.35;
  pointer-events: none;
}
.k-key--books:active::after { opacity: 0.7; }
.k-hold {
  position: absolute;
  inset: auto 0 0 0;
  height: 0;
  background: var(--money);
  /* 0.6, NOT 0.22. MEASURED, not adjusted by eye.
   *
   * At 0.22 this fill is 1.44:1 against the key's own ground, which is below the
   * 3.0 that WCAG 2.2 non-text contrast asks of a control indicator and a long
   * way below anything that reads on a cheap panel at the 468 nits this product
   * is specified against. That was survivable while the fill was only ever seen
   * with a finger on the key and an eye already there.
   *
   * It stopped being survivable when the key started using this same fill to
   * TEACH the gesture to somebody who is not looking at it (KSF-FR-172). A
   * demonstration nobody can see is the defect it was built to fix, wearing a
   * different coat.
   *
   * 0.6 measures 3.03:1. The demonstration and the real hold are deliberately
   * the same picture, so this improves the feedback during a genuine hold in
   * sunlight as well, which was the weaker half all along.
   *
   * [VERIFY: read on the reference handset, a Galaxy A07 4G, outdoors. A
   * computed ratio is a floor and is not the same as legible on that panel.] */
  opacity: 0.6;
  transition: height 60ms linear;
  pointer-events: none;
}

/* THE KEY TEACHES THE GESTURE. See WAY_IN in src/interaction.js for the
 * argument and for the three designs that lost to this one.
 *
 * A faint dashed ring that never moves is a decoration, and a decoration is what
 * the eye discards. It was discarded: the proprietor opened his own product and
 * found a number pad. So while the way in is UNLEARNED the ring is solid and at
 * full strength, and the key runs the fill a real hold produces, on itself, over
 * the real hold duration. She is shown the exact picture she then has to make,
 * which is the one instruction that needs no language.
 *
 * NOTHING HERE CARRIES A FIGURE, and nothing here reads the log. The key looks
 * identical on an empty till and on a full one, so this is the state of the
 * affordance and never the state of the business, which is what keeps it outside
 * KSF-FR-171 and KSF-FR-228. */

.k-key--books[data-wayin="unlearned"]::after {
  border-style: solid;
  opacity: 0.85;
}

/* --hold-ms is written by app.js from LAYOUT.booksHoldMs, so this file cannot
 * hold a second opinion about how long a hold takes. The fallback is the same
 * number and exists only for the frames before boot. */
.k-key--books[data-demo="on"] .k-hold {
  animation: k-demo var(--hold-ms, 550ms) linear forwards;
}
@keyframes k-demo { from { height: 0; } to { height: 100%; } }

/* THE REDUCED-MOTION FALLBACK, and it has to be explicit rather than inherited.
 *
 * The blanket rule further down collapses every animation to 0.01 ms, which with
 * `forwards` would leave the key stuck permanently full: worse than showing
 * nothing. So a device refusing motion gets a STEP instead of a sweep, set by
 * app.js. Full, held for a moment, gone. Two resting states and no travel
 * between them. The unrequested sweep is refused outright on such a device and
 * the solid ring above carries the affordance on its own. */
.k-key--books[data-demo="step"] .k-hold {
  transition: none;
  height: 100%;
}

/* ---- tender ----------------------------------------------------------- */

.k-tenders {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--pad-gap);
}

.k-tender {
  min-height: 72px;                 /* tenderKeyDp */
  border-radius: var(--radius);
  color: #fff;
  display: grid;
  grid-template-rows: auto auto;
  place-items: center;
  gap: 4px;
  padding: 8px 2px;
  transition: transform 90ms ease, filter 90ms ease;
}
.k-tender:active { transform: scale(0.97); filter: brightness(1.12); }

/* The tender keys are NEVER faded out and never disabled. Fading them breaks the
   colour channel, which is the channel a person who cannot read is using, and an
   app whose bottom row is grey on opening reads as broken rather than as ready.
   A tap with no amount entered is simply a no-op. */
.k-tender--nudge { animation: nudge 260ms ease; }
@keyframes nudge {
  0%, 100% { transform: none; }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

/* Position is the primary channel. Colour is the second. The mark is the third,
   and it is a SHAPE, because an icon is a word you cannot look up. */
.k-tender__mark { width: 24px; height: 24px; background: #fff; flex: none; }
.k-tender__name { font-size: 18px; font-weight: 600; letter-spacing: 0.01em; }

/* The two light keys take ink, not white. A light key with white text would be
   the one place in this interface that fails the contrast target. */
[data-method="CASH"]   { background: var(--cash);   color: #fff; }
[data-method="CARD"]   { background: var(--card);   color: var(--ink); }
[data-method="MOBILE"] { background: var(--mobile); color: #fff; }
[data-method="CREDIT"] { background: var(--credit); color: var(--ink); }

[data-method="CASH"]   .k-tender__mark,
[data-method="MOBILE"] .k-tender__mark { background: #fff; }
[data-method="CARD"]   .k-tender__mark,
[data-method="CREDIT"] .k-tender__mark { background: var(--ink); }

/* Four distinct silhouettes, distinguishable in monochrome and at arm's length */
[data-method="CASH"]   .k-tender__mark { border-radius: 2px; }                    /* a note   */
[data-method="CARD"]   .k-tender__mark { border-radius: 2px; height: 17px; }      /* a card   */
[data-method="MOBILE"] .k-tender__mark { border-radius: 5px; width: 15px; }       /* a phone  */
[data-method="CREDIT"] .k-tender__mark { border-radius: 50%; }                    /* a circle */

/* ======================================================================= */
/* CONFIRMATION. The number repeated large IS the receipt.                  */
/* ======================================================================= */

.k-confirm {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--money);
  color: #fff;
  z-index: 40;
  animation: rise 140ms ease-out;
}
.k-confirm[hidden] { display: none; }

/* The confirmation takes the SAME ground as the key that was pressed, so the
   whole screen becomes the answer to "which one did she press". Text flips with
   it, for the same reason it flips on the keys. */
.k-confirm[data-method="CARD"]   { background: var(--card);   color: var(--ink); }
.k-confirm[data-method="MOBILE"] { background: var(--mobile); color: #fff; }
.k-confirm[data-method="CREDIT"] { background: var(--credit); color: var(--ink); }

.k-confirm__inner { display: grid; justify-items: center; gap: 14px; padding: 24px; }

.k-confirm__mark { width: 46px; height: 46px; background: currentColor; opacity: 0.9; }

.k-confirm__amount {
  /* customerFacingTotalSp 64: readable across a counter, and upside down. */
  font-size: clamp(64px, 22vw, 120px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
}
.k-confirm__who { font-size: 22px; opacity: 0.92; text-align: center; }

@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ======================================================================= */
/* SHEETS                                                                  */
/* ======================================================================= */

.k-sheet {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 15, 0.46);
  display: grid;
  align-items: end;
  z-index: 50;
}
.k-sheet[hidden] { display: none; }

.k-sheet__panel {
  background: var(--ground);
  border-radius: 20px 20px 0 0;
  padding: 20px 16px max(20px, env(safe-area-inset-bottom));
  display: grid;
  gap: 14px;
  animation: slide 160ms ease-out;
  max-height: 88dvh;
  overflow-y: auto;
}
@keyframes slide { from { transform: translateY(16px); } to { transform: none; } }

.k-sheet__title { margin: 0; font-size: 22px; font-weight: 700; }
.k-sheet__body  { margin: 0; font-size: 18px; color: var(--ink-soft); }
.k-sheet__actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

.k-btn {
  min-height: 56px;
  border-radius: var(--radius);
  background: var(--money);
  color: #fff;
  font-size: 18px;
  font-weight: 600;
  padding: 0 16px;
}
.k-btn:active { filter: brightness(1.12); }
.k-btn--quiet { background: var(--ground-sunk); color: var(--ink); }
.k-btn--wide  { width: 100%; }

.k-input {
  min-height: 56px;
  border-radius: var(--radius);
  border: 2px solid var(--rule);
  background: #fff;
  padding: 0 14px;
  font-size: 20px;
  width: 100%;
}
.k-input:focus { border-color: var(--money); outline: none; }
.k-input--num { font-size: 30px; font-weight: 700; text-align: right; }

.k-who { display: grid; gap: 8px; max-height: 34dvh; overflow-y: auto; }
.k-who button {
  min-height: 56px;
  border-radius: var(--radius);
  background: var(--ground-sunk);
  text-align: left;
  padding: 0 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 18px;
}
.k-who button[aria-pressed="true"] {
  background: var(--money);
  color: #fff;
}
.k-who span:last-child { font-weight: 700; font-variant-numeric: tabular-nums; }

/* ======================================================================= */
/* THE BOOKS                                                               */
/* ======================================================================= */

.k-books {
  position: fixed;
  inset: 0;
  background: var(--ground);
  display: grid;
  grid-template-rows: auto 1fr;
  z-index: 30;
}
.k-books[hidden] { display: none; }

.k-books__bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: max(8px, env(safe-area-inset-top)) 12px 8px;
  border-bottom: 2px solid var(--rule);
}
.k-books__title { margin: 0; font-size: 20px; font-weight: 700; flex: 1; }
.k-books__date { font-size: 18px; color: var(--ink-soft); }

.k-back {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: var(--radius);
  margin-left: -6px;
}
.k-back:active { background: var(--ground-sunk); }
.k-back svg { width: 28px; height: 28px; }

.k-books__scroll {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 12px max(24px, env(safe-area-inset-bottom));
  display: grid;
  gap: 12px;
  align-content: start;
}

.k-card {
  background: #fff;
  border: 2px solid var(--rule);
  border-radius: var(--radius);
  padding: 14px;
  display: grid;
  gap: 10px;
}
.k-card--lead { background: var(--money); border-color: var(--money); color: #fff; }
.k-card__label { margin: 0; font-size: 18px; font-weight: 600; letter-spacing: 0.04em;
                 text-transform: uppercase; opacity: 0.82; }
/* Solid tints, not opacity. Composited, 0.82 gave 6.03:1 and 0.86 gave 6.46:1,
   both under the 7.0 floor. These clear it without touching the design. */
.k-card--lead .k-card__label { opacity: 1; color: #CFE0D6; }
.k-card__note  { margin: 0; font-size: 18px; color: var(--ink-soft); }
.k-card--lead .k-card__note { color: #EAF2EC; }

.k-figure { margin: 0; font-size: 44px; font-weight: 800; line-height: 1; letter-spacing: -0.02em; }

.k-split, .k-owed { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.k-split li, .k-owed li {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: 18px;
  padding: 6px 0;
  border-bottom: 1px solid var(--rule);
}
.k-split li:last-child, .k-owed li:last-child { border-bottom: 0; }
.k-split b, .k-owed b { font-weight: 700; }

.k-swatch { width: 14px; height: 14px; border-radius: 3px; flex: none; margin-right: 8px; }

.k-result { font-size: 18px; border-radius: var(--radius); padding: 12px; display: grid; gap: 6px; }
.k-result[data-label="reconciled"]       { background: #E7F1EA; color: #08421F; }
.k-result[data-label="not written down"] { background: #F6EDDF; color: var(--unrecorded); }
.k-result[data-label="short"]            { background: #F6E4E4; color: var(--short); }
.k-result b { font-size: 26px; }

.k-meta { margin: 0; display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; font-size: 18px; }
.k-meta dt { color: var(--ink-soft); }
.k-meta dd { margin: 0; font-weight: 600; text-align: right; }

/* ======================================================================= */

/* A TOAST MUST NEVER EAT A TAP.
 *
 * This sat at `bottom: 16px` with z-index 60 and no pointer-events rule, which
 * put it directly over the tender row for the full 2.6 seconds it is up. QA
 * measured it: 88 per cent of Card and 88 per cent of Phone, and a tap at the
 * centre of either key hit the toast instead of the button. Nothing was
 * recorded and nothing told her.
 *
 * A vendor takes in stock, a customer hands her a card, she taps Card, and the
 * till does nothing. That is the one thing this product exists to prevent, and
 * there are 56 places in the code that open the window.
 *
 * TWO fixes, because either alone is not enough: pointer-events so it cannot
 * take a tap even while it overlaps, and a position above the tender row so it
 * does not overlap in the first place. The first is the safety property; the
 * second is the reason nobody has to rely on it.
 */
.k-toast {
  position: fixed;
  pointer-events: none;
  left: 50%; bottom: calc(96px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--ground);
  padding: 12px 18px;
  border-radius: 999px;
  font-size: 18px;
  z-index: 60;
  max-width: calc(100vw - 32px);
  text-align: center;
}
.k-toast[hidden] { display: none; }

/* A bigger screen is a bigger counter, never a desktop layout. The person is
   still holding it one-handed. */
@media (min-width: 560px) {
  .k-counter { max-width: 520px; margin: 0 auto; }
  .k-books__scroll { max-width: 620px; margin: 0 auto; width: 100%; }
}

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

/* The counting aid. Numerals only. */
.k-denoms {
  display: grid;
  /* 168, not 148. The row's own columns need 76.9 + 34 + 56 = 167, so at 148 the
     minus key landed at x=365 on a 360 px screen: two-dimensional scrolling,
     which is a 1.4.10 failure at the REFERENCE width, not only at 320. */
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 8px;
}
.k-denom {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 6px;
  border: 2px solid var(--rule);
  border-radius: var(--radius);
  padding: 4px 4px 4px 0;
  background: var(--ground);
}
.k-denom[data-counted="true"] { border-color: var(--money); background: #F1F6F2; }

.k-denom__add {
  min-height: 56px;
  border-radius: 10px;
  font-size: 22px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
  padding: 0 10px;
  width: 100%;
}
.k-denom__add:active { background: var(--ground-sunk); }

.k-denom__n {
  font-size: 18px;
  font-weight: 700;
  color: var(--money);
  min-width: 34px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.k-denom__sub {
  width: 56px; height: 56px;
  border-radius: 10px;
  font-size: 24px;
  line-height: 1;
  color: var(--ink-soft);
  background: var(--ground-sunk);
}
.k-denom__sub[disabled] { opacity: 0.25; }

/* ---- tiles. Learned, never configured. Absent on day one. --------------- */
.k-tiles {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.k-tiles::-webkit-scrollbar { display: none; }
.k-tiles[hidden] { display: none; }

.k-tile {
  flex: 0 0 auto;
  min-width: 92px;
  min-height: 56px;
  border-radius: var(--radius);
  background: var(--ground-sunk);
  border: 2px solid transparent;
  padding: 6px 10px;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "name left" "price left";
  align-items: center;
  gap: 0 8px;
  text-align: left;
}
.k-tile[data-on="true"] { border-color: var(--money); background: #EAF2EC; }
/* Out of stock is carried by the GROUND and by the count, never by fading the
   control. The tile is an active button, so the inactive-component exemption
   does not apply and the faded text measured 3.93:1 and 2.64:1. */
.k-tile[data-empty="true"] { background: #F6EDDF; }
.k-tile__name  { grid-area: name;  font-size: 18px; font-weight: 600; white-space: nowrap;
                 overflow: hidden; text-overflow: ellipsis; max-width: 110px; }
.k-tile__price { grid-area: price; font-size: 18px; color: var(--ink-soft);
                 font-variant-numeric: tabular-nums; }
.k-tile__left  { grid-area: left;  font-size: 20px; font-weight: 700; color: var(--money);
                 font-variant-numeric: tabular-nums; }
.k-tile[data-empty="true"] .k-tile__left { color: var(--short); }

/* ---- stock in the Books ------------------------------------------------ */
.k-card__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.k-btn--small { min-height: 56px; padding: 0 14px; font-size: 18px; }

.k-stock { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.k-stock li { display: grid; gap: 2px; padding-bottom: 8px; border-bottom: 1px solid var(--rule); }
.k-stock li:last-child { border-bottom: 0; padding-bottom: 0; }
.k-stock__top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
                font-size: 18px; }
.k-stock__qty { font-weight: 700; font-variant-numeric: tabular-nums; }
.k-stock__qty[data-out="true"] { color: var(--short); }
.k-stock__bot { font-size: 18px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.k-stock__note { font-size: 18px; color: var(--unrecorded); border-bottom: 0; }

/* ---- the take-in sheet ------------------------------------------------- */
.k-field { display: grid; gap: 6px; }
.k-field > span { font-size: 18px; color: var(--ink-soft); }
.k-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.k-field--unit .k-input { text-align: left; }

.k-margin {
  border-radius: var(--radius);
  padding: 12px;
  font-size: 18px;
  background: #E7F1EA;
  color: #08421F;
}
.k-margin[data-sign="thin"] { background: #F6EDDF; color: var(--unrecorded); }
.k-margin[data-sign="loss"] { background: #F6E4E4; color: var(--short); }

/* A screen that did not start. Named rather than hidden, because a gap nobody
   can explain is worse than a gap with a label on it. */
/* The dashed border is the signal. The opacity took the label to 4.42:1 and the
   note to 3.95:1, making the one message she most needs to read the faintest
   text on the page. */
.k-card--dead { border-style: dashed; }

/* ---- the tender photographs -------------------------------------------
 *
 * The photograph is the MARK inside the key, not the key itself. That is
 * deliberate: if a photograph filled the key it would take the key's colour
 * with it, and the alternating dark/light luminance is the channel that
 * survives a dichromacy and direct sun. So the photograph is ADDITIVE. It adds
 * recognition, which needs no learning, on top of a colour and a position that
 * already work without it.
 *
 * Cash keeps the plain shape until its photograph arrives, and a shape beside
 * three photographs is honest about that rather than hiding it.
 */
.k-tender__mark[data-photo] {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background-size: cover;
  background-position: center;
  box-shadow: 0 0 0 2px rgba(255,255,255,0.85);
}
[data-method="CARD"]   .k-tender__mark[data-photo],
[data-method="CREDIT"] .k-tender__mark[data-photo] {
  box-shadow: 0 0 0 2px rgba(20,20,15,0.55);
}
[data-method="CASH"]   .k-tender__mark[data-photo] { background-image: url('/img/tender-cash.jpg'); }
[data-method="CARD"]   .k-tender__mark[data-photo] { background-image: url('/img/tender-card.jpg'); }
[data-method="MOBILE"] .k-tender__mark[data-photo] { background-image: url('/img/tender-phone.jpg'); }
[data-method="CREDIT"] .k-tender__mark[data-photo] { background-image: url('/img/tender-book.jpg'); }

/* The photograph is larger than the shape it replaces, so the key needs the room */
.k-tender:has(.k-tender__mark[data-photo]) { padding-top: 6px; }


/* ---- the screen container ---------------------------------------------
 *
 * A plain wrapper. Every screen renders its own .k-card sections inside it, so
 * this must NOT be a card: nesting one inside another doubled the border and
 * spent 28 px of a 360 px screen on padding.
 *
 * The !important is load-bearing rather than lazy. The registry hides a screen
 * that threw by setting `root.hidden = true`, and the UA rule for [hidden] is
 * beaten by any class selector that sets display. Without this, a screen that
 * drew wrong once would keep being shown.
 */
.k-screen { display: grid; gap: 12px; }
.k-screen[hidden] { display: none !important; }

/* LAYOUT.minTypeSp is 18 and LAYOUT.minTouchDp is 56. Several sizes in this
 * file sat below both, and this file's own header settles it: where the two
 * disagree, interaction.js is right and the CSS is the defect. Three separate
 * screen authors reported it independently before it was corrected. */


/* Settling a debt is a money control and gets the full 56dp floor, like every
   other one. It was inline-styled to 44px, which made it the smallest control
   in the Books and the only one below LAYOUT.minTouchDp. */
.k-owed .k-btn { min-height: 56px; padding: 0 16px; }

/* ---- putting a sale right ----------------------------------------------
 *
 * On the confirmation, because that is where she realises. One tap and no
 * reason asked: a mistyped amount two seconds old does not need explaining,
 * and asking for a reason is how a vendor ends up keeping the wrong number
 * rather than arguing with a form.
 */
.k-undo {
  margin-top: 10px;
  min-height: 56px;
  border-radius: 999px;
  padding: 0 22px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 18px;
  font-weight: 600;
  color: inherit;
  background: rgba(255,255,255,0.18);
  border: 2px solid currentColor;
}
.k-confirm[data-method="CARD"] .k-undo,
.k-confirm[data-method="CREDIT"] .k-undo { background: rgba(20,20,15,0.10); }
.k-undo svg { width: 22px; height: 22px; }
.k-undo:active { filter: brightness(1.15); }

/* ---- today's entries ---------------------------------------------------
 *
 * This lives in the BOOKS and never on the Counter. Rule four: the Counter
 * shows no totals and no history, so somebody looking over a shoulder learns
 * nothing. Finding a mistake is a books job.
 */
.k-entries { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.k-entries li {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding-bottom: 8px; border-bottom: 1px solid var(--rule);
}
.k-entries li:last-child { border-bottom: 0; padding-bottom: 0; }
.k-entries__what { display: flex; align-items: center; gap: 8px; font-size: 18px; min-width: 0; }
.k-entries__amt { font-weight: 700; font-variant-numeric: tabular-nums; }

/* A voided entry is STRUCK THROUGH, never removed. The log keeps it for ever
   and so does the screen: a gap where a sale used to be is what makes a book
   look edited. */
.k-entries li[data-void="true"] .k-entries__what,
.k-entries li[data-void="true"] .k-entries__amt {
  text-decoration: line-through;
  color: var(--ink-soft);
}
.k-entries__tag {
  font-size: 18px; color: var(--unrecorded); font-weight: 600;
}

/* ---- clearing the phone -------------------------------------------------
 *
 * Destructive, so it does not look like the controls beside it, and it states
 * what goes and how much BEFORE it asks. "Are you sure" is not informed
 * consent when the thing being destroyed is a year of a merchant's records.
 */
.k-wipe { color: var(--short); border: 2px solid var(--short); background: transparent; }
.k-wipe:active { background: #F6E4E4; }
.k-wipe__confirm { display: grid; gap: 12px; background: #F6E4E4; border-radius: var(--radius); padding: 14px; }
.k-wipe__confirm[hidden] { display: none; }
.k-wipe__what { margin: 0; font-size: 18px; font-weight: 600; color: var(--short); }
.k-btn--danger { background: var(--short); color: #fff; }

/* ---- the iOS install route ---------------------------------------------
 * Safari offers none, so this tells rather than offers. Two lines, one icon,
 * and it disappears the moment the app is on the home screen.
 */
.k-ios { display: grid; gap: 6px; background: var(--ground-sunk); border-radius: var(--radius); padding: 14px; }
.k-ios[hidden] { display: none; }
.k-ios__line { margin: 0; display: flex; align-items: center; gap: 10px; font-size: 18px; }
.k-ios__line svg { width: 24px; height: 24px; flex: none; color: var(--money); }

/* ---- her own photograph of her own product ------------------------------ */
.k-photo {
  width: 96px; height: 96px;
  border-radius: var(--radius);
  border: 2px dashed var(--rule);
  background: var(--ground-sunk);
  display: grid; place-items: center;
  position: relative; overflow: hidden;
  justify-self: start;
}
.k-photo__img { position: absolute; inset: 0; background-size: cover; background-position: center; }
.k-photo__cam { width: 34px; height: 34px; color: var(--ink-soft); position: relative; }
.k-photo[data-has="true"] { border-style: solid; border-color: var(--money); }
.k-photo[data-has="true"] .k-photo__cam { display: none; }

/* on the tile and in the stock list */
.k-tile { grid-template-areas: "pic name left" "pic price left"; grid-template-columns: auto 1fr auto; }
.k-tile__pic {
  grid-area: pic; width: 40px; height: 40px; border-radius: 8px;
  background-size: cover; background-position: center; margin-right: 8px;
}
.k-stock__pic {
  width: 44px; height: 44px; border-radius: 8px; flex: none;
  background-size: cover; background-position: center; margin-right: 10px;
}

/* =====================================================================
   THE NAME OVER THE DOOR, AND KEEPING THE APP.

   Both were in the Books, behind the fingerprint, at the bottom of a list
   of thirty-eight cards. The proprietor could not find the name field in
   his own product after being told where it was, which is the only test
   of placement that counts.

   They sit in the DEAD ZONE, the top quarter that LAYOUT already reserves
   as unreachable by thumb. Nothing there competes with a selling control,
   and it is the part of the screen she sees every time she opens this.
   ===================================================================== */

/* `display: block` BEATS THE `hidden` ATTRIBUTE, so both of these need an
   explicit guard. Without it a hidden keep-it line still painted as an empty
   bordered box on the first-run counter, directly under the amount. The
   attribute is the state; the class must not out-rank it. */
.k-shopname[hidden],
.k-keepit[hidden] { display: none !important; }

.k-shopname {
  display: block;
  width: 100%;
  margin: 2px 0 0;
  padding: 10px 4px;
  min-height: 44px;              /* not a till key: this is setup, tapped once */
  border: 0;
  background: none;
  font: inherit;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.2;
  text-align: left;
  color: var(--ink);
  cursor: pointer;
}
/* Unnamed, it is an invitation and reads as one. Named, it is a fact and
   recedes: she does not need her own name shouted at her all day. */
.k-shopname[data-named="false"] {
  font-weight: 600;
  font-size: 18px;
  color: var(--money);
  text-decoration: underline;
  text-underline-offset: 4px;
}
.k-shopname:focus-visible { outline: 3px solid var(--money); outline-offset: 2px; }

.k-keepit {
  display: block;
  width: 100%;
  margin: 6px 0 0;
  padding: 12px;
  min-height: 48px;
  border: 1px solid var(--money);
  border-radius: 10px;
  background: none;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.35;
  text-align: left;
  color: var(--money);
  cursor: pointer;
}
.k-keepit:focus-visible { outline: 3px solid var(--money); outline-offset: 2px; }

/* The example shop reads as an offer rather than an instruction, so it is
   quieter than the keep-it line it sits beside. */
.k-keepit--try {
  border-style: dashed;
  color: var(--ink-soft);
  border-color: var(--rule);
  font-weight: 600;
}

/* THE RECOVERY PIECES. Long, and meant to be read aloud, written on paper or
   pasted into a message, so they break on any character rather than running off
   the edge of a phone. A monospace face keeps 0 and O apart, which matters more
   here than anywhere else in the product. */
.k-pieces { display: grid; gap: 8px; margin: 10px 0 14px; }
.k-piece {
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--ground-sunk);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.5;
  overflow-wrap: anywhere;
  user-select: all;                 /* one tap selects the whole piece */
}
.k-piece__n {
  display: block;
  font-family: var(--sans, system-ui);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 4px;
}

/* ADD THEM. Sits beside the undo on the confirm, same weight: both are things
   she may do in the seconds after a sale, and neither is the main event. */
.k-undo--add { margin-left: 10px; }
.k-undo--add[hidden] { display: none !important; }

/* One checkbox in the whole product, and it earns its place: consent must be
   refusable, and a toggle she can see is the only honest way to offer that. */
.k-check {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0 4px;
  font-size: 16px;
  line-height: 1.35;
  cursor: pointer;
}
.k-check input {
  width: 24px; height: 24px;       /* 24x24 is the SC 2.5.8 floor */
  flex: none;
  accent-color: var(--money);
}
