/* OperHand — hero section.
   The mark is the hero object: a machined sculpture, turning, on a bright
   cream ground. Content sits on the left in the calm space the shot leaves. */

/* Glassy ground: a cool pearl sheet lit from behind by brand-toned blooms,
   with a diagonal sheen across it so the page reads as a pane of glass rather
   than a fill. Fixed attachment keeps the light still while content moves.

   The base is deliberately COOL and deliberately not #fef9ee. The app's own
   surface is warm oat, so a cream page ground made every screenshot dissolve
   into it — the capture had no edge and the page looked flat. Cooling the
   ground is what gives the warm product captures something to sit on. */
body {
  /* Flat base only. The expensive gradient stack lives on body::before. */
  background: #e6edf0;
  overflow-x: clip;
}

/* The ground is a FIXED, composited pseudo-element rather than
   `background-attachment: fixed` on body. That property is one of the worst
   scroll-jank sources there is — the browser can't cache it as a layer, so it
   repaints the whole viewport (five gradients plus a tiled noise bitmap) on
   every scroll frame. As a fixed positioned element it becomes its own
   composited layer the compositor simply holds still, costing nothing per
   frame. z-index:-1 keeps it behind content; being a CHILD of body (not an
   ancestor of the sections) it cannot affect the papers' 3D transforms. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    /* Grain. A pure CSS gradient is mathematically smooth, and smooth is what
       reads as "flat" no matter how many stops it has — real glass has tooth.
       Greyscale is deliberate: feTurbulence outputs colour noise by default,
       which would put the multicolour straight back. */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23g)' opacity='0.34'/%3E%3C/svg%3E"),
    /* Specular streaks — light raking across the sheet at a shallow angle */
    linear-gradient(101deg,
      rgba(255, 255, 255, 0.00) 11%,
      rgba(255, 255, 255, 0.62) 18%,
      rgba(255, 255, 255, 0.00) 25%,
      rgba(255, 255, 255, 0.00) 43%,
      rgba(255, 255, 255, 0.40) 49%,
      rgba(255, 255, 255, 0.00) 56%,
      rgba(255, 255, 255, 0.00) 70%,
      rgba(255, 255, 255, 0.72) 78%,
      rgba(255, 255, 255, 0.00) 86%),
    /* Broad light falloff, white only — depth from LIGHT, never from hue */
    radial-gradient(78rem 56rem at 14% -14%, rgba(255, 255, 255, 0.98), transparent 58%),
    radial-gradient(62rem 48rem at 92% 106%, rgba(255, 255, 255, 0.72), transparent 60%),
    /* A real light-to-shade range in ONE tone. The old base spanned about
       seven levels of grey, which is why it read as a dead fill. */
    linear-gradient(163deg,
      #f4f9fb 0%,
      #dae7ed 24%,
      #c1d3dd 50%,
      #cfdde4 72%,
      #e6edf0 100%);
}

/* ---------- Nav ---------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 20;
  /* Thinner bar — it sits over the hero film, so every pixel of height is
     film it hides. */
  padding-top: 12px;
  padding-bottom: 10px;
  pointer-events: none;
}

/* The nav is fixed (owner wants the logo to stay put), so scrolling content
   inevitably passes under it — with nothing behind the logo, headlines
   collided with the wordmark and both became unreadable. A frosted bar
   fades in once the hero is behind you, giving that content something to
   pass BEHIND instead of through. Kept off at the very top so the hero
   opens clean. */
.nav::before {
  content: "";
  position: absolute;
  inset: 0;
  /* No backdrop-filter. This bar spans the full width and sits over moving
     content, so a live blur had to be recomputed every scroll frame — one of
     the largest remaining costs. At this opacity the bar reads the same; the
     blur was buying almost nothing visually and costing a lot. */
  background: rgba(252, 250, 245, 0.94);
  border-bottom: 1px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 8px 24px rgba(110, 119, 92, 0.07);
  /* OFF over the hero so the film is never covered, ON once the hero is
     behind you, where scrolling content would otherwise collide with the
     wordmark. Safe to fade now that .nav-inner sits above this layer — the
     earlier version of this hid the logo, which was a z-index bug, not a
     reason to keep the panel permanently on. */
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}

.nav.is-stuck::before { opacity: 1; }

.nav-inner {
  /* Must sit ABOVE .nav::before. The panel is absolutely positioned, which
     paints it above static in-flow content — so without this it covered the
     logo and the CTA entirely. That is why the logo "disappeared". */
  position: relative;
  z-index: 1;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--margin-desktop);
  display: flex;
  align-items: center;
  justify-content: space-between;
  pointer-events: auto;
}
.nav-logo { height: 50px; width: auto; }

/* Cream fill, terracotta edge, terracotta label — owner-specified. */
.nav-cta {
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--terracotta-clay);
  text-decoration: none;
  padding: 10px 18px;
  border-radius: var(--radius-full);
  background: var(--surface);
  border: 1px solid var(--terracotta-clay);
  transition: background 0.2s ease, border-color 0.2s ease;
}
.nav-cta:hover { background: #fffdf6; border-color: var(--primary); }

/* ---------- Hero ---------- */

/* The hero keeps a warm pool of its own: the mark was rendered on cream, and
   .hero-veil feathers that cream leftward behind the copy. Cooling the whole
   page would have left the video's ground fighting it. So the warmth is local,
   and dissolves at the bottom edge into the glass ground below. */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
  background: linear-gradient(180deg,
    #fdfaf1 0%,
    #fdfaf1 52%,
    rgba(253, 250, 241, 0.78) 72%,
    rgba(253, 250, 241, 0.38) 88%,
    rgba(253, 250, 241, 0) 100%);
}

/* The object occupies the right of the screen; the left stays clean cream
   for the copy. Sizing the stage beats masking a full-bleed video with a veil. */
.hero-stage {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 66%;
  z-index: 0;
  /* The film is a rectangle sitting on a different-coloured ground, so its
     clipped edges read as hard borders. Two gradients multiplied (vertical
     for the bottom edge, horizontal for the left) fade it into the page.

     The fade must stay in the EMPTY FLOOR outside the object. A mask reduces
     opacity, so any part of the mark inside the ramp goes hazy — an earlier
     pass started the fades at 58%/76% and visibly softened the mark's base
     and left side. The object clears the bottom ~14% and the left ~21% of the
     stage, so the ramps live there and the mark stays fully crisp. */
  -webkit-mask-image:
    linear-gradient(180deg, #000 0%, #000 84%, rgba(0,0,0,0.6) 93%, transparent 100%),
    linear-gradient(270deg, #000 0%, #000 79%, rgba(0,0,0,0.5) 91%, transparent 100%);
  mask-image:
    linear-gradient(180deg, #000 0%, #000 84%, rgba(0,0,0,0.6) 93%, transparent 100%),
    linear-gradient(270deg, #000 0%, #000 79%, rgba(0,0,0,0.5) 91%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

.hero-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 46% 50%;
  display: block;
}

/* Feathers the stage's left edge into the page so there is no visible seam. */
.hero-veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg,
      rgba(253, 250, 241, 1) 0%,
      rgba(253, 250, 241, 0.84) 14%,
      rgba(253, 250, 241, 0.34) 30%,
      rgba(253, 250, 241, 0) 46%);
}

/* The hero centres this block vertically, so the top padding is what pushes
   the copy down off centre. Reduced from --space-16: the block moves up by
   half of whatever comes off here, since shrinking the box also re-centres it. */
.hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-block: var(--space-2) var(--space-12);
}

.eyebrow {
  font-size: 12px;
  font-weight: 650;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--secondary);
  margin-bottom: var(--space-3);
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 600;
  /* Stepped down from 86px max. The previous headline was two short lines
     ("The program runs / itself."); this one is ~22 characters per line and at
     the old size its second line ran across the hero object. Sized so the
     longest line clears the object's left edge at desktop widths. */
  font-size: clamp(36px, 4.8vw, 66px);
  line-height: 1.04;
  /* A serif at display size needs far less negative tracking than the
     geometric sans this replaced — -0.03em closed the counters up. */
  letter-spacing: -0.012em;
  /* opsz high = the warm, soft-shouldered cut Fraunces is chosen for;
     SOFT adds the roundness; WONK allows its quirkier alternates. */
  font-variation-settings: "opsz" 120, "SOFT" 40, "WONK" 1;
  color: var(--ink);
  /* Sized to the longest line ("Operations Reimagined.") so neither line of the
     headline wraps inside its own reveal box. Was 13ch for the shorter previous
     headline; a too-narrow value here doesn't shrink the type, it breaks the
     line in the wrong place. */
  max-width: 22ch;
}
/* Masked line reveal: each line rides up out of its own clip box */
/* The clip box has to extend BELOW the baseline or it shears the descenders
   off — the "g" in "program" was being cut, and p/y/q/j would go the same way.
   Tight display line-height leaves no room under the baseline, so pad the box
   down and pull the next line back up by the same amount: descenders clear,
   line spacing unchanged. */
.hero-title .line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.16em;
  margin-bottom: -0.16em;
}
.hero-title .line > span { display: block; }

.hero-sub {
  margin-top: var(--space-3);
  font-size: clamp(16px, 1.35vw, 19px);
  line-height: 1.65;
  color: var(--ink-muted);
  max-width: 40ch;
}

.hero-actions {
  margin-top: var(--space-6);
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 26px;
  border-radius: var(--radius-full);
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
  transition: filter 0.18s ease, background 0.18s ease, border-color 0.18s ease;
}
/* Terracotta fill with an Oat label: on #8a4b30 that clears AA comfortably */
.btn-primary { background: var(--primary); color: #fef9ee; }
.btn-primary:hover { filter: brightness(1.08); }

.btn-quiet {
  color: var(--ink);
  background: rgba(255, 253, 246, 0.72);
  border: 1px solid var(--outline-variant);
}
.btn-quiet:hover { background: rgba(255, 253, 246, 0.85); border-color: var(--secondary); }

/* The section "Learn more" buttons carry a terracotta edge rather than the
   default pale outline — same 1px weight, just more of the brand colour in it.
   Scoped to these three (not .btn-quiet globally) so the hero's "See how it
   works" keeps its quieter outline and the primary CTA stays the loudest thing
   on the page. Hover deepens to the darker terracotta instead of the olive the
   base rule would otherwise apply. */
.sec-cta,
.bill-cta { border-color: var(--terracotta-clay); }
.sec-cta:hover,
.bill-cta:hover { border-color: var(--primary); }

/* ---------- Section 2: the gather ----------
   Papers arrive from every direction, collapse into a bright core, and the
   core opens into the Authorizations dashboard on a laptop. */

/* Sections stay translucent so the page's light shows through them.
   The frost lives on a ::before layer, never on the section itself —
   backdrop-filter on an ancestor flattens the papers' 3D transforms. */
.gather { position: relative; }
.gather::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* No backdrop-filter here. Blur would smooth the ground's grain back into
     the flat wash it was, and there is no content behind these panes to
     refract — only the page ground. A translucent tint carries the glass;
     the blur only cost us the tooth.

     FLAT, not a gradient — and identical to .chaos::before's value. Each
     panel used to fade its own alpha along its own height (0% -> 100% of
     ITS OWN section, which is several viewports tall once pinned scroll
     distance is added). Two independent gradients meeting at the section
     boundary landed on slightly different alpha at that exact pixel AND, more
     to the point, on different SLOPES either side of it — a Mach band, which
     the eye reads as a hard line even when the values are close. A flat tint
     has no slope to clash, so there is nothing left to produce a seam. */
  background: rgba(255, 255, 255, 0.09);
}

.gather-pin {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  /* The nav is fixed, so centring in the full viewport pushes the top of the
     stack underneath it on shorter screens. Reserve its height and the
     content centres in the space that is actually visible. */
  /* Matches section 4: no nav reservation (the two-column block is far shorter
     than the viewport, so this only pushed it down) and the film capped by its
     grid track rather than the viewport. */
  padding-top: 0;
  /* Divided by 1.055 because the pinned timeline settles the laptop at
     scale(1.055). Without this the SETTLED laptop overhangs its column by
     ~19px a side and breaks the nav alignment the owner set (film left edge on
     the logo). The resting size is what compensates; the settled size is what
     you see. */
  --lap-w: min(calc(100% / 1.055), calc(56svh * 1.845));
}

/* Copy and laptop as ONE flex column: the browser lays them out and keeps
   the gap between them, so they cannot physically overlap no matter how the
   viewport height or the copy's rendered line count changes (page zoom,
   narrow window, larger system font). Centred as a pair by the grid above,
   rather than each piece independently guessing its own vertical offset. */
/* Two columns, mirroring section 4 exactly: visual left, copy right, and the
   same nav-aligned container so the laptop's left edge sits on the OperHand
   logo and the copy's right edge on the Start Free Trial button. */
.gather-stack {
  position: relative;
  z-index: 3;
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--margin-desktop);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 34%);
  align-items: center;
  gap: clamp(28px, 4vw, 72px);
}
/* grid-row stated as well as grid-column — with only the column set,
   auto-placement drops the second item onto a new row. */
.gather-frame { position: relative; grid-column: 1; grid-row: 1; }
.gather-copy  { grid-column: 2; grid-row: 1; }

.gather-copy {
  text-align: left;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.gather-copy .eyebrow { margin-bottom: var(--space-2); }
.gather-sub {
  margin: var(--space-3) 0 0;
  max-width: 34ch;
  font-size: clamp(15px, 1.15vw, 17px);
  line-height: 1.6;
  color: var(--ink-muted);
}
/* A consecutive line sits closer than the gap under the headline, so the two
   read as a pair rather than two separate blocks. Same type, only the gap. */
.gather-sub + .gather-sub { margin-top: var(--space-2); }
.gather-cta { margin-top: var(--space-4); }

/* Papers */

/* One shared deep perspective so sheets genuinely travel in z —
   far ones shrink, near ones sweep past the camera. */
.papers {
  position: absolute;
  inset: 0;
  pointer-events: none;
  perspective: 1100px;
  transform-style: preserve-3d;
}
.paper {
  /* No will-change here: measured, promoting all 15 sheets to their own
     compositor layers made it WORSE (8.1% long frames vs 6.8% without) —
     layer count costs more than the repaint it saves at this scale. */
  position: absolute;
  top: 50%;
  left: 50%;
  width: 118px;
  margin: -76px 0 0 -59px;
  transform-style: preserve-3d;
}
.paper-flap {
  transform-style: preserve-3d;
  /* box-shadow, NOT filter: drop-shadow. Measured: the jank was concentrated
     in this section (15% long frames vs 2% elsewhere). drop-shadow is a real
     blur pass over the element's rendered pixels and it re-runs every frame
     for every sheet while they rotate in 3D. The sheets are rectangles, so a
     box-shadow draws the same result from the box geometry alone — no blur
     pass, no per-frame filter cost. */
  box-shadow: 0 10px 18px rgba(96, 92, 70, 0.30);
}
/* Back face shows through when a sheet flips — slightly duller, like paper */
.paper svg {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 2px;
  backface-visibility: visible;
}

/* The core: papers compress into this, it opens into the screen */

/* Centred inside .gather-frame with offsets and auto margins, NOT a transform:
   GSAP animates this element's scaleX/scaleY, and a CSS transform here would
   be overwritten the moment that timeline runs. */
.core {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  margin: -3px auto 0;
  width: min(100%, 900px);
  height: 6px;
  border-radius: var(--radius-full);
  background: linear-gradient(90deg,
    rgba(196,122,92,0) 0%, rgba(196,122,92,0.85) 22%,
    rgba(255,247,238,1) 50%,
    rgba(196,122,92,0.85) 78%, rgba(196,122,92,0) 100%);
  box-shadow: 0 0 40px rgba(196, 122, 92, 0.45);
  opacity: 0;
}

/* Laptop — modern proportions: near edge-to-edge glass in a thin dark
   frame, slim aluminium deck, grounded by shadow and a faint reflection. */

/* Shared by both screens (section 2's dashboard, section 3's film) so they
   read as literally the same laptop, per spec — one shared rule, so there is
   no way for the two to diverge in size.

   The first two terms (66vw / 980px) are what actually fills the frame on a
   normal desktop window — they used to be a conservative 54vw/760px, which
   left real unused space now that overlap is prevented structurally by the
   .gather-stack/.chaos-stack flex flow (see there) rather than by keeping
   the laptop small. The third term is the real safety net: it caps the
   laptop's height to ~58svh (bezel aspect-ratio is 1900/1030, so width =
   height * 1.845), so on a short or zoomed-in viewport the laptop shrinks to
   leave room for the copy above it instead of pushing the flex column taller
   than the pinned screen — checked at real viewport heights from 900px down
   to 620px and at page zoom up to 125%, nothing overlaps or overflows at any
   of them. */
.laptop {
  position: relative;
  width: var(--lap-w);
  /* Centred in its column. --lap-w is deliberately smaller than the column so
     that the settle scale(1.055) lands the laptop exactly on the column edges;
     that only works if the smaller box is centred, since scale grows both ways
     from the centre. Left-aligned, the scaled edge overhung by ~18px. */
  margin-inline: auto;
  opacity: 0;
}

.laptop-lid {
  border-radius: 14px 14px 0 0;
  padding: 3px 3px 0;
  background: linear-gradient(180deg, #c9c3b6 0%, #b8b1a2 100%);
  box-shadow:
    0 60px 110px rgba(90, 96, 74, 0.30),
    0 16px 34px rgba(90, 96, 74, 0.18);
}

.laptop-bezel {
  position: relative;
  border-radius: 11px 11px 0 0;
  overflow: hidden;
  background: #101010;
  padding: 9px 9px 12px;
  aspect-ratio: 1900 / 1030;
}
.laptop-screen {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
  border-radius: 4px;
}
/* One restrained diagonal sheen on the glass */
.laptop-glare {
  position: absolute;
  inset: 0;
  background: linear-gradient(118deg,
    rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.035) 22%,
    rgba(255,255,255,0) 38%);
  pointer-events: none;
}

/* (The block-3 dark veil was removed at the owner's instruction — the film
   now runs straight through to its settled frame with no dark beat.) */

/* Slim deck with a machined front lip */
.laptop-base {
  position: relative;
  height: 11px;
  margin-inline: -4.6%;
  border-radius: 2px 2px 10px 10px;
  background: linear-gradient(180deg, #ddd8cc 0%, #c9c3b5 42%, #a49d8e 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.laptop-notch {
  position: absolute;
  left: 50%;
  top: 0;
  width: 11%;
  height: 4px;
  transform: translateX(-50%);
  border-radius: 0 0 8px 8px;
  background: linear-gradient(180deg, #b0a999 0%, #c4beb0 100%);
}
/* Ground: contact shadow plus a whisper of reflection */
.laptop::after {
  content: "";
  position: absolute;
  left: 2%;
  right: 2%;
  bottom: -34px;
  height: 34px;
  background:
    radial-gradient(48% 100% at 50% 0%, rgba(90, 96, 74, 0.28), transparent 70%);
  filter: blur(6px);
  pointer-events: none;
}

/* Alignment lives on the .gather-copy rule further up, with the rest of the
   two-column treatment. The `text-align: center` that used to sit here was
   left over from the stacked layout and, being later in the file, silently
   overrode it — the copy rendered centred in a left-aligned column. */
.gather-title {
  margin-top: 0;
  font-family: var(--font-display);
  font-weight: 600;
  /* Capped for the ~34% column, same as .chaos-title. */
  font-size: clamp(30px, 3.1vw, 46px);
  letter-spacing: -0.012em;
  line-height: 1.06;
  font-variation-settings: "opsz" 110, "SOFT" 40, "WONK" 1;
  color: var(--ink);
}

/* Motion-only initial states */
html.motion .laptop,
html.motion .gather-copy { opacity: 0; }

@media (max-width: 760px) {
  .gather-pin, .chaos-pin { --lap-w: min(94vw, calc(44svh * 1.845)); }
  .paper { width: 74px; margin: -50px 0 0 -37px; }
}

/* Section CTA. The copy block is centred and sits above the screen in the
   flex stack, so the button centres under the headline. min-height 48px is
   inherited from .btn, which keeps it a comfortable tap target. */
/* The HEADLINE MUST NOT MOVE. Centring headline+button as a flex pair shifted
   the headline off centre to make room, which was wrong. Instead the anchor is
   an inline-block that shrinks to the headline's own width and stays centred
   exactly where the headline always sat; the button is absolutely positioned
   off its right edge, so it takes part in neither the centring nor the height.

   Vertical centring uses top + negative margin, not translateY, because GSAP
   writes its own transform on this element and would overwrite it. */
.title-row { text-align: center; }
.title-anchor { position: relative; display: inline-block; }
.sec-cta {
  position: absolute;
  /* Just clear of the screen's right edge, not floating away from it.
     `left` % resolves against the anchor's width, and the anchor is centred,
     so 50% + half the screen width lands exactly on the screen's right edge —
     then a small gap. Independent of how wide the headline happens to be. */
  /* 0.528 = 1.055/2 — the screen scales to 1.055 on its settle push, so half
     of its SCALED width, or the pill ends up only a few px clear at rest. */
  left: calc(50% + var(--lap-w) * 0.528 + clamp(14px, 1.4vw, 26px));
  /* Sits on the baseline of the headline, not centred on it. The anchor's box
     bottom falls just under the baseline (line-height 1.06), so nudge up by
     that leading to land the pill on the written line. */
  bottom: 0.12em;
  white-space: nowrap;
}

/* Proportionate spacing down the column: label, headline, line, button. */
.chaos-copy .eyebrow { margin-bottom: var(--space-2); }
.chaos-sub {
  margin: var(--space-3) 0 0;
  max-width: 34ch;
  font-size: clamp(15px, 1.15vw, 17px);
  line-height: 1.6;
  color: var(--ink-muted);
}
.chaos-cta { margin-top: var(--space-4); }

/* Narrow screens: no room beside the headline, so it returns to normal flow
   underneath it. */
@media (max-width: 900px) {
  .title-anchor { display: block; }
  .sec-cta {
    position: static;
    bottom: auto;
    margin: var(--space-3) auto 0;
  }
}
/* Revealed in site.js alongside the section copy. */
html.motion .sec-cta { opacity: 0; }

/* ---------- Section 3: order from chaos ----------
   A tall window rides down the real daily queue while the old world —
   sticky notes, notebook pages — is pulled in from the edges and absorbed. */

.chaos { position: relative; }
.chaos::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Flat and identical to .gather::before — see the comment there. */
  background: rgba(255, 255, 255, 0.09);
}

.chaos-pin {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  /* No nav reservation here any more. That 76px was added when this section was
     a full-height vertical stack whose top could slide under the fixed nav; the
     two-column layout is barely half the viewport tall, so the padding only
     pushed the whole block down and left a dead band above it — measured 208px
     of space above versus 132px below, the difference being exactly this
     padding. Centring alone clears the nav comfortably at every height the
     film can take. */
  padding-top: 0;
  /* Two-column layout now, so the film is capped by its COLUMN rather than the
     viewport: 100% of the grid track, with the svh term still stopping it from
     growing taller than the screen on short windows. */
  /* Divided by 1.055 because the pinned timeline settles the laptop at
     scale(1.055). Without this the SETTLED laptop overhangs its column by
     ~19px a side and breaks the nav alignment the owner set (film left edge on
     the logo). The resting size is what compensates; the settled size is what
     you see. */
  --lap-w: min(calc(100% / 1.055), calc(56svh * 1.845));
}

/* Same flow-column structure as .gather-stack, same reasoning. */
/* Film left, copy right — the Bill section's composition, flipped. Grid rather
   than the old vertical flex column, so the copy no longer competes with the
   film for vertical space: the third line and the button now cost nothing at
   all, because they sit beside the film instead of above it. */
.chaos-stack {
  position: relative;
  z-index: 3;
  width: 100%;
  /* Standard container, NOT a wider one. Owner: the section's vertical edges
     are the nav's — the film's left edge lines up with the OperHand logo, the
     copy column's right edge with the Start Free Trial button. .nav-inner uses
     exactly these two values, so anything wider here breaks that alignment. */
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--margin-desktop);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 34%);
  align-items: center;
  gap: clamp(28px, 4vw, 72px);
}
/* Explicit tracks: the copy stays FIRST in the DOM for screen readers while
   the film renders on the left. grid-row must be stated too — with only
   grid-column set, auto-placement puts the copy in row 1 col 2, then can only
   move forward, so the film landed in row 2 and the section stacked instead of
   sitting side by side. */
.chaos-frame { grid-column: 1; grid-row: 1; }
.chaos-copy  { grid-column: 2; grid-row: 1; }

/* A single left-aligned column, evenly spaced, matching .bill-copy's treatment
   on the opposite side of the page. */
.chaos-copy {
  text-align: left;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.chaos-title {
  margin-top: 0;
  font-family: var(--font-display);
  font-weight: 600;
  /* Capped lower than the full-width version was: it now lives in a ~34%
     column, and at 60px "Order From Chaos" wrapped inside it. */
  font-size: clamp(30px, 3.1vw, 46px);
  letter-spacing: -0.012em;
  line-height: 1.06;
  font-variation-settings: "opsz" 110, "SOFT" 40, "WONK" 1;
  color: var(--ink);
}

/* The film now sits inside the SAME laptop component as section 2 (below,
   .chaos-laptop carries the shared .laptop class) — same width, same chrome,
   same shadow. .chaos-frame is left as a bare reveal wrapper only; it used to
   be its own glass card at a much larger size, which is what made the film
   too tall to fit inside the pinned viewport. */
.chaos-frame { position: relative; }

/* Sticky notes and notebook scraps */

.notes { position: absolute; inset: 0; pointer-events: none; z-index: 2; }
.note {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 120px;
  margin: -60px 0 0 -60px;
  filter:
    drop-shadow(0 4px 8px rgba(85, 98, 77, 0.30))
    drop-shadow(0 16px 30px rgba(138, 75, 48, 0.22));
}
.note svg { width: 100%; height: auto; display: block; }

/* Motion-only initial states */
/* .chaos-frame is a bare wrapper now — the .laptop it contains owns its own
   motion-hidden default (see the .laptop rule above) and its own JS reveal. */
html.motion .chaos-copy { opacity: 0; }

@media (max-width: 760px) {
  .note { width: 84px; margin: -42px 0 0 -42px; }
}

/* Narrow: no room for two columns, so it stacks — film first, copy under it,
   which is the same reading order the Bill section falls back to. */
@media (max-width: 900px) {
  .chaos-stack,
  .gather-stack {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(20px, 3.4svh, 40px);
  }
  .chaos-frame, .gather-frame { grid-column: 1; grid-row: 1; }
  .chaos-copy,  .gather-copy  { grid-column: 1; grid-row: 2; align-items: center; text-align: center; }
  .chaos-sub, .gather-sub { max-width: 46ch; }
  .chaos-pin, .gather-pin { --lap-w: min(100%, calc(46svh * 1.845)); }
}

/* ---------- Section 4: Bill ----------
   No product screen here by rule — Bill is carried by a 3D character and the
   copy. Same glass panel and pin structure as sections 2 and 3. */

.bill { position: relative; }
.bill::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: rgba(255, 255, 255, 0.09);
}

/* Not a full viewport. Bill and the section below it are both centred blocks,
   so at 100svh each they left a 277px empty band where they meet (107px under
   Bill's content, 170px over the next section's). Section 4 can't give that
   back — it's pinned, and pinning a block shorter than the viewport exposes
   the footer behind it — so the height comes out of Bill, which has no pin. */
.bill-pin {
  position: relative;
  min-height: 76vh;
  min-height: 76svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  padding-top: 76px;
  --lap-w: min(66vw, 980px, calc(58svh * 1.845));
}

/* The character sits behind the copy, right of centre, mirroring the hero's
   composition so the two 3D moments rhyme. Unlike the hero/gather/chaos
   plates, this is a keyed cutout with real transparency around it — no
   rectangular edge to fade into the page, so no edge mask is needed here. */
.bill-stage {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 58%;
  z-index: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 6%;
  /* Clearance so the raised hand never touches the viewport's right edge,
     independent of exact window width. */
  padding-right: 5%;
}
/* NOTE: bill-character.png is the FULL 1254x1254 keyed frame, deliberately
   uncropped. An earlier version was tightly cropped to its content bbox and
   that crop sliced a fingertip off the right hand — `cropdetect` under-reported
   the bounding box, so every measurement said it was fine while the render was
   visibly cut. Do not "tidy up" this asset by cropping it again; the
   transparent margin is what guarantees nothing is lost.
   Width is 70% (not 66%) to offset that margin, so the character renders at
   the same on-screen size as before. */
.bill-figure {
  /* 70% -> 84%: 20% bigger, owner's call. */
  width: 84%;
  height: auto;
  max-height: 92%;
  object-fit: contain;
  display: block;
}
@media (prefers-reduced-motion: no-preference) {
  .bill-figure { animation: bill-float 6s ease-in-out infinite; }
}
@keyframes bill-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-14px); }
}

.bill-copy {
  position: relative;
  z-index: 3;
  width: 100%;
  text-align: left;
}
.bill-title {
  margin-top: var(--space-1);
  max-width: 14ch;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(30px, 4.4vw, 60px);
  letter-spacing: -0.012em;
  line-height: 1.06;
  font-variation-settings: "opsz" 110, "SOFT" 40, "WONK" 1;
  color: var(--ink);
}
.bill-sub {
  margin-top: var(--space-3);
  max-width: 42ch;
  font-size: clamp(16px, 1.3vw, 18px);
  line-height: 1.65;
  color: var(--ink-muted);
}
.bill-note {
  margin-top: var(--space-2);
  font-size: 13px;
  color: var(--ink-muted);
  opacity: 0.75;
}
.bill-cta { margin-top: var(--space-4); }

html.motion .bill-copy { opacity: 0; }

/* ---------- Footer ---------- */

.foot { background: var(--olive-deep); color: #fef9ee; }
.foot-inner {
  padding-block: var(--space-8);
  display: flex;
  gap: var(--space-6);
  align-items: flex-start;
  flex-wrap: wrap;
}
.foot-logo { height: 28px; width: auto; }
.foot-note { font-size: 13.5px; line-height: 1.7; max-width: 54ch; }

/* ---------- Motion-only initial states ---------- */

html.motion .hero-title .line > span { transform: translateY(105%); will-change: transform; }
html.motion .eyebrow,
html.motion .hero-sub,
html.motion .nav-inner { opacity: 0; }
html.motion .hero-video { opacity: 0; }

/* ---------- Responsive ---------- */

@media (max-width: 1100px) {
  .hero-stage { width: 74%; }
}

@media (max-width: 760px) {
  .nav-inner { padding-inline: var(--margin-mobile); }
  .nav-logo { height: 40px; }

  /* Phone: stack it. The object gets the top, the copy sits below it on clean
     cream — no text over picture, which never reads well on a small screen. */
  .hero {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-height: 100svh;
    padding-top: 64px;
  }

  .hero-stage {
    position: relative;
    inset: auto;
    width: 100%;
    height: 42svh;
    flex: none;
  }
  .hero-video { object-position: 50% 46%; }

  /* Only a soft bottom fade into the page ground */
  .hero-veil {
    background:
      linear-gradient(180deg,
        rgba(253, 250, 241, 0) 0%,
        rgba(253, 250, 241, 0) 62%,
        rgba(253, 250, 241, 0.9) 88%,
        rgba(253, 250, 241, 1) 100%);
  }

  .hero-content {
    padding-block: var(--space-2) var(--space-12);
    margin-top: calc(var(--space-2) * -1);
  }

  .hero-title { max-width: 16ch; }
  .btn { flex: 1 1 auto; }

  /* Phone: same stack-it rule as the hero — the absolute-overlay layout that
     lets the character sit behind centred copy only works with room either
     side of it on desktop. */
  .bill-pin {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: auto;
    padding-top: var(--space-8);
    padding-bottom: var(--space-8);
    gap: var(--space-4);
  }
  .bill-stage {
    position: relative;
    inset: auto;
    width: 100%;
    height: 38svh;
    padding-bottom: 0;
  }
  .bill-figure { width: 62%; }
}
