/* site.css: fastlift.app HOLD THE LINE. Values from design/2026-09-10/VISUAL-SYSTEM.md. No em dashes. */
:root{
  --g0:#0B0B0C;--g1:#101114;--frame:#111214;
  --ink-1:#F4F5F2;--ink-2:#A6A9B3;--ink-3:#7E828C;
  --lime:#C6F432;--lime-deep:#9BC417;--lime-10:rgba(198,244,50,.10);--lime-16:rgba(198,244,50,.16);--lime-24:rgba(198,244,50,.24);--lime-45:rgba(198,244,50,.45);
  --hair:rgba(255,255,255,.08);--hair-hi:rgba(255,255,255,.14);--glass:rgba(255,255,255,.04);--glass-top:rgba(255,255,255,.06);--track:rgba(255,255,255,.10);--mark:rgba(255,255,255,.28);
  --ease-out:cubic-bezier(.22,1,.36,1);--ease-standard:cubic-bezier(.33,0,.20,1);--ease-odometer:cubic-bezier(.33,0,.15,1);--ease-loop:cubic-bezier(.65,0,.35,1);
  --d90:90ms;--d180:180ms;--d320:320ms;--d500:500ms;--d700:700ms;--d900:900ms;--d1400:1400ms;
}
@font-face{font-family:"Hanken Grotesk";src:url(fonts/HankenGrotesk-400.woff2) format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url(fonts/HankenGrotesk-600.woff2) format("woff2");font-weight:600;font-display:swap}
@font-face{font-family:"Hanken Fallback";src:local("Arial");size-adjust:100%;ascent-override:92%;descent-override:24%;line-gap-override:0%}
html{background:var(--g0);color:var(--ink-1);overflow-x:clip;-webkit-text-size-adjust:100%}
/* Safe-area insets on the sides (post-launch 5, 2026-09-11): viewport-fit=cover is set in the head,
   and only the store bar honoured an inset, so on a notched phone in landscape the nav and every
   headline ran under the notch. Zero everywhere else, so nothing changes on a plain screen. */
body{margin:0;padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right);font-family:"Hanken Grotesk","Hanken Fallback",Arial,sans-serif;font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased}
a{color:var(--lime)}a:hover{color:var(--lime-deep)}
/* height:auto (snag 05, 2026-09-11): every screenshot carries width="712" height="1444", and max-width
   alone clamps the width to its container while the height attribute still resolves to 1444px, so
   the fallback shots rendered at 240x1444, a 3x vertical stretch (the ring became a tall ellipse and
   the document grew to 14,750px). height:auto keeps the declared aspect ratio at any width. */
img{display:block;max-width:100%;height:auto}
/* focus: visible on every interactive element, never removed (task 10) */
a:focus-visible,button:focus-visible,input:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--lime);outline-offset:2px}
.skip{position:absolute;left:-9999px;top:16px;background:var(--lime);color:var(--g0);padding:12px 16px;border-radius:999px;z-index:50}
.skip:focus{left:16px}
/* type roles */
.eyebrow{font-size:11px;line-height:1;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--lime)}
h2.eyebrow{margin:0}
.display-xl{font-size:clamp(48px,6vw + 25.5px,112px);line-height:1.02;letter-spacing:-.025em;font-weight:600;margin:0}
.display-l{font-size:36px;line-height:1.05;letter-spacing:-.02em;font-weight:600;margin:0}
.lead{font-size:17px;line-height:1.5;color:var(--ink-2);margin:0}
.caption{font-size:13px;line-height:1.45;color:var(--ink-3);margin:0}
@media(min-width:1024px){.display-l{font-size:72px;line-height:1.02;letter-spacing:-.025em}.lead{font-size:22px;line-height:1.45}.caption{font-size:14px}.eyebrow{font-size:12px}}
/* Display type follows its COLUMN, not only the viewport (snag 02 and 03, 2026-09-11). The hero and
   story copy tracks now shrink before the phone does (see the two grids below), so between 900 and
   about 1300px the copy column can be far narrower than the viewport suggests, and a viewport-sized
   headline in a narrow column wraps one word to a row. Where container units exist the size is also
   capped by the column (14cqw keeps "Lose fat" on one row of a 322px column at 48px); engines without
   them keep the viewport clamp above, which fits because the column can never meet the phone. */
@supports(font-size:1cqw){
  .hero .copy,.beats{container-type:inline-size}
  .display-xl{font-size:min(clamp(48px,6vw + 25.5px,112px),clamp(48px,14cqw,112px))}
}
/* nav */
.nav{position:sticky;top:0;z-index:20;height:56px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;background:transparent}
.nav.is-scrolled{background:rgba(16,17,20,.92);backdrop-filter:blur(24px) saturate(140%);border-bottom:1px solid var(--hair)}
.nav .brand{display:flex;align-items:center;gap:8px;color:var(--ink-1);text-decoration:none;font-weight:600;font-size:17px}
.nav .links{display:none;gap:32px;font-size:14px}
.nav .links a{color:var(--ink-2);text-decoration:none}
.pill{display:inline-flex;align-items:center;height:36px;padding:0 16px;border-radius:999px;background:var(--lime);color:var(--g0);font-weight:600;font-size:14px;text-decoration:none;transition:background var(--d180) var(--ease-standard),color var(--d180) var(--ease-standard)}
.pill.is-ghost{background:transparent;color:var(--lime);box-shadow:inset 0 0 0 1px var(--lime-45)}
@media(min-width:900px){.nav{height:72px;padding:0 clamp(24px,4vw,80px)}.nav .links{display:flex;align-items:center}}
/* hero */
/* padding-bottom reserves the phone's slot (48px clearance + the 400px cropped frame) so the hero
   keeps its height whether or not #device is currently in it. site.js moves the device out to
   .sticky-col when the story starts and back again on the way up; with the device in normal flow
   that move deleted 448px from above the reader mid-scroll (measured: a 100px scroll threw #story
   548px up the viewport and shrank the document by 448px, a CLS far over the 0.00 budget). Desktop
   already avoided this by positioning the device absolutely inside a min-height hero; this does the
   same below 900px, so the move is layout-neutral at every width. */
.hero{position:relative;padding:32px 16px 448px;overflow:clip}
.hero .copy{position:relative;z-index:2;max-width:800px}
.hero .lead{margin-top:16px;max-width:520px}
.badges{display:flex;gap:12px;align-items:center;margin-top:24px}
.badge-app img{height:48px;width:144px}
.badge-play img{height:76px;width:auto;margin:-14px -12px}   /* visible black block 48px, edges aligned with the App Store badge */
[data-platform="ios"] .badge-play{order:2}[data-platform="android"] .badge-app{order:2}
.trust{margin-top:16px}
.device{position:relative}          /* positioning context in both the hero and the sticky column */
/* Absolute, bottom-anchored, in the slot .hero's padding-bottom reserves above. Centred with
   left/right 0 plus margin auto rather than translateX(-50%): the reduced-motion block below kills
   every transform, which would have thrown a transform-centred phone off to the left. */
/* z-index:2 (2026-09-11, seen in the first screenshot anyone took of this hero): #line and #line-bloom
   are z-index:1 and the device was z-index:auto, so the lime rule painted straight across the phone's
   screen instead of passing behind it as the canvas draws it (SixteenHoursDesktop: "phone standing
   on the line"). The copy is z-index:2 as well and no longer shares a column with the phone. */
.hero .device{position:absolute;left:0;right:0;bottom:0;margin:0 auto;width:300px;height:400px;overflow:hidden;z-index:2}          /* cropped at the fold on phone */
@media(min-width:900px){
  /* Fluid tracks, not 800px 1fr (snag 02, 2026-09-11). With a fixed 800px copy track and the phone
     pinned to the viewport's right edge, the two converged as the window narrowed and 80px display
     type painted straight over the screenshot from 900 to about 1230px (measured overrun: 202px of
     headline ink at 902 wide). The copy track now shrinks first, minmax(0,800px), and the phone holds
     a 372px track of its own, so the headline can never reach it at any width. The device is back in
     flow for the same reason; the content-box min-height of 900px (1004px with the top padding, the
     same as before) exceeds the device's 816px (36 margin + 780 frame), so the hero keeps one height
     whether or not #device is currently in it, since site.js moves it to .sticky-col and back, which
     is exactly what the absolute positioning used to buy. Measured 2026-09-11: 1004px at 902, 1026,
     1154, 1282, 1366 and 1440 wide, before and after the move. The 20px right margin keeps the
     phone's right edge where it was (100px in from the viewport at the 80px padding). */
  .hero{padding:104px clamp(24px,4vw,80px) 0;min-height:900px;display:grid;grid-template-columns:minmax(0,800px) minmax(372px,auto);column-gap:48px;justify-content:space-between;align-items:start}
  .hero .copy{padding-top:0}.hero .lead{margin-top:24px}.badges{margin-top:32px}.trust{margin-top:20px}
  .hero .device{position:relative;left:auto;right:auto;top:auto;bottom:auto;grid-column:2;justify-self:end;margin:36px 20px 0 0;width:372px;height:780px;overflow:visible}
}
/* device frame */
.frame{position:relative;width:300px;height:630px;border-radius:44px;background:var(--frame);border:1px solid var(--hair-hi);box-shadow:0 40px 80px -20px rgba(0,0,0,.70),0 0 0 1px rgba(0,0,0,.60);box-sizing:border-box}
.frame .screen{position:absolute;left:6px;top:6px;width:288px;height:618px;border-radius:38px;overflow:hidden;background:var(--g0)}
.frame .screen img{position:absolute;left:0;top:0;width:100%;height:100%;object-fit:cover;object-position:top;opacity:0;transition:opacity var(--d320) var(--ease-standard),transform var(--d320) var(--ease-out);transform:translateY(12px)}
.frame .screen img.is-active{opacity:1;transform:none}
.glow{position:absolute;width:480px;height:480px;border-radius:50%;background:radial-gradient(circle,var(--lime-16) 0%,rgba(198,244,50,.06) 35%,transparent 60%);filter:blur(32px);pointer-events:none;z-index:0}
@media(min-width:900px){.frame{width:372px;height:780px;border-radius:52px}.frame .screen{left:8px;top:8px;width:356px;height:764px;border-radius:44px}.glow{width:720px;height:720px;filter:blur(40px)}}
/* the line */
#line{position:absolute;left:0;height:1px;width:100%;background:linear-gradient(90deg,rgba(198,244,50,0),var(--lime) 12%,var(--lime));transform-origin:left center;transform:scaleX(0);z-index:1;pointer-events:none}
#line-bloom{position:absolute;left:0;height:40px;width:100%;margin-top:-20px;background:linear-gradient(90deg,rgba(198,244,50,0),var(--lime-24));filter:blur(40px);opacity:.25;transform-origin:left center;transform:scaleX(0);pointer-events:none;z-index:1}
/* ring overlay (spec section 5): sits over the capture's own ring, which #ring-mask hides */
#ring-mask{position:absolute;border-radius:50%;background:var(--g0)}
#ring{position:absolute;pointer-events:none;overflow:visible}
#ring .track{fill:none;stroke:var(--track);stroke-width:3}
#ring .mark{fill:var(--mark)}
#ring .casing{fill:none;stroke:var(--g0);stroke-width:13;stroke-linecap:round;stroke-dasharray:552.9;stroke-dashoffset:552.9}
#ring .arc{fill:none;stroke:var(--lime);stroke-width:10;stroke-linecap:round;stroke-dasharray:552.9;stroke-dashoffset:552.9;filter:drop-shadow(0 0 12px var(--lime-45))}
#ring .dot{fill:var(--lime);opacity:0}#ring .halo{fill:rgba(198,244,50,.25);opacity:0}
#ring.is-drawn .casing,#ring.is-drawn .arc{transition:stroke-dashoffset var(--d1400) var(--ease-out)}
#ring.is-drawn .dot,#ring.is-drawn .halo{opacity:1;transition:opacity var(--d180) var(--ease-standard) 200ms}
#ring.is-drawn .halo{animation:breathe 4000ms var(--ease-loop) 3000ms infinite}
#ring[hidden],#ring-mask[hidden]{display:none}
@keyframes breathe{0%,100%{opacity:.25}50%{opacity:.10}}
/* beat overlays (spec section 5): workout light sweep, coach rows, today rails. Masks position
   their spans in JS (layoutOverlays(), site.js) from data-box fractions of the natural 712x1444
   image, through the same cover mapping used for the ring, so nothing here sets left/top/width/height. */
.ov{position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity var(--d320) var(--ease-standard)}
.ov.is-on{opacity:1}
.ov.masks{opacity:1}
.ov[hidden]{display:none}
.masks span{position:absolute;background:var(--g0);border-radius:12px;transition:opacity var(--d320) var(--ease-out);opacity:1}
.masks.is-on span{opacity:0}
.masks span:nth-child(2){transition-delay:80ms}.masks span:nth-child(3){transition-delay:160ms}
#ov-workout-light{background:linear-gradient(90deg,transparent 0%,var(--lime-24) 50%,transparent 100%);width:60%;transform:translateX(-100%);transition:transform var(--d900) linear,opacity var(--d320)}
#ov-workout-light.is-on{transform:translateX(200%)}
/* reveals */
/* Gated on can-reveal, which site.js adds to <html> only once the reveal observer exists. The
   arming class used to be .js alone, set unconditionally by the head snippet, so anything that
   stopped the observer being built (setupTour()'s no-pin bail, a throw above it, or site.js
   failing to load at all) left the facts rail, all twelve depth cards, the Why and Premium
   panels, the whole close band including both store badges, and the cheat sheet form at opacity 0
   for good. Nothing else on the page ever adds is-in below the hero. Hidden-until-armed is now
   impossible to reach without the code that unhides it. */
.js.can-reveal .reveal{opacity:0;transform:translateY(32px);transition:opacity var(--d500) var(--ease-out),transform var(--d500) var(--ease-out)}
/* Both selectors on purpose. Adding can-reveal to the arming rule above took it from two classes
   to three, which outranks a bare .reveal.is-in and would have left every reveal at opacity 0 for
   good (measured: 31 of 31). The four-class form wins back, and the bare one still covers the
   un-armed page, where nothing is hiding anything in the first place. */
.reveal.is-in,.js.can-reveal .reveal.is-in{opacity:1;transform:none}
.h1-line{display:block;transition:clip-path var(--d700) var(--ease-out),transform var(--d700) var(--ease-out)}
/* Armed by hero-armed, which heroLoad() sets as its FIRST statement, never by the bare .js the head
   snippet adds during parsing (snag 07, 2026-09-11). Keyed on .js alone, any failure of site.js (a
   404 from a partial deploy, a filtering proxy, an extension, Esc during load) left both spans clipped
   to nothing inside an empty 98px box for the whole visit: the page's only h1, invisible. Same shape
   as .js.can-reveal .reveal above: the class that hides is set by the code that shows. */
.js.hero-armed .h1-line{clip-path:inset(0 0 100% 0);transform:translateY(24px)}
.h1-line:nth-child(2){transition-delay:90ms}
.hero-armed.is-loaded .h1-line{clip-path:inset(0);transform:none}
/* story: fact rail, six beats, sticky phone, mid badges (UX-STRUCTURE 2.1 and P-3, P-4) */
.facts{margin:32px 16px 0;padding:14px 16px;border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);text-align:center;font-size:13px;color:var(--ink-2)}
.story{position:relative;padding:0 16px}
.beat{min-height:60vh;display:flex;flex-direction:column;justify-content:center;gap:16px;padding:48px 0}
.beat .display-l{font-size:32px}
.inline-shot{width:240px;margin-top:24px}
/* visually hidden, not display:none, while the pinned phone stands in for it (task 10): the beat's
   own screenshot stays in the accessibility tree with its descriptive alt even though the sighted
   layout shows the pinned device instead. */
/* Keyed on can-reveal, not bare .js (snag 07): setupTour() sets can-reveal as its last statement, once
   the pinned phone that stands in for these shots is actually being driven. If site.js never runs, or
   bails to no-pin, nothing hides them and the story keeps its imagery. */
.js.can-reveal .inline-shot{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.pills{display:flex;flex-wrap:wrap;gap:8px;list-style:none;padding:0;margin:0}
.pills li{padding:8px 12px;border-radius:999px;border:1px solid var(--hair-hi);color:var(--ink-2);font-size:13px;transition:color var(--d180) var(--ease-standard),border-color var(--d180) var(--ease-standard)}
.pills li.is-lit{color:var(--lime);border-color:var(--lime-45)}
.four-lines{display:grid;gap:18px;margin-top:24px}
.four-lines span{display:block;height:1px;background:var(--lime);transform-origin:left;transform:scaleX(0);transition:transform var(--d900) var(--ease-out)}
.beat.is-active .four-lines span{transform:scaleX(1)}
.story-line{position:absolute;left:8px;top:0;bottom:0;width:1px;background:var(--lime);transform-origin:top;transform:scaleY(0);opacity:.6}
/* Anchored to the story, not the whole document (post-launch 2, 2026-09-11): driven by scroll(root)
   the rule was only about half drawn when the six beats ended, because the page keeps going for
   another 4,000px. view() tracks this element's own passage through the viewport, and the element
   spans the story exactly (top:0 bottom:0), so cover 0% is the story entering at the bottom and
   cover 85% is its lower edge a little above the viewport's bottom, about where the last beat sits
   centred; the line is fully drawn by then. */
@supports(animation-timeline:view()){.story-line{animation:grow linear both;animation-timeline:view();animation-range:cover 0% cover 85%}@keyframes grow{to{transform:scaleY(1)}}}
@supports not (animation-timeline:view()){.story-line{transform:scaleY(1)}}
/* Phones never pin (founder, 2026-09-11, watching the page on his own phone: "the phone screen
   covers everything behind, that's not very good"). The pinned column was opaque and sat over the
   top 300px of every screen, so each section's copy slid under it the moment it had been read; the
   snag-01 fix reserved a band above the copy and measured clean at the centred instant, and the
   founder's eye still read the mechanic as covering. Below 900px each beat shows its own screenshot
   under its copy instead, the same fallback path short phones and no-JS readers already had, and
   nothing on the page ever paints over text. The tour with the crossfading screens is desktop only.
   The device stays in the hero on phones; placeDevice() (site.js) pins only at 900px and up. */
@media(max-width:899.98px){
  .sticky-col{display:none}
  .inline-shot,.js.can-reveal .inline-shot{position:static;width:240px;height:auto;padding:0;margin-top:24px;overflow:visible;clip:auto;white-space:normal;border:0;display:block}
}
/* desktop: two columns, sticky phone */
@media(min-width:900px){
  /* Fluid, like the hero (snag 03, 2026-09-11). A fixed 800px track started the phone column at
     x=968 at EVERY width, and html{overflow-x:clip} made the overflow unreachable rather than
     scrollable: 0px of phone visible at 902, 58 at 1026, 186 at 1154, the whole frame only from 1442,
     while the inline shots stayed at 1px because the code pins on width alone. The copy track shrinks
     first, the phone keeps its 372px, and the story rule is measured from the column the phone
     actually occupies rather than from a constant. */
  .story{--story-pad:clamp(24px,4vw,80px);--story-gap:clamp(32px,4vw,88px);display:grid;grid-template-columns:minmax(0,800px) minmax(372px,auto);justify-content:space-between;padding:0 var(--story-pad);column-gap:var(--story-gap)}
  .story-line{left:auto;right:calc(var(--story-pad) + 372px + var(--story-gap) / 2)}
  .beat{min-height:100vh;padding:0}.beat .display-l{font-size:72px}
  .sticky-col{position:sticky;top:120px;height:780px;align-self:start;width:372px}
}
@supports(font-size:1cqw){@media(min-width:900px){.beat .display-l{font-size:clamp(36px,9cqw,72px)}}}
/* Browsers that cannot pin (no-pin, set by setupTour when sticky or IntersectionObserver is missing)
   and viewports placeDevice() decides not to pin (unpinned, set from the same predicate the CSS
   above uses, so a fractional width at the breakpoint can never land with neither a pinned phone
   nor a fallback shot: with JavaScript the class decides, without it the shots are never hidden). */
.no-pin .sticky-col,.unpinned .sticky-col{display:none}.no-pin .inline-shot,.js.can-reveal.no-pin .inline-shot,.js.can-reveal.unpinned .inline-shot{position:static;width:240px;height:auto;padding:0;margin-top:24px;overflow:visible;clip:auto;white-space:normal;border:0;display:block}
.mid{justify-content:center;flex-wrap:wrap;padding:48px 16px}.mid .caption{width:100%;text-align:center;margin-bottom:8px}
/* reduced motion: final states, 120ms crossfades only */
@media(prefers-reduced-motion:reduce){
  /* :not(.device) exempts the phone from the blanket (snag 04, 2026-09-11). The tall-phone column
     used to scale the device with a transform as LAYOUT, and the blanket stripped it: measured at
     375x812, a 300px device at left 30 inside a 200px overflow:hidden column. The previous attempt
     restated the scale under an "id-free but heavier" selector and could never win: :not() carries
     the specificity of its argument, so *:not(#ring-head) scores as an id, and no stack of classes
     beats an id whatever the order. The phone pin has since been removed below 900px (see the story
     rules above), so no layout transform remains on the device today; the exemption stays because
     it is the correct shape for any layout transform a later change puts on it, and nothing on the
     device animates, so it loses no reduced-motion protection by being exempt. */
  *:not(#ring-head):not(.device),*::before,*::after{animation:none!important;transition-duration:120ms!important;transition-delay:0ms!important;transition-property:opacity!important;transform:none!important;clip-path:none!important}
  /* .js.can-reveal .reveal, not .reveal: the plain class is (0,1,0) and lost to the arming rule
     above, media queries adding no specificity of their own, so this safety net computed to
     nothing and reduced-motion visitors still waited on the observer. Same selector, later in the
     file, therefore it wins. The two restatements that used to sit beside it (.h1-line clip-path,
     .sticky transform) lost to the blanket's !important and promised nothing; the blanket is the
     rule, and it already delivers both. */
  .js.can-reveal .reveal{opacity:1}
  #ring .casing,#ring .arc{stroke-dashoffset:var(--ring-target,276.45)}#ring .dot,#ring .halo{opacity:1}
  /* The workout light sweep does not run under reduced motion (site.js, BEATS beat-count), and
     with the blanket stripping its translateX(-100%) the 60 per cent wide gradient would park over
     the left of the phone; it stays invisible instead (post-launch 3, 2026-09-11). */
  #ov-workout-light{opacity:0!important}
}
/* print: the same "final state" reset, for the same reason. A printed page has no scrolling and
   no observer callbacks, so anything still waiting to be revealed would print blank. */
@media print{.js.can-reveal .reveal{opacity:1;transform:none}.js.hero-armed .h1-line{clip-path:none;transform:none}}
/* depth grid, why, premium, close band, cheat sheet, footer (task 7) */
.depth,.why,.premium,.close,.band{padding:64px 16px}
.grid{list-style:none;padding:0;margin:24px 0 0;display:grid;grid-template-columns:repeat(1,minmax(0,1fr));gap:12px}
.grid li{padding:16px;border-radius:24px;background:var(--glass);border:1px solid var(--hair);box-shadow:inset 0 1px 0 var(--glass-top);color:var(--ink-2)}
.grid li b{color:var(--ink-1);font-weight:600}
.compare,.two{display:grid;gap:12px;margin-top:24px}
.compare>div,.two>div{padding:24px;border-radius:24px;background:var(--glass);border:1px solid var(--hair);box-shadow:inset 0 1px 0 var(--glass-top)}
.compare h3,.two h3{margin:0 0 8px;font-size:20px;line-height:1.2;font-weight:600}
.compare p,.two p{margin:0;color:var(--ink-2)}
.close{text-align:center;display:flex;flex-direction:column;align-items:center;gap:16px}
.icon-ring{position:relative;width:160px;height:160px;display:grid;place-items:center}
.icon-ring img{border-radius:22px}
.close-ring{position:absolute;inset:0;transform:rotate(-90deg)}
.close-ring .arc{fill:none;stroke:var(--lime);stroke-width:6;stroke-dasharray:552.9;stroke-dashoffset:552.9;transition:stroke-dashoffset var(--d1400) var(--ease-out)}
.close.is-in .close-ring .arc{stroke-dashoffset:0}
/* The code is drawn dark on its own white tile (2026-09-11): light modules on the page's black
   read as an inverted code, which OpenCV could not decode at all and some phone scanners refuse;
   drawn the right way round it decodes to https://fastlift.app/get. */
.qr{display:none;border-radius:12px}
@media(min-width:900px){.depth,.why,.premium,.close,.band{padding:120px 80px}.grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.compare,.two{grid-template-columns:repeat(2,minmax(0,1fr))}.qr{display:block}}
/* cheat sheet band: dark glass panel, lime primary button, ink-2 helper text, copy and endpoint
   moved verbatim from archive/index-2026-08-28.html (form#wf, ids and field names unchanged) */
.band{background:var(--glass);border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);box-shadow:inset 0 1px 0 var(--glass-top);text-align:center}
.band .lead,.band .caption{max-width:480px;margin-left:auto;margin-right:auto}
.band .caption{margin-top:16px}
.waitform{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;max-width:480px;margin:24px auto 0}
.waitform input[type=email]{flex:1;min-width:220px;background:var(--g1);border:1px solid var(--hair);color:var(--ink-1);border-radius:16px;padding:16px 18px;font-size:16px;outline:none}
.waitform input[type=email]:focus{border-color:var(--hair-hi);outline:2px solid var(--lime);outline-offset:2px}
.waitform input::placeholder{color:var(--ink-3)}
.btn{border:0;cursor:pointer;border-radius:16px;padding:0 20px;height:52px;font-size:16px;font-weight:600;font-family:inherit}
.btn.primary{background:var(--lime);color:var(--g0)}
.btn.primary:hover{background:var(--lime-deep)}
.btn[disabled]{opacity:.6;cursor:default}
.wmsg{font-size:14px;margin-top:14px;min-height:18px;color:#FF8A8A}
.wok{display:none;font-size:16px;color:var(--ink-1);margin-top:12px}
/* footer */
.footer{padding:48px 16px;border-top:1px solid var(--hair)}
.footer .frow{display:flex;flex-direction:column;gap:16px}
.footer .tagline{margin:0;color:var(--ink-1);font-weight:600}
.footer .copy{margin:4px 0 0;color:var(--ink-3);font-size:13px}
.footer .fl{display:flex;flex-wrap:wrap;gap:16px;font-size:13px;padding:0;margin:0;list-style:none}
.footer .fl a{text-decoration:none}
.footer .disc{margin:24px 0 0;font-size:12px;line-height:1.6;color:var(--ink-3)}
@media(min-width:900px){.footer{padding:64px 80px}.footer .frow{flex-direction:row;align-items:center;justify-content:space-between}}
/* sticky store bar (task 8) */
.sticky{position:fixed;left:0;right:0;bottom:0;z-index:30;height:calc(64px + env(safe-area-inset-bottom));padding:0 max(16px,env(safe-area-inset-right)) env(safe-area-inset-bottom) max(16px,env(safe-area-inset-left));display:flex;align-items:center;gap:12px;background:rgba(11,11,12,.92);border-top:1px solid var(--hair);font-size:15px;transform:translateY(100%);transition:transform var(--d180) var(--ease-standard)}
.sticky.is-shown{transform:none}
/* The author rule above sets display:flex, which beats the UA stylesheet's [hidden]{display:none}
   whatever the specificity, so bar.hidden did nothing: the store link stayed in the tab order
   while the bar was "hidden", and with reduced motion (which kills the translate that was the only
   thing holding the bar off screen) the bar sat over the hero from the first paint and could never
   leave. Same shape as #ring[hidden] and .ov[hidden] above; this element was the one that missed
   it. With motion on the transform still does the 200ms enter and exit. */
.sticky[hidden]{display:none}
.sticky span{flex:1}
.sticky .badge-app,.sticky .badge-play{display:none}
[data-platform="ios"] .sticky .badge-app,[data-platform="android"] .sticky .badge-play,[data-platform="desktop"] .sticky .badge-app{display:block}
.sticky .badge-play img{margin:-12px -10px}
@media(min-width:900px){.sticky{display:none}}
#depth,#why,#premium,#close,#cheatsheet{scroll-margin-bottom:72px}
/* scroll-margin-top clears the sticky nav (M9). Without it every in-page jump, the four nav links,
   the desktop nav pill and the skip link, landed with the target's top tucked underneath it. */
#hero-badges,#story,#beat-coach,#beat-food,#beat-groups,#depth,#why,#premium,#close,#cheatsheet{scroll-margin-top:56px}
@media(min-width:900px){#hero-badges,#story,#beat-coach,#beat-food,#beat-groups,#depth,#why,#premium,#close,#cheatsheet{scroll-margin-top:72px}}
@media(max-width:899px){.footer{padding-bottom:72px}}
