/* Carnival Running — member PWA
   Design tokens taken verbatim from the Claude Design handoff.
   The palette is deliberately monochrome; --alert is the only non-neutral colour. */

:root {
  --bg: #050505;
  /* the app is dark by design. Declaring it stops Chrome on Android from
     applying its own "force dark" pass, which inverted the white member card and
     left the QR unreadable */
  color-scheme: dark;
  --surface: #101010;
  /* Packshots stay on their own studio white; the tile carries the same white so
     there is no edge between photo and plate. Must be pure #fff, not off-white —
     any difference draws exactly the outline we are trying to get rid of. */
  --shot-bg: #ffffff;
  --invert: #ffffff;
  --invert-text: #050505;
  --line: rgba(255, 255, 255, .14);
  --line-soft: rgba(255, 255, 255, .10);
  --border: rgba(255, 255, 255, .16);
  --text: #ffffff;
  --text-60: rgba(255, 255, 255, .6);
  --text-45: rgba(255, 255, 255, .45);
  --text-35: rgba(255, 255, 255, .35);
  --text-30: rgba(255, 255, 255, .30);
  /* On this #050505 ground, .35 alpha reads at only 3.05:1 and .45 at 4.49:1 —
     both under the 4.5:1 WCAG AA floor for small text. Small copy uses these. */
  --text-lo: rgba(255, 255, 255, .62);   /*  7.8:1 */
  --text-mid: rgba(255, 255, 255, .78);  /* 12.2:1 */
  --alert: oklch(0.68 0.2 27);
  --shadow: 0 14px 40px rgba(0, 0, 0, .7);
  --font: 'Space Grotesk', 'Noto Sans Thai', -apple-system, sans-serif;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* The transparent tap highlight above removes the browser's own tap feedback,
   so every control has to supply its own :active state — without it a tap on a
   phone gives no signal at all and reads as "the button is broken".
   touch-action: manipulation also drops the ~300ms double-tap-zoom delay. */
button, .pill, .action-card, .row, .notif-row, .card .cta button, label.upload {
  touch-action: manipulation;
}
button:active { transform: scale(.97); }
.pill:active { background: rgba(255, 255, 255, .14); }
.pill.is-on:active { background: #e2e2e2; }
.btn:active { filter: brightness(.88); }
.action-card:active { filter: brightness(.9); }
.notif-row:active, .row:active { background: rgba(255, 255, 255, .05); }
.tab:active { opacity: .6; }
.stepper button:active { background: rgba(255, 255, 255, .16); }
/* keep the tap target comfortable on a phone: 44px is the platform minimum */
.pill { min-height: 40px; }
.stepper button { min-width: 30px; min-height: 30px; }

html, body {
  margin: 0; padding: 0;
  background: var(--bg); color: var(--text);
  font-family: var(--font); font-size: 15px; line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: contain;
}
body { min-height: 100svh; }

button, input, textarea, select { font-family: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; }
a { color: inherit; text-decoration: none; }

/* ---------------------------------------------------------------- shell */
#app {
  max-width: 430px; margin: 0 auto; position: relative;
  min-height: 100svh; overflow-x: hidden;
}
/* The enter animation is opt-in via .cr-enter, added by render() only when the
   screen actually changed. Putting it on .screen itself replayed it on every
   in-place update, which showed up as a flicker while typing. */
.screen { padding: 62px 20px 112px; }
.screen.cr-enter { animation: cr-up .26s ease; }
/* svh, not dvh: dvh shifts as the browser toolbar and keyboard animate, and a
   column with a flex:1 spacer re-lays-out on every shift. */
.screen--auth { padding: 0 26px 34px; min-height: 100svh; display: flex; flex-direction: column; }
.screen--otp { padding: 72px 26px 34px; min-height: 100svh; display: flex; flex-direction: column; }

@keyframes cr-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------- type */
.eyebrow {
  font-weight: 600; font-size: 10.5px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--text-lo);
}
/* Most labels carry Thai. Thai has no uppercase, and letter-spacing separates
   vowel and tone marks from the consonant they belong to — so neither is used
   here. `.label--en` keeps the tracked treatment for the Latin-only ones. */
.label {
  font-weight: 500; font-size: 12.5px; letter-spacing: 0;
  text-transform: none; color: var(--text-lo); line-height: 1.5;
}
.label--en {
  font-size: 10.5px; letter-spacing: .18em; text-transform: uppercase;
}
.h1 { font-weight: 700; font-size: 22px; line-height: 1.2; margin: 0; }
.h2 { font-weight: 700; font-size: 21px; line-height: 1.2; margin: 0; }
.sub { font-size: 13.5px; line-height: 1.7; color: var(--text-lo); margin: 7px 0 0; }
.mono { font-weight: 500; font-size: 12px; letter-spacing: .08em; color: var(--text-lo); }
.num  { font-variant-numeric: tabular-nums; }
.alert-text { color: var(--alert); }

/* ------------------------------------------------------------- buttons */
.btn {
  display: block; width: 100%; border-radius: 12px; padding: 18px 0;
  font-weight: 700; font-size: 14px; text-align: center;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}
.btn--primary { background: var(--invert); color: var(--invert-text); }
.btn--disabled { background: rgba(255, 255, 255, .18); color: rgba(255, 255, 255, .45); }
.btn--outline { border: 1px solid var(--border); color: var(--text); background: none; }
.btn--sm { padding: 13px 0; font-size: 13px; border-radius: 10px; }
.btn[disabled] { cursor: default; }

.pill {
  border-radius: 999px; padding: 8px 14px; font-size: 12px; font-weight: 500;
  border: 1px solid var(--border); color: var(--text-60); background: none;
  white-space: nowrap;
}
.pill.is-on { background: var(--invert); color: var(--invert-text); border-color: var(--invert); }
.pill-row { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.pill-row::-webkit-scrollbar { display: none; }

.back {
  font-size: 13px; font-weight: 500; color: var(--text-45);
  display: inline-block; margin-bottom: 22px;
}

/* --------------------------------------------------------------- login */
.auth-logo-wrap { flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 18px; }
.auth-logo { width: 132px; height: auto; display: block; }
.wordmark { font-weight: 700; font-size: 21px; line-height: 1.15; letter-spacing: .02em; }
.wordmark-sub { font-weight: 500; font-size: 11.5px; letter-spacing: .22em; color: var(--text-lo); }

.field { margin-bottom: 14px; }
.field .label { display: block; margin-bottom: 9px; }
.input-row {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface); border-radius: 12px; padding: 16px;
  border: 1px solid rgba(255, 255, 255, .12); transition: border-color .2s ease;
}
.input-row.is-filled { border-color: rgba(255, 255, 255, .32); }
.input-row .prefix { color: rgba(255, 255, 255, .4); font-weight: 500; font-size: 16px; }
.input-row .divider { width: 1px; height: 18px; background: rgba(255, 255, 255, .14); }
.input-row input {
  flex: 1; min-width: 0; background: none; border: 0; outline: none;
  font-weight: 500; font-size: 16px; letter-spacing: .06em;
}
.input-row input::placeholder { color: rgba(255, 255, 255, .25); letter-spacing: .06em; }
.footnote {
  text-align: center; font-size: 12.5px; line-height: 1.75;
  color: var(--text-lo); margin-top: 16px;
}
.auth-switch { text-align: center; margin-top: 14px; font-size: 13.5px; color: var(--text-lo); }
.auth-switch button { text-decoration: underline; color: var(--text); font-size: 13.5px; }

/* ----------------------------------------------------------------- OTP */
.otp-grid { position: relative; display: grid; grid-template-columns: repeat(6, 1fr);
  gap: 8px; margin: 26px 0 14px; }
.otp-cell {
  aspect-ratio: 1 / 1.2; border-radius: 10px; background: var(--surface);
  border: 1px solid rgba(255, 255, 255, .12);
  display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 20px;
}
.otp-cell.is-cursor { border-color: var(--invert); }
.otp-grid.is-error .otp-cell { border-color: var(--alert); color: var(--alert); }
.otp-catcher {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; border: 0; background: none; font-size: 16px;
}
.otp-status { display: flex; justify-content: space-between; align-items: center; font-size: 12.5px; }
.otp-status .left { color: var(--text-lo); }
/* Tabular digits and a reserved width stop the row from twitching sideways
   each second as the countdown ticks from "28 วิ" to "9 วิ". */
.otp-status .resend { color: var(--text-lo); font-size: 12.5px;
  font-variant-numeric: tabular-nums; min-width: 118px; text-align: right; }
.otp-status .resend.is-on { color: var(--invert); }
.dev-hint {
  margin-top: 18px; border: 1px dashed var(--border); border-radius: 10px;
  padding: 13px 15px; font-size: 12.5px; color: var(--text-lo); line-height: 1.75;
}
.dev-hint b { color: var(--text); letter-spacing: .2em; font-size: 15px; }

/* ---------------------------------------------------------------- home */
.home-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 24px; }
.home-head .grow { flex: 1; min-width: 0; }
.member-name { font-weight: 700; font-size: 23px; line-height: 1.2; margin: 5px 0 4px; }
.logo-tile { width: 40px; height: auto; flex: none; }

/* notification bell — sits beside the logo tile, same 40px optical size so the
   header row stays balanced. The dot is the only unread affordance. */
.bell {
  width: 40px; height: 40px; border-radius: 6px; flex: none;
  border: 1px solid var(--border); position: relative;
  display: grid; place-items: center;
}
.bell svg { width: 17px; height: 17px; stroke: var(--text); stroke-width: 1.5;
  fill: none; stroke-linecap: round; stroke-linejoin: round; }
/* red, and ringed in the page colour — the same unread signal as the dot on the
   HOME tab, so one glance reads the same thing in both places */
.bell .badge {
  position: absolute; top: 5px; right: 5px;
  width: 8px; height: 8px; border-radius: 999px;
  background: var(--alert); border: 1.5px solid var(--bg); box-sizing: content-box;
}
.bell.is-unread svg { stroke: var(--invert); }

.notif-row {
  display: flex; gap: 12px; align-items: flex-start;
  border-top: 1px solid var(--line-soft); padding: 15px 0;
}
.notif-row .pip {
  width: 6px; height: 6px; border-radius: 999px; flex: none; margin-top: 7px;
  background: var(--invert);
}
.notif-row.is-read .pip { background: rgba(255,255,255,.16); }
.notif-row .grow { flex: 1; min-width: 0; }
.notif-row .kind {
  font-weight: 600; font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-lo); margin-bottom: 6px;
}
.notif-row .t { font-size: 14px; font-weight: 600; line-height: 1.5; }
.notif-row.is-read .t { font-weight: 500; color: var(--text-mid); }
.notif-row .b { font-size: 12.5px; line-height: 1.75; color: var(--text-lo); margin-top: 6px; }
.notif-row .when { font-size: 11.5px; color: var(--text-lo); margin-top: 7px;
  letter-spacing: .03em; }

.budget-block { border-top: 1px solid var(--line); padding-top: 18px; margin-bottom: 24px; }
.budget-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; }
.budget-amount { display: flex; align-items: flex-end; gap: 10px; }
.budget-amount .big {
  font-weight: 700; font-size: 60px; line-height: .86; letter-spacing: -.035em;
  font-variant-numeric: tabular-nums;
}
.budget-amount .mid {
  font-weight: 700; font-size: 42px; line-height: .9; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.budget-amount .side { font-size: 14px; line-height: 1.4; color: var(--text-lo); padding-bottom: 4px; }
.bar { height: 6px; border-radius: 3px; background: rgba(255,255,255,.14); margin-top: 16px; overflow: hidden; }
.bar > i { display: block; height: 100%; background: var(--invert); width: 0;
  transition: width .4s ease; border-radius: 3px; }
.bar--soft > i { background: rgba(255, 255, 255, .55); }
.bar-foot { display: flex; justify-content: space-between; font-size: 12.5px;
  color: var(--text-lo); margin-top: 10px; line-height: 1.5; }

.action-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; margin-bottom: 20px; }
.action-card { border-radius: 14px; padding: 17px 16px; text-align: left; }
.action-card .t { font-weight: 700; font-size: 14px; }
.action-card .s { font-size: 12px; margin-top: 5px; line-height: 1.5; }
.action-card--solid { background: var(--invert); color: var(--invert-text); }
.action-card--solid .s { color: rgba(5, 5, 5, .68); }
.action-card--ghost { background: var(--surface); border: 1px solid var(--border); }
.action-card--ghost .s { color: var(--text-lo); }

.notice {
  border: 1px solid var(--border); border-radius: 12px; padding: 14px;
  display: flex; gap: 11px; align-items: flex-start;
  font-size: 12.5px; line-height: 1.75; color: var(--text-mid); margin-bottom: 24px;
}
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--invert);
  flex: none; margin-top: 5px; animation: pulse 2s ease infinite; }
@keyframes pulse { 0%,100% { opacity: .3 } 50% { opacity: 1 } }

.list-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px; }
.row {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  border-top: 1px solid var(--line-soft); padding: 14px 0;
}
.row .grow { flex: 1; min-width: 0; }
.row .t { font-size: 13.5px; font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.row .s { font-size: 12px; color: var(--text-lo); margin-top: 5px; line-height: 1.5; }
.row .amount { font-weight: 600; font-size: 13.5px; font-variant-numeric: tabular-nums; flex: none; }
.empty { padding: 34px 0; text-align: center; font-size: 13.5px; color: var(--text-lo); }

/* ---------------------------------------------------------------- shop */
/* minmax(0, 1fr), not 1fr: a quantity line inside a card ("M − 2 + 5,180 ฿ ✕")
   has a wide min-content, and a bare 1fr lets that push its column wider than
   the other one, so the two photos come out different sizes. */
.grid2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 11px; margin-top: 16px; }
.grid2 > .card { min-width: 0; }
.card { display: flex; flex-direction: column; }
/* Cutting the studio white out of a packshot left a pale fringe around the
   garment — feathering the mask softened it but never removed it. The photo is
   left untouched instead and the tile is given the same white, so there is no
   seam and no border to see. `contain` because these are packshots: cropping to
   fill cut the toe off a shoe and the hem off a pair of shorts. */
.card .shot {
  aspect-ratio: 1 / 1.15; border: 0; border-radius: 4px;
  background: var(--shot-bg); overflow: hidden; position: relative;
}
.card .shot img { width: 100%; height: 100%; object-fit: contain; display: block; }
.shot--ph {
  background-color: var(--surface);
  border: 1px solid var(--line-soft);
  background-image: repeating-linear-gradient(135deg,
    rgba(255,255,255,.05) 0 6px, transparent 6px 12px);
}
.shot .ph-label {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 10px; letter-spacing: .1em; color: var(--text-45); text-align: center; padding: 8px;
}
/* the thumbnail is a button into the photo popup */
.card .shot[data-shot] { cursor: pointer; }
.card .shot[data-shot]:active img { transform: scale(.97); }
.card .shot img { transition: transform .18s ease; }
.shot-more {
  position: absolute; bottom: 6px; right: 6px; z-index: 2;
  background: rgba(5, 5, 5, .62); color: #fff;
  font-size: 9px; letter-spacing: .1em; font-weight: 600;
  padding: 3px 6px; border-radius: 4px;
}

/* Photo popup — front and back, full width. Fixed, so the catalogue keeps its
   scroll position underneath and nothing has to be re-rendered to show it. */
.shotbox {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(5, 5, 5, .82);
  display: flex; align-items: center; justify-content: center;
  padding: 20px; animation: cr-fade .16s ease;
}
.shotbox-card {
  width: min(430px, 100%); max-height: 92svh; overflow-y: auto;
  background: var(--surface); border-radius: 16px; padding: 16px;
  box-shadow: var(--shadow);
}
.shotbox-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 12px; }
.shotbox-head .grow { flex: 1; min-width: 0; }
.shotbox-nm { font-weight: 600; font-size: 14px; line-height: 1.45; }
.shotbox-pr { font-size: 13px; color: var(--text-lo); margin-top: 3px;
  font-variant-numeric: tabular-nums; }
.shotbox-x {
  flex: none; width: 32px; height: 32px; border-radius: 9px;
  border: 1px solid var(--border); background: none; color: var(--text-lo);
  display: grid; place-items: center; font-size: 13px;
}
.shotbox-x:active { background: rgba(255, 255, 255, .12); color: var(--text); }
.shotbox-img {
  background: var(--shot-bg); border-radius: 10px; overflow: hidden;
  aspect-ratio: 1 / 1.15;
}
.shotbox-img img { width: 100%; height: 100%; object-fit: contain; display: block; }
.shotbox-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 12px; }
.shotbox-tab {
  min-height: 40px; border: 1px solid var(--border); border-radius: 10px;
  font-weight: 600; font-size: 12.5px; color: var(--text-mid);
}
.shotbox-tab.is-on { background: var(--invert); color: var(--invert-text);
  border-color: var(--invert); }
.shotbox-note { font-size: 11.5px; color: var(--text-lo); margin: 10px 0 0; text-align: center; }

@keyframes cr-fade { from { opacity: 0 } to { opacity: 1 } }

/* new arrivals are sorted to the top of the catalogue; the badge says why they
   are there, so the order does not look arbitrary */
.new-tag {
  position: absolute; top: 8px; left: 8px; z-index: 2;
  background: var(--invert); color: var(--invert-text);
  font-family: 'Space Grotesk', sans-serif; font-weight: 700;
  font-size: 9.5px; letter-spacing: .12em;
  padding: 4px 7px; border-radius: 5px;
}
/* back in stock, not newly released — quieter than "ใหม่" on purpose, because it
   is news to whoever was waiting for a size, not to everyone.
   Dark ink on a white chip, not white ink on transparent: this badge sits on the
   pack shot, and --shot-bg is pure white, so a white-on-transparent chip was
   invisible on every card that had a photo. */
/* not on sale yet — the loudest of the three, because it is the one that
   explains why a card cannot be tapped */
.new-tag--soon {
  background: var(--invert-text); color: var(--invert);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .35);
}
.new-tag--back {
  background: rgba(255, 255, 255, .9); color: var(--invert-text);
  box-shadow: inset 0 0 0 1px rgba(5, 5, 5, .3);
}

.card .nm { font-weight: 500; font-size: 12.5px; line-height: 1.45; margin: 9px 0 4px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.card .pr { font-weight: 600; font-size: 13px; color: var(--text-mid); margin-bottom: 8px; }
.card .sz { font-size: 11.5px; letter-spacing: .04em; color: var(--text-lo); margin-bottom: 6px; }
.card .cta { margin-top: auto; }
/* A sold-out card is still a real listing — it used to fade to 42%, which read
   as broken rather than unavailable. The SOLD OUT tag carries the message. */
.card.is-blocked { opacity: .74; }
.card.is-blocked .shot { filter: saturate(.55); }
.card .blocked-why { font-size: 11.5px; color: var(--alert); line-height: 1.5; margin-top: 3px; }
/* a size that differs from the registered one is information, not an error —
   deliberately neutral, never --alert */
.size-hint { font-size: 11.5px; color: var(--text-lo); line-height: 1.5; margin-top: 4px; }
.line .size-hint { font-size: 12px; margin-top: 6px; }
.stock-warn { font-size: 11.5px; color: var(--text-lo); margin-top: 4px; line-height: 1.45; }

/* Size picker inside a product card. One card per product now, so the size is
   chosen here rather than by scrolling past five identical photos. */
.size-pick { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.szchip {
  min-width: 34px; min-height: 32px; padding: 0 8px;
  border: 1px solid var(--border); border-radius: 8px;
  font-weight: 600; font-size: 12px; color: var(--text-mid);
  display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  font-variant-numeric: tabular-nums; position: relative;
}
.szchip:active { background: rgba(255, 255, 255, .14); }
.szchip.is-on { background: var(--invert); color: var(--invert-text); border-color: var(--invert); }
.szchip.is-on:active { background: #e2e2e2; }
.szchip.is-out {
  color: var(--text-30); border-color: rgba(255, 255, 255, .08);
  text-decoration: line-through; text-decoration-thickness: 1px;
  cursor: default;
}
/* a size that cannot be chosen yet: dimmed, but never struck through — the size
   exists, it is the date that has not arrived */
.szchip:disabled:not(.is-out) {
  color: var(--text-30); border-color: rgba(255, 255, 255, .08); cursor: default;
}
/* the registered size gets a quiet marker, never a colour: it is a hint */
.szchip.is-mine::after {
  content: ""; position: absolute; bottom: 3px; left: 50%; transform: translateX(-50%);
  width: 10px; height: 1px; background: currentColor; opacity: .55;
}
.szchip i { font-style: normal; font-size: 10px; opacity: .75; }
.pick-hint { font-size: 11.5px; color: var(--text-lo); line-height: 1.4; }

/* Quantities are adjusted where the size was chosen. Tapping a size chip adds
   one; every size already picked then gets its own line here with − / + / ✕, so
   nobody has to walk to the cart to fix a count. */
.picked:not(:empty) { margin-top: 8px; border-top: 1px solid var(--line-soft); padding-top: 7px; }
.pickrow {
  display: flex; align-items: center; gap: 7px; padding: 3px 0; min-width: 0;
}
.pickrow .pk-sz {
  min-width: 26px; font-weight: 600; font-size: 11.5px; color: var(--text-mid);
  font-variant-numeric: tabular-nums;
}
.pickrow .stepper { gap: 5px; }
.pickrow .stepper button { width: 24px; height: 24px; border-radius: 6px; font-size: 14px; }
.pickrow .q { min-width: 13px; font-size: 12px; }
.pickrow .pk-del {
  margin-left: auto; width: 24px; height: 24px; flex: none; border-radius: 6px; border: 0;
  background: none; color: var(--text-lo); font-size: 13px; line-height: 1;
  display: grid; place-items: center;
}
.pickrow .pk-del:active { background: rgba(255, 255, 255, .14); color: var(--text); }

/* Members are never shown a quantity — only whether a size can be had. */
.soldout-tag {
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 700; font-size: 11px; letter-spacing: .16em;
  color: var(--alert); border: 1px solid var(--alert);
  border-radius: 6px; padding: 5px 9px; align-self: flex-start; margin-top: 2px;
}

.cartbar {
  position: fixed; left: 50%; transform: translateX(-50%);
  width: min(390px, calc(100% - 40px)); bottom: 98px;
  background: var(--invert); color: var(--invert-text); border-radius: 12px;
  padding: 15px 16px; display: flex; justify-content: space-between; align-items: center;
  font-weight: 700; font-size: 13px; box-shadow: var(--shadow); z-index: 40;
  animation: cr-up .26s ease;
}

/* ---------------------------------------------------------------- cart */
.line { display: flex; gap: 12px; align-items: center; border-top: 1px solid var(--line-soft);
  padding: 14px 0; }
.line .thumb { width: 48px; height: 54px; border: 0; border-radius: 3px;
  background: var(--shot-bg);
  flex: none; overflow: hidden; background: var(--surface); }
.line .thumb img { width: 100%; height: 100%; object-fit: contain; }
.line .grow { flex: 1; min-width: 0; }
.line .nm { font-size: 13px; font-weight: 500; line-height: 1.45; }
.line .calc { font-size: 12px; color: var(--text-lo); margin-top: 5px; font-variant-numeric: tabular-nums; }
.stepper { display: flex; align-items: center; gap: 8px; flex: none; }
.stepper button {
  width: 26px; height: 26px; border-radius: 7px; border: 1px solid var(--border);
  display: grid; place-items: center; font-size: 15px; line-height: 1; color: var(--text);
}
.stepper .q { min-width: 16px; text-align: center; font-weight: 600; font-size: 13px;
  font-variant-numeric: tabular-nums; }

/* how the basket is paid for: allowance first, or all of it at the member
   discount. Shown above the numbers because it changes every line below it. */
.paymode { border-top: 1px solid var(--line); margin-top: 20px; padding-top: 18px; }

.summary { border-top: 1px solid var(--line); margin-top: 20px; padding-top: 18px; }
.summary .r { display: flex; justify-content: space-between; font-size: 13.5px;
  color: var(--text-mid); padding: 6px 0; }
.summary .r b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.summary .final { display: flex; justify-content: space-between; align-items: baseline;
  margin-top: 12px; }
.summary .final .v { font-weight: 700; font-size: 22px; font-variant-numeric: tabular-nums; }
.note { font-size: 12.5px; line-height: 1.75; color: var(--text-lo); margin: 16px 0 20px; }
.note--alert { color: var(--alert); }

/* -------------------------------------------------------------- ticket */
.panel { background: var(--invert); color: var(--invert-text); padding: 26px 22px;
  margin: 22px 0 0; }
/* A QR has to stay black-on-white to be readable. These two rules make that
   survive a phone that recolours pages on its own. */
.qr, .memcard .qr, .panel .qr { background: #fff; forced-color-adjust: none; }
.panel .qr { width: 190px; height: 190px; margin: 0 auto; display: block; }
.panel .qr svg, .qr svg { width: 100%; height: 100%; display: block; }
.panel .code { text-align: center; font-weight: 700; font-size: 16px; letter-spacing: .16em;
  margin-top: 18px; }
.panel .hair { height: 1px; background: rgba(5, 5, 5, .12); margin: 18px 0; }
.panel .kv { display: flex; justify-content: space-between; font-size: 13.5px; padding: 6px 0; }
.panel .kv span:first-child { color: rgba(5, 5, 5, .66); }
.panel .kv b { font-weight: 600; font-variant-numeric: tabular-nums; }
.pickup {
  margin-top: 14px; border: 1px solid var(--border); border-radius: 12px; padding: 14px;
  text-align: center;
}
.pickup .big { font-weight: 700; font-size: 28px; letter-spacing: .28em;
  font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- card */
.memcard { background: var(--invert); color: var(--invert-text); padding: 22px; margin-top: 20px; }
.memcard .top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.memcard .eyebrow { color: rgba(5, 5, 5, .6); }
.memcard .nm { font-weight: 700; font-size: 18px; margin: 6px 0 3px; }
.memcard .id { font-weight: 500; font-size: 12px; letter-spacing: .08em; color: rgba(5,5,5,.66); }
.memcard .qr { width: 100%; aspect-ratio: 1; margin: 20px 0; }
.memcard, .panel { forced-color-adjust: none; }
.memcard .bot { display: flex; justify-content: space-between; align-items: flex-end;
  border-top: 1px solid rgba(5,5,5,.12); padding-top: 16px; }
.memcard .disc { font-weight: 700; font-size: 30px; line-height: 1; }
.memcard .lbl { font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(5,5,5,.62); margin-bottom: 6px; }
.memcard .right { text-align: right; }
.memcard .right .v { font-weight: 700; font-size: 18px; font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- chips */
.chip { font-size: 11.5px; font-weight: 600; border-radius: 999px; padding: 5px 10px;
  white-space: nowrap; }
.chip--solid { background: var(--invert); color: var(--invert-text); }
.chip--outline { border: 1px solid rgba(255,255,255,.28); color: var(--text-mid); }
.chip--alert { border: 1px solid var(--alert); color: var(--alert); }

/* --------------------------------------------------------------- claim */
.underline-field { border-bottom: 1px solid rgba(255,255,255,.2); padding-bottom: 10px;
  margin-bottom: 22px; }
.underline-field input, .underline-field textarea {
  width: 100%; background: none; border: 0; outline: none; padding: 0;
}
.underline-field input.amount { font-weight: 700; font-size: 34px; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; }
.underline-field textarea { font-size: 13.5px; line-height: 1.5; resize: none;
  font-family: var(--font); min-height: 42px; }
.upload {
  border: 1px dashed rgba(255,255,255,.28); border-radius: 10px; padding: 24px 14px;
  text-align: center; font-size: 12.5px; letter-spacing: 0; text-transform: none;
  color: var(--text-lo); display: block; line-height: 1.6; margin-bottom: 12px;
}
.upload:active { background: rgba(255, 255, 255, .06); color: var(--text); }
.upfile {
  display: flex; align-items: center; gap: 8px; min-height: 26px; margin-bottom: 22px;
  font-size: 12px; color: var(--text-lo); line-height: 1.5;
}
.upfile.is-set { color: var(--text); }
.upfile .up-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upfile .up-size { flex: none; color: var(--text-lo); font-variant-numeric: tabular-nums; }
.upfile .pk-del { margin-left: auto; }

/* ------------------------------------------------------------ tab bar */
.tabbar {
  position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
  width: 100%; max-width: 430px; z-index: 50;
  padding: 8px 8px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid rgba(255, 255, 255, .09);
  /* Solid, not a fade to transparent: the old gradient let the page show through
     its top third, and a row of white packshots ending under the bar read as
     bright streaks across it. */
  background: var(--bg);
  display: grid; grid-template-columns: repeat(4, 1fr);
}
.tab {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 9px 0 5px; border-radius: 12px; color: var(--text-lo);
  transition: color .18s ease, transform .12s ease;
}
.tab .ico { position: relative; width: 24px; height: 24px; display: grid; place-items: center; }
.tab .ico svg { width: 22px; height: 22px; display: block; overflow: visible; }
.tab .lb {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 9.5px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
}
.tab:active { transform: scale(.93); color: var(--text); }
.tab.is-on { color: var(--invert); }
.tab.is-on .ico svg { stroke-width: 1.9; }
/* a 2px cap at the top of the active tab: enough to find at a glance without
   painting a whole pill behind it */
.tab.is-on::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 18px; height: 2px; border-radius: 0 0 2px 2px; background: var(--invert);
}

/* counts that belong on the tab that answers them: items waiting in the basket,
   unread notifications */
.tab .tdot {
  position: absolute; top: -3px; right: -7px; min-width: 15px; height: 15px;
  padding: 0 3px; border-radius: 999px;
  background: var(--invert); color: var(--invert-text);
  font-family: 'Space Grotesk', sans-serif; font-size: 9.5px; font-weight: 700;
  font-style: normal; line-height: 15px; text-align: center;
  border: 2px solid var(--bg); box-sizing: content-box;
}
.tab .tdot--plain {
  min-width: 0; width: 7px; height: 7px; padding: 0; top: 0; right: -2px;
  background: var(--alert); border-width: 1.5px;
}

/* --------------------------------------------------------------- toast */
.toast {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 114px;
  width: min(390px, calc(100% - 40px));
  background: var(--invert); color: var(--invert-text);
  border-radius: 11px; padding: 14px 16px; font-weight: 600; font-size: 13px;
  line-height: 1.55; z-index: 90; box-shadow: var(--shadow); animation: cr-up .26s ease;
}
.toast--alert { background: var(--alert); color: #fff; }

/* A reversed record stays in the member's history rather than disappearing:
   their remaining budget changed, and something they can still see is something
   they can ask about. Dimmed and struck through so it never reads as active. */
.row.is-voided { opacity: .62; }
.row.is-voided .t { text-decoration: line-through; }

/* The ticket screen for a reversed ticket: say so above the panel, and take the
   white QR plate away so nothing looks scannable. */
.voided-note { margin-top: 14px; padding: 13px 15px; border-radius: 12px;
  background: rgba(255, 90, 90, .12); border: 1px solid rgba(255, 90, 90, .34);
  color: #ffb4b4; font-size: 13.5px; line-height: 1.5; }
.voided-note .s { color: #d99; font-size: 12.5px; }
.panel.is-voided { opacity: .66; }
.panel.is-voided .code { text-decoration: line-through; }

/* Where the code is coming from, said before it is asked for: staff testing the
   app sat waiting for an SMS that was never going to arrive. */
.via-mail { display: flex; gap: 12px; align-items: flex-start; margin: 4px 0 16px;
  padding: 13px 14px; border-radius: 12px; background: var(--card);
  border: 1px solid var(--line); }
.via-mail b { font-size: 13.5px; font-weight: 600; }
.via-mail .s { margin-top: 3px; font-size: 12px; color: var(--muted); line-height: 1.45; }
.via-mail-ic { flex: none; width: 26px; height: 26px; border-radius: 8px;
  display: grid; place-items: center; font-size: 14px;
  background: var(--invert); color: var(--invert-text); }

.spinner { width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,.2); border-top-color: #fff;
  animation: spin .7s linear infinite; margin: 40px auto; }
@keyframes spin { to { transform: rotate(360deg) } }

.sheet-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.6); z-index: 80; }
.sheet {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 0; z-index: 81;
  width: 100%; max-width: 430px; background: var(--surface);
  border-top: 1px solid var(--line); border-radius: 16px 16px 0 0;
  padding: 22px 20px calc(28px + env(safe-area-inset-bottom));
  animation: cr-up .26s ease;
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

@media (min-width: 431px) {
  body { background: #000; }
  #app { border-left: 1px solid rgba(255,255,255,.06); border-right: 1px solid rgba(255,255,255,.06); }
}
