/* Self-hosted Inter (variable font — one file covers all normal weights, one
   covers all italic weights) so no request ever leaves the site's own origin
   for type, unlike the Google Fonts @import this replaces. */
@font-face{font-family:'Inter';font-style:normal;font-weight:400;font-display:swap;src:url('../assets/fonts/inter-normal.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:normal;font-weight:500;font-display:swap;src:url('../assets/fonts/inter-normal.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:normal;font-weight:600;font-display:swap;src:url('../assets/fonts/inter-normal.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:normal;font-weight:700;font-display:swap;src:url('../assets/fonts/inter-normal.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:normal;font-weight:800;font-display:swap;src:url('../assets/fonts/inter-normal.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:italic;font-weight:600;font-display:swap;src:url('../assets/fonts/inter-italic.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:italic;font-weight:700;font-display:swap;src:url('../assets/fonts/inter-italic.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:italic;font-weight:800;font-display:swap;src:url('../assets/fonts/inter-italic.woff2') format('woff2');}

:root{
  --black:#000000;
  --white:#ffffff;
  --blue:#002fff;
  --blue-soft:rgba(0,47,255,.35);
  --gray-line:rgba(255,255,255,.16);
  --text-dim:rgba(255,255,255,.72);
  --gray-line-dark:rgba(0,0,0,.12);
  --text-dim-dark:rgba(0,0,0,.62);
  --ease:cubic-bezier(.65,0,.35,1);
  --spring:cubic-bezier(.34,1.56,.64,1);
  --header-h:84px;
  --glass-bg:rgba(255,255,255,.08);
  --glass-border:rgba(255,255,255,.32);
  --glass-hi:rgba(255,255,255,.7);
}

/* Liquid-glass refraction, used sparingly on small interactive surfaces */
.glass-distort{ backdrop-filter:url(#liquid-glass) blur(14px) saturate(180%); -webkit-backdrop-filter:blur(14px) saturate(180%); }
@supports (backdrop-filter:url(#x)) or (-webkit-backdrop-filter:url(#x)){
  .glass-distort{ backdrop-filter:url(#liquid-glass) blur(10px) saturate(180%); }
}

*,*::before,*::after{box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{
  margin:0;
  min-height:100vh;
  display:flex;
  flex-direction:column;
  background:var(--black);
  color:var(--white);
  font-family:'Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
/* .site-header is position:fixed (out of flow), so main is body's only flex
   child — grow it to fill the viewport and push .site-footer (nested inside
   it, at the end) down to the bottom on pages whose content is shorter than
   the viewport, e.g. journal.html with only one post. */
main{
  flex:1 1 auto;
  display:flex;
  flex-direction:column;
}
img{max-width:100%;display:block;}
a{color:inherit;text-decoration:none;}
h1,h2,h3,p{margin:0;}
section{position:relative;}

.brand{
  color:var(--blue);
  font-style:italic;
  font-weight:800;
}

.eyebrow{
  font-size:clamp(2.25rem,4vw,3.4rem);
  font-weight:600;
  letter-spacing:-0.01em;
}

/* ---------- Buttons ---------- */
.btn{
  position:relative;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:.5rem;
  padding:.85rem 1.9rem;
  border-radius:999px;
  background:var(--white);
  color:var(--black);
  border:1.5px solid var(--black);
  font-family:inherit;
  font-size:.95rem;
  font-weight:600;
  cursor:pointer;
  overflow:hidden;
  isolation:isolate;
  transition:color .38s var(--ease), border-color .38s var(--ease), transform .38s var(--ease), box-shadow .38s var(--ease);
}
.btn::before{
  content:"";
  position:absolute;
  inset:0;
  background:var(--blue);
  transform:translateY(105%);
  transition:transform .42s var(--ease);
  z-index:-1;
  border-radius:inherit;
}
.btn::after{
  content:"";
  position:absolute;
  top:-60%;left:-35%;
  width:30%;height:220%;
  background:linear-gradient(120deg, transparent, rgba(255,255,255,.4), transparent);
  transform:rotate(20deg) translateX(-160%);
  transition:transform .85s cubic-bezier(.22,.85,.32,1);
  z-index:-1;
  pointer-events:none;
  /* Plain alpha blending, not mix-blend-mode: a blended pseudo-element forces
     its own GPU compositing layer, which several browsers fail to clip
     correctly to the parent's rounded overflow:hidden — the sweep's own
     rectangular bounding box can flash through the corners during the
     transition. Alpha blending clips correctly everywhere. */
}
.btn:hover,.btn:focus-visible{
  color:var(--white);
  border-color:var(--blue);
  transform:translateY(-3px);
  box-shadow:0 14px 28px -10px var(--blue-soft);
}
.btn:hover::before,.btn:focus-visible::before{transform:translateY(0);}
.btn:hover::after,.btn:focus-visible::after{transform:rotate(20deg) translateX(420%);}
.btn:active{transform:translateY(-1px) scale(.98);}

.btn--blue{color:var(--blue);}
.btn--blue::before{background:var(--blue);}
.btn--blue:hover{color:var(--white);}

.btn--outline-light{
  background:transparent;
  color:var(--white);
  border-color:rgba(255,255,255,.5);
}
.btn--outline-light::before{background:var(--white);}
.btn--outline-light:hover{color:var(--black);border-color:var(--white);}

/* ---------- Text links (Learn more / Read) ---------- */
.link{
  position:relative;
  display:inline-flex;
  align-items:center;
  gap:.3rem;
  color:var(--blue);
  font-weight:700;
  font-style:italic;
  white-space:nowrap;
}
.link::after{
  content:"";
  position:absolute;
  left:0;
  bottom:-2px;
  width:100%;
  height:2px;
  background:currentColor;
  transform:scaleX(0);
  transform-origin:right;
  transition:transform .4s var(--ease);
}
.link:hover::after{transform:scaleX(1);transform-origin:left;}
.link .arrow{
  display:inline-block;
  transition:transform .38s var(--ease);
}
.link:hover .arrow{transform:translateX(5px);}

/* ---------- Header ---------- */
.site-header{
  position:fixed;
  top:0;
  left:0;
  right:0;
  height:var(--header-h);
  z-index:100;
  display:flex;
  align-items:center;
  padding:0 clamp(20px,4vw,56px);
  background:#000;
  transition:background .4s var(--ease);
}
.site-header.scrolled{
  background:#000;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.14), 0 8px 32px -16px rgba(0,0,0,.6);
}
.brand-logo{
  display:flex;
  align-items:center;
  gap:.6rem;
  /* No transition on opacity: #logoHome's opacity is driven every animation
     frame by main.js, already eased via the scroll-progress lerp — a CSS
     transition here would re-trigger on each frame's new value and fight
     that easing, reading as laggy/stuttery instead of smooth. */
  transition:transform .35s var(--ease);
  z-index:2;
}
#logoHome{opacity:0;}
.brand-logo img{height:48px;width:auto;}
.brand-logo:hover{transform:scale(1.06) rotate(-2deg);}
.brand-logo span{
  font-weight:800;
  font-style:italic;
  font-size:1.15rem;
  color:var(--white);
}

.main-nav{
  position:absolute;
  left:50%;
  transform:translateX(-50%);
  display:flex;
  align-items:center;
  background:rgba(255,255,255,.07);
  border:1px solid var(--glass-border);
  border-radius:999px;
  padding:5px;
  backdrop-filter:blur(10px) saturate(160%);
  -webkit-backdrop-filter:blur(10px) saturate(160%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.4), inset 0 -1px 8px rgba(0,0,0,.25), 0 12px 30px -16px rgba(0,0,0,.55);
}
.nav-indicator{
  position:absolute;
  top:5px;
  left:5px;
  height:calc(100% - 10px);
  border-radius:999px;
  background:linear-gradient(180deg, #ffffff, #f0f3ff);
  box-shadow:0 3px 12px rgba(0,0,0,.3), inset 0 1px 1px rgba(255,255,255,.95), inset 0 -6px 10px -4px rgba(0,47,255,.18);
  transition:transform .6s var(--spring), width .6s var(--spring), opacity .3s;
  z-index:0;
  opacity:0;
}
.main-nav.ready .nav-indicator{opacity:1;}
.main-nav a{
  position:relative;
  z-index:1;
  padding:.6rem 1.15rem;
  font-size:.92rem;
  font-weight:600;
  color:var(--white);
  border-radius:999px;
  transition:color .35s var(--ease);
  white-space:nowrap;
}
.main-nav a.active,.main-nav a:hover{color:var(--black);}

.header-cta{margin-left:auto;z-index:2;}

/* "Coming Jan 2027" pill (desktop header + the mobile hero-body copy of it,
   .hero-cta-mobile below) and the "Notify me" submit button — restyled to
   match .nav-indicator's glossy white pill instead of the site's other
   blue-fill buttons, since these float on their own rather than sliding
   inside a matching glass-capsule nav. With nothing for them to slide
   to/from, the diagonal shine .btn::after already inherits (normally a
   hover-only sweep) loops on its own instead — keeps the pill feeling
   alive at rest, including on mobile/touch where there's no hover to
   trigger it otherwise. .notify-submit's own size (padding/font-size, set
   separately) is untouched here — only color/background/shadow/animation. */
.header-cta,
.hero-cta-mobile,
.notify-submit{
  background:linear-gradient(180deg, #ffffff, #f0f3ff);
  border-color:transparent;
  box-shadow:0 3px 12px rgba(0,0,0,.3), inset 0 1px 1px rgba(255,255,255,.95), inset 0 -6px 10px -4px rgba(0,47,255,.18);
}
.header-cta::before,
.hero-cta-mobile::before,
.notify-submit::before{content:none;}
.header-cta:hover,.header-cta:focus-visible,
.hero-cta-mobile:hover,.hero-cta-mobile:focus-visible,
.notify-submit:hover,.notify-submit:focus-visible{
  color:var(--blue);
  border-color:transparent;
  box-shadow:0 8px 20px -6px rgba(0,0,0,.35), inset 0 1px 1px rgba(255,255,255,.95), inset 0 -6px 10px -4px rgba(0,47,255,.28), 0 14px 28px -14px var(--blue-soft);
}
@keyframes ctaShine{
  0%, 15%{transform:rotate(20deg) translateX(-160%);}
  55%, 100%{transform:rotate(20deg) translateX(420%);}
}
.header-cta::after,
.hero-cta-mobile::after,
.notify-submit::after{
  animation:ctaShine 3.6s ease-in-out infinite;
}

/* ---------- Pinned hero/about animation ---------- */
.pin-wrap{
  position:relative;
  height:213.3vh; /* 320vh / 1.5 — less scroll per frame so the scrub reads as continuous motion instead of discrete steps */
  background:rgb(0,0,0);
}
.pin-stage{
  position:sticky;
  top:0;
  height:100vh;
  overflow:hidden;
  display:flex;
  align-items:center;
}
.frame-viewport{
  position:absolute;
  inset:0;
  overflow:hidden;
}
.frame-stage{
  position:absolute;
  inset:0;
  will-change:transform;
}
.frame-img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  max-width:none;
  object-fit:cover;
  opacity:0;
}
.frame-img.active{opacity:1;}

/* Large brand mark over the hero animation — no copy, so it never has to
   compete with the sequence for space; it fades out early in the scrub.
   Positioned by its horizontal CENTER (not left edge) so it lines up with
   the same "center of the left half" spot the About text/form column uses
   (see the about-copy-inner rules) — nudged slightly further left (22% of
   viewport instead of the true-center 25%) per how it read visually.
   left can't be swapped for a transform:translateX(-50%) here because
   main.js overwrites this element's whole inline transform every tick for
   the scroll fade-out drift (translateY only) — see the mobile
   .hero-logo-mark rule's comment for the same constraint. */
.hero-logo-mark{
  position:absolute;
  z-index:3;
  --logo-w:clamp(140px,18vw,320px);
  left:calc(22% - (var(--logo-w) / 2));
  top:50%;
  transform:translateY(-50%);
  width:var(--logo-w);
  height:auto;
  pointer-events:none;
}

/* On mobile the header carries no CTA slot (just the nav pill), so the button
   moves into the hero body, centered under the logo mark. Desktop-hidden. */
.hero-cta-mobile{display:none;}

/* Invisible marker inside the pin-wrap so "About us" nav jumps land while the
   About copy is on screen; its real position is set in JS from actual pin height. */
.pin-anchor{position:absolute;left:0;width:1px;height:1px;}

/* About copy — overlaid directly on the pinned sequence's held last frame,
   never a separate picture. Anchored near the top (not centered) by default
   so it never overlaps the product, which settles centered/lower in the
   final frames on mobile — a fixed offset from the top clears that
   reliably at any viewport size, where centering (content-height-dependent)
   can't. Desktop doesn't have that overlap problem and is switched back to
   centered further down (see the min-width override near the other
   mobile/desktop hero split). */
.about-copy{
  position:absolute;
  inset:0;
  z-index:3;
  opacity:0;
  display:flex;
  align-items:flex-start;
}
.about-copy-inner{
  width:100%;
  max-width:1320px;
  margin:0 auto;
  padding:max(11vh, calc(var(--header-h) + 32px)) clamp(20px,5vw,64px) 0;
  display:flex;
  flex-direction:column;
  gap:1.1rem;
}
.about-copy h2{
  font-size:clamp(1.9rem,3.2vw,2.6rem);
  font-weight:600;
  line-height:1.15;
  color:#0d0d0f;
}
.about-copy p{
  max-width:620px;
  color:var(--text-dim-dark);
  font-size:1.05rem;
  line-height:1.65;
}

/* Notify-me signup — on mobile this section sits below the pinned hero
   animation in normal page flow; on desktop main.js relocates the form
   into the hero's About overlay instead, so this wrapper only ever
   actually renders (and needs centering) on mobile. */
.notify-section-inner{
  max-width:480px;
  margin:0 auto;
}
.notify-form{
  margin-top:.6rem;
  max-width:480px;
}
.notify-row{
  display:flex;
  padding:.35rem;
  background:var(--white);
  border:1.5px solid rgba(0,0,0,.14);
  border-radius:999px;
  transition:border-color .3s var(--ease), box-shadow .3s var(--ease);
  /* Optical correction: a fully-rounded left edge reads as inset compared to
     the flat-edged text above it at the same x-position, so nudge it left to
     look flush rather than geometrically-but-not-visually aligned. */
  margin-left:-8px;
}
.notify-row:focus-within{
  border-color:var(--blue);
  box-shadow:0 0 0 4px var(--blue-soft);
}
.notify-input{
  flex:1;
  min-width:0;
  border:none;
  outline:none;
  background:transparent;
  padding:.6rem .9rem;
  font-family:inherit;
  font-size:.95rem;
  color:#0d0d0f;
}
.notify-input::placeholder{color:rgba(0,0,0,.4);}
/* Chrome fills autofilled inputs with an opaque yellow box that ignores our
   transparent background and the input's square corners poke past the
   pill's rounded border. Force it back to our own background/text color and
   round the corners to match. */
.notify-input:-webkit-autofill,
.notify-input:-webkit-autofill:hover,
.notify-input:-webkit-autofill:focus,
.notify-input:-webkit-autofill:active{
  border-radius:999px;
  -webkit-text-fill-color:#0d0d0f;
  caret-color:#0d0d0f;
  transition:background-color 5000s ease-in-out 0s;
  -webkit-box-shadow:0 0 0 1000px var(--white) inset;
  box-shadow:0 0 0 1000px var(--white) inset;
}

/* Explicit opt-in consent — unchecked by default, sits between the email
   field and the submit button so it can't be missed or bypassed. */
.notify-consent{
  display:flex;
  align-items:flex-start;
  gap:.6rem;
  margin-top:.9rem;
  cursor:pointer;
}
.notify-consent-input{
  flex-shrink:0;
  width:18px;
  height:18px;
  margin-top:.15rem;
  accent-color:var(--blue);
  cursor:pointer;
}
.notify-consent span{
  font-size:.85rem;
  line-height:1.5;
  color:var(--text-dim-dark);
}
.notify-consent a{color:var(--blue);text-decoration:underline;}

.notify-submit{
  display:flex;
  margin-top:1rem;
  padding:.65rem 1.4rem;
  font-size:.88rem;
  white-space:nowrap;
}
.notify-success,.notify-error{
  display:none;
  font-size:.95rem;
  font-weight:600;
  margin-top:.6rem;
}
.notify-success{color:var(--blue);}
.notify-error{color:#c0392b;}

.notify-form.submitted .notify-row,
.notify-form.submitted .notify-consent,
.notify-form.submitted .notify-submit{display:none;}
.notify-form.submitted .notify-success{display:block;}
.notify-form.error .notify-error{display:block;}

.scroll-cue{
  position:absolute;
  left:50%;
  bottom:5vh;
  transform:translateX(-50%);
  z-index:3;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:.5rem;
  color:var(--text-dim);
  font-size:.72rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  transition:opacity .4s var(--ease);
}
.scroll-cue .dot{
  width:1px;height:34px;
  background:linear-gradient(to bottom, rgba(255,255,255,.8), transparent);
  position:relative;
  overflow:hidden;
}
.scroll-cue .dot::after{
  content:"";
  position:absolute;
  top:-100%;left:0;width:100%;height:100%;
  background:linear-gradient(to bottom, transparent, var(--blue), transparent);
  animation:cueMove 1.8s ease-in-out infinite;
}
@keyframes cueMove{
  0%{top:-100%;}
  100%{top:100%;}
}

/* ---------- Section shells ---------- */
.section{
  padding:clamp(60px,10vh,120px) clamp(20px,5vw,64px);
}
.section-light{background:var(--white);color:var(--black);}
.section-head{
  max-width:1320px;
  margin:0 auto 3rem;
}
.section-head h2{
  font-size:clamp(2.1rem,4.4vw,3.2rem);
  font-weight:500;
}
.section-head-link{
  display:inline-flex;
  align-items:center;
  gap:1rem;
  color:inherit;
}
.section-head-link h2{transition:color .35s var(--ease);}
.section-head-link .arrow{
  display:inline-block;
  font-size:clamp(1.4rem,3vw,2.2rem);
  color:var(--text-dim-dark);
  transition:transform .38s var(--ease), color .35s var(--ease);
}
.section-head-link:hover h2,
.section-head-link:hover .arrow{color:var(--blue);}
.section-head-link:hover .arrow{transform:translateX(10px);}

/* ---------- Blog ---------- */
.blog-list{
  max-width:1320px;
  margin:0 auto;
  display:flex;
  flex-direction:column;
  gap:1.5rem;
}
.blog-card{
  position:relative;
  overflow:hidden;
  background:var(--white);
  color:var(--black);
  border:1px solid rgba(0,0,0,.08);
  border-radius:28px;
  padding:1.5rem;
  display:flex;
  align-items:center;
  gap:2rem;
  box-shadow:0 2px 24px -8px rgba(0,0,0,.1);
  transition:transform .45s var(--ease), box-shadow .45s var(--ease);
}
.blog-card:hover{
  transform:translateY(-6px) scale(1.005);
  box-shadow:0 26px 55px -24px rgba(0,0,0,.25);
}
.blog-thumb{
  width:220px;min-width:220px;
  height:130px;
  border-radius:20px;
  overflow:hidden;
  background:#0a0a0a;
}
.blog-thumb img{width:100%;height:100%;object-fit:cover;transition:transform .6s var(--ease);}
.blog-card:hover .blog-thumb img{transform:scale(1.08);}
.blog-card p{font-size:1.02rem;line-height:1.6;}

/* ---------- Card specular highlight (mouse-tracked glass sheen) ---------- */
.blog-card{--mx:50%;--my:50%;}
.blog-card::after{
  content:"";
  position:absolute;
  inset:0;
  background:radial-gradient(circle 260px at var(--mx) var(--my), rgba(0,47,255,.14), rgba(0,47,255,0) 70%);
  opacity:0;
  transition:opacity .45s var(--ease);
  pointer-events:none;
}
.blog-card:hover::after{opacity:1;}

/* ---------- Footer ---------- */
.site-footer{
  margin-top:auto;
  background:var(--white);
  color:var(--black);
  border-top:1px solid var(--gray-line-dark);
  padding:3rem clamp(20px,5vw,64px) 2.5rem;
  display:flex;
  flex-wrap:wrap;
  gap:1.5rem;
  align-items:center;
  justify-content:space-between;
}
.footer-links{
  display:flex;
  gap:1.75rem;
  flex-wrap:wrap;
}
.footer-links a{
  font-size:.9rem;
  color:var(--text-dim-dark);
  position:relative;
}
.footer-links a::after{
  content:"";
  position:absolute;left:0;bottom:-3px;width:0;height:1px;background:var(--blue);
  transition:width .35s var(--ease);
}
.footer-links a:hover{color:var(--black);}
.footer-links a:hover::after{width:100%;}
.footer-copy{font-size:.82rem;color:rgba(0,0,0,.42);}

/* Legal links + business identity block — sits where the old bare copyright
   line used to, stacked as its own two-line group. */
.footer-legal{
  display:flex;
  flex-direction:column;
  align-items:flex-end;
  gap:.6rem;
}
.footer-links--legal{gap:1.25rem;}
.footer-links--legal a{font-size:.82rem;}

@media (max-width:640px){
  .footer-legal{align-items:flex-start;}
}

/* ---------- Reveal-on-scroll ---------- */
.reveal{
  opacity:0;
  transform:translateY(28px) scale(.985);
  filter:blur(10px);
  transition:opacity .9s var(--ease), transform .9s var(--ease), filter .9s var(--ease);
}
.reveal.in{opacity:1;transform:translateY(0) scale(1);filter:blur(0);}

/* ---------- Responsive ----------
   No hamburger at any width — the nav pill stays put and just tightens up;
   the header CTA drops out and reappears inside the hero body instead,
   matching the mobile Figma frame. The corner logo (#logoHome) stays —
   it's already hidden (opacity:0) until the About dwell on index.html, then
   fades in top-left, and is the page's only header logo on every other
   page, always visible. */
/* iPads in portrait (744–1024px wide) share this same "compact chrome" —
   the header CTA drops into the hero body just like on phones, so it's never
   simply lost. Landscape iPad widths (1133px+) all clear this and keep the
   full desktop header. */
@media (max-width:900px), (max-width:1024px) and (orientation:portrait){
  .header-cta{display:none;}
  .blog-card{flex-direction:column;align-items:stretch;text-align:left;}
  .blog-thumb{width:100%;height:200px;}
}

/* Hero sizing/positioning: shared by phones and portrait tablets. The mobile
   frame set is now a genuine portrait render (not a shrunk landscape one), so
   it uses the same unconditional object-fit:cover rule as desktop (see
   .frame-img above) and needs no size/position override here — it already
   fills the screen edge-to-edge at its own aspect ratio, cropped rather than
   letterboxed, on phones. Portrait tablets need a bottom-anchored crop
   instead (object-position, not object-fit) — see their own more specific
   breakpoint further down. */
@media (max-width:640px), (max-width:1024px) and (orientation:portrait){
  .hero-logo-mark{
    /* Centered via left/right/margin, not transform: JS drives this element's
       transform for the fade-out drift (translateY only), so horizontal
       centering can't live in transform without being clobbered by that. */
    left:0;
    right:0;
    margin:0 auto;
    /* Kept as the exact midpoint between the header's pill nav (whose bottom
       edge sits a fixed ~63.5px down at this breakpoint's nav font-size,
       regardless of viewport height) and the CTA button below — so the logo
       stays centered in that gap on every phone, instead of drifting as a
       standalone percentage. This is algebraically half of the CTA's own
       top formula (unchanged, still 104px below this) plus a constant:
       midpoint = (63.5px + [cta top]) / 2 = 83.75px + [cta top]/2. The CTA's
       23%-of-viewport/header-floor logic (see its own comment) still flows
       through automatically, so short-viewport clipping protection is
       preserved without duplicating that floor here. */
    top:calc(83.75px + max(23%, calc(var(--header-h) + 96px)) / 2);
    transform:translateY(-50%);
    width:clamp(120px,42vw,200px);
  }
  .hero-cta-mobile{
    display:inline-flex;
    position:absolute;
    z-index:3;
    left:50%;
    /* 23% of a typical phone's height clears the fixed header fine, but on
       short viewports (small phones, or anything under ~715px tall) that
       point sits high enough to risk the logo above it getting clipped
       under the header. Floor it at header-height + 96px so the top edge
       never goes higher than just below the header, on any viewport. Stays
       pinned 104px below the logo mark's own center (see .hero-logo-mark's
       formula above, which derives from this one). */
    top:calc(max(23%, calc(var(--header-h) + 96px)) + 104px);
    transform:translateX(-50%);
    white-space:nowrap;
    text-align:center;
  }
  /* .btn:hover/:active set their own transform for the lift/press effect,
     which would otherwise overwrite (not combine with) the translateX(-50%)
     centering above — snapping the button sideways on tap/hover. Re-apply
     the centering alongside each state's own transform instead. */
  .hero-cta-mobile:hover,
  .hero-cta-mobile:focus-visible{
    transform:translateX(-50%) translateY(-3px);
  }
  .hero-cta-mobile:active{
    transform:translateX(-50%) translateY(-1px) scale(.98);
  }

  /* First screen only: the whole cue (word + line) centered on the left
     half of the screen — the same "center of the left half" spot the About
     text/form column and hero logo mark line up on — instead of centered
     on the full page. */
  .scroll-cue{
    left:25%;
    transform:translateX(-50%);
    align-items:center;
  }
}

/* Portrait tablets (iPads) specifically — narrower range than the general
   mobile block above, which also covers phones; phones keep everything as
   tuned there. Two problems specific to this range, found by testing on a
   real iPad:

   1. Default center-center object-fit:cover crops a tall phone-shot frame's
      top/bottom more aggressively on a tablet's squarer aspect ratio than on
      an actual phone, cropping the bottom of the product off-screen.
      Anchoring the crop to the bottom instead (object-position) fixes that
      without needing object-fit:contain at all: every one of these frames
      has a large dead zone of pure background at the top (never less than
      43.5% of the frame's own height, checked across all 49 frames by
      pixel-scanning each one), and cover-with-bottom-anchor on every iPad
      portrait size from mini to 12.9" only ever needs to crop 14–25% off the
      top to fill the screen — comfortably inside that dead zone, verified
      per-device, so the product's bottom edge is never touched and the
      frame fills edge-to-edge with no pillarboxing or letterboxing on any
      side, on any of them.
   2. The notify form now lives inside the About overlay too (see the
      portraitTabletMQ relocation in main.js), so this block restyles that
      combined heading/paragraph/form column to sit centered, rather than
      full-width and top-anchored the way phones need it. */
@media (min-width:641px) and (max-width:1024px) and (orientation:portrait){
  .frame-img{object-fit:cover;object-position:center bottom;}

  /* Logo centered in the gap between the header's pill selector and the CTA
     button below it, not across the full header-to-product span (that read
     as floating in the middle of a lot of empty space, with the product
     starting well below the button). The button itself is untouched — still
     top:calc(21% + 104px), exactly as before — so this is the midpoint of
     that formula and the pill nav's own bottom edge, which sits a fixed
     ~66.6px down regardless of viewport height since --header-h and the
     nav's own size are both fixed, not vh-relative: midpoint of 66.6px and
     (21% + 104px) reduces to 10.5% + 85px. Measured directly against the
     rendered nav/button positions, not estimated. */
  .hero-logo-mark{top:calc(10.5% + 85px);}
  .hero-cta-mobile{top:calc(21% + 104px);}

  /* Vertically centered in the gap between the top of the screen and the
     product below it — not the full screen, which would push it down past
     the product's own top edge. Sized against the LAST frame specifically,
     frame 49, since that's the frame this overlay actually appears over
     (held during the dwell) — not frame 1, whose product sits much higher
     and would give a false budget. Frame 49's solid product content starts
     at 64.32% of the frame's own height (measured by pixel scan), but
     unlike the old object-fit:contain approach, object-fit:cover crops a
     different amount off the top on every device — so where that same
     64.32% source point lands on screen isn't one fixed number here, it
     ranges from ~58.3% (iPad mini, which crops least) down to ~52.5% (iPad
     Pro 12.9", which crops most), checked by rendering frame 49 through the
     exact same cover+bottom-anchor math at every supported size. Using the
     smallest of those (with a little safety margin) keeps this correct —
     never overlapping the product — on every iPad in the range; it's just
     not perfectly centered on the roomier ones. */
  .about-copy{
    align-items:center;
    height:51vh;
  }
  .about-copy-inner{
    width:100%;
    padding:0 clamp(20px,5vw,48px);
    gap:.6rem;
  }
  .about-copy h2{font-size:1.7rem;}
  .about-copy p{max-width:460px;font-size:.92rem;line-height:1.4;}
  .notify-form{margin-top:.25rem;max-width:400px;}
  .notify-row{padding:.3rem;}
  .notify-input{padding:.5rem .8rem;font-size:.88rem;}
  .notify-consent{margin-top:.5rem;gap:.5rem;text-align:left;}
  .notify-consent-input{width:17px;height:17px;}
  .notify-consent span{font-size:.85rem;line-height:1.4;}
  .notify-submit{margin-top:.5rem;padding:.55rem 1.2rem;font-size:.85rem;}
}

/* Desktop/landscape-tablet — the exact inverse of the breakpoint above (not
   just a min-width, since landscape tablets need to land here too, not in
   the portrait-tablet "mobile" block). Restores the centered layout the
   About text/notify form had before the top-anchored default was
   introduced; desktop doesn't have the product-overlap problem that made
   top-anchoring necessary on mobile, so there's nothing to trade off here. */
@media (min-width:1025px), (min-width:641px) and (orientation:landscape){
  /* The left-half region itself now lives here (instead of on
     .about-copy-inner) so the inner column can shrink-wrap to its widest
     child below — .about-copy already spans the full screen via inset:0,
     and adding an explicit width alongside that forces the left/right
     inset math to resolve to just the left half. justify-content centers
     the shrink-wrapped column within it, horizontally; align-items keeps
     the existing vertical centering. */
  .about-copy{
    align-items:center;
    justify-content:center;
    width:50%;
  }
  /* The column shrinks to the width of its widest child (whichever of the
     heading/paragraph/form that is) and that whole column is centered in
     the left half above — same spot the old per-child centering put it.
     align-items:flex-start then lines up every child's left edge with the
     column's own left edge (the widest child's), instead of each child
     centering independently and producing a ragged left edge. */
  .about-copy-inner{
    width:auto;
    max-width:none;
    margin:0;
    padding-top:0;
    align-items:flex-start;
  }
  /* The product's on-screen position/crop shifts with viewport aspect ratio
     (object-fit:cover), and a fixed 480px form stays that same absolute
     width at any desktop size — narrow enough windows near the 1025px
     breakpoint let its right edge reach into the product's silhouette
     (checked pixel-by-pixel against the actual rendered frame, not just its
     bounding box). Scaling the cap down with viewport width keeps it clear
     without changing anything at the wider widths this was already sized
     for. */
  .notify-form{max-width:min(480px, 34vw);}
}

/* Phone-width-only chrome tightening — tablets have plenty of room and keep
   the header/nav at full size. */
@media (max-width:640px){
  /* .main-nav is always independently centered via its own absolute
     positioning (left:50%), never via this flex rule — .brand-logo is the
     only in-flow flex child left once .header-cta hides at this width
     (see the max-width:900px rule above), so justify-content:center here
     was actually centering the LOGO on top of the already-centered nav
     pill. flex-start keeps it pinned to the header's left edge instead,
     on every page that uses this shared header, including ones where the
     logo only becomes visible after scrolling (index.html's fade-in). */
  .site-header{padding:0 16px;justify-content:flex-start;}
  .main-nav a{padding:.5rem .7rem;font-size:.78rem;}
  .pin-wrap{height:173.3vh;} /* 260vh / 1.5, same reasoning as the desktop value */
  .about-copy-inner{gap:.9rem;}
  .about-copy p{font-size:.95rem;}
  /* Nudge the email field/consent/button up closer to the animation's last
     held frame — the section's default clamp(60px,10vh,120px) top padding
     (~80-90px on most phones) left a big empty gap here. Still comfortably
     positive so this can never creep up into the pinned frame above it,
     which sits in its own box (pin-wrap) right before this section starts. */
  #notify{padding-top:40px;}
  /* Same idea as #notify above — the default clamp(60px,10vh,120px) top
     padding, stacked on top of #notify's own bottom padding right before
     it, left a lot of empty white space above the "Blog" heading. */
  #blog{padding-top:40px;}
}
