/* ============================================================================
   LOYALTY CARDS — the app
   ----------------------------------------------------------------------------
   Everything behind the sign-in: the shop's own screens and the platform
   console. One stylesheet, no per-page CSS, no per-client CSS.

   THE LANGUAGE
   The same one as the marketing site — Figtree, a warm near-white ground,
   large soft radii, colour carried by enormous diffuse glows rather than by
   borders — bent for a tool rather than a page. A tool is scanned, not read:
   so the type is smaller, the rhythm is tighter, and anything that needs
   attention says so by its SHAPE as well as its colour, because a number
   going red is invisible to a third of the people reading it.

   THE ONE THING THAT MAKES THIS APP DIFFERENT
   Every business picks a colour for its card, and its whole interface follows
   that colour. The skin name sits on <html data-skin="…">, skins.css turns it
   into six tokens, and every component below is written against those tokens
   rather than against a hex. So a rebrand is one attribute and nothing else
   has to know.

   What the brand is allowed to touch is deliberately bounded: the active nav
   item, the primary button, panel tints, chart ink and the page bloom. The
   GROUND stays near-white on all twenty themes and body text stays ink, so no
   shop can pick a colour that makes its own staff unable to read the screen.

   LAYOUT, THREE WAYS
     ≥1100  a 252px sidebar, content to 1200
     ≥768   the sidebar collapses to a 76px icon rail
     <768   no sidebar: a top bar with the business switcher, and a bottom tab
            bar — because the person using this most is standing at a counter
            holding a phone in one hand.
   ========================================================================== */

/* ── Tokens ──────────────────────────────────────────────────────────────── */
:root{
  /* Ground and surface. These never take the business's colour. */
  --paper:#FBFAF7;
  --surface:#FFFFFF;
  --surface-2:#F6F4F1;
  --sunken:#F2EFEA;

  /* Ink */
  --ink:#190E2E;
  --text:#241A3A;
  --muted:#5E5478;
  --faint:#8E86A3;
  --line:#E9E5EF;
  --line-soft:#F2EFF6;

  /* Signal. Separate from the brand on purpose: a shop whose card is green
     must not have "everything is fine" and "your card is green" look the
     same. These are fixed across all twenty themes. */
  --ok:#2E7D4F;      --ok-soft:#E8F3EC;
  --warn:#9A6A1E;    --warn-soft:#FAF2E4;
  --danger:#C0392B;  --danger-soft:#FBECEA;
  --info:#2563A8;    --info-soft:#E9F1FA;

  /* Shape */
  --r-xs:10px; --r-sm:14px; --r:20px; --r-lg:28px; --r-xl:36px; --pill:999px;

  /* Light. Tinted towards ink rather than black, so nothing looks smudged
     on a warm ground. */
  --sh-sm:0 1px 2px rgba(25,14,46,.05), 0 2px 10px rgba(25,14,46,.04);
  --sh:0 2px 6px rgba(25,14,46,.05), 0 10px 32px rgba(25,14,46,.06);
  --sh-lg:0 4px 12px rgba(25,14,46,.06), 0 28px 70px rgba(25,14,46,.10);
  --sh-pop:0 10px 20px rgba(25,14,46,.08), 0 40px 90px rgba(25,14,46,.16);

  --font:'Figtree',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,sans-serif;
  --mono:ui-monospace,SFMono-Regular,'SF Mono',Menlo,monospace;

  --rail:252px;
  --rail-sm:76px;
  --tabbar:68px;
  --page:1200px;
  --pad:clamp(16px,3.2vw,34px);

  --t-fast:.14s cubic-bezier(.4,0,.2,1);
  --t:.26s cubic-bezier(.4,0,.2,1);
  --t-slow:.5s cubic-bezier(.22,1,.36,1);
}

*{box-sizing:border-box}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;scroll-behavior:auto!important}
}

html{-webkit-text-size-adjust:100%}
body{
  margin:0;font-family:var(--font);font-size:15px;line-height:1.55;color:var(--text);
  background:var(--paper);-webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
img,svg{max-width:100%;display:block}
a{color:var(--brand-deep);text-decoration:none}
a:hover{text-decoration:underline}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:6px}
hr{border:0;border-top:1px solid var(--line);margin:24px 0}

/* The UA's [hidden] is a type-less attribute rule, so ANY class setting a
   display out-ranks it — and half the classes in this file set one. `hidden`
   is not a style, it is a statement that the element is not part of the page,
   and nothing should be able to argue with it. */
[hidden]{display:none!important}

h1,h2,h3,h4{margin:0;font-weight:500;color:var(--ink);letter-spacing:-.025em;
  line-height:1.15;text-wrap:balance}
h1{font-size:clamp(26px,3.4vw,38px);letter-spacing:-.035em;font-weight:400}
h2{font-size:clamp(20px,2.1vw,25px);letter-spacing:-.03em}
h3{font-size:17px}
h4{font-size:14px;font-weight:600}
p{margin:0 0 12px}
p:last-child{margin-bottom:0}
small{font-size:13px}

.muted{color:var(--muted)}
.nums{font-variant-numeric:tabular-nums}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* A micro-label. Used above every number and at the head of every group. */
.label{font-size:11.5px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--faint);margin:0 0 6px}

/* ============================================================================
   THE SHELL
   ========================================================================== */
.shell{min-height:100dvh;display:grid;grid-template-columns:var(--rail) minmax(0,1fr);
  background:var(--paper)}

/* ── The sidebar ─────────────────────────────────────────────────────────── */
.rail{
  position:sticky;top:0;height:100dvh;display:flex;flex-direction:column;
  background:var(--surface);border-right:1px solid var(--line);
  padding:18px 14px;gap:14px;z-index:40;
}
.rail-top{display:flex;align-items:center;gap:11px;padding:2px 6px 0;min-height:30px}
/* .mark is a LETTERED square — a business's initials where it has no logo.
   The product's own mark is .applogo, the app icon, in css/logo.css. */
.mark{width:30px;height:30px;border-radius:9px;flex-shrink:0;
  background:linear-gradient(145deg,var(--brand),var(--brand-deep));
  color:#fff;display:grid;place-items:center;font-weight:700;font-size:15px;
  letter-spacing:-.04em}
.mark img{width:100%;height:100%;object-fit:cover;border-radius:9px}
.wordmark{font-weight:600;font-size:15.5px;letter-spacing:-.03em;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ── The mark at the top of the rail ─────────────────────────────────────
   The dashboard used to show the favicon here with the product name set as
   text beside it. The favicon is a 34px app icon; the logo is the wordmark,
   and every other screen in the product already uses it.

   Both are rendered and the layout picks one: the wordmark while the rail is
   open, the icon once it has collapsed to 76px, where no wordmark fits. */
.rail-top .rail-icon{display:none}
.rail-top .rail-wm{--wm:23px}
.rail-console{font-size:11px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--brand);background:var(--brand-soft);
  border-radius:var(--pill);padding:3px 8px;white-space:nowrap}
.topbar .topbar-wm{--wm:22px}

/* ── The business switcher ───────────────────────────────────────────────── */
/* The one control that makes three businesses under one password legible.
   It names the business AND its plan, because which plan a business is on
   changes what the screen behind it can do — and someone who runs a Starter
   café and an Unlimited salon needs to know which one they are looking at
   before they wonder why the branch button is missing. */
.switcher{position:relative}
.switch-btn{
  width:100%;display:flex;align-items:center;gap:10px;text-align:left;
  padding:10px;border-radius:var(--r-sm);border:1px solid var(--line);
  background:var(--brand-wash);cursor:pointer;font:inherit;color:var(--ink);
  transition:border-color var(--t-fast),background var(--t-fast);
}
.switch-btn:hover{border-color:var(--brand-line);background:var(--brand-soft)}
.switch-btn[aria-expanded="true"]{border-color:var(--brand);background:var(--brand-soft)}
.switch-face{width:32px;height:32px;border-radius:10px;flex-shrink:0;
  background:linear-gradient(145deg,var(--brand),var(--brand-deep));
  color:#fff;display:grid;place-items:center;font-weight:600;font-size:13px;
  letter-spacing:-.03em;overflow:hidden}
.switch-face img{width:100%;height:100%;object-fit:cover}
.switch-txt{min-width:0;flex:1}
.switch-txt > b{display:block;font-size:14px;font-weight:600;letter-spacing:-.02em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.switch-txt > span:not(.meter):not(.badge){display:block;font-size:11.5px;color:var(--muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.switch-btn i{color:var(--faint);font-size:16px;flex-shrink:0}

.switch-menu{
  position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:60;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:var(--sh-pop);padding:6px;max-height:min(70vh,440px);overflow:auto;
}
.switch-item{display:flex;align-items:center;gap:10px;width:100%;text-align:left;
  padding:9px 10px;border:0;border-radius:var(--r-xs);background:transparent;
  cursor:pointer;font:inherit;color:var(--text)}
.switch-item:hover{background:var(--surface-2);text-decoration:none}
.switch-item.on{background:var(--brand-soft)}
.switch-item .dot{width:26px;height:26px;border-radius:8px;flex-shrink:0;display:grid;
  place-items:center;color:#fff;font-size:11px;font-weight:600;overflow:hidden}
.switch-item > span > b{display:block;font-size:13.5px;font-weight:600;letter-spacing:-.02em}
.switch-item > span > span{display:block;font-size:11.5px;color:var(--muted)}
.switch-sep{height:1px;background:var(--line);margin:6px 4px}
.switch-add{color:var(--brand-deep);font-weight:600;font-size:13.5px}
.switch-add .dot{background:var(--brand-soft);color:var(--brand-deep)}

/* ── Nav ─────────────────────────────────────────────────────────────────── */
.nav{display:flex;flex-direction:column;gap:2px;overflow-y:auto;flex:1;
  margin:0 -4px;padding:0 4px}
.nav-group{margin-top:14px}
.nav-group:first-child{margin-top:0}
.nav-group > .label{padding:0 10px;margin-bottom:4px}
.nav a{
  display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:var(--r-xs);
  color:var(--muted);font-size:14.5px;font-weight:500;letter-spacing:-.01em;
  transition:background var(--t-fast),color var(--t-fast);position:relative;
}
.nav a:hover{background:var(--surface-2);color:var(--ink);text-decoration:none}
.nav a i{font-size:19px;width:20px;text-align:center;flex-shrink:0}
.nav a.on{background:var(--brand-soft);color:var(--brand-deep);font-weight:600}
/* The active item is marked by a bar as well as by colour, so it survives
   both a colourblind reader and the six pale themes where --brand-soft is
   barely a tint. */
.nav a.on:before{content:'';position:absolute;left:-4px;top:9px;bottom:9px;width:3px;
  border-radius:0 3px 3px 0;background:var(--brand)}
.nav a .tail{margin-left:auto;font-size:11.5px;font-weight:600;padding:1px 7px;
  border-radius:var(--pill);background:var(--danger);color:#fff;line-height:1.5}
.nav a .tail.quiet{background:var(--surface-2);color:var(--muted)}

.rail-foot{border-top:1px solid var(--line);padding-top:12px;margin:0 -4px;padding-left:4px;padding-right:4px}
.me{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--r-xs);
  width:100%;border:0;background:transparent;cursor:pointer;font:inherit;text-align:left}
.me:hover{background:var(--surface-2)}
.me .av{width:28px;height:28px;border-radius:50%;background:var(--sunken);
  display:grid;place-items:center;font-size:12px;font-weight:600;color:var(--muted);flex-shrink:0}
.me > span > b{display:block;font-size:13.5px;font-weight:600;color:var(--ink);letter-spacing:-.02em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.me > span > span:not(.meter):not(.badge){display:block;font-size:11.5px;color:var(--faint);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ── The top bar (mobile only) ───────────────────────────────────────────── */
.topbar{display:none;position:sticky;top:0;z-index:45;
  background:color-mix(in srgb,var(--surface) 86%,transparent);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line);padding:8px 14px;
  align-items:center;gap:10px}
@supports not (backdrop-filter:blur(1px)){ .topbar{background:var(--surface)} }
.topbar .switcher{flex:1;min-width:0}
.icon-btn{width:38px;height:38px;border-radius:12px;border:1px solid var(--line);
  background:var(--surface);color:var(--muted);display:grid;place-items:center;
  cursor:pointer;flex-shrink:0;font-size:19px;transition:background var(--t-fast),color var(--t-fast)}
.icon-btn:hover{background:var(--surface-2);color:var(--ink)}
.icon-btn.on{background:var(--brand-soft);color:var(--brand-deep);border-color:var(--brand-line)}
/* Beside a field, it has to be the height of the field. At 38px against a
   46px input it reads as a button that has slipped. */
.input-group .icon-btn{width:46px;height:46px;border-radius:var(--r-sm)}

/* ── The bottom tab bar (mobile only) ────────────────────────────────────── */
.tabbar{display:none;position:fixed;left:0;right:0;bottom:0;z-index:50;
  background:color-mix(in srgb,var(--surface) 92%,transparent);
  backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  border-top:1px solid var(--line);
  padding:6px 6px calc(6px + env(safe-area-inset-bottom));
  display:none;grid-auto-flow:column;grid-auto-columns:1fr}
@supports not (backdrop-filter:blur(1px)){ .tabbar{background:var(--surface)} }
.tabbar a{display:flex;flex-direction:column;align-items:center;gap:3px;
  padding:7px 2px;border-radius:12px;color:var(--faint);font-size:10.5px;font-weight:600;
  letter-spacing:.01em}
.tabbar a i{font-size:21px}
.tabbar a.on{color:var(--brand-deep)}
.tabbar a:hover{text-decoration:none}

/* ── Main ────────────────────────────────────────────────────────────────── */
.main{min-width:0;display:flex;flex-direction:column;position:relative;isolation:isolate}
/* The bloom. One soft wash of the business's own colour at the top of every
   page — the only place the brand is allowed to be large. */
.main:before{content:'';position:absolute;inset:0 0 auto;height:min(460px,52vh);
  z-index:-1;pointer-events:none;opacity:.5;
  background:radial-gradient(90% 120% at 76% -24%,var(--brand-glow),transparent 66%)}
.page{width:100%;max-width:var(--page);margin:0 auto;padding:var(--pad) var(--pad) 72px;
  flex:1}

/* ============================================================================
   THE SPACING SCALE
   ----------------------------------------------------------------------------
   Three steps, and every gap between BLOCKS is one of them. Before this there
   were none: the dashboard separated its sections by 24px, 22px, 20px and 14px
   in four places that look identical on screen, and in one of them — a panel
   followed by a list inside a grid column — by nothing at all, so the two
   touched.

   That is what an absent scale does. Each number was reasonable when it was
   typed; it is only together that they read as carelessness, and there is no
   value a sixth one could take that would make the set look deliberate.

       --gap-tight    rows inside a list, buttons in a row
       --gap-card     between cards, panels, and the columns of a grid
       --gap-section  between the major parts of a page

   They are CLAMPED rather than fixed because a 34px section break on a phone
   is a third of the screen. The ratio between the three holds at every width.

   Use .stack for a column of blocks rather than a margin on each one. A margin
   can be forgotten on the last element added; a gap cannot. Every touching
   pair on the dashboard was a div somebody put in a column without one. */
:root{
  --gap-tight:8px;
  --gap-card:clamp(14px, 1.6vw, 20px);
  --gap-section:clamp(24px, 3vw, 34px);
}

/* A column of blocks. The whole point is that spacing belongs to the
   container, so nothing can be added to it unspaced. */
.stack{display:flex;flex-direction:column;gap:var(--gap-card);min-width:0}
/* Inside a stack the container owns the spacing, so a child's own top margin
   is added to the gap rather than replacing it — which is how a 16px rhythm
   came out as 47 between the dashboard's list groups. */
.stack > .section{margin-top:0}
.stack-tight{gap:var(--gap-tight)}
.stack-loose{gap:var(--gap-section)}

/* ── Page head ───────────────────────────────────────────────────────────── */
.phead{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;
  margin:4px 0 var(--gap-section)}
.phead-txt{flex:1;min-width:min(260px,100%)}
.phead h1{margin-bottom:4px}
.phead p{color:var(--muted);max-width:62ch;margin:0}
.phead-act{display:flex;gap:9px;flex-wrap:wrap;align-items:center}

.section{margin:var(--gap-section) 0 0}
.section:first-child{margin-top:0}
.shead{display:flex;align-items:baseline;gap:12px;margin:0 0 var(--gap-card);flex-wrap:wrap}
.shead h2{font-size:19px;letter-spacing:-.025em}
.shead .more{margin-left:auto;font-size:13.5px;font-weight:600}

/* ============================================================================
   PANELS
   ========================================================================== */
.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--sh-sm);overflow:hidden}
.panel-pad{padding:clamp(16px,2.2vw,24px)}
.panel-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:16px clamp(16px,2.2vw,24px);border-bottom:1px solid var(--line-soft)}
.panel-head h3{flex:1;min-width:0}
.panel-head .label{margin:0}
.panel-foot{padding:14px clamp(16px,2.2vw,24px);border-top:1px solid var(--line-soft);
  background:var(--surface-2);display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.panel.tinted{background:var(--brand-wash);border-color:var(--brand-line)}
.panel.flat{box-shadow:none}

/* A pastel feature row — icon tile, title, line of copy, chevron. The one
   repeating shape in the reference, and the right one for a list of things
   you can set up.

   Note the child combinators on .tile-txt below, and on every other block of
   this shape in this file. Written as `.tile-txt span`, the rule reaches any
   span ANYWHERE inside — including a .badge, which it then out-ranks, because
   `.tile-txt span` is (0,1,1) against `.badge`'s (0,1,0). A status pill in a
   row heading came out as a full-width bar of colour, and nothing in either
   rule says anything about the other. */
.tile{display:flex;align-items:center;gap:14px;padding:16px 18px;border-radius:var(--r);
  background:var(--surface);border:1px solid var(--line);
  transition:border-color var(--t-fast),transform var(--t-fast),box-shadow var(--t-fast);
  color:var(--text)}
a.tile:hover{text-decoration:none;border-color:var(--brand-line);transform:translateY(-1px);
  box-shadow:var(--sh)}
.tile-ico{width:46px;height:46px;border-radius:15px;flex-shrink:0;display:grid;
  place-items:center;font-size:21px;background:var(--brand-soft);color:var(--brand-deep)}
.tile-ico.ok{background:var(--ok-soft);color:var(--ok)}
.tile-ico.warn{background:var(--warn-soft);color:var(--warn)}
.tile-ico.danger{background:var(--danger-soft);color:var(--danger)}
.tile-ico.info{background:var(--info-soft);color:var(--info)}
.tile-txt{flex:1;min-width:0}
.tile-txt > b{display:block;font-size:15px;font-weight:600;color:var(--ink);letter-spacing:-.02em}
.tile-txt > span:not(.meter):not(.badge){display:block;font-size:13.5px;color:var(--muted)}
.tile > i:last-child{color:var(--faint);font-size:18px}

.grid{display:grid;gap:var(--gap-card)}
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g-side{grid-template-columns:minmax(0,1.6fr) minmax(0,1fr)}

/* ============================================================================
   NUMBERS
   ----------------------------------------------------------------------------
   Uppercase micro-label, big tabular figure, a line of context under it.
   Divided by hairlines rather than boxed into cards: four boxes in a row is
   four borders competing with the number they contain.
   ========================================================================== */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--sh-sm);overflow:hidden}
.stat{padding:18px clamp(16px,1.8vw,22px);border-left:1px solid var(--line-soft);min-width:0}
.stat:first-child{border-left:0}
.stat b{display:block;font-size:clamp(25px,2.8vw,32px);font-weight:500;color:var(--ink);
  letter-spacing:-.04em;line-height:1.05;font-variant-numeric:tabular-nums}
.stat .sub{display:block;font-size:12.5px;color:var(--muted);margin-top:4px}
.stat .delta{display:inline-flex;align-items:center;gap:3px;font-size:12px;font-weight:600;
  padding:1px 7px;border-radius:var(--pill);margin-top:6px}
.delta.up{background:var(--ok-soft);color:var(--ok)}
.delta.down{background:var(--danger-soft);color:var(--danger)}
.delta.flat{background:var(--surface-2);color:var(--muted)}

/* ============================================================================
   CONTROLS
   ========================================================================== */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font-family:inherit;font-size:14.5px;font-weight:600;letter-spacing:-.01em;
  padding:11px 20px;border-radius:var(--pill);border:1.5px solid transparent;
  cursor:pointer;white-space:nowrap;line-height:1.2;text-decoration:none!important;
  transition:background var(--t-fast),color var(--t-fast),border-color var(--t-fast),
             transform .08s,box-shadow var(--t-fast)}
.btn:active{transform:translateY(1px)}
.btn i{font-size:17px}
.btn-primary{background:var(--brand);color:#fff;border-color:var(--brand)}
.btn-primary:hover{background:var(--brand-deep);border-color:var(--brand-deep)}
.btn-dark{background:var(--ink);color:#fff;border-color:var(--ink)}
.btn-dark:hover{background:var(--brand);border-color:var(--brand)}
.btn-soft{background:var(--brand-soft);color:var(--brand-deep);border-color:transparent}
.btn-soft:hover{background:var(--brand-line)}
.btn-outline{background:var(--surface);color:var(--ink);border-color:var(--line)}
.btn-outline:hover{border-color:var(--ink);background:var(--surface)}
.btn-ghost{background:transparent;color:var(--muted);border-color:transparent}
.btn-ghost:hover{background:var(--surface-2);color:var(--ink)}
.btn-danger{background:var(--danger);color:#fff;border-color:var(--danger)}
.btn-danger:hover{filter:brightness(.92)}
.btn-sm{font-size:13.5px;padding:8px 15px}
.btn-lg{font-size:16px;padding:15px 30px}
.btn-block{display:flex;width:100%}
/* A column of buttons: all one width, because three widths reads as three
   different kinds of thing. */
.btn[disabled],.btn[aria-disabled="true"]{opacity:.45;pointer-events:none}
.btn.busy{pointer-events:none;position:relative;color:transparent!important}
.btn.busy:after{content:'';position:absolute;width:15px;height:15px;border-radius:50%;
  border:2px solid currentColor;border-top-color:transparent;color:#fff;
  animation:spin .7s linear infinite}
.btn-outline.busy:after,.btn-ghost.busy:after,.btn-soft.busy:after{color:var(--brand-deep)}
@keyframes spin{to{transform:rotate(360deg)}}

/* Fields ------------------------------------------------------------------ */
.field{margin:0 0 16px}
.field > label{display:block;font-size:13.5px;font-weight:600;color:var(--ink);
  margin:0 0 6px;letter-spacing:-.01em}
.field .hint{display:block;font-size:12.5px;color:var(--muted);margin:6px 0 0}
.field .bad{display:block;font-size:12.5px;color:var(--danger);font-weight:500;margin:6px 0 0}
.input,.select,textarea.input{
  width:100%;font:inherit;font-size:15px;color:var(--ink);
  padding:12px 14px;border:1.5px solid var(--line);border-radius:var(--r-sm);
  background:var(--surface);transition:border-color var(--t-fast),box-shadow var(--t-fast);
  -webkit-appearance:none;appearance:none;
}
textarea.input{min-height:110px;resize:vertical;line-height:1.55}
.input::placeholder{color:var(--faint)}
.input:hover,.select:hover{border-color:#D8D2E4}
.input:focus,.select:focus{outline:0;border-color:var(--brand);
  box-shadow:0 0 0 4px color-mix(in srgb,var(--brand) 13%,transparent)}
.input[aria-invalid="true"]{border-color:var(--danger)}
.select{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235E5478' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 13px center;padding-right:40px}
.input-group{display:flex;gap:8px;align-items:center}
.input-group .input{flex:1}

/* A field with an icon in it — search, mostly. */
.search{position:relative}
.search i{position:absolute;left:13px;top:50%;transform:translateY(-50%);
  color:var(--faint);font-size:18px;pointer-events:none}
.search .input{padding-left:40px}

/* Choice cards — the shape the whole onboarding is made of. A radio you can
   hit with a thumb, which is the only kind that works at a counter. */
.choices{display:grid;gap:10px}
.choices.two{grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr))}
.choice{position:relative;display:flex;align-items:flex-start;gap:12px;
  padding:14px 16px;border:1.5px solid var(--line);border-radius:var(--r);
  background:var(--surface);cursor:pointer;
  transition:border-color var(--t-fast),background var(--t-fast),box-shadow var(--t-fast)}
.choice:hover{border-color:var(--brand-line);background:var(--brand-wash)}
.choice input{position:absolute;opacity:0;width:0;height:0}
.choice b{display:block;font-size:15px;font-weight:600;color:var(--ink);letter-spacing:-.02em}
.choice span{display:block;font-size:13.5px;color:var(--muted);margin-top:2px}
.choice:has(input:checked){border-color:var(--brand);background:var(--brand-soft);
  box-shadow:0 0 0 1px var(--brand)}
.choice:has(input:focus-visible){outline:2px solid var(--brand);outline-offset:2px}
.choice .tile-ico{width:38px;height:38px;border-radius:12px;font-size:18px}

/* Segmented control */
.seg{display:inline-flex;padding:3px;background:var(--surface-2);border-radius:var(--pill);
  border:1px solid var(--line);gap:2px;max-width:100%;overflow-x:auto;scrollbar-width:none}
.seg::-webkit-scrollbar{display:none}
.seg button{border:0;background:transparent;font:inherit;font-size:13.5px;font-weight:600;
  color:var(--muted);padding:7px 15px;border-radius:var(--pill);cursor:pointer;
  white-space:nowrap;transition:background var(--t-fast),color var(--t-fast)}
.seg button:hover{color:var(--ink)}
.seg button.on{background:var(--surface);color:var(--ink);box-shadow:var(--sh-sm)}

/* Switch */
.toggle{display:inline-flex;align-items:center;gap:10px;cursor:pointer}
.toggle input{position:absolute;opacity:0;width:0;height:0}
.toggle .track{width:44px;height:26px;border-radius:var(--pill);background:#D8D2E4;
  position:relative;transition:background var(--t);flex-shrink:0}
.toggle .track:after{content:'';position:absolute;top:3px;left:3px;width:20px;height:20px;
  border-radius:50%;background:#fff;box-shadow:var(--sh-sm);transition:transform var(--t)}
.toggle input:checked + .track{background:var(--brand)}
.toggle input:checked + .track:after{transform:translateX(18px)}
.toggle input:focus-visible + .track{outline:2px solid var(--brand);outline-offset:2px}

/* Badges and chips -------------------------------------------------------- */
.badge{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;
  padding:3px 10px;border-radius:var(--pill);background:var(--surface-2);color:var(--muted);
  white-space:nowrap;letter-spacing:-.005em}
.badge.ok{background:var(--ok-soft);color:var(--ok)}
.badge.warn{background:var(--warn-soft);color:var(--warn)}
.badge.danger{background:var(--danger-soft);color:var(--danger)}
.badge.info{background:var(--info-soft);color:var(--info)}
.badge.brand{background:var(--brand-soft);color:var(--brand-deep)}
/* A dot as well as a colour, so status survives a colourblind reader. */
.badge .dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0}

.chip{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:500;
  padding:6px 13px;border-radius:var(--pill);border:1px solid var(--line);
  background:var(--surface);color:var(--muted);cursor:pointer;
  transition:border-color var(--t-fast),color var(--t-fast),background var(--t-fast)}
.chip:hover{border-color:var(--brand-line);color:var(--ink)}
.chip.on{background:var(--brand-soft);border-color:var(--brand-line);color:var(--brand-deep);
  font-weight:600}

/* Notices ----------------------------------------------------------------- */
.note{display:flex;gap:12px;padding:14px 16px;border-radius:var(--r);
  background:var(--brand-soft);border:1px solid var(--brand-line);color:var(--ink);
  font-size:14px;line-height:1.5}
.note i{font-size:19px;flex-shrink:0;color:var(--brand-deep);margin-top:1px}
.note b{font-weight:600}
.note .acts{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap}
.note.ok{background:var(--ok-soft);border-color:color-mix(in srgb,var(--ok) 22%,transparent)}
.note.ok i{color:var(--ok)}
.note.warn{background:var(--warn-soft);border-color:color-mix(in srgb,var(--warn) 24%,transparent)}
.note.warn i{color:var(--warn)}
.note.danger{background:var(--danger-soft);border-color:color-mix(in srgb,var(--danger) 22%,transparent)}
.note.danger i{color:var(--danger)}

/* ============================================================================
   TABLES — rows on a laptop, cards on a phone
   ----------------------------------------------------------------------------
   Not a scrolling table on a phone. A table that scrolls sideways hides its
   own right-hand columns, and the column it hides is always the one with the
   button in it. Below 768 every row becomes a card and each cell carries its
   own label from data-h.
   ========================================================================== */
.tablewrap{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--sh-sm);overflow:auto;overscroll-behavior-x:contain}
table.t{width:100%;border-collapse:collapse;font-size:14px}
table.t th{text-align:left;font-size:11.5px;font-weight:600;letter-spacing:.12em;
  text-transform:uppercase;color:var(--faint);padding:12px 16px;
  border-bottom:1px solid var(--line);background:var(--surface-2);white-space:nowrap}
table.t td{padding:13px 16px;border-bottom:1px solid var(--line-soft);vertical-align:middle}
table.t tbody tr:last-child td{border-bottom:0}
table.t tbody tr{transition:background var(--t-fast)}
table.t tbody tr:hover{background:var(--brand-wash)}
table.t td.num{text-align:right;font-variant-numeric:tabular-nums}
table.t th.num{text-align:right}
table.t td.act{text-align:right;white-space:nowrap}
table.t .who > b{display:block;font-weight:600;color:var(--ink);letter-spacing:-.015em}
table.t .who > span:not(.meter):not(.badge){display:block;font-size:12.5px;color:var(--muted)}

/* ============================================================================
   PROGRESS
   ========================================================================== */
.bar{height:7px;border-radius:var(--pill);background:var(--sunken);overflow:hidden}
.bar > i{display:block;height:100%;border-radius:var(--pill);
  background:linear-gradient(90deg,var(--brand),var(--brand-deep));
  transition:width var(--t-slow)}
.bar.warn > i{background:var(--warn)}
.bar.danger > i{background:var(--danger)}

/* The stamp meter — how far through a card one customer is, small enough to
   sit in a table cell. Dots, not a bar: a stamp card IS dots, and a customer
   asking "how many more" wants to count them. */
.meter{display:flex;gap:3px;align-items:center}
.meter i{width:9px;height:9px;border-radius:50%;background:var(--sunken);
  border:1px solid var(--line)}
.meter i.on{background:var(--brand);border-color:var(--brand)}
.meter i.ready{background:var(--ok);border-color:var(--ok)}
.meter b{font-size:12.5px;font-weight:600;color:var(--muted);margin-left:5px;
  font-variant-numeric:tabular-nums}

/* ============================================================================
   EMPTY, LOADING, ERROR
   ----------------------------------------------------------------------------
   All three exist for every list in this app. The empty one says what to do
   next and gives the button for it; the loading one has the SHAPE of what is
   coming, so the page does not jump when it arrives.
   ========================================================================== */
.empty{text-align:center;padding:clamp(34px,6vw,66px) 24px;max-width:460px;margin:0 auto}
.empty .tile-ico{width:62px;height:62px;border-radius:20px;font-size:28px;margin:0 auto 18px}
.empty h3{font-size:19px;margin-bottom:6px}
.empty p{color:var(--muted);margin-bottom:20px}

.skel{background:linear-gradient(90deg,var(--surface-2) 25%,var(--sunken) 37%,var(--surface-2) 63%);
  background-size:400% 100%;animation:shimmer 1.3s linear infinite;border-radius:8px;
  height:14px}
.skel.tall{height:62px;border-radius:var(--r-sm)}
.skel.round{border-radius:50%}
@keyframes shimmer{to{background-position:-135% 0}}
@media (prefers-reduced-motion:reduce){.skel{animation:none;background:var(--surface-2)}}

/* ============================================================================
   OVERLAYS
   ========================================================================== */
.scrim{position:fixed;inset:0;z-index:70;background:rgba(25,14,46,.42);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  display:grid;place-items:center;padding:20px;
  animation:fade var(--t) both}
@keyframes fade{from{opacity:0}}
.modal{background:var(--surface);border-radius:var(--r-xl);box-shadow:var(--sh-pop);
  width:min(540px,100%);max-height:88dvh;overflow:auto;animation:pop var(--t-slow) both}
@keyframes pop{from{opacity:0;transform:translateY(14px) scale(.98)}}
.modal-head{display:flex;align-items:flex-start;gap:14px;padding:22px 24px 0}
.modal-head h3{flex:1;font-size:20px}
.modal-body{padding:16px 24px}
.modal-foot{padding:16px 24px 22px;display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap}

.toast{position:fixed;left:50%;bottom:calc(22px + env(safe-area-inset-bottom));
  transform:translateX(-50%);z-index:90;
  background:var(--ink);color:#fff;padding:12px 20px;border-radius:var(--pill);
  box-shadow:var(--sh-pop);font-size:14px;font-weight:500;display:flex;align-items:center;
  gap:9px;max-width:min(460px,calc(100vw - 32px));animation:rise var(--t-slow) both}
@keyframes rise{from{opacity:0;transform:translate(-50%,16px)}}
.toast.ok i{color:#7FE0A8}
.toast.bad i{color:#FF9D91}

/* ============================================================================
   RESPONSIVE
   ========================================================================== */
/* ── The icon rail ───────────────────────────────────────────────────────
   Between 768 and 1100 the sidebar keeps its icons and drops every word.
   Everything hidden here is listed explicitly rather than by `*`, because a
   rail that hides the wrong thing is a rail with no way back — and because
   the one that matters, the nav label, was the one left visible: 76px of
   width with "Card studio" still in it wrapped onto two lines and pushed the
   icon out of the way. Each link keeps a title attribute, so the label is a
   hover away rather than gone. */
@media (max-width:1099px){
  .shell{grid-template-columns:var(--rail-sm) minmax(0,1fr)}
  .rail{padding:16px 10px;align-items:center}
  .rail .rail-wm,
  .rail .rail-console,
  .rail .wordmark,
  .rail .switch-txt,
  .rail .switch-btn > i,
  .rail .nav a > span,
  .rail .nav-group > .label,
  .rail .me > span,
  .rail .me > i{display:none}
  /* Only the icon survives at 76px — the wordmark above is hidden. */
  .rail .rail-icon{display:block}
  .rail-top{justify-content:center;padding-left:0;padding-right:0}
  .switch-btn{justify-content:center;padding:8px;width:auto}
  .switch-menu{left:0;width:252px;right:auto}
  .nav{width:100%;align-items:center}
  .nav a{justify-content:center;padding:11px 0;gap:0;width:100%}
  .nav a i{width:auto}
  .nav a .tail{position:absolute;top:3px;right:6px;margin:0;padding:0 5px;font-size:10px}
  .nav a.on:before{left:-10px}
  .rail-foot{width:100%}
  .me{justify-content:center;padding:8px 0;gap:0}
  .g-side{grid-template-columns:minmax(0,1fr)}
}

@media (max-width:767px){
  .shell{grid-template-columns:minmax(0,1fr)}
  .rail{display:none}
  .topbar{display:flex}
  .tabbar{display:grid}
  .page{padding-bottom:calc(var(--tabbar) + 26px)}
  .g2{grid-template-columns:minmax(0,1fr)}
  .stats{grid-template-columns:repeat(2,minmax(0,1fr))}
  .stat{border-left:0;border-top:1px solid var(--line-soft)}
  .stat:nth-child(odd){border-right:1px solid var(--line-soft)}
  .stat:nth-child(1),.stat:nth-child(2){border-top:0}
  .phead{margin-bottom:var(--gap-section)}
  .phead-act{width:100%}
  .phead-act .btn{flex:1}
  .modal{border-radius:var(--r-lg) var(--r-lg) 0 0;max-height:92dvh;
    position:fixed;left:0;right:0;bottom:0;width:auto;animation:sheet var(--t-slow) both}
  .scrim{place-items:end stretch;padding:0}
  @keyframes sheet{from{transform:translateY(100%)}}
  .toast{bottom:calc(var(--tabbar) + 16px)}

  /* Tables become cards. */
  .tablewrap{background:transparent;border:0;box-shadow:none;border-radius:0}
  table.t,table.t tbody,table.t tr,table.t td{display:block;width:100%}
  table.t thead{display:none}
  table.t tr{background:var(--surface);border:1px solid var(--line);
    border-radius:var(--r);margin-bottom:10px;padding:6px 4px;box-shadow:var(--sh-sm)}
  table.t tr:hover{background:var(--surface)}
  table.t td{border:0;padding:7px 14px;display:flex;align-items:center;gap:12px;
    justify-content:space-between;text-align:right}
  table.t td:before{content:attr(data-h);font-size:11.5px;font-weight:600;
    letter-spacing:.11em;text-transform:uppercase;color:var(--faint);
    text-align:left;flex-shrink:0}
  table.t td:empty{display:none}
  table.t td.act,table.t td.num{text-align:right}
  table.t td.wide{display:block;text-align:left}
  table.t td.wide:before{display:block;margin-bottom:4px}
}

/* On a narrow phone four numbers stacked is four screens of scrolling before
   anything you can act on. One column, but each number laid on its side:
   label and context on the left, the figure on the right, four rows deep. */
@media (max-width:420px){
  .stats{grid-template-columns:minmax(0,1fr)}
  .stat{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;
    column-gap:16px;padding:13px 18px;
    border-left:0;border-right:0;border-top:1px solid var(--line-soft)}
  .stat:first-child{border-top:0}
  .stat .label{grid-column:1;grid-row:1;margin:0;align-self:end}
  .stat b{grid-column:2;grid-row:1/3;font-size:28px;text-align:right}
  .stat .sub{grid-column:1;grid-row:2;margin-top:2px}
  .stat .delta{grid-column:1;grid-row:3;margin-top:5px;justify-self:start}
  .stat .bar{grid-column:1/-1;grid-row:4;margin-top:9px!important}
}

/* Print: the poster and the invoices are the only things printed here. */
@media print{
  .rail,.topbar,.tabbar,.phead-act,.btn{display:none!important}
  .shell{display:block}
  .main:before{display:none}
  .panel{box-shadow:none;border-color:#ccc}
}

/* ============================================================================
   THE STUDIO'S TWO PICKERS
   ----------------------------------------------------------------------------
   Both wrap rather than sit in a fixed grid, so the swatches fill whatever
   column they are given instead of leaving a stranded one on a line of its
   own. That was the whole point of going from eleven colours to twenty.
   ========================================================================== */
.sws{display:flex;flex-wrap:wrap;gap:10px}
.sw{width:46px;height:46px;padding:3px;border-radius:14px;border:2px solid transparent;
  background:transparent;cursor:pointer;transition:border-color var(--t-fast),transform var(--t-fast)}
.sw span{display:block;width:100%;height:100%;border-radius:10px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.3),inset 0 0 0 1px rgba(0,0,0,.08)}
.sw:hover{transform:translateY(-2px)}
.sw.on{border-color:var(--brand)}
.sw:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

.ics{display:flex;flex-wrap:wrap;gap:8px}
.ic{width:42px;height:42px;border-radius:13px;border:1.5px solid var(--line);
  background:var(--surface);color:var(--muted);font-size:19px;cursor:pointer;
  display:grid;place-items:center;
  transition:border-color var(--t-fast),color var(--t-fast),background var(--t-fast)}
.ic:hover{border-color:var(--brand-line);color:var(--ink)}
.ic.on{background:var(--brand);border-color:var(--brand);color:#fff}

/* The range input, which has no useful default anywhere. */
input[type="range"]{-webkit-appearance:none;appearance:none;height:28px;cursor:pointer}
input[type="range"]::-webkit-slider-runnable-track{height:7px;border-radius:var(--pill);
  background:var(--sunken)}
input[type="range"]::-moz-range-track{height:7px;border-radius:var(--pill);background:var(--sunken)}
input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:22px;height:22px;
  border-radius:50%;background:var(--brand);border:3px solid #fff;box-shadow:var(--sh-sm);
  margin-top:-7.5px}
input[type="range"]::-moz-range-thumb{width:22px;height:22px;border-radius:50%;
  background:var(--brand);border:3px solid #fff;box-shadow:var(--sh-sm)}
input[type="range"]:focus-visible{outline:2px solid var(--brand);outline-offset:4px;border-radius:var(--pill)}

/* A bare <span> in one of the row shapes above is a line of text and is laid
   out as a block. A COMPONENT that happens to be a span — a .badge, a .meter —
   brings its own layout and has to keep it, which is what the :not() pairs are
   for. Without them `.tillrow .who > span` (0,2,1) out-ranks `.meter` (0,1,0),
   the meter stops being a flex row, and its ten circles — inline elements
   again — ignore their own width and height and collapse into a solid smear
   that still looks deliberate. */

/* ============================================================================
   THE TILL ROW
   ----------------------------------------------------------------------------
   One target, the whole width, 72px tall. Not a row with a button at the end:
   the person tapping it is holding a phone one-handed with somebody waiting,
   and a 32px button inside a 72px row is a miss waiting to happen.
   ========================================================================== */
.tillrow{display:flex;align-items:center;gap:14px;width:100%;text-align:left;
  padding:14px 18px;border:0;border-bottom:1px solid var(--line-soft);
  background:transparent;cursor:pointer;font:inherit;
  transition:background var(--t-fast)}
.tillrow:last-child{border-bottom:0}
.tillrow:hover{background:var(--brand-wash)}
.tillrow:active{background:var(--brand-soft)}
.tillrow .av{width:44px;height:44px;border-radius:14px;flex-shrink:0;display:grid;
  place-items:center;background:var(--brand-soft);color:var(--brand-deep);
  font-weight:600;font-size:15px;letter-spacing:-.02em}
.tillrow .who{flex:1;min-width:0}
.tillrow .who > b{display:block;font-size:16px;font-weight:600;color:var(--ink);
  letter-spacing:-.02em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tillrow .who > span:not(.meter):not(.badge){display:block;font-size:13px;color:var(--muted)}
.tillrow .meter{margin-top:6px}
.tillrow .go{width:46px;height:46px;border-radius:50%;flex-shrink:0;display:grid;
  place-items:center;background:var(--brand);color:#fff;font-size:21px;
  transition:transform var(--t-fast)}
.tillrow:active .go{transform:scale(.92)}
.tillrow.full .av{background:var(--ok-soft);color:var(--ok)}
.tillrow.full .go{background:var(--ok)}

/* ============================================================================
   THE BELL
   ----------------------------------------------------------------------------
   One panel, anchored to the window rather than to either button, because the
   two buttons live in different places at different widths and a panel
   positioned against one of them is a panel off-screen at the other.
   ========================================================================== */
.bellbtn{position:relative;display:flex;align-items:center;gap:11px;width:100%;
  padding:9px 11px;border:0;border-radius:var(--r-xs);background:transparent;
  color:var(--muted);font:inherit;font-size:14.5px;font-weight:500;cursor:pointer;
  text-align:left;transition:background var(--t-fast),color var(--t-fast)}
.bellbtn:hover{background:var(--surface-2);color:var(--ink)}
.bellbtn i{font-size:19px;width:20px;text-align:center;flex-shrink:0}
.bellbtn.has{color:var(--ink)}
.bellbtn[aria-expanded="true"]{background:var(--brand-soft);color:var(--brand-deep)}
/* The top-bar one is an icon button and brings its own box. */
.topbar .bellbtn{width:38px;height:38px;padding:0;justify-content:center;
  border:1px solid var(--line);background:var(--surface);border-radius:12px}
.topbar .bellbtn .lbl{display:none}

.belldot{position:absolute;top:4px;left:24px;min-width:17px;height:17px;padding:0 4px;
  border-radius:var(--pill);background:var(--brand);color:#fff;font-size:10.5px;
  font-weight:700;line-height:17px;text-align:center;
  box-shadow:0 0 0 2px var(--surface)}
.bellbtn.loud .belldot{background:var(--danger)}
.topbar .bellbtn .belldot{top:-5px;left:auto;right:-5px}

.bellwrap{position:fixed;inset:0;z-index:80;pointer-events:none}
.bellpanel{position:absolute;pointer-events:auto;
  left:calc(var(--rail) + 10px);bottom:18px;width:min(380px,calc(100vw - 32px));
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--sh-pop);overflow:hidden;
  animation:pop var(--t-slow) both}
@media (max-width:1099px){ .bellpanel{left:calc(var(--rail-sm) + 10px)} }
@media (max-width:767px){
  .bellpanel{left:16px;right:16px;width:auto;top:64px;bottom:auto;max-height:70dvh;
    display:flex;flex-direction:column}
}

.bellhead{display:flex;align-items:center;gap:12px;padding:14px 18px;
  border-bottom:1px solid var(--line-soft)}
.bellhead b{font-size:15px;font-weight:600;color:var(--ink);flex:1}
.bellclear{border:0;background:transparent;font:inherit;font-size:13px;font-weight:600;
  color:var(--brand-deep);cursor:pointer;padding:4px}
.bellclear:hover{text-decoration:underline}

.belllist{max-height:min(54vh,420px);overflow-y:auto;overscroll-behavior:contain}
.bellitem{display:flex;gap:12px;padding:13px 18px;border-bottom:1px solid var(--line-soft);
  color:var(--text);position:relative}
a.bellitem:hover{background:var(--brand-wash);text-decoration:none}
.bellitem:last-child{border-bottom:0}
/* Unread is a bar as well as a weight, because "slightly bolder" is not a
   signal anybody notices on a phone in daylight behind a counter. */
.bellitem:not(.read):before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;
  background:var(--brand)}
.bellitem.read{opacity:.62}
.bellitem.good:not(.read):before{background:var(--ok)}
.bellitem.warn:not(.read):before{background:var(--warn)}
.bellitem.urgent:not(.read):before{background:var(--danger)}
.bellitem .ico{width:34px;height:34px;border-radius:11px;flex-shrink:0;display:grid;
  place-items:center;font-size:17px;background:var(--brand-soft);color:var(--brand-deep)}
.bellitem.good .ico{background:var(--ok-soft);color:var(--ok)}
.bellitem.warn .ico{background:var(--warn-soft);color:var(--warn)}
.bellitem.urgent .ico{background:var(--danger-soft);color:var(--danger)}
.bellitem .txt{min-width:0;flex:1}
.bellitem .txt > b{display:block;font-size:14px;font-weight:600;color:var(--ink);
  letter-spacing:-.015em;line-height:1.35}
.bellitem .txt > span{display:block;font-size:13px;color:var(--muted);margin-top:2px;
  line-height:1.45}
.bellitem time{display:block;font-size:11.5px;color:var(--faint);margin-top:5px}

.bellempty{padding:34px 24px;text-align:center}
.bellempty i{font-size:30px;color:var(--ok);display:block;margin-bottom:10px}
.bellempty b{display:block;font-size:15px;color:var(--ink);margin-bottom:4px}
.bellempty span{display:block;font-size:13px;color:var(--muted);line-height:1.5}

.bellfoot{width:100%;border:0;border-top:1px solid var(--line-soft);background:var(--surface-2);
  padding:11px;font:inherit;font-size:13px;font-weight:600;color:var(--muted);cursor:pointer}
.bellfoot:hover{background:var(--sunken);color:var(--ink)}

/* ============================================================================
   THE KNOWLEDGE BASE
   ========================================================================== */
.kb-ask{display:flex;gap:10px;align-items:stretch;margin-bottom:20px;flex-wrap:wrap}
.kb-ask .search{flex:1;min-width:min(100%,260px)}
.kb-ask .search .input{font-size:16px;padding-top:15px;padding-bottom:15px}
.kb-ask .btn{flex-shrink:0}
@media (max-width:560px){ .kb-ask .btn{width:100%} }

/* An article's own typography. Narrower measure than the app around it,
   because this is the one place somebody is reading rather than scanning. */
.kb{font-size:15px;line-height:1.62;color:var(--text);max-width:64ch}
.kb h3{font-size:16px;font-weight:600;color:var(--ink);margin:22px 0 8px;letter-spacing:-.015em}
.kb h3:first-child{margin-top:0}
.kb p{margin:0 0 12px}
.kb ul,.kb ol{margin:0 0 14px;padding-left:22px}
.kb li{margin:0 0 6px}
.kb b{font-weight:600;color:var(--ink)}
.kb code{font-family:var(--mono);font-size:13px;background:var(--surface-2);
  padding:2px 6px;border-radius:6px}
.kb-note{margin:0 0 14px;padding:12px 15px;border-radius:var(--r-sm);
  background:var(--brand-soft);border-left:3px solid var(--brand);
  font-size:14.5px;color:var(--ink)}

/* ============================================================================
   THE APP MENU
   ----------------------------------------------------------------------------
   The "More" tab used to open a modal holding a vertical list of full-width
   rows: nine of them, taller than the screen, so the first was already cut off
   when it appeared and the rest had to be scrolled. On a phone that is a list
   to read, not a menu to use.

   It is a grid of icon cards now — every destination visible at once, each a
   target the thumb can hit without aiming — in a sheet that comes up from the
   bar that opened it and can be thrown back down.

   WHY THE MOVEMENT IS NOT IN THIS FILE
   The open and close are springs driven in JS (app.js), not transitions or
   keyframes here, because a CSS animation cannot be grabbed half way through
   and reversed. A sheet you can catch on its way down and pull back up is the
   whole difference between a panel that appears and an object you are holding.
   What CSS keeps is the resting appearance, and the reduced-motion fallback at
   the foot, where there is no gesture to honour.
   ========================================================================== */
.appsheet-scrim{
  position:fixed;inset:0;z-index:70;
  background:rgba(25,14,46,.42);
  opacity:0;                       /* JS drives this with the drag */
  -webkit-tap-highlight-color:transparent;
}
.appsheet{
  position:fixed;left:0;right:0;bottom:0;z-index:71;
  background:color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter:blur(26px) saturate(170%);
  -webkit-backdrop-filter:blur(26px) saturate(170%);
  border-top:1px solid rgba(255,255,255,.5);
  border-radius:22px 22px 0 0;
  box-shadow:0 -12px 50px -12px rgba(25,14,46,.42);
  padding:10px 14px calc(18px + env(safe-area-inset-bottom));
  transform:translate3d(0,100%,0);  /* JS takes it from here */
  will-change:transform;
  touch-action:none;                /* we are handling the drag ourselves */
  max-height:86vh;overflow:hidden;
}
@supports not (backdrop-filter:blur(1px)){ .appsheet{background:var(--surface)} }

/* The grab handle. It is also the hint that this thing is draggable at all. */
.appsheet-grip{display:block;width:40px;height:4px;border-radius:2px;margin:2px auto 12px;
  background:var(--line-2);flex-shrink:0}

.appsheet-head{display:flex;align-items:center;gap:12px;padding:0 6px 14px}
.appsheet-head b{font-size:16px;letter-spacing:-.02em;display:block;color:var(--ink)}
.appsheet-head span{font-size:13px;color:var(--muted);display:block}

/* ── The cards ────────────────────────────────────────────────────────────
   Three across is the most that leaves a label readable at 375px, and nine
   destinations divide into it exactly. */
.appgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.appcard{
  display:flex;flex-direction:column;align-items:center;justify-content:flex-start;
  gap:9px;padding:16px 8px 14px;min-height:96px;
  background:var(--surface);border:1px solid var(--line);border-radius:16px;
  text-decoration:none!important;color:var(--ink);
  /* Pressed feedback is on pointer-DOWN and instant. Waiting for the tap to
     complete before showing anything is what makes a web page feel like a web
     page. */
  transition:transform 110ms ease-out, background 110ms ease-out;
  -webkit-tap-highlight-color:transparent;
}
.appcard:active{transform:scale(.96);background:var(--brand-wash)}
.appcard .appcard-ico{
  width:44px;height:44px;border-radius:14px;display:grid;place-items:center;
  background:var(--brand-soft);color:var(--brand-deep);font-size:21px;flex-shrink:0}
.appcard b{font-size:12.5px;font-weight:600;line-height:1.25;text-align:center;
  letter-spacing:-.01em;text-wrap:balance}
.appcard .appcard-tail{font-size:11px;color:var(--muted);line-height:1.2}

.appsheet-foot{display:flex;gap:10px;padding:14px 2px 0}
.appsheet-foot .btn{flex:1}

/* ── On a mouse-driven screen, a drawer ───────────────────────────────────
   A sheet is a phone shape. A centred panel on a 1440px screen is a small box
   in a large empty room: it reads as a dialog interrupting the page rather
   than as the app's own menu. A drawer is anchored to the edge the button
   lives on, is as tall as the window, and gives the grid room to breathe.

   The class is set by JS, from the same media query named once in app.js, so
   the CSS and the spring that drives it can never disagree about which shape
   this is. That query tests pointer:fine and not width alone — an iPad in
   landscape is 1024px wide and still wants the full-width sheet under a
   thumb. */
.appsheet.is-drawer{
  left:auto;right:0;top:0;bottom:0;
  width:min(460px,92vw);max-height:none;overflow-y:auto;
  border-radius:22px 0 0 22px;
  border:1px solid var(--line);border-right:0;
  box-shadow:-14px 0 60px -16px rgba(25,14,46,.42);
  padding:24px 20px calc(20px + env(safe-area-inset-bottom));
  transform:translate3d(100%,0,0);   /* JS takes it from here, sideways */
  display:flex;flex-direction:column;
}
.appsheet.is-drawer .appsheet-grip{display:none}
.appsheet.is-drawer .appsheet-head{padding:0 2px 18px}
.appsheet.is-drawer .appgrid{gap:12px}
.appsheet.is-drawer .appcard{min-height:112px}
/* The feet sit at the bottom of the window, not under the last row of cards. */
.appsheet.is-drawer .appsheet-foot{margin-top:auto;padding-top:22px}

/* ── When motion is not wanted ────────────────────────────────────────────
   Not "no feedback" — a cross-fade instead of a thrown object. The drag is
   left working; it is the spring that goes. */
@media (prefers-reduced-motion:reduce){
  .appsheet{transition:opacity 180ms ease}
}
@media (prefers-reduced-transparency:reduce){
  .appsheet{background:var(--surface);backdrop-filter:none;-webkit-backdrop-filter:none}
}

/* ============================================================================
   THE CODE, FULL SCREEN
   ----------------------------------------------------------------------------
   Nothing in this product has a scanner in it. The customer's code is read by
   a staff member's phone CAMERA, held over the customer's screen — so the
   thing that matters is how big a module is in millimetres, not how neat the
   card looks.

   On the back of the card a 37-module code renders about 88px wide: a third of
   a millimetre per module, through glass, at an angle, with the screen's own
   glare on it. It frequently will not read. This is the view that is actually
   meant to be scanned, and it is white, because a dark surface behind a code
   costs a camera the contrast it needs.
   ========================================================================== */
.codeview{
  position:fixed;inset:0;z-index:90;
  background:#fff;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  /* A narrow side gutter on purpose: every millimetre of width here is a
     millimetre of code for the camera at the counter. */
  gap:22px;padding:28px 16px;
  transform:translate3d(0,100%,0);   /* the spring in app.js takes it from here */
  will-change:transform;touch-action:none;
  -webkit-tap-highlight-color:transparent;
}
.codeview-grip{position:absolute;top:10px;left:50%;transform:translateX(-50%);
  width:40px;height:4px;border-radius:2px;background:#DCD6E6}
.codeview-box{
  /* The plate's size is named once, because the padding has to be derived from
     it. The same arithmetic as the card: the padding IS the quiet zone, and the
     code fills everything inside it.

     It must be a calc of --plate and NOT a percentage. Percentage padding
     resolves against the CONTAINING BLOCK's width, not the element's own, and
     the containing block here is the full-screen .codeview. At a 1024px
     viewport that made the quiet zone 87px a side and left the code 286px of a
     460px plate — a third of the plate thrown away, and it got worse the wider
     the screen. 9% of 41 modules over the remaining 82% is 4.5 modules of quiet
     zone, which clears the spec's minimum of 4.

     The width term is the viewport minus this view's own two 16px gutters, not
     a vw percentage — 86vw overflowed those gutters on a 375px phone. */
  --plate:min(calc(100vw - 32px),70vh,460px);
  width:var(--plate);height:var(--plate);background:#fff;
  padding:calc(var(--plate) * .09);border-radius:18px;
  box-shadow:0 10px 50px -18px rgba(25,14,46,.45);
  display:flex;align-items:center;justify-content:center;
}
.codeview-box canvas,.codeview-box img{width:100%!important;height:100%!important;display:block}
.codeview-box canvas + img{display:none}
.codeview h2{font-size:19px;letter-spacing:-.025em;text-align:center;color:#190E2E;margin:0}
.codeview p{font-size:14px;color:#5E5478;text-align:center;margin:0;max-width:30ch}
.codeview .btn{margin-top:4px}

@media (prefers-reduced-motion:reduce){ .codeview{transition:opacity 180ms ease} }

/* ── The notifications bar ───────────────────────────────────────────────
   The bell used to sit at the foot of the rail on desktop and in the top bar
   only once the layout narrowed, so "where are my notifications" had two
   different answers depending on the window. It is top-right everywhere now.

   A thin sticky strip above the page rather than a full top bar: the rail
   already carries the navigation and the switcher at this width, so a second
   full-width bar would be mostly empty. */
.mainbar{display:none;position:sticky;top:0;z-index:44;
  justify-content:flex-end;align-items:center;gap:8px;
  padding:10px 16px 0;pointer-events:none}
.mainbar > *{pointer-events:auto}
@media (min-width:1100px){
  .mainbar{display:flex}
  /* One bell, not two. The rail's copy goes when the bar appears. */
  .rail .bellbtn{display:none}
}
.mainbar .bellbtn{width:38px;height:38px;padding:0;justify-content:center;
  border-radius:var(--r-sm);background:var(--surface);
  border:1px solid var(--line);box-shadow:var(--shadow-sm,0 1px 2px rgba(16,8,34,.05))}
.mainbar .bellbtn .lbl{display:none}
.mainbar .bellbtn:hover{border-color:var(--brand-line)}

/* The panel hangs from the bell wherever the bell is. */
.mainbar + .page .bellpanel,
.mainbar .bellpanel{right:16px;top:52px}

/* ── The notifications page ──────────────────────────────────────────── */
.notelist{display:flex;flex-direction:column;gap:10px}
.noteitem{display:flex;gap:13px;align-items:flex-start;
  background:var(--surface);border:1px solid var(--line);
  border-left:3px solid var(--line);border-radius:var(--r);
  padding:15px 17px;text-decoration:none;color:inherit}
a.noteitem:hover{border-color:var(--brand-line);border-left-color:var(--brand)}
.noteitem.urgent{border-left-color:var(--danger)}
.noteitem.warn{border-left-color:var(--warn)}
.noteitem.good{border-left-color:var(--ok)}
.noteitem.info{border-left-color:var(--info)}
/* Unread is the one state worth shouting, so it gets the fill. */
.noteitem.is-unread{background:var(--brand-wash)}
.noteitem .ico{width:38px;height:38px;border-radius:50%;flex:none;
  display:flex;align-items:center;justify-content:center;font-size:19px;
  background:var(--surface-2);color:var(--muted)}
.noteitem.urgent .ico{background:var(--danger-soft);color:var(--danger)}
.noteitem.warn .ico{background:var(--warn-soft);color:var(--warn)}
.noteitem.good .ico{background:var(--ok-soft);color:var(--ok)}
.noteitem.info .ico{background:var(--info-soft);color:var(--info)}
.noteitem .txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
.noteitem .txt b{font-size:15px;color:var(--ink);font-weight:500}
.noteitem .txt span{font-size:13.5px;color:var(--muted);line-height:1.55}
.noteitem .txt time{font-size:12px;color:var(--faint)}
.noteitem .dot{width:8px;height:8px;border-radius:50%;background:var(--brand);
  flex:none;margin-top:6px}
.notegroup{font-size:12px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--faint);margin:18px 0 2px}
.notegroup:first-child{margin-top:0}
