/* ============================================================
   Stempl onboarding
   Every brand-variable below is overwritten at runtime by
   js/brand.js from the brand's JSON config. The values here are
   the fallback that renders before (or instead of) that fetch.
   ============================================================ */

:root{
  --brand:#1F2A44;
  --brand-ink:#FFFFFF;      /* computed for contrast against --brand */
  --brand-dark:#16203A;
  --brand-tint:#EEF0F4;
  --brand-glow:rgba(31,42,68,.28);
  --brand-veil:rgba(255,255,255,.16);
  /* The ACCENT: every brand-coloured thing that sits on the page rather than
     in the header — the button, the selected option, input focus, filled
     stamps, spinners. The header band stays --brand; that is the cafe's own
     surface. These defaults follow --brand, and js/brand.js applyTheme()
     replaces them with the brand's accentColor, or with dark ink when the
     brand is too pale to see on the white card — a white cafe would
     otherwise get a white button on a white page. See resolveAccent(). */
  --accent:var(--brand);
  --accent-ink:var(--brand-ink);
  --accent-dark:var(--brand-dark);
  --accent-glow:var(--brand-glow);
  /* The page FRAME is Stempl's, on the 2026 identity (CLAUDE.md gotcha 20):
     cool white ground, navy text, blue-grey lines. Its own names on
     purpose: applyTheme() in js/brand.js writes --paper and --ink from the
     shared fallback brand, which the owner dashboard also adopts, so
     reusing those names would let the old warm palette paint over this
     one at runtime. Everything the CAFE owns stays --brand / --accent. */
  --page:#F3F6FA;
  --text:#172440;
  --text-soft:#4A5878;
  --card:#FFFFFF;
  --line:#D9E2EC;
  --slot:#BFCCDB;           /* an empty drawn stamp slot */
  --earned:#00804D;         /* the reward-unlocked slot: the identity's green */
  --focus:#172440;          /* keyboard focus: navy shows on any cafe colour */
  --error:#B3362B;
  --radius:18px;
}
/* No colour known yet (first visit, no ?c= in the link, nothing cached).
   Neutral slate rather than the fallback teal: an obviously-neutral chrome
   settling into the brand colour reads as loading, whereas one *brand*
   colour snapping to a different one reads as a glitch. */
.brand-pending{
  --brand:#6B7280;
  --brand-ink:#FFFFFF;
  --brand-dark:#575E69;
  --brand-tint:#F1F2F4;
  --brand-glow:rgba(107,114,128,.28);
}
/* Ease the one transition that is left, so it settles instead of snapping. */
.band,.btn{transition:background-color .28s ease,color .28s ease}
@media (prefers-reduced-motion:reduce){
  .band,.btn{transition:none}
}

/* Header identity, painted from the cached brand before any script runs.
   These only apply while the elements are still empty — as soon as app.js
   writes the real name or logo they stop matching, so there is never both.
   The non-breaking space keeps the header from collapsing on a first-ever
   visit when nothing is known yet. */
.cafe-name:empty::before{content:var(--cafe-name,"\00a0")}
.cafe-tag:empty::before {content:var(--cafe-tag,"\00a0")}
.cafe-logo:empty{
  background-image:var(--cafe-logo,none);
  background-size:cover;
  background-position:center;
}
.cafe-logo:empty::before{content:var(--cafe-initial,"")}

*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  font-family:'Saira',system-ui,-apple-system,sans-serif;
  background:var(--page);
  color:var(--text);
  min-height:100dvh;
  display:flex;
  flex-direction:column;
}

/* ---------- type pass (October 2026, CLAUDE.md gotcha 21) ----------
   Saira a touch wider than its narrowest cut, as on /landing/: at app
   sizes the default width reads cramped. Needs the wdth axis in the font
   link. Form controls don't inherit font-stretch on their own. */
body{font-stretch:104%}
button,input,select,textarea{font-stretch:inherit}
h1,h2,h3{font-stretch:107%}

/* ---------- header band ---------- */
.band{
  background:var(--brand);
  color:var(--brand-ink);
  padding:calc(20px + env(safe-area-inset-top)) 24px 64px;
  position:relative;
}
.band::after{ /* scalloped punch-card edge */
  content:"";
  position:absolute;left:0;right:0;bottom:-1px;height:14px;
  background:radial-gradient(circle 9px at 9px 0, var(--brand) 98%, transparent 100%);
  background-size:28px 14px;
  background-repeat:repeat-x;
}
.cafe-row{display:flex;align-items:center;gap:12px}
.cafe-text{min-width:0}
.cafe-logo{
  width:46px;height:46px;border-radius:12px;flex:none;
  background:var(--brand-veil);
  display:flex;align-items:center;justify-content:center;
  font-family:'Saira',system-ui,sans-serif;font-size:22px;font-weight:600;
  overflow:hidden;
}
.cafe-logo img{width:100%;height:100%;object-fit:cover}
.cafe-name{
  font-family:'Saira',system-ui,sans-serif;font-weight:700;font-size:20px;line-height:1.15;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.cafe-tag{font-size:13px;opacity:.82;margin-top:2px}

/* ---------- main card ---------- */
main{
  flex:1;
  padding:0 16px calc(24px + env(safe-area-inset-bottom));
  max-width:430px;width:100%;margin:0 auto;
}
.sheet{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:var(--radius);
  padding:26px 22px;
  margin-top:-34px;
  position:relative;
  box-shadow:0 10px 30px rgba(23,36,64,.08);
}

.step{display:none}
.step.active{display:block;animation:rise .35s ease both}
@keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

h1{
  font-family:'Saira',system-ui,sans-serif;font-weight:700;
  font-size:26px;line-height:1.15;letter-spacing:-.005em;
}
.sub{color:var(--text-soft);font-size:15px;line-height:1.55;margin-top:10px}
.sub b{color:var(--text)}

/* ---------- stamp card ---------- */
.stamps{
  display:grid;grid-template-columns:repeat(5,1fr);gap:12px;
  margin:22px 0 6px;padding:18px 16px;
  background:var(--page);border:1px dashed var(--line);border-radius:14px;
}
.dot{
  aspect-ratio:1;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  overflow:hidden;position:relative;
}
.dot img{width:100%;height:100%;object-fit:contain;display:block}

/* drawn fallbacks — used when the brand supplies no artwork,
   or when an image URL fails to load */
.dot--drawn.dot--empty{border:2px dashed var(--slot)}
.dot--drawn.dot--filled{
  background:var(--accent);color:var(--accent-ink);
  border:2px solid var(--accent);
}
.dot--drawn.dot--filled svg{width:52%;height:52%;stroke:var(--accent-ink);fill:none}
.dot--drawn.dot--reward{border:2px solid var(--accent);color:var(--accent)}
.dot--drawn.dot--reward svg{width:55%;height:55%;stroke:var(--accent);fill:none}
.dot--drawn.dot--redeemed{
  background:var(--earned);border:2px solid var(--earned);
}
.dot--drawn.dot--redeemed svg{width:55%;height:55%;stroke:#fff;fill:none}

/* the prize slot is the one thing on the card that should catch the eye */
.dot--reward,.dot--redeemed{box-shadow:0 0 0 3px var(--accent-glow)}

.dot--in{animation:pop .34s cubic-bezier(.2,.9,.3,1.3) both}
@keyframes pop{
  from{opacity:0;transform:scale(.55)}
  to{opacity:1;transform:scale(1)}
}
.stamps-caption{font-size:13px;color:var(--text-soft);text-align:center;margin-bottom:4px}

/* ---------- form ---------- */
label{display:block;font-size:14px;font-weight:600;margin:18px 0 6px;letter-spacing:.01em}
label span,legend span{font-weight:500;color:var(--text-soft)}
input[type="text"],input[type="email"]{
  width:100%;padding:14px 15px;font-size:16px;
  font-family:inherit;color:var(--text);
  background:#fff;border:1.5px solid var(--line);border-radius:12px;
  outline:none;transition:border-color .15s;
}
input:focus{border-color:var(--accent)}
input.invalid{border-color:var(--error)}
.hint{font-size:13px;color:var(--text-soft);margin-top:6px}
.field-error{font-size:14px;color:var(--error);margin-top:8px;display:none}
.field-error.show{display:block}

.field-group{border:none;margin-top:18px}
.field-group[hidden]{display:none}
.field-group legend{
  font-size:14px;font-weight:600;letter-spacing:.01em;
  margin-bottom:8px;padding:0;
}

/* ---------- gender · segmented control ---------- */
.segmented{display:flex;gap:8px;flex-wrap:wrap}
.segmented--stack{flex-direction:column}
.seg{
  flex:1 1 auto;margin:0;position:relative;
  min-width:88px;cursor:pointer;
}
/* Visually hidden, still focusable and still announced. The label wraps
   the whole pill, so a tap anywhere on it activates this input. */
.seg input{
  position:absolute;width:1px;height:1px;
  padding:0;margin:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);
  white-space:nowrap;border:0;
}
.seg span{
  display:flex;align-items:center;justify-content:center;
  text-align:center;
  padding:13px 12px;min-height:48px;
  font-size:15px;font-weight:600;line-height:1.25;
  color:var(--text-soft);
  background:#fff;
  border:1.5px solid var(--line);border-radius:999px;
  transition:background .15s,border-color .15s,color .15s;
}
.seg input:checked + span{
  background:var(--accent);border-color:var(--accent);color:var(--accent-ink);
}
.seg input:focus-visible + span{outline:3px solid var(--focus);outline-offset:2px}
.seg input:active + span{transform:scale(.98)}
.segmented.invalid .seg span{border-color:var(--error)}

/* ---------- date of birth · one field, three selects ---------- */
.date-row{
  display:grid;
  grid-template-columns:1.05fr 1.35fr .85fr;
  background:#fff;
  border:1.5px solid var(--line);
  border-radius:12px;
  overflow:hidden;
  transition:border-color .15s;
}
.date-row:focus-within{border-color:var(--accent)}
.date-row.invalid{border-color:var(--error)}
.date-cell{position:relative;min-width:0}
.date-cell + .date-cell{border-left:1.5px solid var(--line)}
.date-label{
  display:block;margin:9px 12px 0;
  font-size:12px;font-weight:600;letter-spacing:.07em;
  text-transform:uppercase;color:var(--text-soft);
}
.date-cell select{
  -webkit-appearance:none;appearance:none;
  width:100%;border:none;outline:none;background:transparent;
  font-family:inherit;font-size:16px;font-weight:500;color:var(--text);
  padding:2px 26px 11px 11px;
  cursor:pointer;
}
.date-cell select:invalid,.date-cell select option[value=""]{color:var(--text-soft)}
.date-cell::after{ /* chevron */
  content:"";position:absolute;right:10px;bottom:17px;
  width:8px;height:8px;pointer-events:none;
  border-right:1.8px solid var(--text-soft);
  border-bottom:1.8px solid var(--text-soft);
  transform:rotate(45deg);
}
.date-cell:focus-within::after{border-color:var(--accent)}

/* ---------- buttons ---------- */
.btn{
  width:100%;margin-top:22px;padding:16px;
  font-family:inherit;font-size:16px;font-weight:700;
  color:var(--accent-ink);background:var(--accent);
  border:none;border-radius:999px;cursor:pointer;
  transition:transform .08s ease,background .15s;
}
.btn:hover{background:var(--accent-dark)}
.btn:active{transform:scale(.98)}
.btn:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
.btn[disabled]{opacity:.55;cursor:default}
/* ---------- add-to-wallet badge ----------
   Apple and Google both require their own artwork and forbid re-drawing,
   recolouring, dimming, rotating or adding effects to it. So this control is
   a transparent frame around an untouched SVG, not a styled button — the
   hover tint sits BEHIND the badge and never touches the artwork.

   Clear space: Apple asks 0.1x the badge height, Google asks 8dp on every
   side. The 10px padding satisfies both at this size. Google also sets a
   48dp minimum height; the badge renders at 54px.

   Both vendors want a white or light ground, which is what --paper is. */
.wallet-badge{
  display:block;margin:22px auto 0;padding:10px;
  background:none;border:none;border-radius:12px;
  cursor:pointer;line-height:0;
  transition:background .15s;
}
.wallet-badge img{display:block;height:54px;width:auto}
.wallet-badge:hover{background:rgba(23,36,64,.06)}
.wallet-badge:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
/* Needed, not redundant: the rule above sets display:block, which beats the
   browser's own [hidden]{display:none}. Without this the badge stays on
   screen with the attribute set. */
.wallet-badge[hidden]{display:none}

/* Busy state, while the pass is being fetched. Dormant while the badge is
   hidden, and kept so that un-hiding it brings this back with it.

   The badge is HIDDEN and the spinner takes its place — the vendor rules above forbid dimming or covering
   the artwork, so the usual disabled-button fade can't be used here (and
   .btn[disabled] deliberately doesn't match this control). The spinner's
   32px + 11px margins reproduce the badge's 54px, so nothing shifts. */
.wallet-badge[aria-busy="true"]{cursor:default}
.wallet-badge[aria-busy="true"]:hover{background:none}
.wallet-badge[aria-busy="true"] img{display:none}
.wallet-spinner{
  /* display:none is the default, and the busy rule below is the ONLY thing
     that shows it. Setting display:block here instead would beat the `hidden`
     attribute's UA rule and leave the spinner on screen permanently. */
  display:none;width:32px;height:32px;margin:11px auto;
  border:4px solid var(--line);border-top-color:var(--accent);border-radius:50%;
  animation:spin 1s linear infinite;
}
.wallet-badge[aria-busy="true"] .wallet-spinner{display:block}
.back{
  display:inline-block;margin-top:16px;
  background:none;border:none;font-family:inherit;
  font-size:15px;font-weight:600;color:var(--text-soft);
  cursor:pointer;text-decoration:underline;text-underline-offset:3px;
}

/* ---------- loading ---------- */
.loading{text-align:center;padding:28px 0 12px}
.cup{width:54px;height:54px;margin:0 auto 18px;position:relative}
.cup .ring{
  width:100%;height:100%;border-radius:50%;
  border:4px solid var(--line);border-top-color:var(--accent);
  animation:spin 1s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---------- the automatic wallet hand-off ----------
   Two phases on one block. `preparing` is the beat before the browser is
   sent to the pass, and the dots are there to buy that beat honestly. `sent`
   is after — the dots would be lying at that point, because nothing on this
   page can see what the wallet app is doing. */
.wallet-status{
  margin:20px auto 0;text-align:center;max-width:34ch;
}
.wallet-dots{display:flex;justify-content:center;gap:7px;margin-bottom:12px}
.wallet-dots span{
  width:8px;height:8px;border-radius:50%;background:var(--accent);
  opacity:.3;animation:walletDot 1.15s ease-in-out infinite;
}
.wallet-dots span:nth-child(2){animation-delay:.16s}
.wallet-dots span:nth-child(3){animation-delay:.32s}
@keyframes walletDot{
  0%,75%,100%{opacity:.3;transform:scale(.8)}
  35%{opacity:1;transform:scale(1)}
}
.wallet-status[data-phase="sent"] .wallet-dots{display:none}
.wallet-status-head{
  font-family:'Saira',system-ui,sans-serif;
  font-size:17px;font-weight:700;color:var(--text);margin:0;
}
.wallet-status-steps{
  font-size:14px;line-height:1.55;color:var(--text-soft);margin:8px 0 0;
}
.wallet-status-steps:empty{display:none}

/* Deliberately not a .btn: the add fires on its own, so this is the way out
   for someone whose card never arrived, not the main action. A filled pill
   here would read as the thing to press and undo that. With the vendor badge
   hidden it is also the only manual control on the screen — which is why it
   is a full-width tap target rather than a footnote. */
.wallet-retry{
  display:block;width:100%;margin-top:26px;padding:8px 4px;
  background:none;border:none;font-family:inherit;cursor:pointer;
}
.wallet-retry .q{display:block;font-size:14px;color:var(--text-soft)}
.wallet-retry .cta{
  display:block;margin-top:3px;
  font-size:15px;font-weight:700;color:var(--text);
  text-decoration:underline;text-underline-offset:3px;
}
.wallet-retry:hover .cta{color:var(--accent)}
.wallet-retry:focus-visible{outline:3px solid var(--focus);outline-offset:2px;border-radius:12px}

/* ---------- returning customer ----------
   The wallet step doubles as the "you already have a card" screen, so this
   has to read as good news rather than a failure — brand-tinted, not the
   red of .error-box. */
.notice{
  margin:14px 0 0;padding:12px 14px;border-radius:12px;
  background:var(--accent-glow, rgba(23,36,64,.08));
  border:1px solid var(--line);
  font-size:15px;line-height:1.5;color:var(--text);
  font-weight:600;text-align:center;
}

/* ---------- error ---------- */
.error-box{
  margin-top:18px;padding:14px 16px;border-radius:12px;
  background:#FBEDEB;border:1px solid #EAC7C2;
  font-size:15px;line-height:1.5;color:#7A2A22;
  word-break:break-word;
}

/* Stempl's signature: quiet, never a link — nobody should be pulled out of
   the form by it. The note under it is the cafe's to reword (copy.footer). */
footer{
  text-align:center;font-size:13px;color:var(--text-soft);
  padding:16px 24px calc(16px + env(safe-area-inset-bottom));
}
footer p{margin:0}
.powered{
  display:inline-flex;align-items:center;gap:7px;
  font-size:13px;font-weight:600;color:var(--text);
}
.powered img{width:22px;height:auto;display:block}
.footer-note{margin-top:3px}

/* ---------- narrow phones ---------- */
@media (max-width:340px){
  .sheet{padding:22px 16px}
  .stamps{gap:8px;padding:14px 12px}
  .seg span{font-size:14px;padding:12px 8px}
  .date-cell select{font-size:15.5px}
}

@media (prefers-reduced-motion:reduce){
  .step.active,.dot--in{animation:none}
  .cup .ring,.wallet-spinner{animation-duration:2.5s}
  .btn:active,.seg input:active + span{transform:none}
  /* Still visible, just still — the dots are a status indicator, so they
     shouldn't vanish for someone who asked for less motion. */
  .wallet-dots span{animation:none;opacity:.55;transform:none}
}
