/* ==========================================================================
   AUTO ASCENSION app design system
   --------------------------------------------------------------------------
   Approved by Dylan, 7 Oct 2026. Everything above the "ADDITIONS" line below
   is the design pack's own aa.css, byte for byte, from
   /root/uploads/app-onboarding-design/aa.css. DO NOT EDIT THAT HALF: the
   screens are built to match the mockups 1:1 and the pack is the reference
   the screenshots are compared against. New components go under ADDITIONS,
   built from the same tokens.

   Fonts (Google Fonts): Bodoni Moda 700 and 600 italic, Geist 400/500/600,
   Geist Mono 500, Michroma.

   Palette, for anything new:
     background  #F2F0ED     ink          #141414
     body grey   #5A554F     small print  #6B6660
     card        #FBFAF8     card border  #E5E1DB
     line        #DCD8D2     focus/accent #2340E0
   ========================================================================== */

.ph{width:390px;height:844px;position:relative;overflow:hidden;background:#F2F0ED;font-family:'Geist',system-ui,sans-serif;color:#141414;-webkit-font-smoothing:antialiased}
.ph *{box-sizing:border-box}
.scr{position:absolute;inset:0;display:flex;flex-direction:column;padding:22px 24px 34px;animation:aaIn .6s cubic-bezier(.22,1,.36,1) both}
@keyframes aaIn{from{opacity:0;transform:translateX(32px)}to{opacity:1;transform:none}}
@keyframes aaUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
@keyframes aaFloat{0%,100%{transform:translate(-50%,-50%) rotate(-8deg)}50%{transform:translate(-50%,-53%) rotate(-5deg)}}
@keyframes aaMq{to{transform:translateX(-50%)}}
@keyframes aaDraw{from{stroke-dashoffset:60}to{stroke-dashoffset:0}}
@keyframes aaPop{0%{transform:scale(.6);opacity:0}60%{transform:scale(1.06);opacity:1}100%{transform:scale(1)}}
@keyframes aaPulse{0%{transform:scale(.8);opacity:.5}100%{transform:scale(1.9);opacity:0}}
.st>*{animation:aaUp .7s cubic-bezier(.22,1,.36,1) both}
.st>*:nth-child(1){animation-delay:.06s}.st>*:nth-child(2){animation-delay:.12s}.st>*:nth-child(3){animation-delay:.18s}.st>*:nth-child(4){animation-delay:.24s}.st>*:nth-child(5){animation-delay:.3s}.st>*:nth-child(6){animation-delay:.36s}.st>*:nth-child(7){animation-delay:.42s}.st>*:nth-child(8){animation-delay:.48s}
.mark{font-family:'Michroma',sans-serif;font-size:14px;letter-spacing:.14em;text-align:center;padding-top:10px}
.h{font-family:'Bodoni Moda',serif;font-weight:700;font-size:42px;line-height:.98;letter-spacing:-.035em;margin:0}
.h em{font-style:italic;font-weight:600}
.c{text-align:center}
.sub{font-size:15px;line-height:1.45;color:#5A554F;margin:0}
.fine{font-size:12px;line-height:1.45;color:#6B6660;margin:0}
.tag{display:inline-flex;align-items:center;height:30px;padding:0 14px;border:1px solid #D9D5CF;border-radius:8px;background:#F8F7F4;font-family:'Geist Mono',monospace;font-weight:500;font-size:10.5px;letter-spacing:.16em;text-transform:uppercase}
.btn{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;height:56px;border-radius:999px;border:0;font-family:'Geist',sans-serif;font-weight:600;font-size:16px;cursor:pointer;text-decoration:none;transition:transform .2s cubic-bezier(.22,1,.36,1),opacity .25s,background .2s}
.btn:active{transform:scale(.97)}
.btn:focus-visible,.tile:focus-visible,.seg:focus-visible,.back:focus-visible,.lnk:focus-visible{outline:2px solid #2340E0;outline-offset:3px}
.pri{background:#141414;color:#fff;box-shadow:0 10px 24px rgba(20,20,20,.18)}
.sec{background:#E9E6E1;color:#141414;box-shadow:inset 0 1px 0 #fff,0 1px 2px rgba(20,20,20,.08)}
.apple{background:#000;color:#fff}
.apple svg{width:18px;height:18px}
.off{opacity:.3;pointer-events:none;box-shadow:none}
.top{display:flex;align-items:center;gap:14px;height:44px;flex:none}
.back{width:44px;height:44px;border-radius:50%;background:#fff;border:1px solid #E3DFD9;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#141414;transition:transform .2s cubic-bezier(.22,1,.36,1)}
.back:active{transform:scale(.92)}
.back svg{width:18px;height:18px}
.bar{flex:1;height:4px;border-radius:4px;background:#E0DCD6;overflow:hidden}
.bar i{display:block;height:100%;border-radius:4px;background:#141414;transition:width .7s cubic-bezier(.22,1,.36,1)}
.step{font-family:'Geist Mono',monospace;font-size:12px;color:#6B6660;width:28px;text-align:right}
.body{flex:1;display:flex;flex-direction:column;gap:16px;padding-top:22px;min-height:0}
.foot{flex:none;display:flex;flex-direction:column;gap:10px;padding-top:12px}
.fld{display:flex;flex-direction:column;gap:6px}
.lb{font-family:'Geist Mono',monospace;font-weight:500;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:#5A554F}
.in{height:52px;border-radius:14px;border:1px solid #DCD8D2;background:#fff;padding:0 16px;font-family:'Geist',sans-serif;font-weight:500;font-size:16px;color:#141414;outline:none;width:100%;transition:border-color .2s,box-shadow .25s}
.in::placeholder{color:#9A948D}
.in:focus{border-color:#141414;box-shadow:0 0 0 4px rgba(20,20,20,.08)}
.or{display:flex;align-items:center;gap:12px;font-size:12px;color:#6B6660}
.or:before,.or:after{content:"";flex:1;height:1px;background:#DCD8D2}
.grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.tile{height:94px;border-radius:18px;background:#FBFAF8;border:1px solid #E5E1DB;box-shadow:inset 0 1px 0 #fff,0 6px 14px rgba(20,20,20,.05);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;font-family:'Geist',sans-serif;font-weight:500;font-size:12.5px;color:#3A3632;cursor:pointer;padding:0 4px;transition:transform .22s cubic-bezier(.22,1,.36,1),box-shadow .25s,border-color .2s,background .2s}
.tile:active{transform:scale(.94)}
.tile svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.tile.on{background:#fff;color:#141414;border-color:#141414;box-shadow:0 0 0 1.5px #141414,0 12px 24px rgba(20,20,20,.12);transform:translateY(-2px)}
.segs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.seg{height:48px;border-radius:14px;border:1px solid #DCD8D2;background:#fff;font-family:'Geist',sans-serif;font-weight:500;font-size:14px;color:#3A3632;cursor:pointer;transition:all .22s cubic-bezier(.22,1,.36,1)}
.seg.on{background:#141414;color:#fff;border-color:#141414}
.seg:active{transform:scale(.96)}
.hero{position:relative;flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:0 4px}
.ring{position:absolute;left:50%;top:47%;width:430px;height:240px;animation:aaFloat 7s ease-in-out infinite;pointer-events:none}
.hero>*:not(.ring){position:relative}
.mqw{margin:0 -24px 22px;overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,#000 14%,#000 86%,transparent);mask-image:linear-gradient(90deg,transparent,#000 14%,#000 86%,transparent)}
.marq{display:flex;gap:10px;width:max-content;animation:aaMq 26s linear infinite;padding:8px 0 14px}
.chip{width:92px;height:92px;flex:none;border-radius:20px;background:#FBFAF8;border:1px solid #E5E1DB;box-shadow:inset 0 1px 0 #fff,0 8px 18px rgba(20,20,20,.06);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;font-size:12px;font-weight:500;color:#3A3632}
.chip svg,.ico{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.card{background:#FBFAF8;border:1px solid #E5E1DB;border-radius:24px;padding:20px;box-shadow:inset 0 1px 0 #fff,0 10px 26px rgba(20,20,20,.06);display:flex;flex-direction:column;gap:14px}
.price{display:flex;align-items:baseline;gap:10px}
.big{font-family:'Bodoni Moda',serif;font-weight:700;font-size:56px;line-height:.9;letter-spacing:-.04em}
.uline{background-image:linear-gradient(#2340E0,#2340E0);background-size:100% 2px;background-repeat:no-repeat;background-position:0 100%;padding-bottom:2px}
.tl{display:flex;flex-direction:column;gap:0}
.tr{display:flex;gap:12px;align-items:flex-start;position:relative;padding-bottom:12px}
.tr:last-child{padding-bottom:0}
.tr:before{content:"";position:absolute;left:5px;top:14px;bottom:-2px;width:1.5px;background:#DCD8D2}
.tr:last-child:before{display:none}
.dot{width:12px;height:12px;border-radius:50%;background:#fff;border:2px solid #141414;flex:none;margin-top:3px}
.dot.f{background:#141414}
.tw{font-family:'Geist Mono',monospace;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#6B6660}
.tt{font-size:14px;font-weight:500;line-height:1.35}
.ck{display:flex;flex-direction:column;gap:9px}
.ck div{display:flex;gap:10px;align-items:center;font-size:14px;color:#2E2A27}
.ck svg{width:18px;height:18px;flex:none;fill:none;stroke:#141414;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.links{display:flex;justify-content:center;gap:18px}
.lnk{background:none;border:0;padding:6px 2px;font-family:'Geist',sans-serif;font-size:12.5px;font-weight:500;color:#3A3632;text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.done{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px}
.tick{position:relative;width:96px;height:96px}
.tick span{position:absolute;inset:0;border-radius:50%;border:1.5px solid #141414;animation:aaPulse 2.2s cubic-bezier(.22,1,.36,1) .5s infinite}
.tick svg{position:relative;width:96px;height:96px;animation:aaPop .7s cubic-bezier(.22,1,.36,1) both}
.tick path{stroke-dasharray:60;animation:aaDraw .6s cubic-bezier(.22,1,.36,1) .35s both}
.qs{width:100%;display:flex;flex-direction:column;gap:8px}
.qr{display:flex;align-items:center;gap:12px;height:56px;padding:0 16px;border-radius:16px;background:#FBFAF8;border:1px solid #E5E1DB;font-size:14.5px;font-weight:500}
.qr .ico{width:20px;height:20px}
.qr em{margin-left:auto;font-style:normal;color:#6B6660}
.rowr{display:flex;justify-content:flex-end}

/* ==========================================================================
   ADDITIONS  (not in the pack: built from the pack's own tokens)
   ========================================================================== */

/* --- THE PHONE FRAME, ON A REAL PAGE -------------------------------------
   The pack's `.ph` is a fixed 390x844 device frame, which is right for a
   mockup and wrong for a page somebody opens on an actual phone: a 430px
   iPhone would show a 390px column with a strip of nothing beside it, and a
   screen whose content is taller than 844px would be clipped with no way to
   scroll to the button. `.ph` is therefore made fluid here and the fixed
   frame is kept as `.aa-frame .ph`, which is what the review harness and the
   screenshot comparison use. Nothing else about the frame changes.           */
.ph{width:100%;max-width:430px;height:auto;min-height:100dvh;margin:0 auto;overflow:visible}
.aa-frame .ph{width:390px;height:844px;min-height:0;overflow:hidden;
  border-radius:28px;border:1px solid #E5E1DB;box-shadow:0 18px 50px rgba(20,20,20,.1)}
/* A real screen scrolls. `position:absolute;inset:0` cannot, so on a page the
   screen becomes an ordinary flow box that fills the viewport at minimum. */
/* `.ph .scr`, DESCENDANT and not child. Every multi-screen page wraps each
   screen in its own [data-step] / [data-screen] div, so a child selector missed
   all of them and they kept the pack's `position:absolute; inset:0`. On a real
   page that clips anything taller than the frame: the plan step's body ended up
   drawn on top of its own Continue button, and the button could not be pressed.
   Found by a click that timed out, not by looking. */
.ph .scr{position:static;min-height:100dvh;padding-top:max(22px,env(safe-area-inset-top));
  padding-bottom:max(34px,env(safe-area-inset-bottom))}
.aa-frame .ph .scr{position:absolute;inset:0;min-height:0;padding:22px 24px 34px}
/* --- THE RING BEHIND THE HEADLINE, WHICH WAS BEING CUT IN HALF ------------
   The owner's words: "the spinning A logo in the background is cut off."

   THE NUMBERS. The pack draws the ring 430px wide and tilted -8deg, which makes
   its painted box 458px across. The pack frames a screen at 390px and lets
   `.ph{overflow:hidden}` trim it, so in the mockup it bleeds 34px past each
   edge of the phone and reads as deliberate.

   Ours was trimming it 58px a side, not 34, because this rule put the clip on
   `.hero` -- and `.hero` sits inside `.scr`'s 24px padding, so it is 342px wide
   on a 390px phone, not 390. Twenty-four more pixels came off each side than
   the design ever intended, and what was left looked like a mistake because it
   was one.

   Rather than restore the pack's bleed, THE RING NOW FITS. 93% of the hero,
   capped at the pack's own 430px: at -8deg that puts the painted box at 339 of
   342 available on a 390 phone, 324 of 327 at 375, and 432 of 432 on desktop.
   Fully visible at every width, centred, nothing clipped, and nothing can
   overflow the page sideways because nothing reaches the edge any more.

   The motion is the pack's own `aaFloat` and is untouched: a seven second
   rise-and-tilt, and it still stops dead under prefers-reduced-motion below. */
.hero{overflow:hidden}
.ring{width:min(430px,93%);height:auto;aspect-ratio:430/240}

/* --- MOTION, HONESTLY -----------------------------------------------------
   The pack's animations are the whole feel of the thing, and they are also a
   problem for somebody who has asked their phone to stop moving. Everything
   lands in its final position instantly instead.                            */
@media (prefers-reduced-motion:reduce){
  .scr,.st>*,.tick svg,.tick path{animation:none!important}
  .ring,.marq,.tick span{animation:none!important}
  .btn,.tile,.seg,.back,.bar i,.in{transition:none!important}
}

/* --- FORM PLUMBING THE MOCKUPS DO NOT SHOW ------------------------------- */
.in.bad{border-color:#B3261E;box-shadow:0 0 0 4px rgba(179,38,30,.1)}
.err{font-size:12.5px;line-height:1.4;color:#B3261E;margin:0}
.ok{font-size:12.5px;line-height:1.4;color:#1A7F4B;margin:0}
/* iOS zooms the page in when a field under 16px takes focus and never zooms
   back out, so every input stays at 16px. This is a behaviour fix, not a
   design choice, and the pack's .in is already 16px: this is the guard for
   anything new. */
input,select,textarea{font-size:16px}
.note{background:#F8F7F4;border:1px solid #E5E1DB;border-radius:14px;padding:12px 14px;
  font-size:13px;line-height:1.5;color:#3A3632;margin:0}
.spin{width:18px;height:18px;border-radius:50%;border:2px solid rgba(255,255,255,.35);
  border-top-color:#fff;animation:aaSpin .7s linear infinite;display:inline-block}
@keyframes aaSpin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.spin{animation-duration:2s}}

/* --- CHECKBOX, for the terms clickwrap ----------------------------------- */
.cbx{display:flex;gap:12px;align-items:flex-start;cursor:pointer;
  font-size:13px;line-height:1.45;color:#3A3632}
.cbx input{appearance:none;-webkit-appearance:none;flex:none;width:24px;height:24px;
  margin:0;border-radius:7px;border:1.5px solid #BDB8B1;background:#fff;cursor:pointer;
  transition:background .2s,border-color .2s}
.cbx input:checked{background:#141414;border-color:#141414}
.cbx input:checked:after{content:"";position:absolute;width:24px;height:24px;
  background:no-repeat center/13px url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E")}
.cbx input:focus-visible{outline:2px solid #2340E0;outline-offset:3px}
.cbx a{color:#141414;text-decoration:underline;text-underline-offset:2px}

/* --- THE PLAN PICKER ----------------------------------------------------
   Two cards, not a dropdown: the difference between them is the whole
   decision, and a dropdown hides one option behind a tap. Built from .card
   with a selected state borrowed from .tile.on, so the two picks on the
   sign-up (plan, then trade) feel like the same gesture.                     */
.plans{display:flex;flex-direction:column;gap:12px}
.plan{position:relative;text-align:left;width:100%;cursor:pointer;
  background:#FBFAF8;border:1px solid #E5E1DB;border-radius:20px;padding:16px 18px;
  box-shadow:inset 0 1px 0 #fff,0 6px 14px rgba(20,20,20,.05);
  display:flex;flex-direction:column;gap:6px;font-family:'Geist',sans-serif;
  transition:transform .22s cubic-bezier(.22,1,.36,1),box-shadow .25s,border-color .2s,background .2s}
.plan:active{transform:scale(.985)}
.plan.on{background:#fff;border-color:#141414;
  box-shadow:0 0 0 1.5px #141414,0 12px 24px rgba(20,20,20,.12)}
.plan:focus-visible{outline:2px solid #2340E0;outline-offset:3px}
.plan-h{display:flex;align-items:baseline;gap:8px;justify-content:space-between}
.plan-n{font-weight:600;font-size:16.5px;color:#141414}
.plan-p{font-family:'Bodoni Moda',serif;font-weight:700;font-size:26px;
  letter-spacing:-.03em;color:#141414;white-space:nowrap}
.plan-p span{font-family:'Geist',sans-serif;font-weight:500;font-size:12.5px;color:#6B6660;margin-left:4px}
.plan-b{font-size:13.5px;line-height:1.45;color:#5A554F}
/* APPLE 3.1.2, INSIDE THE PLAN BOX. The length of the subscription, the trial,
   the price after it and the auto-renewal, stated where the plan is chosen.
   Small print by design: it is a legal disclosure under a feature list, not a
   selling line, and it must not compete with the price above it. */
.plan-t{display:block;margin-top:2px;font-size:11.5px;line-height:1.45;color:#6B6660}
.plan-t span{font-weight:500;color:#3A3632;white-space:nowrap}
.plan-f{display:flex;flex-wrap:wrap;gap:6px}
.pill{display:inline-flex;align-items:center;height:24px;padding:0 10px;border-radius:999px;
  background:#EFEDE8;font-size:11.5px;font-weight:500;color:#3A3632}
.plan.on .pill{background:#F2F0ED}
.plan-tick{position:absolute;top:14px;right:16px;width:22px;height:22px;border-radius:50%;
  background:#141414;display:none;align-items:center;justify-content:center}
.plan.on .plan-tick{display:flex}
.plan.on .plan-h .plan-p{padding-right:26px}
.plan-tick svg{width:13px;height:13px;stroke:#fff;stroke-width:3;fill:none;
  stroke-linecap:round;stroke-linejoin:round}

/* --- THE SQUARE CARD FIELD ---------------------------------------------
   Square's SDK renders its own iframe and will not inherit our CSS, so the
   container is styled to match `.in` and the SDK is told the few values it
   does accept. A bare iframe here looks like a different website dropped
   into the middle of the page, which on a payment form reads as unsafe.      */
.cardbox{border:1px solid #DCD8D2;border-radius:14px;background:#fff;padding:4px 10px;
  min-height:56px;transition:border-color .2s,box-shadow .25s}
.cardbox.focus{border-color:#141414;box-shadow:0 0 0 4px rgba(20,20,20,.08)}
.cardbox.bad{border-color:#B3261E;box-shadow:0 0 0 4px rgba(179,38,30,.1)}
.secure{display:flex;align-items:center;justify-content:center;gap:7px;
  font-size:11.5px;color:#6B6660}
.secure svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.8}

/* --- A WIDER SCREEN ------------------------------------------------------
   /start is opened on desktops too. The design stays a single column, because
   that is what it was drawn as and a two-column version of it is a different
   design; it just gets room to breathe and stops being a phone-width ribbon
   in the middle of a 1440px window.                                          */
@media (min-width:760px){
  .ph{max-width:520px}
  .ph .scr{padding:40px 44px 56px}
  .h{font-size:46px}
}

/* --- TAP TARGETS ON THE PACK'S OWN LINK ROW -------------------------------
   `.lnk` (Restore / Terms / Privacy on the trial screen, Forgot password? on the
   login screen) comes out of the pack at 29px tall, and the pack's own README
   says touch targets are at least 44px. The pack contradicts itself, and 29px is
   a real miss on a phone: Apple's own floor is 44 and these three sit in a row
   where a near miss hits the wrong one.

   Grown WITHOUT changing how it looks. The text, the underline and its offset are
   the pack's; the extra height is padding, and the negative margin takes the extra
   back out of the layout so the row sits exactly where it was drawn.             */
.lnk{min-height:44px;display:inline-flex;align-items:center;padding:6px 10px;
  margin:-6px -8px}
.links{gap:8px;flex-wrap:wrap}
  /* THREE LINKS, NOT TWO. "Restore purchases | Terms of Use | Privacy Policy"
     is 300px of text before padding, so on a 390px phone it wraps rather than
     pushing the screen sideways. Checked by the geometry assertions in
     tools/test_app_paywall_layout.py, which is the only thing that can tell
     the difference between "it wrapped" and "it overflowed". */
  .plan-t{font-size:11px}
/* The same for the row with one link in it (Forgot password?), where the negative
   margin would otherwise pull it off the right edge. */
.rowr .lnk{margin-right:-10px}

/* --- [hidden] MUST WIN ----------------------------------------------------
   THE BUG THIS FIXES, found on a screenshot and not by a test: the sign-up page
   rendered its "You're in" success screen underneath the live form, and the
   "What is your trade?" free-text box was visible without Other being picked.

   `hidden` is a presentation hint with the specificity of a UA default, and
   every one of those elements also carries a class from the design pack that
   sets `display:flex`. A class beats the UA sheet, so `hidden` did nothing at
   all. The same collision took out a modal backdrop on this codebase before
   (`[hidden]` vs `display:grid`), which is why this is declared once, here, for
   everything rather than patched per element.                                  */
[hidden]{display:none!important}


/* --- MADE TO FEEL LIKE AN APP, NOT A PAGE --------------------------------
   The owner's words on 8 Oct: "it needs to be optimised for app" and "it still
   feels like it's [off]". Four things give a web view away, and none of them is
   the design:

     * the grey flash under every tap (the tap highlight),
     * the rubber-band bounce when you drag past the end,
     * text you can accidentally select by holding a label,
     * the double-tap zoom, which also makes every tap wait ~300ms to find out
       whether a second one is coming.

   The inputs keep their own selection and their own callout, because a person
   does need to select and paste a password.                                   */
.ph{-webkit-tap-highlight-color:transparent;overscroll-behavior:none;
  touch-action:manipulation;-webkit-text-size-adjust:100%;text-size-adjust:100%}
.ph .h,.ph .sub,.ph .lb,.ph .fine,.ph .mark,.ph .btn,.ph .tile,.ph .seg{
  -webkit-user-select:none;user-select:none}
.ph input,.ph textarea{-webkit-user-select:text;user-select:text;
  -webkit-touch-callout:default}
/* The notch and the home indicator. Without these the wordmark sits under the
   status bar and the button under the home bar on every iPhone with a notch. */
.ph .scr{padding-top:max(22px,calc(env(safe-area-inset-top) + 10px));
  padding-bottom:max(34px,calc(env(safe-area-inset-bottom) + 16px))}
/* A full-height screen that does not jump when the keyboard opens. dvh follows
   the visible viewport; vh does not, and the difference is the button sliding
   off the bottom the moment a field takes focus. */
@supports (height:100dvh){.ph .scr{min-height:100dvh}}


/* --- WHAT THE APP MAY NOT SHOW -------------------------------------------
   Apple: inside the iPhone app a sign-up must never lead to a price, a card
   form or a web payment page. Everything that would is marked
   `data-browser-only` and rendered hidden by the server; app_surface.js reveals
   it in a browser and removes it in the app.

   This rule is the backstop between those two moments. The attribute lands on
   <html> before the first paint, so even if the script that removes the markup
   is slow, nothing forbidden is ever drawn. Belt, braces, and a second pair of
   braces, because the cost of getting this wrong is the app being pulled.      */
html[data-surface="app"] [data-browser-only]{display:none!important}

/* --- WHAT IS IN EACH PLAN ------------------------------------------------
   The owner's own list, six lines on The App and five on App + Website, built
   from the pack's own tokens: the same Geist 13.5px the `.plan-b` blurb it
   replaces used, the same tick glyph as `.plan-tick`, and `.pill` underneath
   unchanged.

   A LIST, NOT A PARAGRAPH, because that is how somebody compares two boxes.
   The blurb it replaces was one sentence per plan and a customer deciding
   between GBP 99 and GBP 197 could not see what the difference bought them.

   `align-items:start` on the row rather than `center`: a feature that wraps to
   two lines must keep its tick beside the FIRST line, and centring floats the
   tick into the gap between them. "Your own 30 to 40 page website built to
   bring in local enquiries" wraps on every phone there is.                   */
.plan-l{display:flex;flex-direction:column;gap:7px;margin-top:2px}
.plan-i{display:flex;align-items:start;gap:8px;font-size:13.5px;line-height:1.45;
  color:#5A554F;text-align:left}
.plan-i svg{flex:0 0 15px;width:15px;height:15px;margin-top:3px;fill:none;
  stroke:#141414;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}
.plan.on .plan-i{color:#3A3632}

/* --- ROOM FOR THE KEYBOARD ------------------------------------------------
   `--kb-inset` and `data-kb` are published by app_keyboard.js. Nothing here
   does anything until a keyboard is actually up, so every screen without one
   -- and every desktop -- is byte-for-byte what it was.

   WHY A PADDING AND A STICKY FOOT, rather than shrinking the screen. The
   native shell runs Capacitor's Keyboard at `resize: 'none'`, so the web view
   keeps its full height and the keys are drawn over the bottom of it. The
   layout does not know they are there; `100dvh` does not know either. So the
   page is given exactly that much more to scroll through, and the Continue
   button is pinned to the top edge of the keyboard rather than being left
   underneath it.

   The foot carries the page's own ground colour because it now has content
   scrolling behind it, and a shadow above so the join reads as a bar rather
   than as text overlapping text.                                            */
html[data-kb="1"] .ph .scr{
  padding-bottom:calc(max(34px,env(safe-area-inset-bottom)) + var(--kb-inset,0px))}
html[data-kb="1"] .foot{
  position:sticky;bottom:calc(var(--kb-inset,0px) + 10px);z-index:3;
  background:#F2F0ED;
  /* FULL BLEED. `.scr` pads 24px either side, so a sticky bar left inside that
     padding paints a rectangle with two visible vertical edges and reads as a
     floating card rather than as the bottom of the screen. The negative margin
     takes it out to the edges and the padding puts the button back where it
     was, so nothing moves -- only the band behind it grows. 24px is the phone
     padding, and a phone is the only place a keyboard exists. */
  margin-left:-24px;margin-right:-24px;padding:12px 24px 10px;
  box-shadow:0 -10px 18px -10px rgba(20,20,20,.14)}

/* The hero's ring floats behind the headline on a screen with no fields on it;
   with a keyboard up the screen is a form, and an absolutely positioned
   decoration inside a scrolling column drags the scroll height around while
   the field is being centred. It is simply not drawn while typing.          */
html[data-kb="1"] .ring{display:none}

/* --- EVERY STEP ON ONE SCREEN -------------------------------------------
   The owner, on an iPhone SE: the plan screen ran 246px past the bottom and
   took Continue with it, and the trade grid ran 49px past on the website.
   A sign-up step you have to scroll to finish is a step people do not finish.

   THE PLAN BOXES ARE THE DENSE ONE on every phone, so they are tightened for
   everybody rather than only on short screens: a comparison is read by
   scanning two columns, and air between the lines does not help that. The
   "See everything" tap carries the bullets that do not fit. */
/* The pack's own ink, not the focus blue. #2340E0 is the focus ring colour and
   appears nowhere else as text; a bright blue link in a cream and black palette
   reads as somebody else's component. */
.plan-see{display:inline-block;margin-top:4px;font-size:12.5px;font-weight:500;
  color:#3A3632;text-decoration:underline;text-underline-offset:3px;
  text-decoration-color:#B9B4AD;cursor:pointer}
.plan-more{display:none;flex-direction:column;gap:5px;margin-top:5px}
.plan-more[hidden]{display:none}
.plan.open .plan-more{display:flex}

/* --- A SHORT SCREEN ------------------------------------------------------
   iPhone SE is 667 CSS px tall and an iPhone 14 is 844, so the same layout
   cannot be generous on both. Everything below is spacing only: no component
   changes shape, nothing is removed, and a 390x844 phone never enters this
   query so the approved design is untouched where it was signed off.         */
@media (max-height: 780px) {
  .h{font-size:34px}
  .body{gap:11px;padding-top:12px}
  .foot{gap:8px;padding-top:8px}
  .sub{font-size:14px;line-height:1.35}
  .ph .scr{padding-top:max(14px,env(safe-area-inset-top));
           padding-bottom:max(20px,env(safe-area-inset-bottom))}
  .btn{height:50px}
  .in{height:48px}
  .fld{gap:4px}
  .tile{height:78px;gap:6px;font-size:12px}
  .grid3{gap:8px}
  .plan{padding:14px 16px;gap:4px}
  .plan-l{gap:5px;margin-top:0}
  .plan-i{font-size:12.5px;line-height:1.3}
  .plan-n{font-size:15.5px}
  .plan-p{font-size:21px}
  .plans{gap:10px}
  .mark{padding-top:4px;font-size:13px}
}

/* --- THE TROUBLE BLOCK, when the App Store cannot be reached ------------- */
.trouble{display:flex;flex-direction:column;gap:8px;align-items:center;
  padding:12px 14px;border-radius:16px;background:#FBF4F3;border:1px solid #EBD5D2}
.trouble[hidden]{display:none}
.trouble .btn{height:44px;width:auto;padding:0 22px}

/* --- WHAT'S INCLUDED: A BOTTOM SHEET ------------------------------------
   "See everything" expanded the box in place, which pushed the page taller
   under the finger that tapped it: the owner's words were "glitch and jump
   down". A sheet slides over the page instead, so nothing below it moves and
   the scroll position it was read from is still there when it closes.

   `translateY` and `opacity` only -- both composited, so the slide does not
   relayout anything behind it. The page itself is frozen while it is open
   (`.sheet-open` on <html>) so a scroll gesture on the backdrop cannot move
   the page underneath.                                                      */
.sheet-wrap{position:fixed;inset:0;z-index:60;display:flex;align-items:flex-end;
  justify-content:center;pointer-events:none}
.sheet-wrap[hidden]{display:none}
.sheet-back{position:absolute;inset:0;background:rgba(20,20,20,.38);opacity:0;
  transition:opacity .26s cubic-bezier(.22,1,.36,1);pointer-events:auto}
/* The sheet is appended to <body>, OUTSIDE `.ph`, so it inherits none of the
   pack's typography: the first version rendered the whole feature list in
   Bodoni because that is the document default here. Stated explicitly. */
.sheet{font-family:'Geist',system-ui,sans-serif;color:#141414;
  position:relative;width:100%;max-width:430px;background:#F7F6F3;
  border-radius:24px 24px 0 0;padding:10px 24px max(22px,env(safe-area-inset-bottom));
  box-shadow:0 -14px 40px rgba(20,20,20,.18);pointer-events:auto;
  transform:translateY(100%);transition:transform .3s cubic-bezier(.22,1,.36,1);
  max-height:82vh;overflow-y:auto;-webkit-overflow-scrolling:touch}
.sheet-wrap.on .sheet-back{opacity:1}
.sheet-wrap.on .sheet{transform:translateY(0)}
.sheet-grab{width:40px;height:4px;border-radius:999px;background:#D6D1CA;margin:0 auto 14px}
.sheet-h{font-weight:600;font-size:17px;margin:0 0 12px;color:#141414}
.sheet .plan-i{font-size:14px;line-height:1.45;margin-bottom:9px}
.sheet-close{width:100%;height:50px;margin-top:14px;border-radius:999px;border:0;
  background:#141414;color:#fff;font-family:'Geist',sans-serif;font-weight:600;
  font-size:15px;cursor:pointer}
html.sheet-open,html.sheet-open body{overflow:hidden;touch-action:none}
@media (prefers-reduced-motion:reduce){
  .sheet,.sheet-back{transition:none}
}

/* --- THE STEP NEVER SCROLLS; THE LIST INSIDE IT DOES --------------------
   The owner on his own iPhone: "Continue is pushed off the bottom, and when
   scrolled the heading slides up under the status bar and the clock."

   Both are the same fault. The step was as tall as its content and the PAGE
   scrolled, so on a phone whose usable height is less than the content the
   button went below the fold, and scrolling to reach it dragged the heading up
   behind a transparent status bar that the page draws underneath.

   A height breakpoint could not fix this: his phone reports 844, so the
   `max-height: 780px` rules never applied, and guessing which devices are
   "short" is how this gets missed again on the next handset.

   So the screen is pinned to the visible viewport and the one part that can
   grow -- the plan boxes -- scrolls inside it. The header and the Continue
   button are always on screen, at any height, with no breakpoint involved.
   `100dvh` is the DYNAMIC viewport, which already excludes the browser chrome,
   and the safe-area padding keeps the top clear of the clock.                */
html[data-surface="app"] .ph .scr,
.ph .scr.scr-fixed{
  height:100dvh;min-height:100dvh;max-height:100dvh;overflow:hidden}
html[data-surface="app"] .ph .scr > .body,
.ph .scr.scr-fixed > .body{min-height:0;overflow:hidden;display:flex;flex-direction:column}

/* The one scrollable region. Only the plan list: every other step's body is
   short enough that pinning it would be fussing with nothing. */
.plans{overflow-y:auto;-webkit-overflow-scrolling:touch;min-height:0;flex:1 1 auto;
  /* so the last box does not sit flush against the fade of the foot */
  padding-bottom:2px}

/* NOTHING UNDER THE CLOCK. The top padding already asked for the inset; this
   makes the header itself a flex item that cannot be scrolled away, which is
   what "the heading slid under the status bar" actually was.               */
.ph .scr > .top{flex:none}
.ph .scr > .foot{flex:none}

/* ---------------------------------------------------------------------------
   THE WEBSITE ONBOARDING. Four components the approved mockups do not contain,
   built from the pack's own surfaces: the photo grid, the day rows, the chips
   and the chip list.

   HERE RATHER THAN IN A PAGE. They lived in a `<style>` block inside
   templates/trial_onboarding.html, which was fine while that page was the only
   one that asked these questions. It is not any more -- /start and the app's
   own sign-up walk include the same screens -- and the first run of
   tools/test_website_onboarding.py found every chip rendering at 21px high
   instead of 44 on both of the new hosts, because the rules were in a file
   neither of them loads. A shared component needs a shared stylesheet.

   EVERY CLASS IS PREFIXED `wb-`, which is not decoration. `.chk`, `.pg`, `.pt`
   and `.hr` were the names in that page, and `.hr` and `.pt` in particular are
   exactly the sort of two-letter name that something two hundred lines further
   down this file will want one day -- which is how the Outreach send panel
   became a 42px-tall button (see CLAUDE.md). Checked with
   `grep -n "\.wb-" static/*.css` before they were written.
   --------------------------------------------------------------------------- */
.wb-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.wb-cell{position:relative;aspect-ratio:1;border-radius:14px;overflow:hidden;
  background:#FBFAF8;border:1px solid #E5E1DB}
.wb-cell img{width:100%;height:100%;object-fit:cover;display:block}
.wb-cell.add{display:flex;align-items:center;justify-content:center;cursor:pointer;
  border-style:dashed;border-color:#C9C4BC;color:#6B6660}
.wb-cell.add svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round}
/* THE DAY ROW STACKS, AND IT HAS TO.
   It was `1fr auto`: the day name on the left, two time fields and a Closed
   toggle on the right, all on one line. That needs about 349px inside a card
   whose inner width on a 375px phone is 303, so every one of the seven rows ran
   off the right-hand edge and took the page's scrollWidth with it -- caught by
   the overflow assertion in tools/test_website_onboarding.py, which is the only
   thing that tells "it wrapped" apart from "it overflowed".

   A browser's time field is not a width we control either: Chrome draws
   "08:00 AM" plus a clock glyph, iOS draws something else again, and both grow
   with the locale. So the fields flex and the name sits above them. */
.wb-day{display:grid;grid-template-columns:minmax(0,1fr);gap:8px;
  padding:12px 0;border-bottom:1px solid #EAE7E1}
.wb-day:last-child{border-bottom:0}
.wb-day b{font-family:'Geist Mono',monospace;font-weight:500;font-size:11px;
  letter-spacing:.1em;text-transform:uppercase;color:#6B6660}
.wb-day .tt2{display:flex;gap:8px;align-items:center}
.wb-day input[type=time]{flex:1 1 0;min-width:0;width:100%;height:44px;
  border-radius:10px;border:1px solid #DCD8D2;box-sizing:border-box;
  background:#fff;padding:0 8px;font:500 15px 'Geist',sans-serif;color:#141414}
.wb-day.closed input[type=time]{opacity:.4}
/* THE CLOCK GLYPH COSTS 24px AND BUYS NOTHING ON A PHONE.
   With it, two time fields and a Closed toggle leave about 105px each inside a
   299px card, and Chrome needs 110 to draw "08:00 AM" -- so the field read
   "08:00 /" with the AM clipped off. The geometry assertions passed, because
   nothing was outside anything; it was just unreadable, which is what the
   screenshots are for. Tapping the field opens the picker on every phone with
   or without the glyph. */
.wb-day input[type=time]::-webkit-calendar-picker-indicator{display:none}
.wb-day input[type=time]::-webkit-date-and-time-value{text-align:left}
.wb-closed{font-family:'Geist Mono',monospace;font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;color:#6B6660;background:none;border:0;cursor:pointer;
  text-decoration:underline;text-underline-offset:3px;
  /* 44px, not the 6px of padding it had. A "Closed" toggle beside two time
     fields is the smallest thing on this screen and the one most often
     mis-tapped. */
  min-height:44px;min-width:44px;padding:6px;flex:0 0 auto;white-space:nowrap}
.wb-chips{display:flex;flex-wrap:wrap;gap:8px}
.wb-chip{display:inline-flex;align-items:center;gap:8px;
  /* MIN-height, not height. `height:44px` collapses to the content box the
     moment a chip's label wraps, which is what "Which? Trusted Trader" does at
     375px, and a 21px chip is not a tap target. */
  min-height:44px;padding:10px 14px;
  border-radius:999px;border:1px solid #DCD8D2;background:#fff;cursor:pointer;
  font:500 14px 'Geist',sans-serif;color:#3A3632;text-align:left;
  transition:background .2s cubic-bezier(.22,1,.36,1),
             color .2s cubic-bezier(.22,1,.36,1),
             border-color .2s cubic-bezier(.22,1,.36,1)}
.wb-chip.on{background:#141414;color:#fff;border-color:#141414}
.wb-chip:focus-visible{outline:2px solid #2340E0;outline-offset:3px}
.wb-chip:active{transform:scale(.97)}
@media (prefers-reduced-motion: reduce){
  .wb-chip{transition:none}
  .wb-chip:active{transform:none}
}
