/* hako — the one-page site. Tokens are inlined from the design system's
   tokens/*.css so this file stands alone; there is no build step and no @import
   chain to resolve at request time. */
*,*::before,*::after{box-sizing:border-box}
:root{
  --bg:#F5F4F1;--card:#FFFFFF;--recess:#E7E5E0;
  --ink:#000000;--ink-2:#3D3D3D;--ink-3:#676460;--label:#676460;
  --line:#D3D0CA;--line-strong:#767370;--red:#D71921;--dot:rgba(0,0,0,.18);
  /* Single-face system: Geist Mono sets everything. --font-grotesk is kept so the
     two-face pairing is one edit away. */
  --font-mono:"Geist Mono",ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;
  --font-sans:var(--font-mono);
  --font-grotesk:"Geist","Helvetica Neue","Segoe UI Variable Display","Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --wrap:72rem;--gutter:clamp(1.25rem,4vw,3.5rem);
}
/* Dark is first-class, not an inversion filter. True black for OLED. Values are
   the design system's own dark ramp (tokens/colors.css), including the red:
   #D71921 measures 4.05:1 on black and fails, so dark uses #FF4D5E. */
:root[data-theme="dark"]{--bg:#000000;--card:#0B0B0B;--recess:#161616;--ink:#FFFFFF;--ink-2:#B4B4B4;--ink-3:#8C8C8C;--label:#8C8C8C;--line:#2E2E2E;--line-strong:#7A7A7A;--red:#FF4D5E;--dot:rgba(255,255,255,.15)}
html{-webkit-text-size-adjust:100%;background:var(--bg)}
html,body{color-scheme:light dark}
body{margin:0;background:var(--bg);color:var(--ink);font:400 1rem/1.55 var(--font-sans);font-synthesis-weight:none}
a{color:var(--ink);text-decoration:none}
a:hover{color:var(--red)}
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--gutter)}
.skip{position:absolute;left:-9999px}
.skip:focus{left:1rem;top:1rem;background:var(--ink);color:var(--bg);padding:.5rem .75rem;z-index:99}

/* -- header: the one sanctioned blur in the whole system -- */
.head{position:sticky;top:0;z-index:20;height:56px;background:color-mix(in srgb,var(--bg) 82%,transparent);backdrop-filter:blur(12px);border-bottom:1px solid var(--line)}
.head-in{height:56px;display:flex;align-items:center;gap:1.5rem}
.brand{display:flex;align-items:center;line-height:0}
.nav{margin-left:auto;display:flex;align-items:center;gap:clamp(.75rem,2vw,1.75rem)}
.nav a{font:400 .6875rem/1 var(--font-mono);letter-spacing:.14em;color:var(--ink-2)}
.nav a:hover{color:var(--ink)}
.nav a.cta{text-transform:uppercase;border:1px solid var(--line-strong);border-radius:999px;padding:.5rem .9rem;color:var(--ink)}
.nav a.cta:hover{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.themebtn{font:400 .625rem/1 var(--font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--ink);background:none;border:1px solid var(--line-strong);border-radius:4px;padding:.45rem .7rem;cursor:pointer;transition:background 110ms cubic-bezier(.2,0,0,1),color 110ms cubic-bezier(.2,0,0,1)}
.themebtn:hover{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.themebtn:focus-visible{outline:2px solid var(--red);outline-offset:2px}

/* -- the rig. ONE VIEWPORT TALL, AND NOT PINNED. It used to be a 100svh sticky
      stage on a 420vh track (360vh on mobile), which made the animation a toll
      gate: the beats were scrubbed by the scrollbar, so reaching the first line
      of copy under the hero cost three and a bit screens of scrolling and there
      was no way to skip it. The track height and the `position:sticky` were the
      whole of that mechanism, so both are gone — with the track, `.rig-track`
      went too, since a wrapper whose only job was to be scrubbed against is a
      dead rule waiting to be re-lit. The loop now runs on its own clock in
      page.js and the page scrolls past it like any other section.

      The header is sticky and IN FLOW, so it occupies 56px above the stage: a
      100vh stage always overflows by exactly the header height at scroll 0,
      clipping the beat readout. Size to the viewport MINUS the header. svh, not
      vh, so mobile browser chrome does not re-clip. -- */
.rig-stick{position:relative;isolation:isolate;height:calc(100svh - 56px);min-height:26rem;display:flex;flex-direction:column;overflow:hidden}
.rig-bg{position:absolute;inset:0;background-image:radial-gradient(var(--dot) 1px,transparent 1px);background-size:22px 22px;pointer-events:none}
.rig-in{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;pointer-events:none;padding:clamp(.75rem,2.5vh,1.5rem) var(--gutter) clamp(.75rem,2vh,1.25rem)}
.rig-in::before,.rig-in::after{content:"";position:absolute;left:0;right:0;z-index:1;pointer-events:none}
.rig-in::before{top:0;height:32%;background:linear-gradient(to bottom,var(--bg) 12%,transparent 100%)}
.rig-in::after{bottom:0;height:24%;background:linear-gradient(to top,var(--bg) 14%,transparent 100%)}
.rig-in>:not(.stage-wrap){position:relative;z-index:2}
.chip{align-self:center;font:400 .625rem/1 var(--font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--ink);background:var(--bg);border:1px solid var(--line-strong);border-radius:4px;padding:.45rem .7rem;margin-bottom:clamp(.75rem,2vh,1.25rem)}
h1.h1{min-height:2.12em;display:flex;align-items:center;justify-content:center;margin:0;text-align:center;font-weight:300;font-size:clamp(1.5rem,.85rem + 3.1vw,3.25rem);line-height:1.06;letter-spacing:-.02em;text-wrap:balance}
.lede{margin:.9rem auto 0;max-width:38rem;text-align:center;color:var(--ink-2);font-size:clamp(.875rem,.8rem + .3vw,1rem);text-wrap:pretty}
.stage-wrap{position:absolute;inset:0;z-index:0}
/* object-fit:cover, because the stage is inset:0 and takes the viewport's
   aspect while the loop is rendered at a fixed 16:9 or 9:16. The two
   renders are framed with margin for exactly this crop. */
video#stage{display:block;width:100%;height:100%;object-fit:cover;pointer-events:none;background:transparent}
.loading[hidden]{display:none}
.loading{position:absolute;inset:0;display:grid;place-items:center;font:400 .6875rem/1 var(--font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--label)}

/* -- staged reveals: one line per beat, cross-faded as the loop crosses it. Stacked in a
      grid cell so they occupy one fixed slot and nothing below them shifts. -- */
/* The mobile hero CTA.
   ⚠️ `.rig-in` above sets `pointer-events:none` so the loop behind it stays
   untouchable. A CONTROL PLACED INSIDE IT MUST TAKE POINTER EVENTS BACK or it
   renders perfectly, highlights on tap, and does nothing at all. That is the
   whole trap in this rule.
   Below 48rem only, which is the same cut the nav collapses behind the burger
   on — above it the header already carries a WAITLIST pill and a second one
   would be noise. Shaped like that pill on purpose.
   It sits before `.reveals`, which carries `margin-top:auto` and therefore
   absorbs the free space, so this lands under the lede at the top of the hero
   rather than floating in the middle of the object. */
/* padding is set to clear a 44px tap target with an 11px line - .85rem gave 40,
   which is under the floor for a primary control on a phone. */
.herocta{display:none;align-self:center;pointer-events:auto;margin-top:1.15rem;font:400 .6875rem/1 var(--font-mono);letter-spacing:.14em;text-transform:uppercase;padding:1rem 1.5rem;border:1px solid var(--red);border-radius:999px;color:var(--red);text-decoration:none;background:var(--bg);transition:background 110ms cubic-bezier(.2,0,0,1),color 110ms cubic-bezier(.2,0,0,1)}
.herocta:hover{background:var(--red);border-color:var(--red);color:var(--bg)}
.herocta:focus-visible{outline:2px solid var(--red);outline-offset:3px}
@media (max-width:48rem){.herocta{display:inline-flex;align-items:center}}
.reveals{margin-top:auto;display:grid;place-items:center;min-height:3.25em;padding:.5rem 1rem 0}
.rev{grid-area:1/1;margin:0;text-align:center;font-weight:300;font-size:clamp(1.05rem,.8rem + 1.1vw,1.85rem);letter-spacing:-.012em;color:var(--ink);opacity:0;transform:translateY(.45rem);transition:opacity 420ms cubic-bezier(.2,0,0,1),transform 420ms cubic-bezier(.2,0,0,1);text-wrap:balance}
.rev[data-on="1"]{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){.rev{transition:opacity 180ms linear}.rev[data-on="1"]{transform:none}}

/* -- beat readout: cells switching on, never a filling bar -- */
.beats{order:2;width:100%;display:flex;justify-content:center;gap:clamp(.75rem,2.5vw,2.25rem);padding-top:.75rem;flex-wrap:wrap}
.beat{display:flex;align-items:center;gap:.5rem;font:400 .625rem/1 var(--font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--label);opacity:.42}
.beat[data-on="1"]{opacity:1;color:var(--ink)}
.beat i{width:6px;height:6px;border-radius:999px;border:1px solid currentColor;display:block}
.beat[data-on="1"] i{background:currentColor}
.hint{order:1;display:flex;align-items:center;justify-content:center;gap:.5rem;padding-bottom:1rem;font:400 .625rem/1 var(--font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--label);transition:opacity 200ms cubic-bezier(.2,0,0,1)}

/* -- the scramble strip: text face, not the dot face -- */
.strip{border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:var(--recess);overflow:hidden}
.strip-in{padding-block:clamp(2.5rem,7vh,5rem);display:flex;flex-direction:column;gap:.9rem;align-items:center}
.strip b{font:400 clamp(1rem,.6rem + 2.4vw,2.25rem)/1.1 var(--font-mono);letter-spacing:.06em;text-transform:uppercase;font-weight:400;white-space:nowrap;text-align:center}

/* -- steps -- */
.steps{border-bottom:1px solid var(--line)}
.steps-in{padding-block:clamp(3rem,9vh,6rem)}
/* The problem line above the steps heading. `h2` carries `margin:0` on top, so
   the gap between the two lives here rather than there. Set a shade smaller
   than the heading and in --ink-2 so it reads as the setup and the heading
   still reads as the answer. */
.problem{margin:0 0 1.1rem;max-width:34rem;color:var(--ink-2);font-size:clamp(.9375rem,.9rem + .3vw,1.0625rem);line-height:1.6;text-wrap:pretty}
h2{margin:0 0 2.5rem;font-weight:300;font-size:clamp(1.5rem,1.1rem + 2vw,2.5rem);letter-spacing:-.012em}
.grid3{display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));border-top:1px solid var(--line);border-left:1px solid var(--line)}
.cell{border-right:1px solid var(--line);border-bottom:1px solid var(--line);padding:clamp(1.5rem,3vw,2.25rem)}
.cell .idx{font:400 .625rem/1 var(--font-mono);letter-spacing:.16em;color:var(--label);display:block;margin-bottom:1rem}
.cell h3{margin:0 0 .5rem;font-weight:400;font-size:1.0625rem;letter-spacing:-.01em}
.cell p{margin:0;color:var(--ink-2);font-size:.9375rem;text-wrap:pretty}
footer{padding:clamp(2rem,5vh,3.5rem) 0;font:400 .625rem/1.8 var(--font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--label)}
/* The lede is the only plain-language line that says what hako IS. Hiding it
   under 48rem left a mobile visitor with an evocative headline, an animation
   and no explanation, which is a worse first screen than a tighter one. It
   comes back smaller instead. The max-height rule below still drops it on
   landscape phones, where the vertical room genuinely is not there. */
@media (max-width:48rem){.lede{margin-top:.55rem;font-size:.8125rem}}
/* Short viewports: the object is the subject, so prose yields to it before it does. */
@media (max-height:46rem){.lede{display:none}h1.h1{font-size:clamp(1.5rem,.9rem + 2.4vw,2.25rem)}.chip{margin-bottom:.6rem}.reveals{min-height:2.6em}.rev{font-size:clamp(.95rem,.8rem + .6vw,1.25rem)}}
@media (max-height:34rem){.chip{display:none}}
@media (prefers-reduced-motion:reduce){.themebtn{transition:none}.hint{transition:none}}

/* -- Bit, bottom-trailing over the content, exactly as in the apps. Never in a
      row of his own, never the only place a state is stated. -- */
.bit{position:fixed;right:clamp(1rem,3vw,2.25rem);bottom:clamp(1rem,3vh,1.75rem);z-index:30;display:flex;align-items:center;gap:.6rem;pointer-events:none}


.foot-links{padding-block:clamp(2rem,6vh,3rem);border-top:1px solid var(--line);color:var(--label);font:400 .6875rem/1.6 var(--font-mono);letter-spacing:.1em;text-transform:uppercase;display:flex;flex-wrap:wrap;gap:.4rem 1.6rem}
.foot-links a{color:var(--label)}
.foot-links a:hover{color:var(--red)}

/* ============ SECTIONS ============ */
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
/* Honeypot. NOT .sr - that hides from sight but keeps the field in the
   accessibility tree and in the tab order, so a screen-reader user would be
   asked for a company and a keyboard user would tab into nothing. display:none
   removes it from both; the bots this catches parse the markup rather than
   render it, so they fill it in anyway. Paired with aria-hidden and
   tabindex=-1 in the markup - keep all three. */
.hp{display:none}
section.band{border-top:1px solid var(--line);scroll-margin-top:56px}
.band-in{padding-block:clamp(2.5rem,7vh,4.5rem)}
section.band h2{margin:0 0 1.7rem;font-weight:300;font-size:clamp(1.375rem,1rem + 1.6vw,2.25rem);letter-spacing:-.012em}
.note{margin:1.4rem 0 0;max-width:36rem;color:var(--ink-2);font-size:.875rem;line-height:1.65;text-wrap:pretty}
.figs{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:1px;background:var(--line)}
.fig{background:var(--bg);padding:1.3rem 1.1rem}
.fig b{display:block;font-weight:300;font-size:clamp(1.5rem,1.1rem + 1.5vw,2.375rem);letter-spacing:-.02em;line-height:1}
.fig span{display:block;margin-top:.5rem;font:400 .625rem/1.5 var(--font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--label)}
.fig small{display:block;margin-top:.5rem;color:var(--ink-2);font-size:.8125rem;line-height:1.5}
.rows{display:flex;flex-direction:column;gap:1px;background:var(--line)}
.row{background:var(--bg);display:grid;grid-template-columns:minmax(8rem,12rem) 1fr;gap:1rem;padding:.95rem 0}
.row dt{margin:0;font:400 .6875rem/1.6 var(--font-mono);letter-spacing:.14em;text-transform:uppercase;color:var(--label)}
.row dd{margin:0;font-size:.9375rem;line-height:1.6;text-wrap:pretty}
@media (max-width:34rem){.row{grid-template-columns:1fr;gap:.25rem}}
.qa{display:flex;flex-direction:column;gap:1px;background:var(--line)}
.qa details{background:var(--bg);padding:1rem 0}
.qa summary{cursor:pointer;list-style:none;display:flex;gap:.8rem;align-items:baseline;font-size:1rem}
.qa summary::-webkit-details-marker{display:none}
.qa summary::before{content:"+";flex:none;color:var(--label);font-family:var(--font-mono)}
.qa details[open] summary::before{content:"\2212"}
.qa p{margin:.7rem 0 0 1.6rem;max-width:42rem;color:var(--ink-2);font-size:.9375rem;line-height:1.65;text-wrap:pretty}
.actions{display:flex;flex-wrap:wrap;gap:.7rem;margin-top:1.6rem}
.btn{font:400 .6875rem/1 var(--font-mono);letter-spacing:.14em;text-transform:uppercase;padding:.85rem 1.15rem;border:1px solid var(--line-strong);border-radius:4px;color:var(--ink);text-decoration:none;background:none;cursor:pointer;transition:background 110ms cubic-bezier(.2,0,0,1),color 110ms cubic-bezier(.2,0,0,1)}
.btn:hover{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.btn-red{border-color:var(--red);color:var(--red)}
.btn-red:hover{background:var(--red);border-color:var(--red);color:var(--bg)}
.signup{display:flex;flex-wrap:wrap;gap:.6rem;align-items:center;margin-top:1.6rem}
.signup input{flex:1 1 16rem;min-width:0;font:400 .9375rem/1 var(--font-mono);color:var(--ink);background:var(--card);border:1px solid var(--line-strong);border-radius:4px;padding:.8rem .9rem}
.signup input::placeholder{color:var(--label)}
.signup input:focus-visible{outline:2px solid var(--red);outline-offset:2px}
.formnote{flex:1 1 100%;margin:.2rem 0 0;min-height:1.2em;font:400 .6875rem/1.4 var(--font-mono);letter-spacing:.12em;text-transform:uppercase;color:var(--label)}
.formnote a{color:var(--red);text-decoration:underline}
/* The reservation offer, revealed by page.js in the waitlist success state.
   Outside .signup on purpose: that is a wrapping flex row and a block this tall
   inside it fights the input for the line.

   `[hidden]` NEEDS SAYING TWICE. The attribute is display:none from the UA
   sheet at specificity zero, and .reserve sets display, so the rule beats the
   attribute outright and the offer sits open under the form from first paint.
   Same trap .loading fell into above, same fix. */
.reserve{display:flex;flex-direction:column;align-items:flex-start;gap:.7rem;margin:1.1rem 0 0;padding:1.2rem 1.3rem;border:1px solid var(--red);border-radius:4px;background:var(--card)}
.reserve[hidden]{display:none}
.reserve-h{margin:0;font-size:1rem;line-height:1.45;color:var(--ink)}
.reserve-p{margin:0;max-width:38rem;color:var(--ink-2);font-size:.875rem;line-height:1.65;text-wrap:pretty}
.reserve-note{margin:0;font:400 .625rem/1.5 var(--font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--label)}
/* Timeline: one column, hairline spine, a filled marker for what is done. */
.timeline{list-style:none;margin:2.6rem 0 0;padding:0 0 0 1.6rem;border-left:1px solid var(--line);display:flex;flex-direction:column;gap:1.6rem}
.tl{position:relative}
.tl::before{content:"";position:absolute;left:calc(-1.6rem - 4px);top:.34em;width:7px;height:7px;border-radius:999px;border:1px solid var(--line-strong);background:var(--bg)}
.tl.done::before{background:var(--line-strong)}
.tl.now::before{background:var(--red);border-color:var(--red)}
.tl-when{display:block;font:400 .625rem/1 var(--font-mono);letter-spacing:.18em;text-transform:uppercase;color:var(--label);margin-bottom:.45rem}
.tl.now .tl-when{color:var(--red)}
.tl b{font-weight:400;font-size:1rem}
.tl p{margin:.35rem 0 0;max-width:32rem;color:var(--ink-2);font-size:.875rem;line-height:1.6;text-wrap:pretty}

/* ============ MOBILE CHROME ============ */
/* The redesign shipped five nav items in one inline row with no disclosure of
   any kind. That row measures 331px: on a 375px phone it pushed the document to
   526px and gave every page a sideways scrollbar. The nav was the ONLY true
   overflow source on the page — everything else that appeared to overhang was
   position:fixed resolving against the widened document.

   Below 48rem the row becomes a panel behind the old site's burger. The desktop
   row above 48rem is untouched, and .burger is display:none there. */
.burger{display:none;margin-left:auto;align-items:center;justify-content:center;width:40px;height:34px;padding:0;background:none;border:1px solid var(--line-strong);border-radius:4px;color:var(--ink);cursor:pointer;transition:background 110ms cubic-bezier(.2,0,0,1),color 110ms cubic-bezier(.2,0,0,1)}
.burger:hover{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.burger:focus-visible{outline:2px solid var(--red);outline-offset:2px}

@media (max-width:48rem){
  .burger{display:inline-flex}
  /* Absolute against .head, which is sticky and therefore a positioned ancestor.
     Opaque --bg on purpose: the header itself is 82% + blur, and a translucent
     panel over the scroll rig is unreadable. */
  .nav{position:absolute;top:56px;left:0;right:0;margin-left:0;flex-direction:column;align-items:stretch;gap:0;background:var(--bg);border-bottom:1px solid var(--line);padding:0 var(--gutter) 1rem}
  .nav[data-open="0"]{display:none}
  .nav a{padding:1.05rem 0;border-top:1px solid var(--line);font-size:.75rem}
  .nav a.cta{margin-top:.9rem;border:1px solid var(--line-strong);border-radius:4px;text-align:center;padding:.95rem}

  /* nowrap at 370px cleared a 375px viewport by 3px and overflowed every 360px
     phone. The line is a slogan, not a table: it may wrap. */
  .strip b{white-space:normal;text-wrap:balance;line-height:1.3}

  /* The headline rotates through seven strings. At the desktop size the longest
     ("USB-C when you would rather use a cable.") runs to three lines on a phone
     while the shortest runs to two, so the reserved box has to cover the tallest
     or the whole hero resizes every 4.2s. Smaller type puts every string on two
     lines; min-height reserves exactly that. See also the min-height guard in
     scramble.js, without which an inline style undercuts this rule. */
  h1.h1{font-size:clamp(1.1875rem,.8rem + 1.7vw,1.5rem);min-height:2.35em}

  /* 44px is the target-size floor; the desktop .btn computes to 40px tall. Only
     the padding moves, so the type and the border are untouched. */
  .btn{padding:1.05rem 1.2rem}
  /* Bit is removed on phones, not merely shrunk. He is decorative (aria-hidden)
     and nothing he shows is stated only by him, so nothing is lost — and at
     these widths he sat over the beat readout and the waitlist button.
     mountBitWhenWide() in bit.js tears the sprite down as well, so the
     220-900ms redraw timer is not left running behind this rule. */
  .bit{display:none}
}
@media (prefers-reduced-motion:reduce){.burger{transition:none}}
