/* ============================================================================
   auth.css — /signin, /forgot-password, /reset-password, /auth/callback.
   Split-screen, light theme, matched to the site tokens (Gabarito/Inter, blue
   family, --ink/--line). Loaded ONLY on auth pages so the homepage stays lean.

   No site nav/footer here — auth pages are full-bleed chrome-less; a small
   wordmark (top-left, → home) is the only way out. The brand panel runs the
   REAL AsciiBloom canvas (#authBloom wiring in app.js) with the cursor
   "flashlight" hover glow; the form side gets a whisper of drifting glyphs.
   ========================================================================== */
.auth-main{ min-height:100svh; }
.auth{
  min-height:100svh;
  display:grid; grid-template-columns:1fr 1fr; align-items:stretch;
}

/* ---- chrome-less: the one escape hatch ---- */
/* The wordmark is the real artwork, not the name set in --sans. Raster with
   alpha, so it CANNOT follow a colour token — hence two files: the black one
   here (light page surface) and the white one on the dark brand panel below.
   Width is set to match what the old text wordmark measured on the live page
   (61.9px at 20px/800), so the swap is optically a like-for-like. Intrinsic
   300x70 stays on the <img> attrs so the aspect ratio is exact and nothing
   shifts while it loads; CSS owns the rendered size. */
.auth-wm{
  position:fixed; top:22px; left:26px; z-index:10;
  text-decoration:none; line-height:0;
}
.auth-wm img{ display:block; width:62px; height:auto; }
.auth-wm:hover{ opacity:.75; }

/* ---- form side ---- */
.auth-form-side{
  position:relative; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
  padding:clamp(84px,12vh,140px) clamp(22px,5vw,64px) 56px;
}
.auth-card{ position:relative; z-index:2; width:100%; max-width:384px; animation:authIn .6s var(--ease) both; }
@keyframes authIn{ from{ opacity:0; transform:translateY(14px); } to{ opacity:1; transform:none; } }
.auth-card h1{
  font-family:var(--sans); font-weight:800; letter-spacing:-.02em;
  font-size:clamp(1.75rem,3vw,2.15rem); margin:0 0 6px; color:var(--ink);
}
.auth-card .auth-sub{ color:var(--ink-soft); font-size:.98rem; margin:0 0 26px; line-height:1.5; }

/* whisper of drifting glyphs on the white side (kept near the edges, away
   from the form column). Mono glyphs, near-invisible, long slow drifts. */
.auth-glyph{
  position:absolute; z-index:1; pointer-events:none; user-select:none;
  font-family:ui-monospace,Menlo,monospace; font-weight:600; color:var(--ink);
  opacity:.06; will-change:transform;
  animation:authDrift var(--dur,26s) ease-in-out var(--del,0s) infinite alternate;
}
@keyframes authDrift{
  from{ transform:translate3d(0,0,0) rotate(0deg); }
  to  { transform:translate3d(var(--dx,8px),var(--dy,-26px),0) rotate(var(--rot,6deg)); }
}

.auth-google{
  display:flex; align-items:center; justify-content:center; gap:10px;
  width:100%; box-sizing:border-box; padding:12px 16px;
  border:1px solid var(--line-strong); border-radius:var(--radius-pill); background:#fff; color:var(--ink);
  font-family:var(--body); font-weight:600; font-size:.98rem; cursor:pointer;
  box-shadow:0 1px 2px rgba(17,17,19,.05);
  transition:box-shadow .2s var(--ease), border-color .15s var(--ease), transform .15s var(--ease);
}
.auth-google:hover{
  border-color:rgba(17,17,19,.24); transform:translateY(-1px);
  box-shadow:0 6px 16px rgba(17,17,19,.10);
}
.auth-google:active{ transform:translateY(0); box-shadow:0 1px 2px rgba(17,17,19,.05); }
.auth-google svg{ width:18px; height:18px; flex:none; }

.auth-divider{ display:flex; align-items:center; gap:14px; margin:20px 0; color:var(--ink-faint); font-size:.82rem; }
.auth-divider::before,.auth-divider::after{ content:""; flex:1; height:1px; background:var(--line); }

.auth-field{
  display:block; width:100%; box-sizing:border-box; margin:0 0 12px; padding:12px 14px;
  border:1.5px solid var(--line); border-radius:11px; background:#fff; color:var(--ink);
  /* 16px minimum: iOS Safari zooms the whole page when a focused input's text
     is under 16px, and .98rem (15.68px) tripped that on every sign-in. Blocking
     zoom with maximum-scale would fail WCAG, so the type size is the fix. */
  font-family:var(--body); font-size:16px;
  transition:border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.auth-field::placeholder{ color:var(--ink-faint); }
.auth-field:focus{ outline:none; border-color:var(--blue); box-shadow:0 0 0 3px rgba(18,134,230,.13); }

/* ---- password field show/hide toggle ---- */
.pw-wrap{ position:relative; margin:0 0 12px; }
.pw-wrap .auth-field{ margin:0; padding-inline-end:46px; }      /* room for the eye button */
.pw-toggle{
  position:absolute; top:0; inset-inline-end:0; height:100%; width:44px;
  display:flex; align-items:center; justify-content:center;
  background:none; border:0; padding:0; cursor:pointer; color:var(--ink-faint);
  transition:color .15s var(--ease);
}
.pw-toggle:hover{ color:var(--ink-soft); }
.pw-toggle:focus-visible{ outline:2px solid var(--blue); outline-offset:-2px; border-radius:8px; }
.pw-toggle svg{ width:20px; height:20px; display:block; }
.pw-toggle .pw-eye-off{ display:none; }
.pw-toggle.is-shown .pw-eye{ display:none; }
.pw-toggle.is-shown .pw-eye-off{ display:block; }

/* ---- resend confirmation email (in the check-your-email interstitial) ---- */
.auth-resend-row{ margin:14px 0 0; color:var(--ink-soft); font-size:.9rem; }
.auth-resend{
  background:none; border:0; padding:0 2px; cursor:pointer;
  font-family:var(--body); font-weight:600; font-size:.9rem; color:var(--blue);
}
.auth-resend:hover:not([disabled]){ text-decoration:underline; }
.auth-resend[disabled]{ color:var(--ink-faint); cursor:default; }

/* ---- the Continue button (ported reference: pill + icon circle + looping
   dotted arrow + cursor-position glow via --mx/--my, set from JS) ---- */
.btn-go{
  --mx:50%; --my:50%;
  position:relative; overflow:hidden;
  display:flex; align-items:center;
  width:100%; box-sizing:border-box; margin-top:2px;
  padding:5px 5px 5px 22px;
  /* Same fill as .btn-primary. This is the primary action on the auth pages,
     so it should not be a bespoke navy found nowhere else on the site — only
     its GEOMETRY is special (full-width, icon capsule, cursor glow). */
  background:linear-gradient(to bottom, var(--brand-blue-ink), var(--blue-deep));
  border:0; border-radius:var(--radius-pill); cursor:pointer;
  box-shadow:0 5px 14px rgba(18,134,230,.30);
  transition:box-shadow .25s var(--ease), transform .12s var(--ease);
}
.btn-go:hover{ box-shadow:0 8px 22px rgba(18,134,230,.38); }
.btn-go:active{ transform:translateY(1px); }
.btn-go[disabled]{ opacity:.65; cursor:default; }
/* cursor glow follows --mx/--my */
.btn-go::before{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(130px circle at var(--mx) var(--my), rgba(75,166,241,.5), transparent 65%);
  opacity:0; transition:opacity .25s var(--ease);
}
.btn-go:hover::before{ opacity:1; }
.btn-go-text{
  position:relative; flex:1; text-align:center;
  color:#fff; font-family:var(--body); font-weight:700; font-size:1rem; letter-spacing:.04em;
}
.btn-go-ic{
  position:relative; flex:none; width:45px; height:45px; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
  background:var(--brand-blue); border-radius:50%; border:3px solid var(--blue-deep);
  box-sizing:border-box;
}
.btn-go-ic svg{ display:block; }
.btn-go-ic svg circle{ fill:#fff; }
.btn-go:hover .btn-go-ic svg{ animation:goArrow 1s linear infinite; }
@keyframes goArrow{
  0%  { opacity:0; margin-inline-start:0; }
  100%{ opacity:1; margin-inline-start:10px; }
}

/* solid-blue primary kept for the small utility pages (forgot/reset) */
.auth-primary{
  width:100%; box-sizing:border-box; padding:13px 16px; margin-top:2px;
  border:0; border-radius:var(--radius-pill); background:var(--brand-blue-ink); color:#fff;
  font-family:var(--body); font-weight:700; font-size:1rem; cursor:pointer;
  box-shadow:0 6px 18px rgba(18,134,230,.28);
  transition:background .15s var(--ease), transform .08s, box-shadow .2s var(--ease);
}
.auth-primary:hover{ background:var(--blue-deep); }
.auth-primary:active{ transform:translateY(1px); }
.auth-primary[disabled]{ opacity:.6; cursor:default; box-shadow:none; }

.auth-forgot{ display:inline-block; margin-top:15px; color:var(--ink-soft); font-size:.88rem; text-decoration:none; }
.auth-forgot:hover{ color:var(--blue); }
.auth-msg{ min-height:1.2em; margin:15px 0 0; font-size:.9rem; line-height:1.45;
  transition:opacity .2s var(--ease), transform .22s var(--ease); }
.auth-msg:empty{ opacity:0; transform:translateY(-3px); }
.auth-msg.err{ color:#c0392b; } .auth-msg.ok{ color:#0d8a55; } .auth-msg.info{ color:var(--ink-soft); }

/* interstitial (check-your-email) */
.auth-inter{ display:none; }
/* The form is replaced wholesale by its outcome ("check your email", reset
   done, link expired). display:none -> block cannot transition, so this is an
   animation — same idiom as licIn/clIn. ONE rule covers all four panels:
   signin #auth-inter, forgot-password #fp-sent, reset-password #rp-done and
   #rp-bad all carry this class. */
@keyframes interIn{ from{ opacity:0; transform:translateY(6px); } to{ opacity:1; transform:none; } }
.auth-inter.show{ display:block; animation:interIn .24s var(--ease) both; }
.auth-inter .badge{
  display:inline-flex; align-items:center; justify-content:center; width:52px; height:52px; margin-bottom:16px;
  border-radius:50%; background:rgba(18,134,230,.10); color:var(--blue);
}
.auth-inter .badge svg{ width:26px; height:26px; }
.auth-back{ background:none; border:0; color:var(--ink-soft); font-family:var(--body); font-size:.88rem; cursor:pointer; margin-top:18px; padding:0; }
.auth-back:hover{ color:var(--blue); }

/* ---- brand side ---- */
.auth-brand-side{
  position:relative; overflow:hidden;
  display:flex; flex-direction:column; justify-content:center;
  padding:clamp(48px,7vw,96px); color:#fff;
  /* carries white body copy, so the AA-safe ramp — not --brand-gradient */
  background:var(--brand-gradient-ink);
}
.auth-brand-side .wm{
  position:relative; z-index:3; line-height:0;
  animation:authIn .6s var(--ease) .08s both;
}
/* WHITE artwork here — this panel is dark. 99px matches what the old 2rem/800
   text measured. Do not point this at the black file. */
.auth-brand-side .wm img{ display:block; width:99px; height:auto; }
.auth-brand-side .line{
  position:relative; z-index:3;
  margin-top:18px; font-family:var(--sans); font-weight:600; line-height:1.16; letter-spacing:-.02em;
  font-size:clamp(1.55rem,2.5vw,2.15rem); max-width:15ch;
  animation:authIn .6s var(--ease) .16s both;
}
/* the REAL glyph field (AsciiBloom via #authBloom wiring in app.js, with the
   cursor flashlight hover glow — glowHost is this panel) */
.auth-brand-side canvas.bloom{
  inset:0; top:0; left:0; width:100%; height:100%;
  opacity:.8; z-index:1;
}

/* ---- mobile: brand panel collapses, form centers ---- */
@media (max-width:820px){
  .auth{ grid-template-columns:1fr; }
  .auth-brand-side{ display:none; }
  .auth-form-side{ padding-top:clamp(100px,17vh,150px); }
}
@media (prefers-reduced-motion:reduce){
  .auth-google,.auth-field,.btn-go{ transition:none; }
  .auth-glyph{ animation:none; }
  .btn-go:hover .btn-go-ic svg{ animation:none; }
  .auth-msg{ transition:opacity .2s var(--ease); transform:none; }
  .auth-msg:empty{ transform:none; }
}

/* ============================================================================
   Account pages (/account, /account/license) — normal site chrome (nav+footer),
   calm cards on white matched to the system.
   ========================================================================== */
.acct-hero{ padding-top:clamp(130px,16vh,170px); padding-bottom:10px; }
.acct-hero h1{
  font-family:var(--sans); font-weight:800; letter-spacing:-.02em;
  font-size:clamp(1.9rem,3.4vw,2.6rem); margin:0 0 6px; color:var(--ink);
}
.acct-hero .sub{ color:var(--ink-soft); font-size:1rem; margin:0; }

.acct-stack{ max-width:640px; margin-inline:auto; padding-bottom:40px; }
.acct-card{
  background:#fff; border:1px solid var(--line); border-radius:var(--radius-card);
  box-shadow:0 1px 2px rgba(17,17,19,.04);
  padding:26px 26px 24px; margin-bottom:18px;
  /* .18s, not .25s — the hover response has to feel immediate */
  transition:transform .18s var(--ease), box-shadow .18s var(--ease);
}
#license-card, #plan-card{ box-shadow:inset 3px 0 0 var(--blue); }
@media (hover: hover) and (pointer: fine){
  .acct-card:hover{ transform:translateY(-2px); box-shadow:0 10px 28px rgba(17,17,19,.07); }
  #license-card:hover, #plan-card:hover{ box-shadow:inset 3px 0 0 var(--blue), 0 10px 28px rgba(17,17,19,.07); }
}

/* owned license = the MEMBERSHIP CARD — the post-purchase trophy, not a
   receipt: deep brand navy with blue corner glows, wordmark, glowing Active
   badge, machine slots. The card is ALREADY DARK while loading (.is-loading),
   so the common owned path never changes colour — the skeleton just crossfades
   into the real content in place. Only the rare no-license path fades the dark
   ::before back to white (gradients can't transition, hence the layer). */
#license-card{ position:relative; overflow:hidden; }
#license-card::before{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  border-radius:inherit; opacity:0; transition:opacity .5s var(--ease);
  background:
    radial-gradient(120% 150% at 88% -30%, rgba(75,166,241,.38) 0%, transparent 55%),
    radial-gradient(100% 130% at 8% 115%, rgba(18,134,230,.30) 0%, transparent 58%),
    var(--brand-gradient-ink);   /* white text sits on this — AA-safe ramp */
}
#license-card > *{ position:relative; z-index:1; }
#license-card.is-loading::before, #license-card.is-owned::before{ opacity:1; }
#license-card.is-loading, #license-card.is-owned{
  border-color:rgba(255,255,255,.09);
  box-shadow:0 18px 40px -18px rgba(16,40,110,.5);
  padding:24px 26px 22px;
}
@media (hover: hover) and (pointer: fine){
  #license-card.is-owned:hover{ box-shadow:0 24px 48px -18px rgba(16,40,110,.6); }
}
/* grid stack: skeleton + the two states occupy the SAME cell, so swapping
   between them is an in-place crossfade with no layout jump */
.lic-stack{ display:grid; position:relative; }
.lic-stack > *{ grid-area:1 / 1; min-width:0; position:relative; z-index:1; }
/* skeleton must PAINT ON TOP of the resolved block so it can fade to reveal it
   (positioned siblings paint by z-index, not DOM order) */
.lic-skel{ z-index:2; }
.lic-mark{
  /* positioned against #lic-owned (its positioned parent since the ::before
     crossfade change), so 0/0 aligns it with the plan-name head row */
  position:absolute; top:2px; inset-inline-end:0;
  font-family:var(--sans); font-weight:800; font-size:.95rem; letter-spacing:-.02em;
  color:rgba(255,255,255,.85);
}
.lic-mark i{ font-style:normal; color:var(--blue-bright); }
#license-card.is-owned .plan-name{ color:#fff; }
#license-card.is-owned .plan-dateline{ color:rgba(233,240,252,.62); }
#license-card.is-owned .plan-badge{
  background:rgba(74,222,128,.14); color:#4ADE80;
  box-shadow:0 0 14px rgba(74,222,128,.28);
}
#license-card.is-owned .plan-actions{ margin-top:18px; }
.lic-head{ padding-inline-end:74px; }   /* keep the plan name clear of the wordmark */
.lic-slots{ display:flex; gap:8px; margin-top:16px; }
.lic-slot{
  width:36px; height:27px; border-radius:7px; display:grid; place-items:center;
  border:1.5px dashed rgba(255,255,255,.26); color:rgba(255,255,255,.38);
}
.lic-slot svg{ width:15px; height:15px; }
.lic-slot.is-on{
  border-style:solid; border-color:rgba(75,166,241,.7);
  background:rgba(75,166,241,.18); color:#CDE3FF;
  box-shadow:0 0 10px rgba(75,166,241,.25);
}
.lic-machines{ font-size:.84rem; margin-top:8px; }

/* ---- loading choreography ------------------------------------------------
   The DB-backed cards boot as a SKELETON that sits inside the final card
   shape (dark for the membership card). When license-me resolves, the skeleton
   crossfades — in place, same grid cell — into the real content, which settles
   in on one calm ease-out. No colour flip, no height jump, no pop. */
.ease-calm{}   /* doc marker: entrance ease is cubic-bezier(.22,.61,.36,1) */
.skel{
  position:relative; display:block; border-radius:8px;
  background:#EDF1F6; overflow:hidden;
}
.skel::after{
  content:""; position:absolute; inset:0; transform:translateX(-100%);
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.75), transparent);
  animation:skelSweep 1.35s ease-in-out infinite;
}
@keyframes skelSweep{ to{ transform:translateX(100%); } }
/* skeleton bars on the DARK membership card = translucent white, not grey */
#license-card.is-loading .skel{ background:rgba(255,255,255,.10); }
#license-card.is-loading .skel::after{
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.16), transparent);
}
.sk-row{ display:flex; align-items:center; gap:10px; }
.sk-title{ width:158px; height:24px; }
.sk-badge{ width:64px; height:20px; border-radius:999px; }
.sk-slots{ margin-top:16px; }
.sk-slot{ width:36px; height:27px; border-radius:7px; }
.sk-line{ width:172px; height:13px; margin-top:11px; }
.sk-btn{ width:98px; height:40px; border-radius:999px; margin-top:18px; }
/* skeleton fades out (fast) as the real content fades in beneath it */
.lic-skel{ transition:opacity .3s var(--ease); }
.lic-skel.is-done{ opacity:0; pointer-events:none; }
/* content entrance: one calm ease-out — a whisper of rise + fade. slots and
   badge join the SAME motion a hair later (gentle fade + micro-scale, no pop) */
@keyframes licIn{ from{ opacity:0; transform:translateY(6px); } to{ opacity:1; transform:none; } }
.lic-in{ animation:licIn .55s cubic-bezier(.22,.61,.36,1) both; }
@keyframes slotIn{ from{ opacity:0; transform:scale(.9); } to{ opacity:1; transform:none; } }
.lic-in .lic-slot{ opacity:0; animation:slotIn .45s cubic-bezier(.22,.61,.36,1) both; }  /* per-slot delay set in JS */
@keyframes badgeIn{ from{ opacity:0; transform:scale(.94); } to{ opacity:1; transform:none; } }
.lic-in .plan-badge{ animation:badgeIn .45s cubic-bezier(.22,.61,.36,1) .18s both; }
@media (prefers-reduced-motion:reduce){
  .skel::after{ animation:none; }
  .lic-skel{ transition:none; }
  .lic-in, .lic-in .lic-slot, .lic-in .plan-badge{ animation:none; opacity:1; transform:none; }
  #license-card::before{ transition:none; }
}
.acct-card h3{
  font-family:var(--sans); font-weight:700; letter-spacing:-.015em;
  font-size:1.18rem; margin:0 0 6px; color:var(--ink);
}
.acct-card p{ color:var(--ink-soft); font-size:.95rem; line-height:1.55; margin:0 0 14px; }
.acct-card .btn{ font-size:.95rem; padding:12px 20px; }

/* plan card — generic structure (name / status / date / actions) so the same
   card renders Lifetime today and Monthly when that tier exists */
.plan-name{ font-family:var(--sans); font-weight:800; font-size:1.25rem; letter-spacing:-.015em; color:var(--ink); }
.plan-badge{
  display:inline-flex; align-items:center; gap:5px; margin-inline-start:10px; vertical-align:2px;
  font-family:var(--body); font-weight:600; font-size:.72rem; letter-spacing:.02em;
  padding:3px 9px; border-radius:var(--radius-pill);
  background:rgba(13,138,85,.10); color:#0d8a55;
}
.plan-badge::before{ content:""; width:6px; height:6px; border-radius:50%; background:currentColor; flex:none; }
.plan-dateline{ color:var(--ink-soft); font-size:.9rem; margin-top:6px; }
.plan-note{ color:var(--ink-faint); font-size:.83rem; margin-top:10px; }
.plan-actions{ margin-top:16px; display:flex; gap:10px; flex-wrap:wrap; }

/* license detail rows */
.lic-row{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:13px 0; border-top:1px solid var(--line);
}
.lic-row:first-of-type{ border-top:0; }
.lic-row .k{ color:var(--ink-soft); font-size:.88rem; flex:none; }
.lic-row .v{ color:var(--ink); font-size:.92rem; text-align:right; overflow-wrap:anywhere; }
.lic-deact{
  background:none; border:1px solid var(--line-strong); border-radius:9px; cursor:pointer;
  font-family:var(--body); font-weight:600; font-size:.82rem; color:var(--ink-soft);
  padding:7px 12px; transition:color .15s var(--ease), border-color .15s var(--ease);
}
.lic-deact:hover{ color:#c0392b; border-color:#c0392b; }

/* quick links row — icon + label | arrow. The hover is a tactile gesture, not
   a color swap: the tint floods instantly, the whole label nudges right on a
   springy back-out ease (slight overshoot), the icon pops, the arrow slides
   further — and the row compresses on press. */
.acct-links{ display:flex; flex-direction:column; padding:10px 10px 8px; }
.acct-link{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  width:100%; box-sizing:border-box; text-align:left;
  padding:14px 12px; border:0; border-top:1px solid var(--line); background:none; cursor:pointer;
  border-radius:12px;
  font-family:var(--body); font-weight:600; font-size:.97rem; color:var(--ink); text-decoration:none;
  transition:background .13s var(--ease), color .11s var(--ease), scale .12s var(--ease);
}
.acct-link:first-child{ border-top:0; }
.acct-link .lk{
  display:flex; align-items:center; gap:12px;
  transition:translate .22s cubic-bezier(.34,1.56,.64,1);   /* back-out: tiny overshoot */
}
.acct-link .ic{
  width:19px; height:19px; flex:none; color:var(--ink-faint);
  transition:color .11s var(--ease), scale .22s cubic-bezier(.34,1.56,.64,1);
}
.acct-link .arr{
  color:var(--ink-faint);
  transition:translate .22s cubic-bezier(.34,1.56,.64,1), color .11s var(--ease);
}
.acct-link:hover{ background:#F3F7FE; color:var(--blue); border-top-color:transparent; }
.acct-link:hover .lk{ translate:3px 0; }
.acct-link:hover .ic{ color:var(--blue); scale:1.12; }
.acct-link:hover .arr{ color:var(--blue); translate:5px 0; }
.acct-link:active{ scale:.98; }
.acct-link:focus-visible{ outline:2px solid var(--blue); outline-offset:-2px; }
/* the tinted row hides the separator above the NEXT row too, so the pill
   doesn't sit against a floating hairline */
.acct-link:hover + .acct-link{ border-top-color:transparent; }
.acct-link.danger:hover{ background:#FDF3F2; color:#c0392b; }
.acct-link.danger:hover .ic, .acct-link.danger:hover .arr{ color:#c0392b; }
@media (prefers-reduced-motion:reduce){
  .acct-link:hover .lk, .acct-link:hover .arr{ translate:0; }
  .acct-link:hover .ic{ scale:1; }
  .acct-link:active{ scale:1; }
}

/* name-backfill nudge — reuses .survey-resume's card shell (same "quiet
   inline prompt" pattern); this input is the one nudge-specific addition */
.acct-name-input{
  flex:1 1 180px; min-width:140px; box-sizing:border-box; padding:9px 12px;
  border:1.5px solid var(--line-strong); border-radius:9px; background:var(--bg);
  font-family:var(--body); font-size:.92rem; color:var(--ink);
  transition:border-color .15s var(--ease);
}
.acct-name-input:focus{ outline:none; border-color:var(--blue); }
/* A border-colour change alone is a colour-only cue and vanishes in
   forced-colors mode; keyboard users get a real ring back here. */
.acct-name-input:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }

/* ============================================================================
   SETTINGS PAGE (/account) — unified two-column: section panels (left) +
   profile card & section nav (right). Reuses the skeleton (.lic-skel/.skel),
   .lic-row/.lic-deact and .acct-name-input from above.
   ========================================================================== */
.set-head{ padding-top:clamp(120px,15vh,160px); padding-bottom:6px; }
.set-head h1{ font-family:var(--sans); font-weight:800; letter-spacing:-.02em;
  font-size:clamp(1.9rem,3.4vw,2.5rem); margin:0 0 6px; color:var(--ink); }
.set-head .sub{ color:var(--ink-soft); font-size:1rem; margin:0; }

.settings-grid{
  display:grid; grid-template-columns:minmax(0,1fr) 340px; gap:26px;
  max-width:1060px; margin-inline:auto; padding-bottom:56px; align-items:start;
}

/* ---- left: panels -------------------------------------------------------- */
.settings-main{ min-width:0;
  background:#fff; border:1px solid var(--line); border-radius:var(--radius-card);
  box-shadow:0 1px 2px rgba(17,17,19,.04); padding:clamp(20px,2.4vw,30px); }
.set-panel[hidden]{ display:none; }
@keyframes panelIn{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:none; } }
.panel-in{ animation:panelIn .4s var(--ease) both; }
.set-block + .set-block, .set-block{ }
.set-h{ font-family:var(--sans); font-weight:700; letter-spacing:-.015em; font-size:1.25rem; margin:0 0 4px; color:var(--ink); }
.set-h3{ font-family:var(--sans); font-weight:700; font-size:1.02rem; margin:0 0 4px; color:var(--ink); }
.set-sub{ color:var(--ink-soft); font-size:.92rem; line-height:1.5; margin:0 0 16px; }
.set-label{ font-weight:600; font-size:.82rem; color:var(--ink-soft); margin:0 0 8px; }
.set-divider{ height:1px; background:var(--line); margin:24px 0; }

/* your-subscription card */
.sub-card{ border:1px solid var(--line); border-radius:14px; padding:18px 18px; background:var(--bg);
  transition:box-shadow .4s var(--ease); }
.sub-row{ display:flex; align-items:center; justify-content:space-between; gap:14px; flex-wrap:wrap; }
.sub-plan-line{ display:flex; align-items:center; gap:9px; }
.sub-plan{ font-family:var(--sans); font-weight:700; font-size:1.05rem; color:var(--ink); }
.sub-meta{ color:var(--ink-soft); font-size:.88rem; margin-top:2px; }
.sub-price{ font-family:var(--sans); font-weight:800; font-size:1.05rem; color:var(--ink); white-space:nowrap; }
.sub-mark{ display:none; }
.sub-badge{
  display:inline-flex; align-items:center; gap:5px;
  font-family:var(--body); font-weight:600; font-size:.72rem; letter-spacing:.02em;
  padding:3px 9px; border-radius:var(--radius-pill);
  background:rgba(13,138,85,.10); color:#0d8a55;
}
.sub-badge::before{ content:""; width:6px; height:6px; border-radius:50%; background:currentColor; flex:none; }

/* ── LIFETIME = the membership-card trophy ─────────────────────────────────
   Deep brand navy with blue corner glows, a Subtitl wordmark and a glowing
   Active badge — the post-purchase reward, not a receipt. The skeleton (light)
   crossfades to reveal this card fading in beneath it, so the load reads as
   "unveiling the trophy" rather than a colour flip. Monthly is untouched. */
.sub-card.is-lifetime{
  position:relative; overflow:hidden; border-color:rgba(255,255,255,.09);
  background:
    radial-gradient(120% 150% at 88% -30%, rgba(75,166,241,.38) 0%, transparent 55%),
    radial-gradient(100% 130% at 8% 115%, rgba(18,134,230,.30) 0%, transparent 58%),
    var(--brand-gradient-ink);   /* white text sits on this — AA-safe ramp */
  box-shadow:0 18px 40px -18px rgba(16,40,110,.5);
  padding:22px 20px 20px;
}
@media (hover:hover) and (pointer:fine){
  .sub-card.is-lifetime:hover{ box-shadow:0 24px 48px -18px rgba(16,40,110,.62); }
}
.sub-card.is-lifetime .sub-plan{ color:#fff; }
.sub-card.is-lifetime .sub-meta{ color:rgba(233,240,252,.62); }
.sub-card.is-lifetime .sub-price{ color:rgba(255,255,255,.9); }
.sub-card.is-lifetime .sub-badge{
  background:rgba(74,222,128,.14); color:#4ADE80;
  box-shadow:0 0 14px rgba(74,222,128,.28);
}
.sub-card.is-lifetime .sub-mark{
  display:block; position:absolute; top:16px; inset-inline-end:18px; z-index:2;
  line-height:0; pointer-events:none;
}
/* WHITE artwork — this card's background is --brand-gradient-ink (dark). 45px
   matches what the old .9rem/800 text measured, using the same px-per-font
   ratio the rest of the wordmark migration used (~3.09x). */
.sub-card.is-lifetime .sub-mark img{ display:block; width:45px; height:auto; }
.sub-card.is-lifetime .sub-row{ padding-inline-end:60px; }
/* dark card → skeleton bars become translucent white so they read on navy */
.sub-card.is-lifetime .skel{ background:rgba(255,255,255,.10); }
.sub-card.is-lifetime .skel::after{
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.16), transparent); }
.sub-actions{ margin-top:14px; display:flex; gap:10px; flex-wrap:wrap; }
.sub-actions .btn{ font-size:.9rem; padding:10px 18px; }
.sub-note{ margin-top:12px; font-size:.86rem; color:var(--ink-soft);
  background:#FBFAF3; border:1px solid #EFE7C6; border-radius:10px; padding:11px 13px; }
.sub-note.is-good{ background:rgba(13,138,85,.06); border-color:rgba(13,138,85,.2); color:#0d6f47; }
/* a cancel that FAILED — the subscription is still active and still billing,
   so this has to read as a problem, not as a neutral status line. */
.sub-note.is-bad{ background:rgba(193,42,42,.06); border-color:rgba(193,42,42,.22); color:#a32020; }
/* action needed but nothing broken YET — a live Monthly still dangling after a
   Lifetime upgrade (money at risk on the next cycle, not lost). Amber, between
   is-good and is-bad on purpose. */
.sub-note.is-warn{ background:rgba(178,125,8,.07); border-color:rgba(178,125,8,.28); color:#8a6106; }
/* second press of the two-step cancel: the button stops looking neutral once
   the next click actually stops someone's billing. */
.sub-actions .btn.is-danger{ border-color:rgba(193,42,42,.55); color:#a32020; }
@media (hover:hover){
  .sub-actions .btn.is-danger:hover{ border-color:#a32020; background:rgba(193,42,42,.06); }
}

/* plan usage bar (device activations) */
.usage-top{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
.usage-label{ font-weight:700; font-size:.9rem; color:var(--ink); }
.usage-count{ color:var(--ink-soft); font-size:.9rem; }
.usage-count strong{ color:var(--blue); font-variant-numeric:tabular-nums; }
.usage-bar{ height:8px; border-radius:999px; background:var(--blue-wash,rgba(18,134,230,.08)); overflow:hidden; margin:8px 0 6px; }
.usage-fill{ display:block; height:100%; border-radius:999px; background:linear-gradient(90deg,var(--blue-bright),var(--brand-blue)); transition:width .8s var(--ease); }
.usage-foot{ display:flex; align-items:center; justify-content:space-between; }
.usage-foot span{ color:var(--ink-faint); font-size:.82rem; }

/* ---- Device manager rows (device-list / device-deactivate) --------------
   One row per active device: label + last-seen on the left, Remove on the
   right. A single-device account gets one clean row rather than a bar chart
   of one — nothing here implies a missing or broken second slot. */
.dev-row{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:14px 0; border-bottom:1px solid var(--line);
}
.dev-row:last-of-type{ border-bottom:0; }
/* Exit for the row the customer just removed. renderDevices() rebuilds the
   whole list with innerHTML, so the node cannot be transitioned once that
   runs — the JS adds .is-going and waits for this before re-rendering. */
.dev-row{ transition:opacity 200ms var(--ease), transform 200ms var(--ease); }
.dev-row.is-going{ opacity:0; transform:translateX(8px); pointer-events:none; }
.dev-meta{ display:flex; flex-direction:column; gap:3px; min-width:0; }
.dev-name{
  font-family:var(--sans); font-weight:650; font-size:.98rem; color:var(--ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.dev-seen{ font-size:.82rem; color:var(--ink-faint); }
.dev-act{ display:flex; align-items:center; gap:10px; flex:none; }
.dev-remove{ padding:8px 16px; font-size:.88rem; }
.dev-msg{ font-size:.82rem; color:#c0392b; max-width:20ch; }
.dev-notice{
  margin:12px 0 0; padding:10px 14px; border-radius:10px;
  background:var(--blue-wash); color:var(--brand-blue-ink);
  font-size:.86rem; font-weight:500;
}
.dev-empty{
  padding:18px 0; color:var(--ink-soft); font-size:.94rem; line-height:1.5;
}
@media (max-width:560px){
  .dev-row{ flex-direction:column; align-items:stretch; gap:10px; }
  .dev-act{ justify-content:flex-start; }
  .dev-msg{ max-width:none; }
}
.usage-link{ background:none; border:0; cursor:pointer; font-family:var(--body); font-weight:600; font-size:.85rem; color:var(--blue); padding:0; }
.usage-link:hover{ text-decoration:underline; }

/* ---- Device limit: upgrade / downgrade ----------------------------------
   The quote block is deliberately quiet until it has something to say, and
   the downgrade disclosure is the ONE place here allowed to look like a
   warning — it is the only part that takes something away. */
.dev-tier{ margin-top:4px; }
.dev-tier-row{ display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin-top:14px; }
.dev-quote{
  margin-top:16px; padding:16px 18px; border-radius:14px;
  background:var(--wash, #f7f9fc); border:1px solid var(--line);
}
.dev-quote-h{ margin:0; font-family:var(--sans); font-weight:650; font-size:1rem; color:var(--ink); line-height:1.45; }
.dev-quote-warn{ margin:14px 0 6px; font-size:.9rem; font-weight:600; color:#a2412c; line-height:1.5; }
.dev-quote-list{ margin:0 0 4px; padding-inline-start:20px; }
.dev-quote-list li{ font-size:.9rem; color:var(--ink-soft); line-height:1.7; }
.dev-quote-note{ margin:10px 0 0; font-size:.86rem; color:var(--ink-faint); line-height:1.55; }
.dev-quote-act{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:16px; }
.dev-pending{
  margin-top:14px; padding:14px 16px; border-radius:12px;
  background:var(--blue-wash); border:1px solid rgba(18,134,230,.18);
  display:flex; align-items:center; justify-content:space-between; gap:14px; flex-wrap:wrap;
}
.dev-pending p{ margin:0; font-size:.9rem; color:var(--brand-blue-ink); font-weight:500; line-height:1.5; }
.dev-msg-block{ margin:12px 0 0; font-size:.88rem; line-height:1.5; color:var(--ink-soft); }
.dev-msg-block.is-err{ color:#c0392b; }
.dev-msg-block.is-ok{ color:#1c7a4a; }
@media (max-width:560px){
  .dev-tier-row{ align-items:stretch; }
  .dev-tier-row .btn{ width:100%; }
  .dev-pending{ flex-direction:column; align-items:stretch; }
  .dev-quote-act .btn{ width:100%; }
}

/* payment placeholder */
.pay-empty{ display:flex; align-items:center; gap:10px; color:var(--ink-faint); font-size:.9rem;
  border:1px dashed var(--line-strong); border-radius:12px; padding:14px 16px; }
.pay-empty svg{ width:22px; height:22px; flex:none; }

/* profile fields */
.fld{ display:block; margin-bottom:18px; }
.fld-label{ display:block; font-weight:600; font-size:.85rem; color:var(--ink-soft); margin-bottom:7px; }
.fld-row{ display:flex; gap:10px; }
.fld-row .acct-name-input{ flex:1 1 auto; }
.fld-row .btn{ flex:none; padding:0 18px; font-size:.9rem; }
.acct-name-input.is-readonly{ background:#F5F6F8; color:var(--ink-soft); cursor:default; }
.fld-msg{ display:block; font-size:.82rem; margin-top:7px; min-height:1em; }
.fld-msg.is-err{ color:#c0392b; } .fld-msg.is-ok{ color:#0d8a55; }
.login-method .lm-badge{ display:inline-flex; align-items:center; gap:7px;
  font-weight:600; font-size:.88rem; color:var(--ink);
  background:var(--bg); border:1px solid var(--line-strong); border-radius:999px; padding:8px 14px; }

/* ---- right rail: profile card ------------------------------------------- */
.settings-rail{ position:sticky; top:96px; display:flex; flex-direction:column; gap:14px; }
.profile-card{ background:#fff; border:1px solid var(--line); border-radius:18px; overflow:hidden;
  box-shadow:0 1px 2px rgba(17,17,19,.04); }
.pc-band{ position:relative; height:88px;
  background:var(--brand-gradient); }
.pc-plan-pill{ position:absolute; top:14px; right:14px;
  display:inline-flex; align-items:center; gap:6px;
  font-family:var(--sans); font-weight:700; font-size:.76rem; color:#12245a;
  background:#fff; border-radius:999px; padding:6px 12px; box-shadow:0 4px 12px rgba(16,30,80,.22); }
.pc-plan-pill::before{ content:"✦"; color:var(--brand-blue); }
.pc-avatar{ width:66px; height:66px; border-radius:50%; margin:-33px 0 0 20px; position:relative;
  display:grid; place-items:center; color:#fff; font-family:var(--sans); font-weight:800; font-size:1.4rem;
  background:linear-gradient(180deg,var(--blue-bright),var(--brand-blue)); border:3px solid #fff;
  box-shadow:0 6px 16px rgba(16,30,80,.24); }
.pc-body{ padding:10px 20px 20px; }
.pc-name{ display:flex; align-items:center; gap:6px; font-family:var(--sans); font-weight:700; font-size:1.1rem; color:var(--ink); }
.pc-check{ width:17px; height:17px; flex:none; }
.pc-email{ color:var(--ink-soft); font-size:.9rem; margin-top:2px; overflow-wrap:anywhere; }

/* rail action buttons */
.rail-actions{ display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.rail-btn{ display:inline-flex; align-items:center; justify-content:center; gap:8px;
  font-family:var(--body); font-weight:600; font-size:.9rem; color:var(--ink); cursor:pointer;
  background:#fff; border:1px solid var(--line-strong); border-radius:12px; padding:11px 12px;
  transition:background .14s var(--ease), border-color .14s var(--ease), color .12s var(--ease); }
.rail-btn svg{ width:17px; height:17px; }
.rail-btn:hover{ background:var(--bg); border-color:var(--ink); }
.rail-btn.danger{ color:#c0392b; }
.rail-btn.danger:hover{ background:#FDF3F2; border-color:#c0392b; }

/* upgrade nudge (monthly only) */
.rail-nudge{ position:relative; background:#FBFAF3; border:1px solid #EFE7C6; border-radius:14px; padding:15px 16px; }
.rail-nudge p{ margin:0 0 8px; font-size:.9rem; line-height:1.5; color:#6b5b1f; }
.rail-nudge-cta{ font-weight:700; font-size:.9rem; color:var(--blue); }
.rail-nudge-x{ position:absolute; top:9px; right:10px; background:none; border:0; cursor:pointer; color:var(--ink-faint); font-size:.8rem; line-height:1; padding:4px; }

/* section nav */
.set-nav{ display:flex; flex-direction:column; gap:2px;
  background:#fff; border:1px solid var(--line); border-radius:16px; padding:8px; box-shadow:0 1px 2px rgba(17,17,19,.04); }
.set-navitem{ display:flex; align-items:center; gap:11px; width:100%; text-align:left; cursor:pointer;
  font-family:var(--body); font-weight:600; font-size:.94rem; color:var(--ink-soft);
  background:none; border:0; border-radius:11px; padding:11px 12px;
  transition:background .13s var(--ease), color .12s var(--ease); }
.set-navitem svg{ width:18px; height:18px; flex:none; color:var(--ink-faint); transition:color .12s var(--ease); }
.set-navitem:hover{ background:var(--bg); color:var(--ink); }
.set-navitem:hover svg{ color:var(--ink-soft); }
.set-navitem.is-on{ background:#F3F7FE; color:var(--blue); }
.set-navitem.is-on svg{ color:var(--blue); }

/* ---- Invoices panel ------------------------------------------------------
   Skeleton reuses .skel from the license card. Rows are a one-open accordion:
   clicking a row reveals a full "sheet" (the same markup that gets printed),
   so the on-screen preview can never drift from the printed/downloaded copy. */
.inv-skel{ display:flex; flex-direction:column; gap:14px; }
.inv-skel-row{ display:flex; justify-content:space-between; gap:12px; }
.inv-empty{ display:flex; flex-direction:column; align-items:center; text-align:center;
  gap:12px; padding:38px 20px; color:var(--ink-soft); }
.inv-empty svg{ width:32px; height:32px; color:var(--ink-faint); }
.inv-empty p{ margin:0; font-size:.92rem; max-width:32ch; line-height:1.5; }

.inv-list{ border:1px solid var(--line); border-radius:14px; overflow:hidden; }
.inv-row + .inv-row{ border-top:1px solid var(--line); }
.inv-row-main{
  display:flex; align-items:center; gap:14px; width:100%; text-align:left; cursor:pointer;
  background:#fff; border:0; padding:14px 16px; font-family:var(--body);
  transition:background .12s var(--ease);
}
.inv-row-main:hover{ background:var(--bg); }
.inv-date{ flex:0 0 96px; font-size:.86rem; color:var(--ink-soft); }
.inv-plan{ flex:1 1 auto; font-weight:700; font-size:.92rem; color:var(--ink); min-width:0; }
.inv-status{
  flex:none; font-size:.76rem; font-weight:700; letter-spacing:.02em; text-transform:uppercase;
  padding:3px 9px; border-radius:999px;
}
.inv-status.is-paid{ background:rgba(13,138,85,.1); color:#0d6f47; }
.inv-status.is-refunded{ background:rgba(154,154,162,.16); color:var(--ink-soft); }
.inv-status.is-failed{ background:rgba(193,42,42,.1); color:#a32020; }
.inv-amount{ flex:0 0 auto; font-weight:700; font-size:.92rem; color:var(--ink); font-variant-numeric:tabular-nums; }
.inv-chev{ flex:none; width:16px; height:16px; color:var(--ink-faint); transition:transform .18s var(--ease); }
.inv-row-main.is-open .inv-chev{ transform:rotate(180deg); }

/* ---- Invoice modal --------------------------------------------------------
   Clicking a row opens this instead of an inline expand — a real overlay,
   amount-first, icon+value rows, closer to how a payment receipt actually
   reads. The formal printable document (.inv-sheet below) is DELIBERATELY
   separate: something you'd forward to an employer for reimbursement should
   look like an invoice, not like a screenshot of this app's chrome. */
.inv-modal-backdrop{
  position:fixed; inset:0; z-index:200; display:flex; align-items:center; justify-content:center;
  padding:24px; background:rgba(17,17,19,.45);
  opacity:0; pointer-events:none; transition:opacity .2s var(--ease);
}
.inv-modal-backdrop.is-open{ opacity:1; pointer-events:auto; }
.inv-modal{
  width:420px; max-width:100%; max-height:88vh; overflow-y:auto;
  background:#fff; border-radius:var(--radius-card); box-shadow:var(--shadow-lift);
  transform:translateY(10px) scale(.98); transition:transform .22s var(--ease-soft);
}
.inv-modal-backdrop.is-open .inv-modal{ transform:translateY(0) scale(1); }
@media (prefers-reduced-motion:reduce){ .inv-modal-backdrop, .inv-modal{ transition:none; } }

.inv-modal-head{ display:flex; align-items:center; justify-content:space-between;
  padding:18px 20px; border-bottom:1px solid var(--line); }
.inv-modal-head h3{ margin:0; font-family:var(--sans); font-size:1rem; font-weight:800; color:var(--ink); }
.inv-modal-close{ width:30px; height:30px; flex:none; display:flex; align-items:center; justify-content:center;
  border-radius:999px; border:0; background:var(--bg); color:var(--ink-soft); cursor:pointer; }
.inv-modal-close:hover{ background:var(--line); color:var(--ink); }
.inv-modal-close svg{ width:15px; height:15px; }

.inv-modal-amt-wrap{ padding:20px 20px 4px; }
.inv-modal-amt-label{ font-size:.8rem; color:var(--ink-soft); margin:0 0 4px; }
.inv-modal-amt{ font-family:var(--sans); font-weight:800; color:var(--ink); font-size:2.4rem; line-height:1.1; }
.inv-modal-amt .inv-amt-frac{ font-size:1.3rem; font-weight:700; color:var(--ink-faint); }

.inv-kv-list{ padding:8px 20px 4px; }
.inv-kv{ display:flex; align-items:center; gap:12px; padding:11px 0; border-top:1px solid var(--line); }
.inv-kv:first-child{ border-top:0; }
.inv-kv-ico{ flex:none; width:30px; height:30px; border-radius:9px; background:var(--bg);
  display:flex; align-items:center; justify-content:center; color:var(--ink-soft); }
.inv-kv-ico svg{ width:15px; height:15px; }
.inv-kv-label{ flex:1 1 auto; font-size:.88rem; color:var(--ink-soft); }
.inv-kv-val{ flex:none; max-width:56%; text-align:right; font-size:.88rem; font-weight:600; color:var(--ink);
  overflow-wrap:anywhere; }

/* generic pill used for email/date/payment-method values inside a row */
.inv-pill{ display:inline-flex; align-items:center; gap:6px; background:var(--bg);
  border:1px solid var(--line); border-radius:999px; padding:4px 11px 4px 4px; font-weight:600; }
.inv-pill.no-avatar{ padding-inline-start:11px; }
.inv-pill-avatar{ width:20px; height:20px; border-radius:999px; flex:none;
  background:var(--brand-gradient-ink); color:#fff; font-size:.66rem; font-weight:800;
  display:flex; align-items:center; justify-content:center; }
.inv-status-pill{ display:inline-flex; align-items:center; gap:5px; padding:4px 11px;
  border-radius:999px; font-weight:700; font-size:.82rem; }
.inv-status-pill.is-paid{ background:rgba(13,138,85,.1); color:#0d6f47; }
.inv-status-pill.is-refunded{ background:rgba(154,154,162,.16); color:var(--ink-soft); }
.inv-status-pill.is-failed{ background:rgba(193,42,42,.1); color:#a32020; }
.inv-status-pill svg{ width:12px; height:12px; }

.inv-modal-note{ margin:10px 20px 4px; padding:12px 14px; background:var(--bg);
  border-radius:12px; font-size:.85rem; color:var(--ink-soft); line-height:1.5; }
.inv-modal-note b{ color:var(--ink); }

.inv-file-row{ display:flex; align-items:center; gap:12px; margin:14px 20px 4px;
  padding:12px 14px; border:1px solid var(--line); border-radius:12px; }
.inv-file-ico{ flex:none; width:34px; height:34px; border-radius:9px; background:#FBEEEE;
  color:#B3413A; display:flex; align-items:center; justify-content:center; font-size:.64rem; font-weight:800; }
.inv-file-name{ flex:1 1 auto; font-size:.86rem; font-weight:700; color:var(--ink); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.inv-file-hint{ flex:none; font-size:.76rem; color:var(--ink-faint); }

.inv-modal-foot{ display:flex; align-items:center; gap:10px; padding:16px 20px 20px; }
.inv-icon-btn{ display:flex; align-items:center; justify-content:center; gap:8px;
  height:38px; padding:0 16px; border-radius:10px; border:1px solid var(--line-strong);
  background:#fff; color:var(--ink); font-family:var(--body); font-weight:700; font-size:.86rem;
  cursor:pointer; transition:background .12s var(--ease), border-color .12s var(--ease); }
.inv-icon-btn:hover{ background:var(--bg); border-color:var(--ink-faint); }
.inv-icon-btn svg{ width:15px; height:15px; }
.inv-icon-btn.is-primary{ flex:1 1 auto; background:var(--blue); border-color:var(--blue); color:#fff; }
.inv-icon-btn.is-primary:hover{ background:var(--blue-deep); border-color:var(--blue-deep); }
.inv-copy-ok{ color:#0d6f47 !important; border-color:rgba(13,138,85,.35) !important; }

/* the invoice "sheet" — reused as the on-screen detail AND cloned verbatim
   into #print-root when printing, so it is styled once, correctly, in both
   places rather than maintained twice. */
.inv-sheet{ background:#fff; border:1px solid var(--line); border-radius:12px; padding:20px; font-family:var(--body); }
.inv-sheet-head{ display:flex; justify-content:space-between; align-items:flex-start; gap:12px;
  padding-bottom:14px; margin-bottom:14px; border-bottom:1px solid var(--line); }
/* BLACK artwork — .inv-sheet always has a white background (the print-PDF
   invoice sheet). 57px matches what the old 1.15rem/800 text measured, using
   the same px-per-font ratio the rest of the wordmark migration used. */
.inv-sheet-brand{ line-height:0; }
.inv-sheet-brand img{ display:block; width:57px; height:auto; }
.inv-sheet-meta{ text-align:right; font-size:.84rem; color:var(--ink-soft); line-height:1.5; }
.inv-sheet-parties{ display:flex; gap:24px; flex-wrap:wrap; margin-bottom:16px; }
.inv-sheet-parties > div{ flex:1 1 200px; font-size:.86rem; color:var(--ink-soft); line-height:1.55; }
.inv-sheet-label{ display:block; font-size:.72rem; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; color:var(--ink-faint); margin-bottom:4px; }
.inv-sheet-table{ width:100%; border-collapse:collapse; font-size:.9rem; margin-bottom:14px; }
.inv-sheet-table th{ text-align:left; font-size:.72rem; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; color:var(--ink-faint); padding:0 0 8px; border-bottom:1px solid var(--line); }
.inv-sheet-table th:last-child, .inv-sheet-table td:last-child{ text-align:right; }
.inv-sheet-table td{ padding:10px 0; border-bottom:1px solid var(--line); color:var(--ink); }
.inv-sheet-table tfoot td{ padding-top:10px; border-bottom:0; font-weight:800; font-size:1rem; }
.inv-sheet-foot{ font-size:.82rem; color:var(--ink-soft); line-height:1.6; }

/* #print-root stays empty/invisible in normal browsing; only @media print
   flips the visibility so printing an invoice can never show it by mistake. */
#print-root{ display:none; }
@media print{
  body > *:not(#print-root){ display:none !important; }
  #print-root{ display:block !important; max-width:640px; margin:0 auto; }
  #print-root .inv-sheet{ border:0; padding:0; }
}

@media (max-width:560px){
  .inv-row-main{ flex-wrap:wrap; gap:6px 12px; }
  .inv-date{ flex:1 1 auto; order:1; }
  .inv-status{ order:2; }
  .inv-plan{ flex:1 1 100%; order:0; }
  .inv-amount{ order:3; margin-inline-start:auto; }
  .inv-chev{ order:4; }
  .inv-sheet-parties{ gap:14px; }
}

/* ---- responsive: single column, card + nav stack above content ---------- */
@media (max-width:880px){
  /* minmax(0,1fr), NOT 1fr — a bare 1fr track inflates to the widest stacked
     child (the horizontal nav's non-shrinking items), blowing the whole
     column past the viewport. minmax(0,…) + min-width:0 clamp it. */
  .settings-grid{ grid-template-columns:minmax(0,1fr); max-width:560px; gap:16px; }
  .settings-main, .settings-rail{ min-width:0; }
  .settings-rail{ position:static; order:-1; }   /* profile card + nav first */
  .set-nav{ flex-direction:row; overflow-x:auto; -webkit-overflow-scrolling:touch; min-width:0; }
  /* width:auto is load-bearing. The vertical desktop rail sets width:100% on
     .set-navitem (see the base rule); this row inherits it unless reset, which
     made every tab as wide as the whole bar — four tabs at 321px inside a 337px
     container at 375px, so only "Profile settings" was visible and Plan,
     Devices and Invoices sat entirely off-screen on every phone. */
  .set-navitem{ flex:1 0 auto; width:auto; justify-content:center; white-space:nowrap; }
}

/* ── UPI device upgrade ─────────────────────────────────────────────────────
   A multi-step money flow with a real external redirect. The rail exists so the
   customer always knows which of the three real steps they're on — a spinner
   would hide exactly the thing they need to understand.

   Phone-first on purpose: their UPI app is on the phone, so that is where this
   flow actually happens, not a narrow afterthought of the desktop layout. */
.upi-up{
  margin-top:14px; border:1px solid rgba(18,134,230,.28); border-radius:16px;
  background:linear-gradient(180deg, rgba(18,134,230,.05), rgba(18,134,230,0) 58%), var(--card,#fff);
  padding:16px 16px 14px;
}

/* ---- the step rail ---- */
.upi-steps{
  list-style:none; display:flex; align-items:flex-start; gap:0;
  margin:0 0 14px; padding:0; counter-reset:none;
}
.upi-steps li{
  flex:1 1 0; min-width:0; position:relative;
  display:flex; flex-direction:column; align-items:center; gap:6px;
  font-family:var(--body); font-size:.72rem; line-height:1.3; text-align:center;
  color:var(--ink-faint);
}
/* the connector sits BEHIND the dots and stops at the last step */
.upi-steps li::before{
  content:""; position:absolute; top:6px; left:-50%; width:100%; height:2px;
  background:var(--line); z-index:0;
}
.upi-steps li:first-child::before{display:none}
/* The rail exists so the customer always knows which of the three steps they
   are on; advancing it instantly is the one thing that undercuts that. */
.upi-steps li::before{ transition:background 240ms var(--ease); }
.upi-steps li.is-done::before,
.upi-steps li.is-now::before{background:var(--brand-blue,#1286E6)}
.upi-dot{
  position:relative; z-index:1; width:14px; height:14px; border-radius:50%;
  background:#fff; border:2px solid var(--line-strong); flex:none;
  /* background/border already transitioned here; `transform` is the addition —
     the active step now grows slightly so the rail's advance is legible as
     motion, not just a colour swap. */
  transition:background .25s var(--ease,ease), border-color .25s var(--ease,ease),
             transform .2s var(--ease,ease);
}
.upi-steps li.is-now .upi-dot{
  border-color:var(--brand-blue,#1286E6); background:var(--brand-blue,#1286E6);
  box-shadow:0 0 0 4px rgba(18,134,230,.16);
  transform:scale(1.12);
}
.upi-steps li.is-done .upi-dot{ border-color:var(--brand-blue,#1286E6); background:var(--brand-blue,#1286E6); }
.upi-steps li.is-now{ color:var(--ink); font-weight:650; }
.upi-steps li.is-done{ color:var(--ink-soft); }
.upi-lbl{ display:block; max-width:14ch; }

/* ---- body ---- */
.upi-h{ font-family:var(--sans); font-size:1rem; font-weight:750; margin:0 0 5px; color:var(--ink); }
.upi-sub{ font-family:var(--body); font-size:.86rem; line-height:1.5; color:var(--ink-soft); margin:0 0 10px; }
.upi-note{ font-family:var(--body); font-size:.78rem; line-height:1.5; color:var(--ink-faint); margin:10px 0 0; }
/* The redirect warning and the "don't pay twice" warning are the two lines that
   cost money if unread, so neither is a muted footnote. */
.upi-warn{
  font-family:var(--body); font-size:.8rem; line-height:1.5; margin:0 0 12px;
  color:#6B4E12; background:#FFF8E8; border:1px solid rgba(190,140,20,.28);
  border-radius:10px; padding:9px 11px;
}
.upi-act{ display:flex; gap:9px; flex-wrap:wrap; margin-top:4px; }
.upi-act .btn{ padding:11px 18px; font-size:.9rem; }

/* ---- the confirming state ---- */
.upi-wait{
  display:flex; align-items:center; gap:9px; margin:2px 0 0;
  font-family:var(--body); font-size:.84rem; color:var(--ink-soft);
}
.upi-spin{
  width:15px; height:15px; flex:none; border-radius:50%;
  border:2px solid rgba(18,134,230,.25); border-top-color:var(--brand-blue,#1286E6);
  animation:upiSpin .8s linear infinite;
}
@keyframes upiSpin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  .upi-spin{ animation-duration:2.4s; }
}

/* ---- phone ---- */
@media (max-width:560px){
  .upi-up{ padding:14px 13px 13px; border-radius:14px; }
  .upi-steps{ margin-bottom:12px; }
  .upi-steps li{ font-size:.66rem; }
  /* 12ch keeps 'Approve in your UPI app' to two lines; at 9ch it broke to
     three and pushed the other two labels out of alignment. */
  .upi-lbl{ max-width:12ch; }
  /* Full-width, thumb-sized actions stacked in reading order — this step is
     done one-handed, mid-payment, on the device the UPI app lives on. */
  .upi-act{ flex-direction:column; gap:8px; }
  .upi-act .btn{ width:100%; padding:13px 18px; }
}

/* ---- Plan & billing: the change-your-plan cards -------------------------
   Two cards side by side on desktop, stacked on mobile. They sit in the plan
   section rather than under Devices, because choosing what you pay for is a
   billing decision — Devices is where you sign a machine out. */
.plan-cards{
  display:grid; gap:14px; margin-top:14px;
  grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
  align-items:start;
}
.plan-card{
  border:1px solid var(--line); border-radius:16px; padding:18px 18px 16px;
  background:var(--card, #fff); position:relative;
  transition:border-color .25s var(--ease,ease), box-shadow .25s var(--ease,ease);
}
.plan-card:hover{ border-color:rgba(18,134,230,.34); box-shadow:0 6px 22px rgba(12,28,60,.07); }
/* The upgrade offer earns the accent; the device control is a neutral setting.

   HERO = the dark "membership card" object from /#pricing, scaled down for a
   settings column. Deciding to buy Lifetime is the same decision here as it is
   on the pricing page, so it should carry the same visual weight — a light
   tinted panel read as a settings row, which is precisely what this card is
   not. Same navy ramp, same luminous gradient rim, softer glow because it sits
   in a narrow column rather than on an open page. */
.plan-card--hero{
  border:1px solid transparent;
  border-radius:20px;
  color:#fff;
  background:
    linear-gradient(180deg,#1E2942 0%,#131A2B 55%,#101522 100%) padding-box,
    linear-gradient(180deg, rgba(143,176,255,.85) 0%, rgba(143,176,255,.25) 18%, rgba(255,255,255,.07) 45%, rgba(255,255,255,.05) 100%) border-box;
  box-shadow:0 2px 0 rgba(255,255,255,.4), 0 14px 38px rgba(20,26,52,.16);
}
/* the same top bloom, at the smaller scale */
.plan-card--hero::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(60% 34% at 50% 0%, rgba(75,166,241,.18), transparent 72%);
}
.plan-card--hero > *{position:relative;z-index:1}
.plan-card--hero:hover{
  border-color:transparent;
  box-shadow:0 2px 0 rgba(255,255,255,.4), 0 18px 46px rgba(20,26,52,.22);
}
.plan-card--hero .plan-card-h{color:#fff}
.plan-card--hero .plan-card-copy{color:rgba(255,255,255,.66)}
.plan-card--hero .plan-card-price{color:#fff}
.plan-card--hero .plan-card-per{color:rgba(255,255,255,.5)}
.plan-card--hero .plan-card-tag{
  background:rgba(255,255,255,.12); color:#fff;
  border:1px solid rgba(255,255,255,.2);
}

/* ---- What Lifetime actually gets them -------------------------------------
   A price alone is an amount, not a reason. These are the three differences
   that survive from Monthly, stated as outcomes rather than features. */
.plan-perks{list-style:none;margin:0 0 14px;padding:0;display:grid;gap:7px}
.plan-perks li{
  font-family:var(--body); font-size:.83rem; line-height:1.45;
  color:rgba(255,255,255,.78); padding-inline-start:21px; position:relative;
}
.plan-perks li::before{
  content:""; position:absolute; inset-inline-start:0; top:.42em;
  width:11px; height:6px; border-left:1.5px solid #7FC0FF; border-bottom:1.5px solid #7FC0FF;
  transform:rotate(-45deg);
}

/* ---- Price row ------------------------------------------------------------
   The figure is skeletoned until the authoritative price resolves. It must
   never flash a wrong number, and it must never show a stale one. */
.plan-card-price.is-loading .plan-price-fig{
  display:inline-block; min-width:5.2ch; height:1em; border-radius:6px;
  background:linear-gradient(90deg, rgba(255,255,255,.10) 25%, rgba(255,255,255,.20) 37%, rgba(255,255,255,.10) 63%);
  background-size:400% 100%; animation:planShimmer 1.4s ease infinite; color:transparent;
}
@keyframes planShimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}
@media (prefers-reduced-motion:reduce){
  .plan-card-price.is-loading .plan-price-fig{animation:none}
}
/* What they have already put into Monthly. CONTEXT, never a discount — it is
   never subtracted from the price above, and the copy never implies it is. */
.plan-paid{
  font-family:var(--body); font-size:.78rem; color:rgba(255,255,255,.52);
  margin:-6px 0 13px;
}

/* ---- States ---------------------------------------------------------------
   Designed, not generic. Today's real incident was a customer being told
   "try again" for something that could never succeed, so the blocked state and
   the failed state say different things and offer different actions. */
.plan-state{
  font-family:var(--body); font-size:.8rem; line-height:1.5;
  border-radius:12px; padding:11px 13px; margin:0 0 12px;
}
.plan-state-h{font-weight:700;margin:0 0 3px;display:block}
/* BLOCKED — a permanent limitation. No retry affordance anywhere in it. */
.plan-state--blocked{
  background:rgba(255,255,255,.07); border:1px solid rgba(255,255,255,.16);
  color:rgba(255,255,255,.82);
}
/* ORPHAN — Lifetime bought, old subscription still live. Lives OUTSIDE the dark
   card (see the markup comment) so the post-purchase reload cannot destroy it,
   which means it renders on the WHITE panel and needs its own light treatment
   rather than the on-navy one. Amber, not red: nothing failed about their
   purchase, but money will move again if they ignore it. */
.plan-state--orphan{
  background:#FFF8E8; border:1px solid rgba(190,140,20,.30); color:#6B4E12;
  margin-bottom:14px;
}
.plan-state--orphan .plan-state-h{color:#5A3F0B}

/* FAILED — something broke. Says plainly that no money moved, because after a
   payment error that is the only question the customer actually has. */
@keyframes planStateIn{ from{ opacity:0; transform:translateY(-4px); } to{ opacity:1; transform:none; } }
.plan-state{ animation:planStateIn .2s var(--ease); }   /* NO fill-mode on purpose:
   with `both` the from-state (opacity:0) is held before the animation starts, so
   anything that stops it running leaves an ERROR MESSAGE permanently invisible.
   Without a fill the safe default is fully visible, and since delay is 0 the
   first painted frame is still frame 0 — no flash, no risk. */
.plan-state--error{
  background:rgba(255,107,107,.10); border:1px solid rgba(255,107,107,.30);
  color:#FFC9C9;
}
.plan-card--hero .btn.plan-card-cta[disabled]{
  opacity:.45; cursor:not-allowed; transform:none; box-shadow:none;
}
/* The CTA on navy: white button, dark label — maximum separation from the
   card, and it is the only pressable thing in the hero. */
.plan-card--hero .plan-card-cta{
  background:#fff; color:#101522; border:1px solid rgba(255,255,255,.3);
  font-weight:750;
}
.plan-card--hero .plan-card-cta:not([disabled]):hover{
  background:#F2F6FF; transform:translateY(-1px);
  box-shadow:0 8px 22px rgba(10,16,34,.32);
}
.plan-card--hero .plan-card-cta:not([disabled]):active{transform:scale(.985)}
.plan-card-top{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:6px; }
.plan-card-tag{
  font-family:var(--body); font-size:.7rem; font-weight:700; letter-spacing:.02em;
  text-transform:uppercase; color:#fff; background:var(--brand-blue,#1286E6);
  border-radius:var(--radius-pill,999px); padding:3px 9px; line-height:1.5;
}
.plan-card-h{ font-family:var(--sans); font-size:1.02rem; font-weight:750; margin:0; color:var(--ink); }
.plan-card-copy{ font-family:var(--body); font-size:.88rem; color:var(--ink-soft); margin:0 0 10px; }
.plan-card-price{
  font-family:var(--sans); font-weight:800; font-size:1.5rem; letter-spacing:-.02em;
  color:var(--ink); margin:0 0 12px; display:flex; align-items:baseline; gap:7px;
}
.plan-card-per{ font-family:var(--body); font-size:.8rem; font-weight:500; color:var(--ink-faint); letter-spacing:0; }
.plan-card-cta{ width:100%; }
/* The double-charge warning. Deliberately not a muted footnote — it is the
   one thing on this card that costs money if it goes unread. */
.plan-card-note{
  font-family:var(--body); font-size:.78rem; line-height:1.5; margin:11px 0 0;
  color:var(--ink-soft); background:rgba(18,134,230,.06);
  border:1px solid rgba(18,134,230,.14); border-radius:10px; padding:9px 11px;
}
.plan-card--hero .plan-card-note{
  color:rgba(255,255,255,.7); background:rgba(255,255,255,.06);
  border-color:rgba(255,255,255,.14);
}
/* An empty note still paints its background, border and padding — it rendered
   as a mystery slab under the CTA whenever the copy was cleared (loading, and
   both the blocked and failed states, which suppress it). Caught on a real
   render, not by reading the CSS. The state boxes need no equivalent: they are
   toggled with [hidden] and always carry child elements, so :empty would never
   match them anyway. */
.plan-card-note:empty{display:none}
/* Inside a card the tier control no longer needs its own heading spacing. */
.plan-card .dev-tier{ margin-top:0; }
.plan-card .dev-tier .set-h3{ margin-top:0; }
@media (max-width:560px){
  .plan-cards{ grid-template-columns:1fr; }
  .plan-card{ padding:16px 15px 14px; }
  /* Hero first when stacked: the decision outranks the setting. */
  .plan-card--hero{ order:-1; padding:18px 16px 16px; }
  .plan-card-price{ font-size:1.38rem; }
}

/* ── Lifetime card: device chooser + credit breakdown ───────────────────────
   The breakdown exists because a discounted total with no explanation reads as
   a pricing bug. Showing the arithmetic — list, credit, payable — is what makes
   a lower number trustworthy. Every figure comes from lifetime-quote. */
.plan-dev{ display:flex; align-items:center; gap:10px; margin:0 0 12px; flex-wrap:wrap; }
.plan-dev-lbl{
  font-family:var(--body); font-size:.78rem; font-weight:600;
  color:rgba(255,255,255,.6); letter-spacing:.01em;
}
/* the stepper sits on navy here, not on the white settings panel */
.plan-card--hero .plan-dev .dev-sel{
  background:rgba(255,255,255,.08); border-color:rgba(255,255,255,.18);
}
.plan-card--hero .plan-dev .dev-read{ color:rgba(255,255,255,.72); }
.plan-card--hero .plan-dev .dev-read strong{ color:#fff; }
/* NOT color:#fff — .dev-btn is a WHITE circle with a blue-ink glyph, so forcing
   the glyph white made both invisible against it. The default already reads
   well on navy; only the surrounding chrome needs adjusting. */
.plan-card--hero .plan-dev .dev-btn{ box-shadow:0 1px 4px rgba(0,0,0,.35); }

.plan-bd{
  margin:0 0 13px; padding:11px 12px; border-radius:11px;
  background:rgba(255,255,255,.055); border:1px solid rgba(255,255,255,.12);
  font-family:var(--body); font-size:.82rem;
}
.plan-bd > div{ display:flex; justify-content:space-between; gap:12px; padding:2px 0; }
.plan-bd dt{ color:rgba(255,255,255,.66); margin:0; min-width:0; }
.plan-bd dd{ color:rgba(255,255,255,.9); margin:0; white-space:nowrap; font-variant-numeric:tabular-nums; }
.plan-bd .is-credit dd{ color:#7FE3A8; }          /* money coming off reads as a gain */
.plan-bd .is-total{
  margin-top:6px; padding-top:8px; border-top:1px solid rgba(255,255,255,.14);
}
.plan-bd .is-total dt{ color:#fff; font-weight:700; }
.plan-bd .is-total dd{ color:#fff; font-weight:750; }

@media (max-width:560px){
  .plan-bd{ font-size:.79rem; }
  .plan-dev{ gap:8px; }
}

/* Motion added 2026-08-02 for state changes that previously teleported.
   Reduced motion keeps the fade, drops the travel and the scale — the state
   change stays legible without movement, rather than being switched off. */
@media (prefers-reduced-motion:reduce){
  .auth-inter.show{ animation:none; opacity:1; transform:none; }
  .plan-state{ animation:none; opacity:1; transform:none; }
  .upi-dot, .upi-steps li::before{ transition:background 200ms var(--ease), border-color 200ms var(--ease); }
  .upi-steps li.is-now .upi-dot{ transform:none; }
  .dev-row{ transition:opacity 200ms var(--ease); }
  .dev-row.is-going{ transform:none; }
}
