/* ==========================================================================
   OcularAI — layout primitives, buttons, and the reveal engine
   ========================================================================== */

/* --------------------------------------------------------------------------
   Stage
   Sections are composed on the 1920 grid. `.o-section` is the positioning
   context; heights come from the Figma frame heights.
   -------------------------------------------------------------------------- */

/* The Figma frame "Landing Page, Animated" is 1920 x 11559 and its sections
   deliberately overlap (Trusted-by sits over Product, Features over the grey
   backdrop, and so on). Desktop therefore reproduces it as one absolutely
   positioned canvas; each section carries its own `top` from Figma. Below the
   reflow breakpoint everything falls back to normal document flow. */

/* Each page is its own canvas, sized to its Figma frame.

   max-width is half of the 2K/4K story; --s being capped to 1 at 1920 is the
   other half, and neither works without the other. .o-page is the containing
   block for every absolutely positioned section, so it is what all the
   theme's percentages resolve against -- capping it to the same 1920 that --s
   caps at keeps `25%` and `480 * var(--s)` equal at every width. The page
   then stops growing at 1920 and centres, and body's Mono/Dark shows either
   side of it. */
.o-page {
  position: relative;
  width: 100%;
  /* No 1920 cap, for the same reason --s no longer has one: both are lifted
     together, so `25%` and `480 * var(--s)` still agree at 2560 and 3840. */
  margin-inline: auto;
  height: calc(var(--page-h, 11559) * var(--s));
  /* `clip`, never `hidden`: `hidden` makes this a scroll container, which
     traps any `position: sticky` descendant (the How-it-works pin) against
     this box instead of the viewport. `clip` clips without scrolling. */
  overflow: clip;
}

/* The Technology and Solutions sections do not overlap (unlike the front
   page), so those pages run as plain flow columns instead of absolute
   canvases. The gap between sections is then a single token and the page
   height is automatic. Each section keeps its Figma `top` for reference; the
   `!important` below is what neutralises it in flow mode.
     tech      Figma leaves 160 between sections; 64 is the tightened value
               asked for.
     sol       Figma leaves 100, kept as drawn. */
.o-page--tech,
.o-page--sol,
.o-page--about,
.o-page--resources,
.o-page--policy {
  height: auto;
}
.o-page--tech  { --flow-gap: 64; }
/* The policy documents run straight into the footer, no gap. */
.o-page--policy { --flow-gap: 0; }
.o-page--sol   { --flow-gap: 100; }
/* About: the dots band sits 660 below a hero that ends at 1167, and the
   plate starts at 1167 with the band's own frame ending at 1007 -- so the
   design's own spacing is already inside the sections and the gap is 0. */
.o-page--about { --flow-gap: 0; }
/* Resources: each section carries its own padding, as About's do. */
.o-page--resources { --flow-gap: 0; }

.o-page--tech > main > .o-section,
.o-page--sol > main > .o-section,
.o-page--about > main > .o-section,
.o-page--resources > main > .o-section,
.o-page--policy > main > .o-section {
  position: relative;
  top: auto !important;
  left: auto;
}

.o-page--tech > main > .o-section + .o-section,
.o-page--sol > main > .o-section + .o-section,
.o-page--about > main > .o-section + .o-section,
.o-page--resources > main > .o-section + .o-section,
.o-page--policy > main > .o-section + .o-section {
  margin-top: calc(var(--flow-gap) * var(--s));
}

@media (max-width: 1023px), (max-width: 1200px) and (orientation: portrait) {
  .o-page--tech > main > .o-section + .o-section,
  .o-page--sol > main > .o-section + .o-section,
  .o-page--about > main > .o-section + .o-section,
  .o-page--resources > main > .o-section + .o-section,
  .o-page--policy > main > .o-section + .o-section { margin-top: 0; }
}

.o-section {
  position: absolute;
  left: 0;
  width: 100%;
}

@media (max-width: 1023px), (max-width: 1200px) and (orientation: portrait) {
  .o-page { height: auto; }
  .o-section { position: relative; top: auto !important; }
}

/* --------------------------------------------------------------------------
   Reveal engine
   Mirrors Figma's Default -> Animate variants: a clipping parent plus a child
   that is translated out of frame in Default and sits at 0 in Animate.

     <div class="mask"><span class="rv rv-up" style="--rv-d:70">…</span></div>

   `--rv-d` is the Figma offset in design px (scaled by --s).
   `--d`    is the stagger index; delay = --d * --rv-step.
   `.is-in` is applied by assets/js/reveal.js when the section scrolls in.
   -------------------------------------------------------------------------- */

.mask { overflow: hidden; }

/* --rv-lead lets a whole section hold its reveal back — used where a backdrop
   plate has to reach full bleed before the content on it starts to move. */
.rv {
  display: block;
  will-change: transform;
  transition: transform var(--rv-dur) var(--rv-ease);
  transition-delay: calc(var(--rv-lead, 0ms) + var(--d, 0) * var(--rv-step));
}

.rv-up    { transform: translateY(calc(var(--rv-d, 40) *  1 * var(--s))); }
.rv-down  { transform: translateY(calc(var(--rv-d, 40) * -1 * var(--s))); }
.rv-left  { transform: translateX(calc(var(--rv-d, 40) * -1 * var(--s))); }
.rv-right { transform: translateX(calc(var(--rv-d, 40) *  1 * var(--s))); }

.is-in .rv { transform: none; }

/* Fade-only reveal, for elements Figma cross-dissolves rather than slides. */
.rv-fade {
  opacity: 0;
  transition: opacity var(--rv-dur) var(--rv-ease);
  transition-delay: calc(var(--rv-lead, 0ms) + var(--d, 0) * var(--rv-step));
}
.is-in .rv-fade { opacity: 1; }

/* --------------------------------------------------------------------------
   Rules — 1px hairlines that grow from a stub to full width.
   `--rule-from` is the Default-variant width in design px.
   `.rule--rtl` grows right-to-left (anchored to its right edge).
   -------------------------------------------------------------------------- */

.rule {
  position: relative;
  height: 1px;
  overflow: hidden;
}

.rule__fill {
  display: block;
  height: 1px;
  background: var(--rule-color, var(--c-mono-light));
  width: calc(var(--rule-from, 44) * var(--s));
  transition: width var(--rv-dur) var(--rv-ease);
  transition-delay: calc(var(--rv-lead, 0ms) + var(--d, 0) * var(--rv-step));
}

.rule--rtl .rule__fill { margin-left: auto; }

.is-in .rule__fill { width: 100%; }

/* --------------------------------------------------------------------------
   Mono-tech label — Arame, the small uppercase metadata type
   -------------------------------------------------------------------------- */

.mono {
  font-family: var(--f-tech);
  font-weight: 300;
  font-size: max(11px, calc(16 * var(--s)));
  line-height: 1.2;
  color: var(--c-mono-light);
}

.mono--yellow { color: var(--c-accent-yellow); }

/* --------------------------------------------------------------------------
   Buttons
   The corner brackets in Figma are 1px paths with an ~11.22px horizontal arm
   and a 12px vertical arm, inset into each corner. Rebuilt here as borders so
   they scale with --s and inherit colour.
   -------------------------------------------------------------------------- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: calc(10 * var(--s));
  font-family: var(--f-sans);
  font-size: max(11px, calc(16 * var(--s)));
  line-height: 1.2;
  color: var(--c-mono-light);
  background: var(--btn-bg, transparent);
  transition: background-color 240ms ease, color 240ms ease;
}

.brk__c {
  position: absolute;
  width: calc(var(--brk-w, 11.217) * var(--s));
  height: calc(var(--brk-h, 12) * var(--s));
  border: 0 solid var(--brk-color, var(--c-accent-yellow));
  pointer-events: none;
  transition: border-color 240ms ease;
}

/* Hover: each corner extends its two arms until they meet the neighbouring
   corners, closing the four brackets into one continuous outline.
   The resting rule carries the retract timing, the hover rule the close. */
.btn .brk__c {
  transition:
    width var(--brk-dur-out) var(--brk-ease),
    height var(--brk-dur-out) var(--brk-ease),
    border-color 240ms ease;
}

/* is-lit / is-tapped are set only by assets/js/touch.js, which binds nothing
   unless the primary input cannot hover. On a desktop these selectors exist
   and never match. */
.btn:hover .brk__c,
.btn:focus-visible .brk__c,
.btn.is-tapped .brk__c {
  width: 100%;
  height: 100%;
  transition-duration: var(--brk-dur), var(--brk-dur), 240ms;
}
.brk__c--tl { top: 0;    left: 0;  border-top-width: 1px;    border-left-width: 1px;  }
.brk__c--tr { top: 0;    right: 0; border-top-width: 1px;    border-right-width: 1px; }
.brk__c--bl { bottom: 0; left: 0;  border-bottom-width: 1px; border-left-width: 1px;  }
.brk__c--br { bottom: 0; right: 0; border-bottom-width: 1px; border-right-width: 1px; }

/* Variants seen in the composition */
.btn--solid { --btn-bg: var(--c-light-dark);  --brk-color: var(--c-accent-yellow); }
.btn--ghost { --btn-bg: var(--c-ghost-fill);  --brk-color: var(--c-accent-yellow); }
.btn--bare  { --btn-bg: var(--c-mono-dark);   --brk-color: var(--c-mono-light);    }
.btn--light { --btn-bg: var(--c-primary-blue); --brk-color: var(--c-accent-yellow); color: var(--c-mono-light); }

/* The outline closing is the hover state; the plate only lifts very slightly
   so the brackets stay legible against it. */
.btn:hover,
.btn:focus-visible { background: var(--btn-bg-hover, var(--btn-bg)); }

.btn--solid { --btn-bg-hover: #22303F; }
.btn--ghost { --btn-bg-hover: rgba(236, 247, 255, 0.16); }
.btn--bare  { --btn-bg-hover: rgba(236, 247, 255, 0.06); }
.btn--light { --btn-bg-hover: #0B2450; }

/* --------------------------------------------------------------------------
   Dot rings
   The shared motion for every instance of sphere.svg: the whole artwork
   turns clockwise as one rigid piece. Used by the Features glow and the CTA
   halo. The hero uses the same artwork but as a static <img> with no ring.

   The tint is independent of the drawing — sphere.svg becomes the mask and
   the colour comes from --ring-fill — so one asset serves both the blue
   glow and the near-white halo. Build it with tools/build_sphere.py.

     <div class="dotring"><i></i></div>   tinted, vector as mask
   -------------------------------------------------------------------------- */

.dotring {
  position: absolute;
  inset: 0;
}

/* One sphere, one rotation.
   This used to be four concentric rings, each holding a FULL copy of the
   artwork masked to its own annulus and spinning at its own speed, so the rim
   ran faster than the core. The catch is that the annuli are feathered, and in
   every feather band you saw the same artwork twice at two different angles —
   which reads as two or three spheres overlapping, not as depth. There is no
   way to give one piece of artwork several rotation speeds without drawing it
   several times, so the differential is gone and the sphere turns as one. */
.dotring > img,
.dotring > i {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  /* No-JS fallback. dotfield.js cancels this on the hero's chain layer and
     drives the rotation itself, so the chains and the canvas dots — two halves
     of one drawing — cannot hold a phase offset against each other. The
     Features and CTA rings have no canvas half, so they keep this. */
  animation: ocularai-dotring-spin var(--ring-p, 60s) linear infinite;
  will-change: transform;
}

/* Tinted variant: the vector becomes the mask and the colour comes from
   --ring-fill, so tint and motion stay independent. */
.dotring > i {
  background: var(--ring-fill, currentColor);
  -webkit-mask: url("../img/sphere.svg") no-repeat center / 100% 100%;
          mask: url("../img/sphere.svg") no-repeat center / 100% 100%;
}

/* Stroke-draw for inline line art. Paired with pathLength="1" on each shape
   (tools/mark_linedraw.py), so one dasharray covers shapes of any length.
   Used by the Features cards and the About page's standards. */
@keyframes ocularai-linedraw {
  to { stroke-dashoffset: 0; }
}

@keyframes ocularai-dotring-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }   /* positive = clockwise on screen */
}

@media (prefers-reduced-motion: reduce) {
  .dotring > img, .dotring > i { animation: none; }
}

/* --------------------------------------------------------------------------
   Image hover zoom
   The frame keeps its exact size and clips; only the picture inside scales,
   so nothing around it moves. Put `.zoom` on the element that defines the
   picture's box — it must contain the <img> and nothing else that should
   stay still.
   -------------------------------------------------------------------------- */

.zoom { overflow: hidden; }

.zoom img {
  transition: transform var(--zoom-dur) var(--zoom-ease);
  will-change: transform;
  backface-visibility: hidden; /* avoids edge shimmer while scaling */
}

.zoom:hover img,
.zoom:focus-within img { transform: scale(var(--zoom-scale)); }

/* A zoomed frame that is itself inside a link/button should react to that
   element's hover too, not only its own. */
a:hover .zoom img,
button:hover .zoom img { transform: scale(var(--zoom-scale)); }

@media (prefers-reduced-motion: reduce) {
  .zoom img { transition: none; }
  .zoom:hover img,
  .zoom:focus-within img,
  a:hover .zoom img,
  button:hover .zoom img { transform: none; }
}

/* --------------------------------------------------------------------------
   Chip — the outlined eyebrow label on the feature panels
   ("VERIFIED", "KEEP YOUR CAMERAS", "PROACTIVE"). 1px border in the current
   text colour, so it inherits from whichever panel it sits on.
   -------------------------------------------------------------------------- */

/* A label, not a box. It used to carry a 1px outline and 10/16 of padding;
   the frame now sets these as plain Arame Thin on the ground, so the rule is
   the type and nothing else. Used by the Smart Search eyebrow and the three
   in the Human section -- nothing else, which is why this changes here rather
   than becoming a variant. */
.chip {
  display: inline-flex;
  align-items: center;
  font-family: var(--f-tech);
  font-weight: 300;
  font-size: max(11px, calc(16 * var(--s)));
  line-height: 1.4;
  letter-spacing: calc(-0.48 * var(--s));
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Nav link — the roll-up hover Figma builds by stacking two copies of the
   label inside a 19px clip with a blank line between them.
   -------------------------------------------------------------------------- */

.navlink {
  display: block;
  height: calc(19 * var(--s));
  overflow: hidden;
  font-size: max(11px, calc(16 * var(--s)));
}

.navlink__roll {
  display: block;
  transition: transform 420ms var(--rv-ease);
}

.navlink__roll span {
  display: block;
  height: calc(19.2 * var(--s));
  line-height: 1.2;
  white-space: nowrap;
}

.navlink:hover .navlink__roll,
.navlink:focus-visible .navlink__roll {
  transform: translateY(calc(-38.4 * var(--s)));
}

/* Selected nav item stays Accent/Yellow. `.is-active` is set on click by
   assets/js/ui.js; `:active` gives the same colour as immediate press
   feedback before the class lands. */
.navlink { transition: color 200ms ease; }
.navlink:active,
.navlink.is-active { color: var(--c-accent-yellow); }

/* --------------------------------------------------------------------------
   Reduced motion — hold every element at its Animate position.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .rv, .rv-fade, .rule__fill, .navlink__roll, .brk__c { transition: none !important; }
  .rv { transform: none !important; }
  .rv-fade { opacity: 1 !important; }
  .rule__fill { width: 100% !important; }
}

/* Fallback: if JS never runs, show everything rather than an empty page. */
.no-js .rv { transform: none; }
.no-js .rv-fade { opacity: 1; }
.no-js .rule__fill { width: 100%; }

/* --------------------------------------------------------------------------
   Mobile navigation
   The three mastheads hide their desktop nav pill below the reflow
   breakpoint. This is what stands in for it -- one component, styled once
   here rather than three times under each masthead's prefix, because it is
   the same thing in each.

   Above the breakpoint the toggle and the panel are both display: none, so
   nothing here can reach the desktop composition.
   -------------------------------------------------------------------------- */

.mnav__toggle,
.mnav { display: none; }

@media (max-width: 1023px), (max-width: 1200px) and (orientation: portrait) {
  /* --- the toggle ------------------------------------------------------
     Figma "Hamburger" (1149:107800) and "Cross" (1149:107984), both drawn
     on a 28 box. The bars are rows of 2px dots on a 3 pitch -- the same
     lattice as the dot bands -- so they are the exported SVGs used as
     masks rather than backgrounds, which keeps currentColor in charge of
     the colour the way the solid bars were.

     Frame geometry: the long bars are 23x2 at y 5 and y 21, the short one
     14x2 at y 13 and flush right; the cross is two 26.189 bars crossing
     at the box centre. The cross's dot unit is the hamburger's scaled
     1.04756, so the two icons are one shape in two states -- which is why
     the tap morphs the same three elements instead of swapping icons. */

  .mnav__toggle,
  .mnav__close { --mnav-spring: cubic-bezier(0.22, 1.18, 0.36, 1); }

  .mnav__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    /* 44 is the smallest target a thumb reliably hits. */
    width: 44px;
    height: 44px;
    margin-right: -8px;        /* optical: the bars, not the box, align right */
    border-radius: 8px;
    color: var(--c-mono-light);
    -webkit-tap-highlight-color: transparent;
  }
  .mnav__bars {
    display: block;
    position: relative;
    width: 28px;
    height: 28px;
    transition: transform 220ms var(--ui-ease);
  }
  /* A press the thumb can feel, on a control with no other affordance. */
  .mnav__toggle:active .mnav__bars,
  .mnav__close:active .mnav__x { transform: scale(0.88); }

  .mnav__bars i {
    position: absolute;
    height: 2px;
    background: currentColor;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
    transition:
      transform 420ms var(--mnav-spring),
      opacity 180ms var(--ui-ease);
  }
  .mnav__bars i:nth-child(1),
  .mnav__bars i:nth-child(3) {
    left: 3px;
    width: 23px;
    -webkit-mask-image: url("../img/nav-bar-long.svg");
    mask-image: url("../img/nav-bar-long.svg");
  }
  .mnav__bars i:nth-child(1) { top: 5px; }
  .mnav__bars i:nth-child(3) { top: 21px; }
  .mnav__bars i:nth-child(2) {
    left: 12px;
    top: 13px;
    width: 14px;
    transform-origin: right center;
    -webkit-mask-image: url("../img/nav-bar-short.svg");
    mask-image: url("../img/nav-bar-short.svg");
    /* Closing, the short bar is the last thing back, so the cross has
       finished unfolding before it reappears. */
    transition-delay: 170ms;
  }

  /* Open: the outer two meet at the box centre and cross, stretching from
     23 to the cross's 26.189; the short one retracts into its right end.
     The outer pair waits 60ms so the short bar clears first. */
  .mnav__toggle[aria-expanded="true"] .mnav__bars i:nth-child(1) {
    transform: translate(-0.5px, 8px) rotate(45deg) scaleX(1.1386);
    transition-delay: 60ms;
  }
  .mnav__toggle[aria-expanded="true"] .mnav__bars i:nth-child(3) {
    transform: translate(-0.5px, -8px) rotate(-45deg) scaleX(1.1386);
    transition-delay: 60ms;
  }
  .mnav__toggle[aria-expanded="true"] .mnav__bars i:nth-child(2) {
    transform: scaleX(0.12);
    opacity: 0;
    transition-delay: 0ms;
  }

  /* Fixed, so it covers the window rather than the page column, and so it
     cannot be clipped by .o-page's `overflow: clip`. */
  .mnav {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 200;
    background: var(--c-mono-dark);
    opacity: 0;
    visibility: hidden;
    transition: opacity 280ms var(--ui-ease), visibility 0s linear 280ms;
  }
  .mnav.is-open { opacity: 1; visibility: visible; transition-delay: 0s; }
  /* [hidden] is the no-JS state; the script removes it on first use so the
     panel can transition rather than appear. */
  .mnav[hidden] { display: none; }

  /* The links take the free space and centre themselves in it; the two
     buttons sit on the panel's foot rather than following the last link,
     so the menu reads as a screen with a fixed footer. */
  .mnav__panel {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 24px 16px 28px;
  }

  /* Pinned to the top of the panel rather than sitting in the flow, so the
     links stay optically centred in the whole screen instead of being pushed
     below the middle by the bar above them. */
  .mnav__head {
    position: absolute;
    left: 24px;
    right: 18px;
    top: 20px;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .mnav__logo { display: block; width: 120px; height: 29px; }
  .mnav__logo img { width: 100%; height: auto; }
  .mnav__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 8px;
    color: var(--c-mono-light);
  }
  /* The panel covers the masthead, so this cross stands where the toggle
     was -- the toggle's own morph is only visible under the fade. Being a
     separate element it can use the frame's real cross bar: 9 dots on the
     3.1427 pitch, where the morphed toggle carries the hamburger's 8
     stretched to length. Rotating them in on the same spring is what
     carries the motion across the swap. */
  .mnav__x {
    position: relative;
    display: block;
    width: 28px;
    height: 28px;
    transition: transform 220ms var(--ui-ease);
  }
  /* Both bars start as a point at the box's centre and grow outwards into
     the cross, so the dots read as travelling out from the middle rather
     than the whole mark arriving at size. */
  .mnav__x i {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 2.095px;
    height: 26.189px;
    margin: -13.0945px 0 0 -1.0476px;
    transform-origin: 50% 50%;
    background: currentColor;
    -webkit-mask-image: url("../img/nav-cross-bar.svg");
    mask-image: url("../img/nav-cross-bar.svg");
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
    transform: rotate(45deg) scaleY(0);
    opacity: 0;
    transition:
      transform 420ms var(--mnav-spring),
      opacity 140ms var(--ui-ease);
  }
  .mnav.is-open .mnav__x i { opacity: 1; }
  .mnav.is-open .mnav__x i:nth-child(1) { transform: rotate(45deg)  scaleY(1); transition-delay: 90ms; }
  .mnav.is-open .mnav__x i:nth-child(2) { transform: rotate(135deg) scaleY(1); transition-delay: 170ms; }
  .mnav__list {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    justify-content: center;
    gap: 0;
  }
  /* Set in caps and centred, on a much longer measure than a list of links
     would normally take -- the whole point of the screen is that each one
     is a destination, not an item. */
  .mnav__list a {
    display: block;
    padding: 10.8px 0;
    font-family: var(--f-sans);
    font-weight: 400;
    font-size: clamp(16.5px, 4.8vw, 22.5px);
    line-height: 1.25;
    letter-spacing: -0.01em;
    text-align: center;
    text-transform: uppercase;
    color: var(--c-mono-light);
    /* Each link rises as the panel opens, one after the next. */
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 320ms var(--rv-ease), transform 420ms var(--rv-ease);
  }
  .mnav.is-open .mnav__list a { opacity: 1; transform: none; }
  .mnav.is-open .mnav__list li:nth-child(1) a { transition-delay: 60ms; }
  .mnav.is-open .mnav__list li:nth-child(2) a { transition-delay: 110ms; }
  .mnav.is-open .mnav__list li:nth-child(3) a { transition-delay: 160ms; }
  .mnav.is-open .mnav__list li:nth-child(4) a { transition-delay: 210ms; }
  .mnav.is-open .mnav__list li:nth-child(5) a { transition-delay: 260ms; }

  .mnav__actions {
    display: flex;
    flex: none;
    flex-direction: column;
    align-items: stretch;
    gap: 12.5px;
    /* Flush with the logo on the left and with the cross's own glyph on the
       right -- 24 and 350 -- rather than with the panel's gutter. */
    width: auto;
    margin: 0 10px 0 8px;
    padding-top: 24px;
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 320ms var(--rv-ease) 240ms, transform 420ms var(--rv-ease) 240ms;
  }
  .mnav.is-open .mnav__actions { opacity: 1; transform: none; }
  /* Same rule as the desktop pill: the page you are on is Accent/Yellow. */
  .mnav__list a.is-active { color: var(--c-accent-yellow); }

  /* Stacked and full width, but otherwise the theme's own button: square,
     its own fill, and the four bracket corners that close on a tap. Size is
     the hero CTA's -- the other 48-tall button on a phone. */
  .mnav__actions .btn {
    display: flex;
    width: 100%;
    height: 48px;
    padding: 0;
    font-size: 15px;
  }

  /* The page behind must not scroll under the panel. */
  body.mnav-open { overflow: hidden; }
}

@media (prefers-reduced-motion: reduce) {
  .mnav, .mnav__list a, .mnav__actions, .mnav__bars, .mnav__bars i,
  .mnav__x, .mnav__x i { transition: none; }
  .mnav__x i { opacity: 1; }
  .mnav__x i:nth-child(1) { transform: rotate(45deg); }
  .mnav__x i:nth-child(2) { transform: rotate(135deg); }
}

/* --------------------------------------------------------------------------
   Registered properties
   A custom property is a string unless it is registered with a type, and a
   string cannot be interpolated -- it flips from one value to the other at
   the halfway point. Registering --wipe as a number is what lets the
   Technology hero's glow be uncovered gradually rather than in one jump.
   -------------------------------------------------------------------------- */
@property --wipe {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

/* Form submissions ---------------------------------------------------------
   The honeypot is not `display:none`: some robots skip what is hidden that
   way, and a real visitor must never reach it either, so it is taken out of
   the layout and out of the tab order instead (the input carries tabindex=-1
   and the wrapper is aria-hidden, so assistive tech never announces it). */
.ocularai-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* What the form says back. Hidden until there is something to say, so it
   takes no space in a layout measured against the frame. */
.form-status {
  margin-top: calc(12 * var(--s));
  font-size: calc(14 * var(--s));
  line-height: 1.4;
}

.form-status[data-tone="ok"]   { color: var(--c-accent, #ffd11a); }
.form-status[data-tone="bad"]  { color: #ff6b5e; }
.form-status[data-tone="busy"] { opacity: .7; }

form[data-busy="yes"] button[type="submit"] { opacity: .6; pointer-events: none; }
