﻿/* ==========================================================================
   Loyal Sathi — customer stamp-card app
   Mobile-first. Every colour, size, radius, shadow and easing comes from
   tokens.css; nothing is hardcoded here and no font or asset is fetched
   from a third party.
   ========================================================================== */

:root{
  --nav-h: 70px;
  /* Page gutter that keeps full-bleed bars aligned with the centred column. */
  --gutter: max(var(--s-4), calc((100% - var(--app-max)) / 2 + var(--s-4)));
}

html{
  overflow-x: hidden;
  /*
   * Stop the page rubber-banding past its own ends. The bottom nav is
   * position:fixed, so an overscroll drags the page out from under it and
   * shows blank background beyond the bar. This also disables pull-to-refresh,
   * which is what we want: an accidental downward tug on the QR screen should
   * never reload the card mid-scan.
   */
  overscroll-behavior-y: none;
}

body{
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  color: var(--text);
  -webkit-tap-highlight-color: transparent;

  /*
   * Deliberately no overflow or overscroll-behavior here.
   *
   * Setting overflow-x on the body forces its overflow-y to compute to `auto`,
   * which quietly makes the body its own scroll container. The body is exactly
   * as tall as its content, so that container can never scroll — and with
   * overscroll-behavior:none on it as well, a mouse wheel over the page is
   * swallowed by it instead of chaining up to the root that actually scrolls.
   * The scrollbar still drags, which is what makes the bug so confusing.
   *
   * Both jobs belong on <html> above: it is the real scrollport.
   */
}

img{ max-width: 100%; }

main{
  max-width: var(--app-max);
  margin: 0 auto;
  padding-bottom: calc(var(--nav-h) + var(--safe-bottom) + var(--s-4));
  position: relative;
}

.noscript{
  margin: var(--s-4);
  padding: var(--s-4);
  border-radius: var(--r-md);
  background: var(--danger-bg);
  color: var(--danger);
  font-size: var(--t-sm);
  line-height: var(--lh-body);
}

/* ------------------------------------------------------------- top bar --- */
.topbar{
  position: relative;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  background: var(--navy-deep);
  color: var(--text-on-dark);
  padding: calc(var(--safe-top) + var(--s-3)) var(--gutter) var(--s-3);
}
.topbar .brand{
  font-family: var(--font-display);
  font-size: var(--t-md);
  font-weight: 700;
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
}
.topbar .tagline{
  margin-top: 3px;
  font-size: var(--t-xs);
  line-height: 1.3;
  color: var(--gold-soft);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}

/* A quiet pill; only ever used on navy. */
.chip{
  display: inline-block;
  flex: none;
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-soft);
  background: color-mix(in srgb, var(--gold) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--gold) 34%, transparent);
  border-radius: var(--r-full);
  padding: 3px 10px;
  line-height: 1.4;
  white-space: nowrap;
}

/* ------------------------------------------------------------- buttons --- */
.btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  width: 100%;
  min-height: 48px;
  font-family: var(--font-body);
  font-size: var(--t-base);
  font-weight: 600;
  line-height: 1.2;
  padding: var(--s-3) var(--s-6);
  border: 1px solid transparent;
  border-radius: var(--r-full);
  background: var(--navy);
  color: var(--cream);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  text-align: center;
  transition:
    transform var(--dur-fast) var(--ease),
    box-shadow var(--dur) var(--ease),
    background-color var(--dur) var(--ease),
    opacity var(--dur) var(--ease);
}
.btn:hover{ background: var(--navy-soft); }
.btn:active{ transform: scale(0.97); }

.btn.gold{
  background: var(--grad-gold);
  color: var(--navy-deep);
  box-shadow: var(--shadow-gold);
}
.btn.gold:hover{ filter: brightness(1.03); }

.btn.ghost{
  background: transparent;
  border-color: var(--line-strong);
  color: var(--navy);
  box-shadow: none;
}
.btn.ghost:hover{ background: var(--surface-2); border-color: var(--navy-soft); }

.btn.danger{ background: var(--danger); color: var(--surface); }

.btn.small{
  width: auto;
  min-height: 44px;
  font-size: var(--t-sm);
  padding: var(--s-2) var(--s-4);
}
/* Expands a capped list in place — never a new screen. */
.btn.more{
  width: 100%;
  min-height: 44px;
  margin-top: var(--s-3);
  color: var(--text-muted);
  font-weight: 600;
}
.btn.more:hover{ color: var(--navy); }
.btn[disabled]{ opacity: 0.5; cursor: default; box-shadow: none; }
.btn[disabled]:active{ transform: none; }
.btn + .btn{ margin-top: var(--s-3); }

.icon-btn{
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  background: none;
  border: none;
  color: var(--navy);
  font-family: var(--font-body);
  font-size: var(--t-sm);
  font-weight: 600;
  cursor: pointer;
  padding: var(--s-2);
  border-radius: var(--r-sm);
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.icon-btn:hover{ background: var(--surface-2); }
.icon-btn.link{ text-decoration: none; color: var(--gold-ink); }
.icon-btn.link:hover{ color: var(--navy); }

/* --------------------------------------------------------------- forms --- */
.field{ margin-bottom: var(--s-4); }
.field label{
  display: block;
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-2);
}

input, select, textarea{
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--t-base);      /* 16px — stops iOS Safari zooming on focus */
  line-height: 1.4;
  padding: var(--s-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
input::placeholder{ color: var(--text-muted); }
input:focus, select:focus, textarea:focus{
  outline: 2px solid var(--gold);
  outline-offset: 1px;
  border-color: var(--gold);
}
.field.bad input{ border-color: var(--danger); background: var(--danger-bg); }
.field-err{
  font-size: var(--t-sm);
  color: var(--danger);
  margin-top: var(--s-2);
  line-height: var(--lh-snug);
}
.hint{
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-top: var(--s-2);
  margin-bottom: 0;
  line-height: var(--lh-snug);
}
.hint.live{ margin-top: var(--s-2); color: var(--text-muted); }

/* --------------------------------------------------------------- login --- */
.login-pad{
  max-width: var(--app-max);
  margin: 0 auto;
  padding: var(--s-8) var(--s-5) var(--s-12);
}
.login-mark{
  width: 84px;
  height: 84px;
  margin: 0 auto var(--s-6);
  border-radius: 50%;
  display: grid;
  place-items: center;
  overflow: hidden;
  color: var(--gold);
  background: var(--grad-navy);
  box-shadow:
    0 0 0 2px var(--gold),
    0 0 0 9px color-mix(in srgb, var(--gold) 14%, transparent),
    var(--shadow-md);
}
.login-mark .hero-logo{
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  background: transparent;
  box-shadow: none;
}
.login-head{ text-align: center; margin: 0 0 var(--s-8); }
.login-head h1{
  font-family: var(--font-display);
  font-size: var(--t-xl);
  color: var(--navy);
  margin: 0 0 var(--s-3);
  text-wrap: balance;
}
.login-head p{
  font-size: var(--t-base);
  color: var(--text-muted);
  margin: 0;
  line-height: var(--lh-body);
}
.login-pad .hint{ text-align: center; }
.login-pad .field .hint,
.login-pad .field-err{ text-align: left; }

/* Two-step slide. */
.step{ display: block; }
.step.in-right{ animation: ck-step-right var(--dur-slow) var(--ease) both; }
.step.in-left{ animation: ck-step-left var(--dur-slow) var(--ease) both; }
@keyframes ck-step-right{ from{ opacity:0; transform: translateX(24px); } to{ opacity:1; transform:none; } }
@keyframes ck-step-left{ from{ opacity:0; transform: translateX(-24px); } to{ opacity:1; transform:none; } }

.phone-confirm{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-3) var(--s-3) var(--s-4);
  font-size: var(--t-sm);
  margin-bottom: var(--s-5);
  box-shadow: var(--shadow-xs);
}
.phone-confirm .pc-num{
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--text-muted);
  min-width: 0;
}
.phone-confirm b{
  color: var(--navy);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  font-size: var(--t-base);
}

/* ------------------------------------------------------------ messages --- */
.msg{
  font-size: var(--t-sm);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  margin-bottom: var(--s-4);
  line-height: var(--lh-snug);
  border: 1px solid transparent;
}
.msg.ok{ background: var(--ok-bg);     color: var(--ok);     border-color: color-mix(in srgb, var(--ok) 22%, transparent); }
.msg.err{ background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 22%, transparent); }
.msg.warn{ background: var(--warn-bg);  color: var(--warn);   border-color: color-mix(in srgb, var(--warn) 22%, transparent); }
.msg.info{ background: var(--info-bg);  color: var(--info);   border-color: color-mix(in srgb, var(--info) 22%, transparent); }
.msg-slot{ margin: var(--s-4) var(--s-4) 0; }
.msg-slot .msg{ margin-bottom: 0; }
.msg-slot:empty{ display: none; }

/* ---------------------------------------------------------------- hero --- */
.hero{
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--grad-navy);
  color: var(--text-on-dark);
  padding: var(--s-5) var(--s-5) var(--s-5);
  border-radius: 0 0 var(--r-2xl) var(--r-2xl);
  box-shadow: var(--shadow-md);
}
/* Blends the hero into the flat navy bar above it — no visible seam. */
.hero::before{
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 46%;
  z-index: -1;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--navy-deep) 88%, transparent),
    transparent);
}
/* Ambient warm glow. */
.hero-glow{
  position: absolute;
  z-index: -2;
  width: 330px;
  height: 330px;
  right: -110px;
  top: -130px;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--gold) 62%, transparent) 0%,
    color-mix(in srgb, var(--gold) 18%, transparent) 45%,
    transparent 70%);
  filter: blur(34px);
  opacity: 0.5;
}
.hero.compact{ padding: var(--s-4) var(--s-5) var(--s-5); }
.hero.centred{ padding: var(--s-4) var(--s-5) var(--s-5); text-align: center; }

.hero-top{ display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-5); }
.hero.compact .hero-top{ margin-bottom: 0; }
.hero-top .hero-id{ min-width: 0; }

.hero-logo{
  width: 52px;
  height: 52px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  background: var(--navy-deep);
  box-shadow: 0 0 0 2px var(--gold), 0 0 0 6px color-mix(in srgb, var(--gold) 15%, transparent);
}
.hero-logo.fallback{
  display: grid;
  place-items: center;
  color: var(--gold);
}

.biz-name{
  font-family: var(--font-display);
  font-size: var(--t-lg);
  font-weight: 700;
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  overflow-wrap: anywhere;
}
.biz-sub{
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  margin-top: var(--s-2);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-soft);
}
.hero.centred .biz-sub{ justify-content: center; }
.hero.centred .biz-name{ margin-bottom: var(--s-1); }

.hero-count{
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  margin: 0 0 var(--s-3);
}
.hero-count-fig{ display: flex; align-items: baseline; gap: var(--s-2); }
.hero-count-num{
  font-family: var(--font-display);
  font-size: var(--t-3xl);
  font-weight: 700;
  line-height: 0.95;
  font-variant-numeric: tabular-nums;
  color: var(--cream);
}
.hero-count-of{
  font-size: var(--t-lg);
  line-height: 1;
  color: color-mix(in srgb, var(--gold-soft) 60%, transparent);
}
.hero-count-target{
  font-family: var(--font-display);
  font-size: var(--t-xl);
  font-weight: 500;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: color-mix(in srgb, var(--gold-soft) 78%, transparent);
}
.hero-count-label{
  margin-left: auto;
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-soft);
}

.progress-track{
  position: relative;
  height: 10px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--cream) 15%, transparent);
  overflow: hidden;
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--navy-deep) 45%, transparent);
}
.progress-fill{
  position: relative;
  height: 100%;
  width: 0;
  border-radius: var(--r-full);
  background: var(--grad-gold);
  overflow: hidden;
  transition: width var(--dur-slow) var(--ease);
}
.progress-fill::after{
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grad-sheen);
}

/* -------------------------------------------------------------- panels --- */
.panel{
  background: var(--surface);
  margin: var(--s-3) var(--s-4) 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--s-3) var(--s-5) var(--s-4);
  box-shadow: var(--shadow-sm);
}
.panel.centred{ text-align: center; }
.panel h4{
  font-family: var(--font-body);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--s-2);
}
.panel p{ line-height: var(--lh-body); }

/* Next-reward summary. */
.reward-row-top{ display: flex; align-items: flex-start; gap: var(--s-4); }
.reward-row-top .grow{ flex: 1; min-width: 0; }
.reward-icon{
  width: 48px;
  height: 48px;
  flex: none;
  border-radius: var(--r-md);
  background: var(--grad-navy);
  color: var(--gold);
  display: grid;
  place-items: center;
  box-shadow: var(--shadow-sm);
}
.reward-row-top .rtitle{
  font-family: var(--font-display);
  font-size: var(--t-md);
  font-weight: 700;
  color: var(--text);
  line-height: var(--lh-tight);
  overflow-wrap: anywhere;
}
.reward-row-top .rdesc{
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-top: var(--s-1);
  line-height: var(--lh-snug);
}
.reward-row-top .rmeta{
  display: flex;
  gap: var(--s-2);
  align-items: center;
  flex-wrap: wrap;
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-top: var(--s-2);
}
.rmeta .pill{
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  background: var(--gold-wash);
  color: var(--gold-ink);
  padding: 3px 10px;
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.rmeta .ready{
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  color: var(--ok);
  font-weight: 600;
}

/* -------------------------------------------------------------- stamps --- */
/*
 * A grid, not a scroller.
 *
 * This was a horizontal scroller with fixed 46px circles, which meant a
 * ten-stamp card ran off the side of the phone: the stamps a customer had not
 * earned yet were the ones hidden, and nothing about the row looked swipeable.
 *
 * --cols comes from stampCols() in app.js — six or fewer on one line, seven to
 * ten split across two. The 6 here is only the fallback for markup that sets
 * no variable.
 *
 * The columns are 1fr rather than the width of a circle, so a short card
 * spreads itself across the panel instead of bunching against the left edge,
 * and the space between stamps grows rather than being left over at the end.
 */
.stamp-row{
  display: grid;
  grid-template-columns: repeat(var(--cols, 6), minmax(0, 1fr));
  justify-items: center;
  gap: var(--s-2);
  padding: var(--s-2) 0 var(--s-3);
}

.stamp-circle{
  /*
   * Square by aspect-ratio rather than a matching height, so a circle can give
   * width back on a narrow phone and stay round while it does. The cap stops
   * a three-stamp card drawing three saucers.
   */
  width: 100%;
  max-width: 46px;
  aspect-ratio: 1;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 2px dashed var(--line-strong);
  background: var(--surface-2);
  color: var(--text-soft);
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.stamp-circle .icon{ width: 1.4em; height: 1.4em; }

.stamp-circle.filled{
  border: 2px solid var(--navy);
  background: var(--grad-navy);
  color: var(--gold);
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--gold) 20%, transparent),
    var(--shadow-sm);
}
.stamp-circle.next{
  border: 2px dashed var(--gold);
  background: var(--gold-wash);
  color: var(--gold-ink);
  animation: ck-breathe 2600ms var(--ease-out) infinite;
}
.stamp-circle.goal{ border-style: solid; border-color: var(--line-strong); }
.stamp-circle.goal.next{ border-color: var(--gold); }
.stamp-circle.goal.filled{ border-color: var(--navy); }

@keyframes ck-breathe{
  0%, 100%{ box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 40%, transparent); }
  50%     { box-shadow: 0 0 0 7px color-mix(in srgb, var(--gold) 0%, transparent); }
}
@keyframes ck-pop{
  0%  { transform: scale(0.6); opacity: 0.25; }
  55% { transform: scale(1.08); opacity: 1; }
  100%{ transform: scale(1); opacity: 1; }
}
.stamp-circle.pop{ animation: ck-pop var(--dur-slow) var(--ease) both; }

.stamp-note{
  font-size: var(--t-sm);
  text-align: center;
  color: var(--text-muted);
  margin-top: var(--s-1);
  line-height: var(--lh-snug);
}
.stamp-note b{ color: var(--navy); font-weight: 700; }
/* The clause that reconciles a goal further away than one card. Its own line,
   and quieter than the number above it, because it is an explanation rather
   than news. */
.stamp-note-more{
  display: block;
  margin-top: 2px;
  font-size: var(--t-xs, 0.78rem);
  opacity: 0.85;
}

/* The way from the card to the QR screen, now that the code itself is not
   sitting on the home tab. Centred under the stamp note, not full width — it
   is an option, not the thing the page is asking you to do. */
.qr-open{
  display: flex;
  align-items: center;
  gap: var(--s-1);
  margin: var(--s-3) auto 0;
  width: auto;
}

/* ----------------------------------------------------------- info rows --- */
.info-row{
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-sm);
  color: var(--text);
  padding: var(--s-1) 0;
  line-height: var(--lh-snug);
}
.info-row .dot{
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 50%;
  background: var(--gold-wash);
  color: var(--gold-ink);
  display: grid;
  place-items: center;
}
.info-row .val{ font-weight: 700; color: var(--navy); }
.info-row.stack{ align-items: flex-start; }
.info-row .grow{ flex: 1; min-width: 0; }
.info-row a{ color: var(--navy); font-weight: 600; }

/* ------------------------------------------------------------- QR + PIN -- */
.qr-plaque{
  display: flex;
  justify-content: center;
  width: -moz-fit-content;
  width: fit-content;
  margin: var(--s-3) auto 0;
  padding: var(--s-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-md);
  max-width: 100%;
  overflow: hidden;
}
.qr-plaque.lg{ margin-top: 0; padding: var(--s-3); box-shadow: var(--shadow-lg); }
.qr-plaque img,
.qr-plaque canvas{ display: block; border-radius: var(--r-sm); }
.qr-fallback{
  font-size: var(--t-sm);
  color: var(--text-muted);
  padding: var(--s-5) var(--s-3);
  text-align: center;
  max-width: 190px;
  line-height: var(--lh-snug);
  word-break: break-all;
}

.pin-line{
  text-align: center;
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-top: var(--s-2);
  line-height: var(--lh-snug);
}
.pin-line b{
  display: inline-block;
  margin-top: var(--s-2);
  font-family: var(--font-mono);
  font-size: var(--t-md);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.32em;
  padding-left: 0.32em;
  color: var(--navy);
}

.pin-cap{
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: var(--s-3);
}
.pin-big{
  font-family: var(--font-mono);
  font-size: var(--t-2xl);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.34em;
  line-height: 1.25;
  color: var(--navy);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-2) var(--s-4);
  padding-left: calc(var(--s-4) + 0.34em);  /* balances the trailing tracking */
  margin: var(--s-2) 0 var(--s-3);
  box-shadow: var(--shadow-inset);
  overflow-wrap: anywhere;
}

/* The 5-minute window, as a thin bar plus the live m:ss readout. */
.qr-timer{ margin-top: var(--s-2); }
.qr-timer-track{
  height: 4px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  overflow: hidden;
}
.qr-timer-fill{
  display: block;
  height: 100%;
  width: 100%;
  border-radius: var(--r-full);
  background: var(--grad-gold);
  transform-origin: left center;
  animation: ck-drain 300s linear infinite;
}
@keyframes ck-drain{ from{ transform: scaleX(1); } to{ transform: scaleX(0); } }

.countdown{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin-top: var(--s-3);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}
.countdown .tick{
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--gold);
  animation: ck-blink 2000ms var(--ease-out) infinite;
}
@keyframes ck-blink{ 0%,100%{ opacity:1; } 50%{ opacity:0.25; } }

.explain{
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-top: var(--s-3);
  margin-bottom: var(--s-3);
  line-height: var(--lh-snug);
}

/* ------------------------------------------------------- reward tiers ---- */
.rewards-list{ display: flex; flex-direction: column; gap: var(--s-2); }
.reward-row{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-2) var(--s-3);
  font-size: var(--t-sm);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.reward-row .tier{ display: flex; align-items: center; gap: var(--s-3); min-width: 0; }
.reward-row .tier-ico{
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: var(--r-sm);
  background: var(--gold-wash);
  color: var(--gold-ink);
  display: grid;
  place-items: center;
}
.reward-row .tier > span{ min-width: 0; }
.tier-title{
  display: block;
  font-weight: 700;
  color: var(--text);
  overflow-wrap: anywhere;
}
.tier-sub{
  display: block;
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-top: 2px;
  line-height: var(--lh-snug);
  overflow-wrap: anywhere;
}
.reward-row.unlocked{
  border-color: color-mix(in srgb, var(--gold) 62%, transparent);
  background: linear-gradient(180deg, var(--surface), var(--gold-wash));
  box-shadow: var(--shadow-sm);
}
.reward-row.unlocked .tier-ico{ background: var(--grad-navy); color: var(--gold); }

.badge{
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  flex: none;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  background: var(--grad-gold);
  color: var(--navy-deep);
  padding: 5px 11px;
  border-radius: var(--r-full);
  white-space: nowrap;
}
.badge.muted{ background: var(--gold-wash); color: var(--gold-ink); }
.badge.quiet{
  background: var(--surface-3);
  color: var(--text);          /* --text-muted only reaches 4.3:1 on this sand */
  font-weight: 600;
}

/* -------------------------------------------------------- reward vault --- */
/* Rewards earned but not carried home yet. The banner sits above the QR on
   the QR tab, so it has to outrank the code visually. */
.vault-banner{
  position: relative;
  margin: var(--s-3) var(--s-4) 0;
  padding: var(--s-4);
  border-radius: var(--r-lg);
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--gold-wash) 55%, var(--surface)),
    var(--gold-wash));
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  border-left: 4px solid var(--gold);
  box-shadow: var(--shadow-gold);
  color: var(--gold-ink);
}
.vault-banner-head{
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0 0 var(--s-3);
  font-family: var(--font-display);
  font-size: var(--t-md);
  font-weight: 700;
  line-height: var(--lh-snug);
  color: var(--navy-deep);
}
.vault-panel{ border-color: color-mix(in srgb, var(--gold) 55%, transparent); }
.vault-panel h4{ color: var(--gold-ink); }

.vault-card{
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
  border-radius: var(--r-md);
  padding: var(--s-3);
  text-align: center;
  box-shadow: var(--shadow-xs);
}
.vault-card + .vault-card{ margin-top: var(--s-2); }
.vault-card-head{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-width: 0;
}
.vault-ico{
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: var(--r-sm);
  background: var(--grad-navy);
  color: var(--gold);
  display: grid;
  place-items: center;
}
.vault-name{
  font-family: var(--font-display);
  font-size: var(--t-md);
  font-weight: 700;
  color: var(--text);
  min-width: 0;
  overflow-wrap: anywhere;
}
.vault-code{
  font-family: var(--font-mono);
  font-size: var(--t-lg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.28em;
  color: var(--navy-deep);
  background: var(--gold-wash);
  border: 1px dashed var(--gold);
  border-radius: var(--r-md);
  padding: var(--s-2) var(--s-3);
  padding-left: calc(var(--s-3) + 0.28em);  /* balances the trailing tracking */
  margin: var(--s-3) 0 var(--s-2);
  overflow-wrap: anywhere;
  animation: ck-code-glow 2400ms var(--ease-out) infinite;
}
@keyframes ck-code-glow{
  0%, 100%{ box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 0%, transparent); }
  50%     { box-shadow: 0 0 0 5px color-mix(in srgb, var(--gold) 26%, transparent); }
}
.vault-facts{
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--t-sm);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
.vault-fact{ display: inline-flex; align-items: center; justify-content: center; gap: var(--s-1); }
.vault-fact.forever{ color: var(--ok); }
.vault-fact.deadline{ color: var(--gold-ink); font-weight: 600; }
.vault-cta{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin: var(--s-3) 0 0;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--gold-ink);
  text-align: center;
  line-height: var(--lh-snug);
}

/* Profile tab: "My rewards" */
.vault-sub{
  font-size: var(--t-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  font-weight: 700;
  color: var(--gold-ink);
  margin: 0 0 var(--s-3);
}
.vault-sub.muted{ color: var(--text-muted); margin-top: var(--s-4); }
.vault-past{
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-2) var(--s-3);
  margin-bottom: var(--s-2);
  background: var(--surface-2);
}
.vault-past:last-of-type{ margin-bottom: 0; }
.vault-past .vp-top{
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  font-size: var(--t-sm);
}
.vault-past .vp-top b{ color: var(--text); min-width: 0; overflow-wrap: anywhere; }
.vault-past .when{ color: var(--text-muted); white-space: nowrap; font-size: var(--t-xs); }

/* Reward-tab receipt status */
.rstatus{
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  flex: none;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 4px 10px;
  border-radius: var(--r-full);
  background: var(--gold-wash);
  color: var(--gold-ink);
  white-space: nowrap;
}
.rstatus.waiting{ background: var(--grad-gold); color: var(--navy-deep); }
.rstatus.collected{ background: var(--ok-bg); color: var(--ok); }
.rstatus.void{ background: var(--surface-3); color: var(--text-muted); }

.redeem-row{
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-2) var(--s-3);
  margin-bottom: var(--s-2);
  background: var(--surface);
}
.redeem-row:last-child{ margin-bottom: 0; }
.redeem-row.waiting{
  border-color: color-mix(in srgb, var(--gold) 62%, transparent);
  background: linear-gradient(180deg, var(--surface), var(--gold-wash));
  box-shadow: var(--shadow-sm);
}
.redeem-row .rtop{
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  font-size: var(--t-sm);
  align-items: flex-start;
}
.redeem-row .rtop b{ color: var(--text); min-width: 0; overflow-wrap: anywhere; }
.code{
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--t-md);
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--navy);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-3);
  padding-left: calc(var(--s-3) + 0.18em);
  margin-top: var(--s-3);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------- history --- */
.day-head{
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  padding: var(--s-3) 0 var(--s-1);
  border-bottom: 1px solid var(--line);
  margin-bottom: 2px;
}
.day-head:first-child{ padding-top: 0; }

.history-item{
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-sm);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--line);
}
.history-item:last-child{ border-bottom: none; }
.history-item .h-ico{
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--navy);
  color: var(--gold);
}
.history-item.is-redeem .h-ico{ background: var(--gold-wash); color: var(--gold-ink); }
.history-item .h-main{ flex: 1; min-width: 0; }
.history-item .h-label{ color: var(--text); font-weight: 600; overflow-wrap: anywhere; }
.history-item.is-redeem .h-label{ color: var(--gold-ink); }
.history-item .h-meta{ color: var(--text-muted); font-size: var(--t-xs); margin-top: 2px; line-height: var(--lh-snug); }
.history-item .h-delta{
  font-weight: 700;
  color: var(--navy);
  font-size: var(--t-md);
  flex: none;
  font-variant-numeric: tabular-nums;
}
.history-item .h-delta.minus{ color: var(--danger); }
.history-item .when{ color: var(--text-muted); white-space: nowrap; }
.history-item .delta{ font-weight: 700; color: var(--navy); }

.tz-note{ text-align: center; margin-top: var(--s-4); }
.empty{
  font-size: var(--t-sm);
  color: var(--text-muted);
  text-align: center;
  padding: var(--s-4) 0;
}

/* --------------------------------------------------------------- inbox --- */
.inbox{ margin-top: var(--s-2); }
.inbox-item{
  display: flex;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--line);
}
.inbox-item:last-child{ border-bottom: none; }
.inbox-item .ico{
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 50%;
  background: var(--gold-wash);
  color: var(--gold-ink);
  display: grid;
  place-items: center;
}
.inbox-item .grow{ flex: 1; min-width: 0; }
.inbox-item .m-title{ font-size: var(--t-sm); font-weight: 700; overflow-wrap: anywhere; }
.inbox-item .m-body{ font-size: var(--t-sm); color: var(--text-muted); margin-top: 2px; line-height: var(--lh-snug); }
.inbox-item .m-ago{ font-size: var(--t-xs); color: var(--text-muted); margin-top: var(--s-1); }
.inbox-item.unread .m-title::after{
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--gold);
  margin-left: var(--s-2);
  vertical-align: middle;
}

/* ---------------------------------------------------------- bottom nav --- */
.bottomnav{
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  max-width: var(--app-max);
  margin: 0 auto;
  display: flex;
  justify-content: space-around;
  align-items: center;
  background: color-mix(in srgb, var(--surface) 86%, transparent);
  -webkit-backdrop-filter: blur(16px) saturate(1.5);
  backdrop-filter: blur(16px) saturate(1.5);
  border-top: 1px solid var(--line);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: 0 -8px 28px rgba(30, 42, 46, 0.10);
  padding: var(--s-2) var(--s-2) calc(var(--s-3) + var(--safe-bottom));
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){
  .bottomnav{ background: var(--surface); }
}

.bottomnav .navitem{
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-width: 56px;
  min-height: 48px;
  padding: var(--s-1) var(--s-2);
  font-family: var(--font-body);
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--text-muted);
  background: none;
  border: none;
  border-radius: var(--r-md);
  cursor: pointer;
  position: relative;
  transition: color var(--dur) var(--ease), transform var(--dur-fast) var(--ease);
}
.bottomnav .navitem:active{ transform: scale(0.97); }
.bottomnav .navitem.active-nav{ color: var(--navy); font-weight: 600; }
.bottomnav .navicon{ display: inline-flex; line-height: 1; }
.bottomnav .navicon .icon{ width: 1.5em; height: 1.5em; }
.bottomnav .navlabel{ line-height: 1.2; }

.nav-dot{
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--gold);
  opacity: 0;
  transform: scale(0.4);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.navitem.active-nav .nav-dot{ opacity: 1; transform: none; }

.bottomnav .navitem.center{
  width: 60px;
  height: 60px;
  min-width: 60px;
  min-height: 60px;
  flex: none;
  border-radius: 50%;
  background: var(--grad-navy);
  color: var(--gold);
  display: grid;
  place-items: center;
  margin-top: -32px;
  padding: 0;
  gap: 0;
  box-shadow: var(--shadow-gold), 0 0 0 5px var(--surface);
}
.bottomnav .navitem.center .navicon .icon{ width: 1.75em; height: 1.75em; }
.bottomnav .navitem.center.active-nav{
  background: var(--grad-gold);
  color: var(--navy-deep);
}

.nav-badge{
  position: absolute;
  top: -1px;
  right: 4px;
  min-width: 18px;
  height: 18px;
  border-radius: var(--r-full);
  background: var(--grad-gold);
  color: var(--navy-deep);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  padding: 0 5px;
  box-shadow: 0 0 0 2px var(--surface);
}

/* -------------------------------------------------------------- toasts --- */
.toast-wrap{
  position: fixed;
  top: calc(var(--safe-top) + var(--s-3));
  left: 0;
  right: 0;
  z-index: 70;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  pointer-events: none;
  padding: 0 var(--s-3);
}
.toast{
  background: var(--grad-navy);
  color: var(--cream);
  border-left: 4px solid var(--gold);
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-5);
  font-size: var(--t-sm);
  font-weight: 600;
  line-height: var(--lh-snug);
  box-shadow: var(--shadow-lg);
  max-width: min(100%, var(--app-max));
  animation: ck-toast-in var(--dur-slow) var(--ease) both;
}
.toast.leaving{ animation: ck-toast-out var(--dur) var(--ease-in) both; }
@keyframes ck-toast-in{ from{ transform: translateY(-16px); opacity: 0; } to{ transform: none; opacity: 1; } }
@keyframes ck-toast-out{ from{ transform: none; opacity: 1; } to{ transform: translateY(-16px); opacity: 0; } }

/* --------------------------------------------------------- celebration --- */
#ck-confetti{
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 60;
}
.celebrate{
  position: fixed;
  inset: 0;
  z-index: 65;
  background: color-mix(in srgb, var(--navy-deep) 62%, transparent);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-6);
  animation: ck-fade-in var(--dur) var(--ease) both;
}
.celebrate .card{
  background: var(--surface);
  border-radius: var(--r-xl);
  padding: var(--s-8) var(--s-6) var(--s-6);
  text-align: center;
  max-width: 340px;
  width: 100%;
  box-shadow: var(--shadow-lg);
  animation: ck-pop var(--dur-slow) var(--ease-back) both;
}
.celebrate .big{
  width: 76px;
  height: 76px;
  margin: 0 auto var(--s-5);
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--grad-navy);
  color: var(--gold);
  box-shadow: var(--shadow-gold);
}
.celebrate .big .icon{ width: 2.25em; height: 2.25em; }
.celebrate h3{
  font-family: var(--font-display);
  margin: 0 0 var(--s-3);
  font-size: var(--t-lg);
  color: var(--navy);
  text-wrap: balance;
}
.celebrate p{
  margin: 0 0 var(--s-6);
  font-size: var(--t-sm);
  color: var(--text-muted);
  line-height: var(--lh-body);
}
@keyframes ck-fade-in{ from{ opacity: 0; } to{ opacity: 1; } }

/* ------------------------------------------------------------- loading --- */
.loading{
  padding: var(--s-6) var(--s-5);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--t-sm);
}
.spinner{
  width: 28px;
  height: 28px;
  border: 3px solid var(--surface-3);
  border-top-color: var(--gold);
  border-radius: 50%;
  margin: 0 auto var(--s-3);
  animation: ck-spin 900ms linear infinite;
}
@keyframes ck-spin{ to{ transform: rotate(360deg); } }

/* --------------------------------------------------------------- misc ---- */
.row-between{ display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); }
.row-between h4{ margin: 0; }
/* ---------------------------------------------------------- your details ---
   Name, birthday and email in one small form. The two birthday selects sit on
   one line on any phone worth supporting and stack below 300px, which is
   narrower than any device but is what a 200% text zoom looks like. */
.details-form .field:last-of-type{ margin-bottom: var(--s-3); }
.details-form .field label .opt{
  margin-left: var(--s-2);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-soft);
}
.bday-row{ display: flex; gap: var(--s-2); }
/* The day is a two-digit number and the month is a word, so they are not the
   same width. 1fr/1.6fr rather than equal halves. */
.bday-row select:first-child{ flex: 1 1 0; min-width: 0; }
.bday-row select:last-child{ flex: 1.6 1 0; min-width: 0; }
@media (max-width: 300px){
  .bday-row{ flex-direction: column; }
}
.details-acts{ display: flex; align-items: center; gap: var(--s-3); }
.details-acts .btn{ margin-top: 0; }
/* "Birthday not given" — a fact, not a warning, so it is quiet rather than red. */
.info-row .none{ color: var(--text-soft); font-style: italic; }
.stat-line{
  display: flex;
  justify-content: space-between;
  font-size: var(--t-sm);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--line);
}
.stat-line:last-child{ border-bottom: none; }
.stat-line span:last-child{ font-weight: 700; color: var(--navy); }
.push-msg{ margin-top: var(--s-3); }
.push-btn{ margin-top: var(--s-3); }

.sr-only{
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------- narrow phones ------- */
@media (max-width: 359px){
  .stamp-circle{ width: 42px; height: 42px; }
  .pin-big{ font-size: var(--t-xl); letter-spacing: 0.24em; padding-left: calc(var(--s-4) + 0.24em); }
  .hero-count-num{ font-size: var(--t-2xl); }
}

/* ----------------------------------------------------- reduced motion ---- */
@media (prefers-reduced-motion: reduce){
  .stamp-circle.pop,
  .stamp-circle.next,
  .countdown .tick,
  .qr-timer-fill,
  .vault-code,
  .celebrate .card,
  .toast{ animation-name: none !important; }

  .qr-timer-fill{ transform: none; opacity: 0.6; }
  .vault-code{ box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 22%, transparent); }
  .btn:active,
  .bottomnav .navitem:active{ transform: none; }
}

/* ===========================================================================
 *  Shop details on the sign-in screen
 *
 *  Someone opening this has usually just scanned a code on a counter. Before
 *  they type their number in they want to know whose card this is, where the
 *  shop is, and what a full card actually gets them.
 * ======================================================================== */

.shop-info{
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.shop-info h2{
  font-family: var(--font-body);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-soft);
  margin: 0 0 var(--s-3);
}
.shop-info .si-row{
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  font-size: var(--t-sm);
  line-height: var(--lh-snug);
  color: var(--text-muted);
}
.shop-info .si-row .icon{
  flex: none;
  margin-top: 2px;
  color: var(--gold-ink);
}
.shop-info .si-row a{
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
}
.shop-info .si-row a:hover{ color: var(--text); }

/* A running campaign is the one line worth pulling the eye to. */
.shop-info .si-row.is-gold{
  color: var(--gold-ink);
  font-weight: 600;
}

/* ===========================================================================
 *  Spin to win
 *
 *  The wheel is one round element with the prize icons placed around it by
 *  rotation, rather than real pie slices: a shop can have three prizes or
 *  twelve, and this reads correctly either way without generating a different
 *  SVG for each count. The conic gradient behind them does the segment colours.
 * ======================================================================== */

.spin-panel{ text-align: center; }
.spin-panel h4{ justify-content: center; }

.wheel-wrap{
  position: relative;
  width: min(260px, 78vw);
  aspect-ratio: 1;
  margin: var(--s-4) auto var(--s-5);
}

/* The marker the winning segment stops under. */
.wheel-pointer{
  position: absolute;
  top: -6px;
  left: 50%;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-left: 11px solid transparent;
  border-right: 11px solid transparent;
  border-top: 18px solid var(--gold);
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .3));
  z-index: 2;
}

.wheel{
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    repeating-conic-gradient(
      var(--navy) 0deg calc(360deg / var(--n)),
      var(--navy-soft) calc(360deg / var(--n)) calc(720deg / var(--n))
    );
  box-shadow:
    0 0 0 6px var(--gold),
    0 0 0 8px var(--navy-deep),
    var(--shadow-lg);
  transform: rotate(0deg);
  will-change: transform;
}

/* A hub, so the wheel reads as a wheel rather than a pie chart. */
.wheel::after{
  content: "";
  position: absolute;
  inset: 38%;
  border-radius: 50%;
  background: var(--grad-gold);
  box-shadow: inset 0 0 0 3px var(--navy-deep);
}

/* Each icon is pushed out along its own segment's centre line. */
.wheel-seg{
  position: absolute;
  inset: 0;
  transform: rotate(calc(var(--i) * var(--seg) + var(--seg) / 2));
  pointer-events: none;
}
.wheel-seg .ws-ico{
  position: absolute;
  top: 9%;
  left: 50%;
  transform: translateX(-50%);
  color: var(--gold-soft);
  display: block;
}
.wheel-seg .ws-ico .icon{ width: 1.5em; height: 1.5em; }

.spin-result{ min-height: 4px; }

.spin-won{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-1);
  padding: var(--s-4);
  margin-bottom: var(--s-4);
  border-radius: var(--r-lg);
  background: var(--gold-wash);
  color: var(--gold-ink);
  animation: spin-won-in var(--dur-slow) var(--ease-back) both;
}
.spin-won .sw-ico{ color: var(--gold-ink); }
.spin-won strong{ font-size: var(--t-md); color: var(--navy); }
.spin-won .sw-code{ font-size: var(--t-sm); }
.spin-won .sw-code b{
  font-family: var(--font-mono);
  letter-spacing: .08em;
  color: var(--navy);
}

@keyframes spin-won-in {
  from { opacity: 0; transform: scale(.9); }
  to   { opacity: 1; transform: none; }
}

/* What is on the wheel, in words — the icons alone are not enough to know. */
.spin-prizes{
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  justify-content: center;
  margin-top: var(--s-3);
}
.spin-prizes .sp-chip{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: var(--r-full);
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: var(--t-xs);
  color: var(--text-muted);
}
.spin-prizes .sp-chip .icon{ color: var(--gold-ink); }

@media (prefers-reduced-motion: reduce){
  .wheel{ transition: none !important; }
  .spin-won{ animation: none; }
}

/* ------------------------------------------------- prize photographs --- */
/* A shop can put a picture of the actual thing on a prize. It has to read at
   about forty pixels on a wedge, so it is cropped to a circle and centred —
   a photo of a cake at that size is a colour and a shape, which is enough to
   tell the wedges apart at a glance. */
.wheel-seg .ws-img{
  width: 30px; height: 30px; border-radius: 50%;
  object-fit: cover; display: block;
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--on-main) 55%, transparent);
}
.spin-prizes .sp-chip .sp-img{
  width: 20px; height: 20px; border-radius: 50%;
  object-fit: cover; display: inline-block; vertical-align: -5px;
  margin-right: 2px;
}
.spin-won .sw-img{
  width: 56px; height: 56px; border-radius: 50%;
  object-fit: cover; display: block; margin: 0 auto;
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 45%, transparent);
}

/* ------------------------------------------------------- find us online --- */
/* A row of taps, not a list of links: on a phone these are thumbed, and a
   45px target with the network's name under it is easier to hit than a line
   of underlined text. */
.social-row{
  display: grid; gap: var(--s-2);
  grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
}
.social-link{
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: var(--s-3) var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--navy);
  text-decoration: none;
  font-size: var(--t-xs);
  font-weight: 600;
  min-height: 72px; justify-content: center;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.social-link .icon{ width: 22px; height: 22px; color: var(--accent-ink, var(--gold-ink)); }
.social-link:hover{ background: var(--surface-2); border-color: var(--line-strong); }
.social-link:active{ transform: scale(.97); }

/* The QR on a reward waiting to be collected. Smaller than the card's own
   code — it is scanned across a counter, not read at arm's length. */
.vault-qr{
  width: 132px; height: 132px; margin: var(--s-3) auto var(--s-2);
  padding: 8px; border-radius: var(--r-md);
  background: #fff; display: grid; place-items: center;
  box-shadow: var(--shadow-xs);
}
.vault-qr canvas, .vault-qr img{ display: block; width: 100%; height: 100%; }

/* ===========================================================================
 *  Getting there, sharing it, installing it
 *
 *  Four small things that all belong to the same idea: the card is on a phone,
 *  and a phone can do more than show a page.
 * ======================================================================== */

/* "Get directions", sitting on the same line as the address it belongs to.
   Inline rather than a button: it is a second thought after reading where the
   shop is, not the thing the row is for. */
.dir-link{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: var(--s-2);
  white-space: nowrap;
  font-size: var(--t-xs);
  font-weight: 700;
  color: var(--gold-ink);
  text-decoration: none;
  border-bottom: 0;
  vertical-align: baseline;
}
.dir-link .icon{ width: 14px; height: 14px; flex: none; }
.dir-link:hover{ color: var(--navy); text-decoration: underline; }
.shop-info .si-row a.dir-link{ border-bottom: 0; color: var(--gold-ink); }
.shop-info .si-row a.dir-link:hover{ color: var(--navy); }
.info-row a.dir-link{ color: var(--gold-ink); }

/* The same link on the navy hero, where the address is already a caption. */
.hero-link{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--gold) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--gold) 34%, transparent);
  color: var(--gold-soft);
  text-decoration: none;
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  white-space: nowrap;
}
.hero-link .icon{ width: 13px; height: 13px; flex: none; }
.hero-link:hover{ background: color-mix(in srgb, var(--gold) 26%, transparent); }

/* Stamps left, said again in visits. Quieter than the line above it: it is an
   estimate, and it should not read as a promise. */
.stamp-estimate{
  margin-top: 2px;
  font-size: var(--t-xs);
  text-align: center;
  color: var(--text-soft);
  line-height: var(--lh-snug);
}

.share-btn{ display: flex; align-items: center; gap: var(--s-2); margin: 0 auto; }

/* ------------------------------------------------- add to the home screen --- */
.install-row{ display: flex; flex-direction: column; gap: var(--s-3); }
.install-row .ir-main{ display: flex; align-items: flex-start; gap: var(--s-3); }
.install-row .ir-ico{
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 50%;
  background: var(--gold-wash);
  color: var(--gold-ink);
  display: grid;
  place-items: center;
}
.install-row .grow{ flex: 1; min-width: 0; }
.install-row .ir-title{ display: block; font-size: var(--t-sm); font-weight: 700; color: var(--navy); }
.install-row .ir-sub{
  display: block;
  margin-top: 2px;
  font-size: var(--t-xs);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
.install-row .ir-acts{ display: flex; align-items: center; justify-content: flex-end; gap: var(--s-2); }
.install-row .ir-acts .btn{ margin-top: 0; }

/* ----------------------------------------------- a message with a link --- */
/* The whole row becomes the link, so the tap target is the message rather
   than a word inside it. */
a.inbox-item{ color: inherit; text-decoration: none; }
.inbox-item .ico{ display: grid; }
.inbox-item .grow{ display: block; }
.inbox-item .m-title,
.inbox-item .m-body,
.inbox-item .m-ago{ display: block; }
.inbox-item .m-ago{ display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.inbox-item .m-open{
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-weight: 700;
  color: var(--gold-ink);
}
a.inbox-item:hover{ background: var(--surface-2); }
a.inbox-item:hover .m-title{ text-decoration: underline; }

/* ------------------------------------------- why the code keeps changing --- */
.why{
  margin-top: var(--s-1);
  text-align: left;
  font-size: var(--t-sm);
  color: var(--text-muted);
}
.why summary{
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: var(--s-1) 0;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-ink);
}
.why summary::-webkit-details-marker{ display: none; }
.why summary::after{
  content: "";
  width: 7px;
  height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform var(--dur) var(--ease);
}
.why[open] summary::after{ transform: rotate(-135deg) translate(-2px, -2px); }
.why p{
  margin: var(--s-1) 0 0;
  line-height: var(--lh-snug);
  color: var(--text-muted);
}
.panel.centred .why{ text-align: left; }

/* A reward tier's own photograph, when the shop uploaded one.
   Fills the same box the glyph sits in, so a card with a mix of photos and
   glyphs still lines up. See rewardFace() in app.js. */
.reward-icon .reward-pic{
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  border-radius: inherit;
}
.reward-row .tier-ico .tier-pic{
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  border-radius: inherit;
}

/* ==========================================================================
   MILESTONES — halfway, one to go, and a card that is full
   A quiet strip under the circles. Loud enough to notice on the way past,
   never loud enough to compete with the stamps themselves.
   ========================================================================== */
.milestone{
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
}
.milestone .ms-ico{
  width: 34px;
  height: 34px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--gold-wash);
  color: var(--gold-ink);
}
.milestone .ms-ico .icon{ width: 18px; height: 18px; }
.milestone .ms-text{ display: block; min-width: 0; }
.milestone .ms-title{
  display: block;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--navy);
}
.milestone .ms-line{
  display: block;
  margin-top: 1px;
  font-size: var(--t-xs);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
.milestone.is-complete{
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
  background: var(--gold-wash);
}
.milestone.is-complete .ms-ico{ background: var(--gold); color: var(--navy); }

/* ==========================================================================
   VISIT STREAK
   ========================================================================== */
.streak{
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-3);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-full);
  background: var(--surface-3);
}
.streak .streak-ico{
  width: 28px;
  height: 28px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--gold);
  color: var(--navy);
}
.streak .streak-ico .icon{ width: 15px; height: 15px; }
.streak .streak-text{ display: block; min-width: 0; }
.streak .streak-text b{
  display: block;
  font-size: var(--t-sm);
  color: var(--navy);
}
.streak .streak-text span{
  display: block;
  font-size: var(--t-xs);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
/* A streak with nothing in it yet this week is alive but owed a visit. */
.streak.is-due .streak-ico{ background: var(--surface); color: var(--gold-ink); box-shadow: inset 0 0 0 2px var(--gold); }

/* ==========================================================================
   NOTIFICATION SWITCHES
   A real checkbox, moved off screen but still focusable, with the track and
   knob drawn beside it — so the keyboard, a screen reader and the tap target
   are all the input itself rather than a div pretending.
   ========================================================================== */
/* A rule above the switches so they read as their own thing rather than as
   the last row of the inbox that may be open directly above them. */
.prefs{
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  margin: var(--s-3) 0;
  padding-top: var(--s-2);
  border-top: 1px solid var(--line);
}
.switch-row{
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--line);
}
.switch-row:last-child{ border-bottom: 0; }
.switch-row.busy{ opacity: .6; }
.switch-row .sw-text{ flex: 1; min-width: 0; cursor: pointer; }
.switch-row .sw-title{
  display: block;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--navy);
}
.switch-row .sw-sub{
  display: block;
  margin-top: 1px;
  font-size: var(--t-xs);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}
.switch{ position: relative; flex: none; width: 50px; height: 30px; }
.switch input{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 2;
}
.switch .sw-track{
  position: absolute;
  inset: 0;
  border-radius: var(--r-full);
  background: var(--line-strong);
  transition: background var(--dur) var(--ease);
}
.switch .sw-knob{
  position: absolute;
  top: 3px;
  left: 3px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-xs);
  transition: transform var(--dur) var(--ease);
}
.switch input:checked ~ .sw-track{ background: var(--gold); }
.switch input:checked ~ .sw-track .sw-knob{ transform: translateX(20px); }
.switch input:focus-visible ~ .sw-track{ outline: 2px solid var(--navy); outline-offset: 2px; }
.switch input:disabled{ cursor: default; }
@media (prefers-reduced-motion: reduce){
  .switch .sw-track, .switch .sw-knob{ transition: none; }
}

/* ==========================================================================
   THE CARD WITH NO SIGNAL
   Everything here has to read as "this is a copy". The bar is the loudest
   thing on the screen on purpose: a stale card that looks live is worse than
   no card at all.
   ========================================================================== */
.offline-bar{
  display: flex;
  align-items: center;
  gap: var(--s-3);
  /* Matches the panel gutter: the offline screen has no tab body wrapper. */
  margin: var(--s-3) var(--s-4) 0;
  padding: var(--s-3) var(--s-4);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
  border-radius: var(--r-md);
  background: var(--warn-bg);
  color: var(--warn);
}
.offline-bar .ob-ico{ flex: none; display: grid; place-items: center; }
.offline-bar .ob-ico .icon{ width: 22px; height: 22px; }
.offline-bar .ob-text{ display: block; min-width: 0; }
.offline-bar .ob-text b{ display: block; font-size: var(--t-sm); }
.offline-bar .ob-text span{
  display: block;
  font-size: var(--t-xs);
  line-height: var(--lh-snug);
}
/* The card itself, drained of its shine so it cannot be mistaken for live. */
.hero.is-stale{ filter: saturate(.85); }
.hero.is-stale .chip.stale-chip{
  background: rgba(255, 255, 255, .16);
  color: var(--text-on-dark);
  letter-spacing: var(--track-wide);
}
.offline-qr{ text-align: center; }
.offline-qr .oq-ico{
  width: 56px;
  height: 56px;
  margin: 0 auto var(--s-2);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-soft);
}
.offline-qr .btn{ margin-top: var(--s-3); display: inline-flex; align-items: center; gap: var(--s-2); }

/* ==========================================================================
   THE ACHIEVEMENT IMAGE
   ========================================================================== */
.achievement{ text-align: center; }
.achievement .share-btn{ margin-top: var(--s-3); }
.celebrate .card .share-btn{ margin: var(--s-3) auto 0; }

/* ==========================================================================
   LEVELS — bronze, silver, gold
   Recognition, and it has to look like recognition rather than like a reward:
   the rung names are the loud part and there is no button anywhere in here,
   because there is nothing to claim.
   ========================================================================== */
.level-chip{
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  padding: 5px 11px;
  border-radius: var(--r-full);
  background: var(--grad-gold);
  color: var(--navy-deep);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  white-space: nowrap;
}
.level-chip .icon{ width: 13px; height: 13px; }
.level-track{
  height: 8px;
  margin-top: var(--s-3);
  border-radius: var(--r-full);
  background: var(--surface-3);
  overflow: hidden;
}
.level-fill{
  height: 100%;
  border-radius: var(--r-full);
  background: var(--grad-gold);
  transition: width var(--dur-slow) var(--ease);
}
@media (prefers-reduced-motion: reduce){
  .level-fill{ transition: none; }
}
/* One column per rung, so three rungs read as a ladder rather than as a list.
   The last one is right-aligned and the first left, so the ends line up with
   the ends of the bar above them. */
.level-rungs{
  display: flex;
  justify-content: space-between;
  gap: var(--s-2);
  margin-top: var(--s-2);
}
.level-rung{ min-width: 0; text-align: center; }
.level-rung:first-child{ text-align: left; }
.level-rung:last-child{ text-align: right; }
.level-rung b{
  display: block;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-soft);
  overflow-wrap: anywhere;
}
.level-rung span{
  display: block;
  margin-top: 1px;
  font-size: var(--t-xs);
  color: var(--text-muted);
}
.level-rung.is-on b{ color: var(--gold-ink); }
.level-panel .explain{ margin-bottom: 0; }

/* ==========================================================================
   A BIRTHDAY TREAT
   The one panel on the card allowed to be warm. It only ever appears when the
   shop actually offers something, so it never raises a hope by itself.
   ========================================================================== */
.treat{ display: flex; align-items: center; gap: var(--s-3); }
.treat .treat-ico{
  width: 46px;
  height: 46px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--gold-wash);
  color: var(--gold-ink);
}
.treat .treat-text{ display: block; min-width: 0; }
.treat .treat-head{
  display: block;
  font-size: var(--t-md);
  font-family: var(--font-display);
  color: var(--navy);
}
.treat .treat-line{
  display: block;
  margin-top: 2px;
  font-size: var(--t-sm);
  color: var(--text-muted);
  line-height: var(--lh-snug);
  overflow-wrap: anywhere;
}
/* On the day itself, and while the window is still open, it is gold. Coming up
   in three weeks is a note, not an event. */
.treat.is-today,
.treat.is-open{
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
  background: linear-gradient(180deg, var(--surface), var(--gold-wash));
}
.treat.is-today .treat-ico,
.treat.is-open .treat-ico{ background: var(--gold); color: var(--navy); }

/* ==========================================================================
   NOTICES — stamps running out, and a reward you have asked for
   One shape for "something is true right now and you may want to act on it".
   ========================================================================== */
.notice{
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
}
/* Sitting inside a panel of its own on the tab body, it needs the panel gutter. */
#ck-tabbody > .notice{ margin: var(--s-3) var(--s-4) 0; }
.notice .nt-ico{ flex: none; display: grid; place-items: center; }
.notice .nt-ico .icon{ width: 22px; height: 22px; }
.notice .nt-text{ display: block; flex: 1; min-width: 0; }
.notice .nt-text b{ display: block; font-size: var(--t-sm); }
.notice .nt-text span{
  display: block;
  margin-top: 1px;
  font-size: var(--t-xs);
  line-height: var(--lh-snug);
  overflow-wrap: anywhere;
}
.notice .icon-btn{ flex: none; }
/* Stamps about to go. Amber, not red: nothing has happened yet, and there is
   something the customer can still do about it. */
.notice.is-warn{
  border-color: color-mix(in srgb, var(--warn) 40%, transparent);
  background: var(--warn-bg);
  color: var(--warn);
}
.notice.is-warn .nt-text span{ color: inherit; }
/* A reward the customer has asked for. Informational — the asking is done. */
.notice.is-ask{
  border-color: color-mix(in srgb, var(--info) 34%, transparent);
  background: var(--info-bg);
  color: var(--info);
}
.notice.is-ask .nt-text span{ color: inherit; }
.notice.is-ask .icon-btn.link{ color: inherit; text-decoration: underline; }

/* ==========================================================================
   ASK TO REDEEM
   The button lives on the tier itself, on its own line under it — a full-width
   row inside a flex row that is allowed to wrap. It is deliberately a ghost
   button: asking is not the same act as being given something.
   ========================================================================== */
.reward-row.has-ask{ flex-wrap: wrap; }
.ask-row{
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  margin-top: var(--s-1);
  padding-top: var(--s-2);
  border-top: 1px solid color-mix(in srgb, var(--gold) 30%, transparent);
}
.ask-row .btn{ margin-top: 0; display: inline-flex; align-items: center; gap: var(--s-2); }
.ask-row .ask-said{
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--gold-ink);
}
.ask-row .ask-said .icon{ width: 14px; height: 14px; }

/* ==========================================================================
   HOW WAS YOUR VISIT
   Five real buttons, 44px each, because this is asked on a phone that has just
   been put back in a pocket. The number is under the star so the score is
   readable without counting stars.
   ========================================================================== */
.rate-panel{ text-align: center; }
.rate-panel .explain{ margin-top: var(--s-1); }
.rate-stars{
  display: flex;
  justify-content: center;
  gap: var(--s-2);
  margin: var(--s-2) 0 var(--s-3);
}
.rate-star{
  flex: 1 1 0;
  max-width: 72px;
  min-height: 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: var(--s-2) 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text-soft);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.rate-star .icon{ width: 22px; height: 22px; }
.rate-star .rs-num{ font-size: var(--t-xs); font-weight: 700; }
.rate-star:hover:not(:disabled),
.rate-star:focus-visible{
  border-color: var(--gold);
  background: var(--gold-wash);
  color: var(--gold-ink);
}
.rate-star:disabled{ opacity: .55; cursor: default; }
.rate-panel.is-done .explain{ margin-bottom: var(--s-3); }
.rate-review{ display: inline-flex; align-items: center; gap: var(--s-2); margin: 0 auto; }
@media (prefers-reduced-motion: reduce){
  .rate-star{ transition: none; }
}

/* The same block, inside the app rather than on the sign-in screen.

   On the sign-in screen the wrapper supplies the side padding; on the home and
   explore tabs it supplies none, so this ran edge to edge with its text against
   both sides of the phone while every neighbour was an inset card. Here it
   becomes one of those cards. Matches .panel deliberately — it sits directly
   beneath them and any difference would read as a mistake. */
.shop-info-panel{
  background: var(--surface);
  margin: var(--s-3) var(--s-4) 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--s-3) var(--s-5) var(--s-4);
  box-shadow: var(--shadow-sm);

  /* The rule above it belonged to a block that ran full width. A card has its
     own edge, so the line would be a second one drawn just inside the first. */
  border-top: 1px solid var(--line);
  padding-top: var(--s-4);
  margin-top: var(--s-3);
}

/* ==========================================================================
   MOTION — the card should feel like something, not just work
   --------------------------------------------------------------------------
   Restraint is the whole point. This is opened at a counter with somebody
   waiting, often on a cheap phone, so everything here is transform/opacity
   only (the two properties a phone can animate without repainting), fast, and
   attached to a MOMENT rather than sprinkled over the page.

   What is deliberately NOT animated: anything that appears on a poll. The card
   re-renders whenever the shop changes something, and an entrance animation on
   a panel would replay itself while the customer is reading it. Moments the
   customer caused — a tap, a stamp landing — are fair game; things that happen
   to them are not.

   Every block here is switched off under prefers-reduced-motion at the end.
   ========================================================================== */

/* ------------------------------------------------------------ tab change --
   The most-used interaction in the app, and it used to be instant: the whole
   body was replaced and the page jumped to the top. A short, small rise reads
   as the new screen arriving rather than the old one vanishing. Deliberately
   under a fifth of a second — this is the gesture people repeat most, and
   anything slower becomes a wait. */
@keyframes ck-tab-in{
  from{ opacity: 0; transform: translate3d(0, 8px, 0); }
  to  { opacity: 1; transform: none; }
}
#ck-tabbody.tab-in{
  animation: ck-tab-in 180ms var(--ease-out) both;
}

/* --------------------------------------------------------- press feedback --
   A tap that moves nothing feels like a tap that did nothing. 2% is enough to
   register and small enough that nobody notices it happening. */
.btn:active,
.tab-btn:active,
.stamp-circle:active,
.icon-btn:active{ transform: scale(.98); }
.btn, .tab-btn, .icon-btn{ transition: transform var(--dur-fast) var(--ease-out); }

/* ------------------------------------------------------------- the bars ---
   The stamp bar and the level bar are set with an inline width, so they used
   to snap. Growing into place is the one moment the customer is actually
   watching a number change. */
.progress-fill,
.level-fill{ transition: width var(--dur-slow) var(--ease); }

/* ----------------------------------------------------------- the QR code --
   Shown to a member of staff, so it should arrive with a little authority
   rather than blink into existence. */
@keyframes ck-qr-in{
  from{ opacity: 0; transform: scale(.96); }
  to  { opacity: 1; transform: none; }
}
.qr-wrap, .qr-box{ animation: ck-qr-in var(--dur) var(--ease-back) both; }

/* -------------------------------------------------------- reduced motion --
   Everything above becomes nothing. Not "faster" — off. Somebody who has asked
   their phone to stop moving things has usually asked for a reason. */
@media (prefers-reduced-motion: reduce){
  #ck-tabbody.tab-in,
  .qr-wrap, .qr-box{ animation: none; }
  .btn, .tab-btn, .icon-btn,
  .progress-fill, .level-fill{ transition: none; }
  .btn:active, .tab-btn:active,
  .stamp-circle:active, .icon-btn:active{ transform: none; }
}

/* ---------------------------------------------------------------------------
   When the shop is open.

   These hours were collected from every shop for months and shown to nobody.
   Now that they are on the card, the open/closed word is the part that has to
   read at a glance from a pocket, so it carries colour; the week behind it is
   reference and stays quiet.
   ------------------------------------------------------------------------- */

.si-row.is-open  > .i{ color: var(--ok, #3E8E5A); }
.si-row.is-shut  > .i{ color: var(--muted); }

.si-today{
  display: block;
  font-size: 0.92em;
  color: var(--muted);
}

.si-hours{ margin-top: 4px; }
.si-hours > summary{
  cursor: pointer;
  font-size: 0.88em;
  color: var(--muted);
  /* A real tap target rather than a 12px word. */
  padding: 4px 0;
  list-style: none;
}
.si-hours > summary::-webkit-details-marker{ display: none; }
.si-hours > summary::after{ content: ' ▾'; }
.si-hours[open] > summary::after{ content: ' ▴'; }

.si-hlist{
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px var(--s-4);
  font-size: 0.9em;
  color: var(--muted);
  padding-bottom: 4px;
}
.si-hrow{ display: contents; }
.si-hrow > span:last-child{ text-align: right; }
.si-hrow.is-today > span{ color: var(--text); font-weight: 600; }

/* What's on at the shop. The date is the part that decides whether somebody
   reads the rest, so it sits directly under the name and carries the accent. */
.ev-when{
  font-size: 0.88em;
  font-weight: 600;
  color: var(--accent-ink);
  margin-top: 1px;
}
.ev-body{
  font-size: 0.92em;
  color: var(--muted);
  margin-top: 2px;
}

/* ---------------------------------------------------------------------------
   Your data — download it, or end it.

   Quiet by design. These are rights rather than features, and a card that
   shouts about deleting itself is a card nobody trusts with their stamps.
   ------------------------------------------------------------------------- */

.link-btn{
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--accent-ink);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.link-btn:hover{ text-decoration-thickness: 2px; }

/* Folded shut: the one control here that destroys months of somebody's
   stamps should take a deliberate moment to reach. */
.danger-fold{
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}
.danger-fold > summary{
  cursor: pointer;
  font-size: 0.92em;
  color: var(--muted);
  padding: var(--s-2) 0;
  list-style: none;
}
.danger-fold > summary::-webkit-details-marker{ display: none; }
.danger-fold > summary::after{ content: ' ▾'; }
.danger-fold[open] > summary::after{ content: ' ▴'; }
.danger-fold > summary:hover{ color: var(--text); }
.danger-fold .field{ margin-top: var(--s-3); }
.danger-fold .btn.danger{ width: 100%; margin-top: var(--s-2); }

/* ---------------------------------------------------------------------------
   "This is the one I'm saving for."

   A star rather than a checkbox: it is a wish, not a setting, and it sits in a
   row that already has a badge and sometimes a button. Quiet until chosen.
   ------------------------------------------------------------------------- */

.goal-star{
  background: none;
  border: 0;
  padding: 6px;
  /* A real tap target on a phone, without pushing the row taller. */
  margin: -6px 0;
  color: var(--line-strong, #ccc);
  cursor: pointer;
  flex: 0 0 auto;
  border-radius: var(--r-sm);
}
.goal-star:hover{ color: var(--muted); }
.goal-star.is-on{ color: var(--accent); }
.goal-star:focus-visible{ outline: 2px solid var(--accent); outline-offset: 1px; }

.reward-row.is-goal{
  box-shadow: inset 2px 0 0 var(--accent);
}

.goal-flag{
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent-ink);
  font-size: 0.72em;
  font-weight: 700;
  letter-spacing: 0.02em;
  vertical-align: middle;
}

/* ------------------------------------------------------- refer a friend --- */
/*
 * The code is the feature, so it is the thing set large and monospaced: this is
 * read aloud across a counter far more often than it is copied.
 */
.refer-code{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin: var(--s-3) 0 0;
  padding: var(--s-3) var(--s-4);
  border: 2px dashed var(--gold, #D9A93A);
  border-radius: var(--r-md, 12px);
  background: var(--gold-wash, rgba(217,169,58,.10));
}
.refer-code-text{
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: clamp(1.35rem, 6vw, 1.7rem);
  font-weight: 700;
  letter-spacing: .16em;
  color: var(--gold-ink, var(--navy));
  /* Tapping the code selects the whole thing rather than one character. */
  user-select: all;
}
.refer-count{
  margin: var(--s-2) 0 0;
  font-size: var(--t-sm);
  color: var(--text-muted);
}

/* ---------------------------------------------------- order status (B26) --- */
.order-panel.is-ready{ border: 2px solid var(--ok, #4a7c59); }
.order-items{ margin: 0 0 6px; color: var(--text-muted); font-size: var(--t-sm); }
.order-status{ margin: 0 0 10px; }

/* --------------------------------------------------------- your year (C11) - */
.year-line{ margin: 0 0 6px; font-size: var(--t-md, 1rem); }

/* ============================================================================
 *  THE SCRATCH CARD
 *
 *  The other way a shop gives its prizes out — same prize list, same earned
 *  turn, same claim code. Only the reveal differs.
 *
 *  The foil is a <canvas> painted and erased by assets/js/app.js. Everything
 *  under it is ordinary markup and is in the DOM the whole time, so a screen
 *  reader and the "Reveal it for me" button both reach the prize without any
 *  rubbing at all.
 * ==========================================================================*/

.scratch-wrap{
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin: var(--s-4) 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--grad-navy);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 10%), var(--shadow-md);
  touch-action: none;          /* a rub must not scroll the card away */
  user-select: none;
}

/* The prize, sitting under the foil from the moment the card is dealt. */
.scratch-face{
  position: absolute; inset: 0;
  display: grid; place-content: center; justify-items: center;
  gap: 6px; padding: var(--s-4); text-align: center;
  color: var(--cream);
}
.scratch-face .sf-ico{
  display: grid; place-items: center;
  width: 54px; height: 54px; border-radius: 50%;
  background: var(--grad-gold); color: var(--navy-deep);
}
.scratch-face .sf-ico .sw-img{
  width: 100%; height: 100%; border-radius: 50%; object-fit: cover;
}
.scratch-face strong{
  font-family: var(--font-display); font-size: var(--t-lg); line-height: var(--lh-tight);
}
.sf-code{
  font-size: var(--t-sm); color: var(--gold-soft);
  letter-spacing: var(--track-wide);
}
.sf-code b{ font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; }

/* The foil itself. */
.scratch-foil{
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  cursor: grab;
  transition: opacity var(--dur-slow) var(--ease);
}
.scratch-foil:active{ cursor: grabbing; }
.scratch-foil.is-gone{ opacity: 0; pointer-events: none; }

.scratch-hint{
  position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 14px; border-radius: var(--r-full);
  background: rgb(20 24 27 / 62%); color: #fff;
  font-size: var(--t-xs); font-weight: 650; white-space: nowrap;
  pointer-events: none;
  animation: ls-rub 1.8s var(--ease) infinite;
}
.scratch-hint.is-gone{ opacity: 0; animation: none; }

@keyframes ls-rub{
  0%, 100%{ transform: translateX(-50%) translateY(0); }
  50%     { transform: translateX(-50%) translateY(-4px); }
}

/* The prize lands rather than simply appearing, once the foil lifts. */
.scratch-wrap.is-done .scratch-face{ animation: ls-prize-in var(--dur-slow) var(--ease-back) both; }
@keyframes ls-prize-in{
  from{ transform: scale(.9); opacity: .4; }
  to  { transform: none; opacity: 1; }
}

/* Off, not down. With motion switched off the hint sits still and the prize
   is simply there — nothing is hidden waiting for an animation that will
   never run. */
@media (prefers-reduced-motion: reduce){
  .scratch-hint{ animation: none; }
  .scratch-foil{ transition: none; }
  .scratch-wrap.is-done .scratch-face{ animation: none; }
}
