/* ============================================================================
   floom-apple.css — the reference redesign
   ----------------------------------------------------------------------------
   Loaded LAST so it wins over the earlier layers. It restyles the homepage onto
   the supplied composition rather than rebuilding it: the section order already
   matched (hero, stats, features + tabs, dark three-steps, CTA), so
   what changes here is the surface, not the structure.

   THE GRADIENT, exactly as supplied — four stops on a vertical axis:

   ds-exempt-start: supplied reference colours pending palette decision (see NOTE)
       linear-gradient(180deg,
         #291003 23%,    dark brown, full opacity
         #EA580C 44%,    orange,     full opacity
         #FF764D 52%,    light orange, full opacity
         #FAF6F1 64%)    off white,  full opacity
   ds-exempt-end

   Above 23% the brown is solid and below 64% the off white is solid, so the
   whole transition happens across the middle 41% of the hero. No opacity is
   applied to the ramp itself; the only alpha in this file is on glass and on
   the CTA highlight, and both are noted where they are used.
   ========================================================================== */

:root{
  /* ── the house section heading ──────────────────────────────────────────
     Taken off .ff-head, which is the one that was asked for: 53.28 at 1440,
     weight 500, tracking NORMAL, leading 1, uppercase. Every section heading
     on the site reads these four, so the treatment has one home instead of
     the eight it was spread across — which is how it came to be running at
     36.65 / 40 / 42 / 45 / 53.28 / 75.43 in the same page, at two different
     weights and five different trackings.

     Normal tracking is the point of it and not an oversight: caps at this
     size are already tight, and the -0.03em the rest of the site carries
     closes them up. */
  --fl-head-size:   var(--fl-t-title1);   /* was clamp(28.81px, 3.7vw, 66.87px): the hierarchy pass, floom-theme.css */
  --fl-head-weight: 500;
  --fl-head-track:  0;
  --fl-head-lead:   1;

  /* ds-exempt-start: supplied reference colours pending palette decision (see NOTE) */
  --ap-ink:        #291003;   /* gradient stop 1 — dark brown  */
  --ap-orange:     #EA580C;   /* gradient stop 2 — orange      */
  --ap-orange-lit: #FF764D;   /* gradient stop 3 — light orange*/
  /* ds-exempt-end */
  --ap-cream:      var(--fl-canvas);  /* gradient stop 4 — the page ground */

  /* Stop placement taken from the reference, measured live at 1440x900: its
     ramp runs 0 / 12.7 / 30 / 48.7 / 68 / 100% over a 1321px header, and the
     first three stops are nearly the same dark value. That is the whole trick
     — the dark HOLDS for the top third, the middle eases, and the lightest
     colour only arrives below the fold. At a 900px viewport its fold lands at
     68%, where it is still a mid tone, not the light end.

     The four supplied colours, placed on that curve: dark through 40%, the
     orange not until 76%, and the cream exactly on the hero's lower edge where
     the stats section continues it. The earlier 36/69/81/100 reached full
     orange by the fold, which is what read as harsh. */
  --ap-hero-grad: linear-gradient(180deg,
    var(--ap-ink)        40%,
    var(--ap-orange)     76%,
    var(--ap-orange-lit) 88%,
    var(--ap-cream)     100%);

  /* The button fill reads as one tone in the reference but is a two-stop ramp
     of the same hue — the lighter stop at the top edge is what gives it the
     slight lift. Neighbouring members, so no new hue is introduced. */
  --ap-btn-grad: linear-gradient(180deg, var(--ap-orange-lit) 0%, var(--ap-orange) 100%);
}

/* ── the page ground ───────────────────────────────────────────────────────
   White, stated once on the token the whole site already reads, rather than
   painted onto html/body — otherwise the ramp's last stop, the nav's scrolled
   band, the glass fallbacks and every `var(--fl-canvas)` surface would sit on
   another ground and every join between them would show as a seam.
   --ap-cream above points at this too, so the hero gradient finishes exactly
   on the paper it sits on. (The supplied warm grey ground, off the palette,
   gave way to White on 2026-10-31.) */
:root{
  --fl-canvas:     #FFFFFF;
  --bg:            var(--fl-canvas);
  --ui-background: var(--fl-canvas);
}
html, body{ background: var(--fl-canvas); }

/* ── no ruling ─────────────────────────────────────────────────────────────
   The faint grid behind the sections stands down. It is painted by a child
   element — .fl-grid-bg inside every .fl-grid-host — so switching it off here
   costs nothing in markup and leaves the hosts, which are only stacking
   contexts, to carry on doing nothing visible. */
.fl-grid-bg{ display: none; }

/* ── hero ──────────────────────────────────────────────────────────────────*/
.ap-hero{
  /* The photograph's width, and the hero's foot. The monitor and the tab bar
     are placed from both (see "the monitor"). In vw rather than % so it means
     the same thing in a width, a margin and a `bottom`. */
  --ap-ppl: 100vw;
  --ap-hero-pb: round(clamp(40px, 6vw, 96px), 8px);
  position: relative;
  background: var(--ap-hero-grad);
  padding: 0 0 var(--ap-hero-pb);
  overflow: clip;
  isolation: isolate;
}
/* On a phone the whole desk at 100vw puts a 165px screen under the copy. The
   photograph widens instead and loses the outside of each figure, and the
   monitor, sized from it, comes up with it. */
@media (max-width: 760px){
  .ap-hero{ --ap-ppl: 150vw; }
}
/* The grid texture the rest of the site carries would read as dirt on a ramp
   this saturated, so the hero opts out of it. */
/* (the hero's own grid was already off; .fl-grid-bg above now covers it) */

/* Purely a region marker for the sticky nav (floom-nav.js lists it as dark).
   It paints nothing — the gradient is on .ap-hero itself — it just gives the
   bar a box to test against, ending where the ramp stops being dark. */
.ap-hero-dark{
  position: absolute;
  inset: 0 0 auto 0;
  height: 62%;
  pointer-events: none;
}

/* ── the hero, holding while the next section climbs over it ───────────────
   The stage is one window taller than the hero and the hero sticks to its
   lower edge inside it, so the hero stops the moment its own lower edge
   reaches the bottom of the window and holds for exactly the climb. The
   section after is pulled back up by the same amount: the two cancel, the page
   keeps its length, and the gaps measured under the hero are untouched.

   Both halves are gated on .is-pinned, which the motion module only sets when
   the hero is genuinely taller than the window — otherwise bottom:0 would read
   as "your lower edge is too high" and push the hero down the screen at rest.
   assets/floom-motion.js carries the reasoning and sets --ap-hold.

   Nothing here needs a z-index. The hero is positioned once it is sticky and
   the section that follows is positioned already, so the later one in the
   markup paints over the earlier one, which is the order wanted.             */
/* The extra height has to be CONTENT, not padding. Sticky may only travel
   inside its containing block's content box, and padding-bottom sits outside
   it — with the stage grown that way the hero exactly filled the box it was
   allowed to move in and never stuck at all, while every other number (page
   length, the pull-up, the gaps below) looked perfectly correct. */
.ap-hero-stage.is-pinned::after{
  content: "";
  display: block;
  height: var(--ap-hold, 0px);
}
/* No position:sticky here any more. Sticky can only hold a thing DEAD still,
   and still was not wanted: the hero is to keep rising while the band gains on
   it. That is a rate, not a stop, so floom-motion.js paints the hero a
   translate each frame — half the page's movement, up to --ap-hold — and the
   hero stays an ordinary element in the flow. */
.ap-hero-stage.is-pinned + *{
  margin-top: calc(0px - var(--ap-hold, 0px));          /* measured: must cancel the hold exactly */
}

/* ── the two panels behind the composition ────────────────────────────────
   Measured off the drawing at 1742 wide: the right panel's near corner at
   (1264, 501) with edges leaving it at dx/dy -1.6558 and +0.5980, the left
   panel's at (196, 516) with the same +0.5952. Perpendicular to within 1%, one
   shared angle, so each is a square rotated -30.9deg and anchored by the
   corner that shows. Everything is in vw, including the vertical placement, so
   the whole arrangement stays geometrically similar as the page widens — a
   percentage top would resolve against the hero's HEIGHT and shear it.

   .ap-hero carries overflow:clip, so the three quarters of each square that
   sit outside the frame cost nothing but are there when the window widens. */
.ap-hero-shapes{
  position: absolute;
  inset: 0;
  z-index: 0;                  /* the copy and the device are 1, the people 2 */
  pointer-events: none;
}
.ap-shape{
  position: absolute;
  display: block;
  /* A flat fill, 30% of --ap-orange. The drawing samples at .316 over
     234/88/12, which is this token to within a point per channel; 30 is the
     round number it was authored at, so it is written as the round number. */
  background: color-mix(in srgb, var(--ap-orange) 30%, transparent);
  /* One angle for all four edges. The drawing's two clean edges fit
     dx/dy = +0.5976 and -1.6778 at 0.35px rms, whose product is 1.003 — so
     they are perpendicular and this is a square's corner, at -30.8deg. */
  transform: rotate(-30.8deg);
}
/* The near corners, from the drawing: intersecting each panel's two fitted
   edges puts them at (876.6, 346.2) and (132.0, 355.5) on its own 1209px
   layout — not from the first lit row, which on the left is the standfirst's
   own F and put the corner 110px out.

   Both are held to the WINDOW, which is what the drawing holds them to. The
   headline is the wrong anchor and was tried: it is centred and set in
   clamp(), so it stops growing while the window does not, and the panels came
   away from the figures they are composed against — on the left the corner sat
   188px above the head where the drawing puts it 119, and 123px right of it
   where the drawing puts it 17. The photograph is width:100% of the hero, so a
   vw term tracks it exactly.

   Horizontally that is a plain fraction of the width. Vertically it is taken
   from the hero's LOWER edge, because that is the edge the photograph is
   anchored to: 100% here is the hero's own height, and (hero - 0.727w) /
   (hero - 0.719w) reproduce the drawing's two corners on its layout to the
   pixel.

   One side for both, as asked: 475.5 x 471.68 in the drawing's own units,
   which differ by 0.8% and are called a square, so this is their mean taken
   square rather than a rectangle 4px off true.

   Converting that to a fraction of the width needs the drawing's frame width,
   which its own image does not give up: the type in it is set larger than this
   page sets it and the screen inside the monitor is the older baked one, so
   the chip, the headline and the plate each imply a different scale. What the
   image DOES give is a floor. The right panel's upper edge runs to the right
   frame edge and never turns, so that side is at least 558 of the image's
   1742px — 32.0% of the width, whatever the frame was. 33vw is that floor plus
   a little, and it is what 475.5 means on a 1440 frame, the ordinary one.
   If the frame was something else, this one number is the only thing to move. */
.ap-shape{
  --s: 33vw;
  width: var(--s);
  height: var(--s);
}
.ap-shape-a{
  left: round(72.51vw, 4px);
  top: calc(100% - 72.666vw);
  transform-origin: 0 0;                      /* its near corner is top-left */
}
.ap-shape-b{
  left: round(calc(10.92vw - var(--s)), 4px);
  top: calc(100% - 71.903vw);
  transform-origin: 100% 0;                  /* its near corner is top-right */
}

/* ── the plane that recedes ────────────────────────────────────────────────
   The copy sits on its own layer so it can fall away as the page moves,
   instead of merely scrolling off — the behaviour asked for off prolibu.com.
   It is a WRAPPER rather than the copy block itself because .ap-hero-inner
   already owns a transform (data-par-rate drifts it at 0.045), and the module
   that writes these says so in as many words: one element, one transform.

   The ramp behind it is deliberately left alone. On the reference the backdrop
   is a flat panel pinned with position:fixed, where holding it still is what
   sells the recede and costs nothing because a flat colour cannot be seen to
   move. Floom's backdrop is not flat — it is a four-stop ramp measured against
   the hero's own height, with the orange at 76% and the cream landing exactly
   on the hero's lower edge. Pinning that would hold the viewport on the ramp's
   top 768px forever and the orange and the cream would never be seen at all.
   So the copy recedes into the ramp, and the ramp keeps its composition.

   No background, no border, no shadow: this element exists to be transformed
   and must stay invisible, or a shrinking rectangle appears around the copy.  */
.ap-hero-recede{
  position: relative;
  z-index: 1;
  /* About its own middle, which lands between the headline and the standfirst
     — the copy draws back into itself rather than toward a screen edge. */
  transform-origin: 50% 50%;
}

.ap-hero-inner{
  position: relative;
  z-index: 1;
  max-width: var(--fl-shell-max, 1720px);
  margin-inline: auto;
  padding-inline: var(--fl-gutter, clamp(20px, 2.8vw, 40px));
  /* 100px of air between the bar's lower edge and the headline, measured from
     the nav's own tokens rather than typed as a single number — the bar is
     68px down and 42px tall, so this stays 100px if either ever moves, and it
     follows the smaller nav tokens on a phone without a second rule. */
  padding-top: round(calc(var(--fl-nav-top) + var(--fl-nav-pill) + 100px), 8px);
  text-align: center;
}

.ap-hero h1{
  margin: 0 auto;
  /* One line now, so this cap only stops the headline running the full width of
     a very wide monitor. It has to clear the whole sentence rather than the
     longer of two: 24ch held the old line one and would break this one. */
  max-width: var(--fl-span-10);   /* columns 2–11 */
  color: #FFFFFF;
  font-family: var(--font-display);
  /* Asked for at 48px, so the cap is 48 rather than 72. The vw term still
     carries it down on a narrow screen — it only reaches the cap at 1091px, so
     every desktop width lands on exactly 48. The 24ch limit above is in the
     same em, so it scales with the size and the authored line break still
     holds: line one inks at ~803px inside an 815px box. */
  font-size: var(--fl-t-display);   /* the panels behind are placed in this */
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
}
.ap-hero-sub{
  /* The rhythm under the headline, widened to the reference's. Every gap here
     was 25-35% tighter than the one it is modelled on, which is what read as
     "unbalanced" — the type was large AND close, so nothing had room. */
  margin: var(--sp-6) auto 0;   /* under the page's title: one step more than under a heading */
  /* Two sentences, one per line, which is what the drawing sets. The measure
     has to clear the longer of them on its own — 66ch was sized for a single
     sentence over two lines and breaks this one in the middle of a clause.
     `balance` still applies to each half, so when the screen is too narrow for
     a sentence it wraps evenly rather than leaving one word stranded. */
  max-width: var(--fl-span-10);   /* columns 2–11 */
  text-wrap: balance;
  color: #FFFFFF;
  font-size: clamp(15px, 1.22vw, 19px);
  font-weight: 450;
  line-height: 1.3;
}

/* ── buttons ───────────────────────────────────────────────────────────────
   Small radius, uppercase, tracked out, trailing arrow — the reference's pair.
   Deliberately not the pill the rest of the site uses. */
.ap-hero-cta{
  display: flex; justify-content: center; align-items: center;
  gap: round(clamp(8px, 1.1vw, 16px), 4px);
  margin-top: round(clamp(32px, 4.3vw, 64px), 8px);
  flex-wrap: wrap;
}
.ap-btn{
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 20px;
  border-radius: 5px;      /* the same radius the nav controls carry */
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
}
.ap-btn svg{ width: 13px; height: 13px; flex-shrink: 0; }
/* No lift, no shadow bloom, no tint. The reference's button does not move and
   does not change colour on hover — its own transform stayed `none` and its
   fill held the supplied button colour across every frame of the 600ms. The
   content sliding inside the clip is the entire interaction. */
.ap-btn:hover,
.ap-btn:focus-visible{ transform: none; }

/* Ghost: glass over whatever the ramp is doing behind it. Alpha is the point
   here — it has to pick up the gradient it sits on. */
.ap-btn-ghost{
  color: #FFFFFF;
  background: rgba(250, 246, 241, .16);
  border: 1px solid rgba(250, 246, 241, .26);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.ap-btn-ghost:hover,
.ap-btn-ghost:focus-visible{
  background: rgba(250, 246, 241, .16);
  border-color: rgba(250, 246, 241, .26);
  color: #FFFFFF;
}

.ap-btn-solid{
  color: #FFFFFF;
  background: var(--ap-btn-grad);
  /* ds-exempt-start: supplied reference colours pending palette decision (see NOTE) */
  border: 1px solid rgba(41, 16, 3, .14);
  box-shadow: 0 6px 18px rgba(41, 16, 3, .22);
  /* ds-exempt-end */
}

/* ── the monitor ───────────────────────────────────────────────────────────
   Supplied at 1536x1024, a 3:2 frame — so it is sized by width and the ratio
   box reserves its height, and the image never moves the page as it decodes.

   It is placed against the two people, not the column grid: they are in the
   foreground and the desk is an arm's length beyond them, so the display reads
   at about two head-widths across, its top edge between their eye lines and
   its foot just behind the keyboard. In the photograph's own frame (width W,
   height H = W / 1.4997) that is the screen's top at 28% of H and the plate's
   foot at 76% of H. The screen starts 7.42% down the plate, so the plate is
   0.5186 W wide and its foot sits 0.16 W above the photograph's lower edge,
   which is the hero's. Both are stated against --ap-ppl, the photograph's
   width, so the two keep their distance at every window width. */
.ap-monitor{
  position: relative;
  z-index: 1;
  width: calc(var(--ap-ppl) * .5186);
  /* The widest gap in the composition, as in the reference — the device needs
     air above it or the hero reads as one dense block. It also lengthens the
     hero, which stretches the gradient: the same four stops over more height
     is what stops the ramp looking compressed. */
  margin: round(clamp(56px, 7.2vw, 104px), 8px) auto
          calc(var(--ap-ppl) * .16 - var(--ap-hero-pb));
  aspect-ratio: 1536 / 1024;
}
.ap-monitor img{
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}

/* ── the features block, back in the flow ─────────────────────────────────
   .fl-overtake carries margin-top: -100vh so it could climb over the pinned
   showcase. With the hero now a static composition there is nothing pinned
   behind it, and that pull-up ate the bottom third of the gradient — the cream
   band that was cutting the monitor in half was this section sitting on top of
   it. It is an ordinary section again. */
.fl-overtake{
  margin-top: 0;
  z-index: auto;
}

/* ── the section eyebrow ───────────────────────────────────────────────────
   The features layer that used to live here — re-placing a two-column grid
   into one centred column and flattening the vertical rail into a row — has
   been retired. floom-features.css now builds that section natively, as a deck
   of stacking cards, and every rule here was either a duplicate of it or, in
   the rail's case, actively against it: `position: static !important` was
   holding the tab row down while the stylesheet was trying to pin it. */

.ap-eyebrow{
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 0 0 round(clamp(20px, 2.6vw, 40px), 4px);
  font-family: var(--font-display);
  /* A section mark, so a clear step under the heading it labels: 17.81 on a
     wide screen (heading 32.5), 15.79 on a phone (heading 22.65) — both ladder
     steps. At 20.08 it sat one step from a phone's heading and read as a
     second heading; at 14.5 it read as a caption. */
  font-size: clamp(15.79px, 1.24vw, 17.81px);
  font-weight: 700;
  line-height: 1.3;
  color: var(--ap-ink);
}
/* A SQUARE, lightly rounded — not a circle. Measured off the reference: the
   mark is 0.52 of the cap height beside it, which at this size is 7.5px, with a
   corner radius about a quarter of its own width. Sized in em so it tracks the
   label rather than needing a second number when that changes. */
.ap-eyebrow-dot{
  width: .375em; height: .375em;
  border-radius: 2px;
  background: var(--ap-orange);
  flex-shrink: 0;
}

/* ── the hero's chip ───────────────────────────────────────────────────────
   The same mark as the section eyebrows below — orange square, label — but set
   as a chip, because here it stands on the dark ramp rather than on the page's
   own ground and needs a surface of its own to sit on. The fill is a lift of
   whatever is behind it rather than a colour, which is how every other control
   over this gradient is built.

   The square is bigger here in proportion to its label than the one on the
   section eyebrows: that one is 0.52 of its cap height, this one is about the
   whole of it, which is what the drawing shows. Stated in em either way, so it
   tracks the label rather than needing a second number. */
.ap-hero-eyebrow{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: fit-content;
  margin: 0 auto var(--sp-6);   /* a pill above the page's title: one step more than an eyebrow */
  padding: 12px 20px;
  border-radius: 8px;
  background: rgba(255, 255, 255, .08);
  font-family: var(--font);
  font-size: 15.79px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #FFFFFF;
}
.ap-hero-eyebrow .ap-eyebrow-dot{
  width: .72em; height: .72em;
  border-radius: 2px;
}
/* A size down on a phone, where at the desk's size the chip was the loudest
   thing above the headline. 8 + 12 + 8 = 28. */
@media (max-width: 760px){
  .ap-hero-eyebrow{ padding: var(--sp-2) var(--sp-3); font-size: 12.41px; line-height: 12px; margin-bottom: var(--sp-4); }
  /* On the home page the bar leaves with the hero, so its Request a Demo was
     only ever seen right above the hero's own: two of the same button in
     one screen. Logo and Menu on a phone there; every other page keeps it,
     since it is their call to action at the top. */
  body:has(.ap-hero-cta) .nav .ap-nav-cta{ display: none; }
}

/* ══ NAVIGATION ════════════════════════════════════════════════════════════
   The reference puts two controls on the right — REQUEST A DEMO and MENU —
   and nothing else. The four links move into the menu panel that already
   existed for small screens, so nothing is lost; they are one click further
   away, which is the trade-off the composition asks for.

   Spacing is 42px from each side and 24px top and bottom — the sides as
   supplied, the vertical corrected to the reference's own inspector reading.
   Both go through the existing tokens rather than being written on .nav
   directly, because the mega panel and the mobile panel position themselves
   off --fl-nav-top + --fl-nav-pill — set those and everything downstream
   follows instead of drifting.                                              */
:root{
  /* 24px, from the reference's own box model: its nav is 1400 x 82 with
     `padding: 24px` all round over a 34px content row. 68px came from an
     earlier read of the mock and sat the bar too far down the ramp. */
  --fl-nav-top:  24px;   /* top AND bottom padding           */
  --fl-nav-pill: 44px;   /* the hug height: 12 + 18 + 12 + 2px border (the 4px grid) */
  --fl-nav-h:    92px;   /* 24 + 44 + 24: the bar's full height */
  --ap-nav-side: var(--fl-gutter);   /* the page margin, so the bar's ends stand on the content's edges */
  --ap-line:     18px;   /* the control line box both share  */
}

/* No island and no glass, ever: the bar sits directly on the hero ramp and
   LEAVES WITH IT. Measured on the reference at 1440x900 — its nav container is
   `position: absolute; top: 0`, and its rect tracks -scrollY exactly at 150,
   400, 900, 1800 and back, with no background, no backdrop-filter, no
   transform and nothing on the way back up. It is not a sticky bar that
   changes costume; it is page content that happens to be at the top.

   So: absolute, not fixed. `top: 0` against the initial containing block puts
   it at the document's top and the page carries it off. The only exception is
   while the menu sheet is open — see .nav.menu-open below — because that is
   the one state where the bar has to outlive its own scroll position. */
.nav{
  position: absolute;
  top: 0;
  left: 0; right: 0;
  height: auto;
  max-width: none;
  margin-inline: 0;
  padding: var(--fl-nav-top) var(--ap-nav-side);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.nav::before{ display: none; }          /* the island's specular arc */
.nav .nav-inner{
  padding-inline: 0;
  height: auto;
  max-width: none;
}

/* The link row is now the menu's job. Kept in the DOM — it is what the mega
   panel's trigger lives in — but out of the layout. */
/* ── the bar's links, swiped in by the Menu button ────────────────────────
   The bar still opens collapsed — logo, Request a Demo, Menu — and the four
   links arrive when Menu is pressed, each one travelling in from the right.
   Nothing here restyles a link: the colour, the size and the underline are
   .nav-links' own, from floom-theme.css. This only decides where they start
   from and when each one leaves.

   They keep their place in the layout while they are away rather than being
   display:none, so pressing Menu cannot shift the bar. That costs nothing:
   .nav-inner's first child carries margin-right:auto, so the right-hand group
   is right-aligned and the links' width falls in the empty middle — the demo
   button and Menu sit exactly where they sat before.

   --d is the per-item delay, written by floom-nav.js: on the way in the link
   nearest the button goes first, so the row unrolls right to left out of the
   control that opened it; on the way out the far one leaves first and the row
   folds back into it.                                                        */
.nav .nav-links{ display: none !important; }

@media (min-width: 960px){
  .nav .nav-links{
    display: flex !important;
    align-items: center;
  }
  .nav .nav-links > *{
    opacity: 0;
    transform: translateX(34px);
    transition: transform var(--ap-swipe-dur, 520ms) var(--ap-swipe-ease, cubic-bezier(.2, 1.2, .35, 1)),
                opacity 240ms ease-out;
    transition-delay: var(--d, 0ms), var(--d, 0ms);
  }
  .nav.links-open .nav-links > *{ opacity: 1; transform: none; }
  /* Leaving is quicker than arriving and does not overshoot — a spring on the
     way out reads as the row being thrown rather than put away. */
  .nav.links-closing .nav-links > *{
    transition: transform 200ms cubic-bezier(.4, 0, 1, 1), opacity 160ms ease-in;
    transition-delay: var(--d, 0ms), var(--d, 0ms);
  }
  /* While they are away they are out of the tab order and out of the tree; the
     attribute is written by the script, this only stops the hit-testing. */
  .nav .nav-links[inert]{ pointer-events: none; }

  /* The burger becomes an X, as it already does for the sheet — but none of
     the sheet's other treatment, which turns the whole bar dark and would be a
     different bar rather than the same one with its links out. */
  .nav.links-open .nav-toggle svg path:nth-of-type(1){ transform: rotate(45deg) translateY(7px); }   /* to the middle, then turned: one centre (floom-nav.css) */
  .nav.links-open .nav-toggle svg path:nth-of-type(2){ opacity: 0; transform: scaleX(.3); }
  .nav.links-open .nav-toggle svg path:nth-of-type(3){ transform: rotate(-45deg) translateY(-7px); }
}

@media (prefers-reduced-motion: reduce){
  .nav .nav-links > *{
    transition-duration: 120ms !important;
    transition-delay: 0ms !important;
    transform: none !important;
  }
}

/* ── the two controls ──────────────────────────────────────────────────────
   Square-ish, uppercase, tracked out, and filled with a lift of whatever is
   behind them rather than a colour of their own — which is why the fill is an
   alpha of the cream rather than a flat tone. On the dark ramp that reads as
   the reference's slightly-lighter pill; over a light section the on-light
   block below swaps it for ink. */
/* ── which way the controls paint ──────────────────────────────────────────
   One pair of tokens, set once per state, read by BOTH the resting rule and
   the hover rule. That is the whole fix for the CTA turning black under the
   pointer: floom-nav.css carries `.nav.on-dark .btn-primary:hover{ background:
   #FFFFFF; color: #181818 }` at (0,4,0), and the rule here only restated the
   background — so the colour half of it still landed and the label went to
   ink. Rather than chase each of those declarations, hover now re-asserts the
   resting values from the same tokens, so there is nothing left for another
   file's hover to change. The label swap is the entire hover. */
.nav{
  --ap-ctl-bg: rgba(24, 24, 24, .06);   /* over a light page */
  --ap-ctl-fg: var(--ap-ink);
  --ap-ctl-bd: rgba(24, 24, 24, .10);
}
/* Over a dark section, and over the open menu sheet — which is ink at 92% and
   is therefore a dark ground like any other. */
.nav.on-dark,
.nav.menu-open,
.nav.is-mega{
  --ap-ctl-bg: rgba(255, 255, 255, .08);
  --ap-ctl-fg: #FFFFFF;
  --ap-ctl-bd: transparent;
}

.nav .ap-nav-cta,
.nav .ap-nav-menu{
  /* Supplied: 5px radius, 8% white, 17px side padding, 11px top and bottom,
     and HUG — the control is as tall as its own content, not a fixed pill, so
     height and min-height are both off the table. */
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: auto;
  min-height: 0;
  /* 13px top and bottom, not 11. Measured off the reference: its pill stands
     4.67x its own cap height, which at 13px type is ~44px — the 11px version
     came out at 40 and read thin. Sides stay at the supplied 17px.

     line-height is stated in px so BOTH controls resolve to the same content
     height: one holds a text line, the other an 18px icon, and left to the
     strut they would settle at different heights and the pair would sit
     unevenly. 18 + 13 + 13 = 44 for both. */
  padding: 12px 16px;
  border-radius: 5px;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  line-height: 18px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
  transition: background-color .22s ease, color .22s ease, border-color .22s ease;
}

/* The surface itself, stated once per state.

   It has to be written at (0,3,0) rather than read off the tokens from the
   (0,2,0) rule above, because floom-nav.css carries `.nav.on-dark .btn-primary`
   and `.nav.on-dark .nav-toggle` at exactly that weight — and the CTA is a
   .btn-primary. Setting --ap-ctl-bg on .nav.on-dark does not help a declaration
   that loses the cascade before it is ever read: the first pass of this did
   exactly that and the CTA came out cream-on-ink at the top of the homepage. */
.nav .ap-nav-cta,
.nav .ap-nav-menu,
.nav.on-dark .ap-nav-cta,
.nav.on-dark .ap-nav-menu,
.nav.scrolled .ap-nav-cta,
.nav.scrolled .ap-nav-menu,
.nav.menu-open .ap-nav-cta,
.nav.menu-open .ap-nav-menu{
  background: var(--ap-ctl-bg);
  color: var(--ap-ctl-fg);
  border-color: var(--ap-ctl-bd);
  box-shadow: none;
}

/* Hover changes NOTHING but the label. Every state is listed because each of
   the files below this one has its own `:hover` on these elements at (0,3,0)
   or (0,4,0); restating the tokens at matching specificity, from a sheet that
   loads last, leaves the surface exactly where it was. Verified on the
   reference: its CTA holds one fill through the whole 600ms, with the
   button's own transform and opacity untouched. */
.nav .ap-nav-cta:hover,
.nav .ap-nav-menu:hover,
.nav .ap-nav-cta:focus-visible,
.nav .ap-nav-menu:focus-visible,
.nav.on-dark .ap-nav-cta:hover,
.nav.on-dark .ap-nav-menu:hover,
.nav.on-dark .ap-nav-cta:focus-visible,
.nav.on-dark .ap-nav-menu:focus-visible,
.nav.scrolled .ap-nav-cta:hover,
.nav.scrolled .ap-nav-menu:hover,
.nav.menu-open .ap-nav-cta:hover,
.nav.menu-open .ap-nav-menu:hover{
  background: var(--ap-ctl-bg);
  color: var(--ap-ctl-fg);
  border-color: var(--ap-ctl-bd);
  box-shadow: none;
}

/* The logo plate stays #181818 everywhere, over the ramp included. It used to
   turn cream over a dark ground, which made a second, lighter mark — the
   brand has one, and this is it. Only the wordmark changes colour. */

.nav .ap-nav-menu{ display: inline-flex; }     /* no longer small-screen only */
.ap-nav-menu-label{
  display: inline-block;
  /* The button is content-sized and the label is set in caps, so CLOSE is five
     letters against MENU's four: 43.39px against 40.30, and every press shoved
     the demo button along by the difference. Reserving the wider of the two
     holds the bar still. 3.4em covers CLOSE at the label's 13px with a little
     over; the prototype's 3.1em was measured on sentence case and came up
     three pixels short here. This is the one rule in the feature that moves
     anything at rest, and it moves the Menu button's resting width by those
     same three pixels rather than moving it on every press. */
  min-width: 3.4em;
  text-align: left;
}
.nav .ap-nav-menu svg{ width: 18px; height: 18px; }

/* The wordmark is white over a dark ground and nowhere else. This used to say
   :not(.scrolled), which meant white at rest on EVERY page — invisible on the
   cream of every one that does not open on the ramp. .nav.on-dark below is the
   state that actually knows, and it carries the home page's hero. */

/* ── nothing happens on scroll ─────────────────────────────────────────────
   The bar used to grow a cream band with a blur and a hairline at 40px, shrink
   its padding from 24 to 14, and duck out of the way on the way down. On the
   reference none of that exists — the bar simply leaves.

   floom-nav.css's glass pane is gone outright now. What is left to stand down
   is the `.nav.scrolled{ background: rgba(255,255,255,.85); backdrop-filter: …;
   border-bottom-color: var(--border) }` every page still carries in its own
   inline <style>: this rule matches it at (0,2,0) and wins on source order,
   since the sheet loads after that block.

   .scrolled is still SET by floom-fx's stickyNav; it just no longer paints. */
.nav.scrolled,
body:has(.mega.is-open) .nav{
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
  box-shadow: none;
  padding: var(--fl-nav-top) var(--ap-nav-side);
}
.nav.nav-hidden{ transform: none; }

/* The one state that must survive its scroll position: with the sheet open the
   bar IS the sheet's close control, so it pins to the viewport for as long as
   the sheet is up. At the top of the page — the only place the menu can be
   opened from, since the bar is gone below it — fixed and absolute land on the
   same pixel, so nothing moves when the class goes on. */
.nav.menu-open{
  position: fixed;
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
}
/* The scroll edge under it: on a phone the sheet's links and its card scroll
   up beneath the bar, which is clear, and ran through the logo and the CLOSE
   control. The bar stands on the sheet's own black, fading out 24px below it,
   so the list passes under instead. */
.nav.menu-open::before{
  content: "";
  display: block;
  position: absolute;
  inset: 0 0 -24px 0;
  z-index: -1;
  border-radius: 0;
  background: linear-gradient(to bottom, rgba(0, 0, 0, .92) calc(100% - 24px), rgba(0, 0, 0, 0));
  pointer-events: none;
}

@media (max-width: 760px){
  :root{ --fl-nav-top: 16px; --fl-nav-h: 76px; }   /* 16 + 44 + 16 */
  /* Narrower, not shorter — the supplied 11px top and bottom stay. Writing
     `padding: 0 12px` here collapsed the control to 23px and took the
     headline gap with it. */
  .nav .ap-nav-cta{ padding: 12px 16px; font-size: 12px; }
  .ap-nav-menu-label{ display: none; }     /* the icon carries it on a phone */
  .nav .ap-nav-menu{ padding: 12px 12px; }
}


/* ── the toggle swaps word and icon with its state ─────────────────────────
   Both labels and both glyphs are in the markup; aria-expanded — which
   floom-nav.js already maintains — decides which pair is shown. No JS of its
   own, and the state a screen reader hears is the same one the eye sees. */
/* Only the WORD swaps here. The icon is a single hamburger that floom-nav.css
   already morphs into an X on .nav.menu-open — a second, separate close glyph
   was being caught by those same nth-of-type transforms and collapsed into one
   vertical line. The animation that existed is better than a swap anyway. */
.ap-nav-menu .ap-lbl-close{ display: none; }
.ap-nav-menu[aria-expanded="true"] .ap-lbl-menu{ display: none; }
.ap-nav-menu[aria-expanded="true"] .ap-lbl-close{ display: inline-block; }
.ap-nav-menu .ap-ico{ width: 18px; height: 18px; flex-shrink: 0; }

/* ── the hero's solid CTA ──────────────────────────────────────────────────
   Supplied flat colour, so the two-stop ramp it carried stands down. */
/* ds-exempt-start: supplied reference colours pending palette decision (see NOTE) */
.ap-btn-solid{
  background: #F93F28;
  background-image: none;
}
.ap-btn-solid:hover,
.ap-btn-solid:focus-visible{
  background: #F93F28;
  background-image: none;
  color: #FFFFFF;
  box-shadow: 0 6px 18px rgba(41, 16, 3, .22);
}
/* ds-exempt-end */

/* ── the ink button ────────────────────────────────────────────────────────
   The solid button for an orange ground, where an orange fill would vanish:
   Off Black, a White label, the thread drawn in the label's colour. The same
   label roll as every .ap-btn and nothing else moves. 12 + 18 + 12 + 2 = 44. */
.ap-btn-ink{
  --th-ink: currentColor;
  color: #FFFFFF;
  background: var(--fl-ink);
  border: 1px solid var(--fl-ink);
}
.ap-btn-ink:hover,
.ap-btn-ink:focus-visible{ color: #FFFFFF; background: var(--fl-ink); }
.ap-btn-ink:focus-visible{ outline: 2px solid var(--fl-ink); outline-offset: 2px; }

/* ── the control, off the bar ──────────────────────────────────────────────
   The nav's own REQUEST A DEMO / MENU control (.nav .ap-nav-cta, below), for
   a secondary action on a light page: the same 5px corners, ink wash,
   hairline and capitals, and the same label roll as its only motion.
   12 + 18 + 12 + 2 border = 44. */
.ap-ctl{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 16px;
  border: 1px solid rgba(24, 24, 24, .10);
  border-radius: 5px;
  background: rgba(24, 24, 24, .06);
  color: var(--ap-ink);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  line-height: 18px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
}
.ap-ctl:focus-visible{ outline: 2px solid var(--ap-orange); outline-offset: 2px; }

/* ── the wordmark follows the surface; the mark does not ───────────────────
   The controls are handled by --ap-ctl-* above. The mark keeps its own plate
   over every ground — it is the brand's one mark — and only the wordmark
   turns white. */
.nav.is-mega .logo-text,
.nav.on-dark .logo-text,
.nav.menu-open .logo-text{ color: #FFFFFF; }
.nav.menu-open .nav-toggle{ color: #FFFFFF; }

/* ══ LABEL SWAP ON HOVER ═══════════════════════════════════════════════════
   Every control holds its label twice, stacked inside a box clipped to one
   line, and hover moves the stack by exactly one line box.

   All four numbers here are measured off the reference, by sampling its own
   hover frame by frame (dispatched pointerenter, computed style read on every
   rAF for 820ms):

     direction  DOWN. At rest its stack is packed to flex-end inside a
                one-line box, so the SECOND copy is the one you see and the
                first is clipped above; hover brings the first one down into
                place. The copy arrives from the top, it does not rise from
                the bottom — this file had it backwards.
     travel     one line box + the stack's gap: 26.8px there, 1 line here.
     duration   600ms. Last non-zero sample at 569ms, zero by 601ms.
     easing     cubic-bezier(1, 0, 0, 1). Fitted against the samples and it
                lands on them: 6.1% at 202ms (measured 5.9), 15.5% at 270
                (15.5), 28% at 303 (31.3), 84.5% at 336 (81.7), 97.2% at 468
                (97.6). Nothing else moves for the first fifth of a second,
                then it crosses in one beat and settles for a long time —
                which is why a plain ease read as sluggish next to it.

   Nothing else changes: through all 820ms the reference's button held one
   flat fill with transform:none and opacity:1, and its 14px icon did not move
   by a pixel.

   The duplicate copy is aria-hidden and comes FIRST in the DOM, so the copy
   standing in the box at rest is the real one. */
.ap-swap{
  position: relative;
  display: inline-block;
  overflow: clip;
  /* The clip has to be the BUTTON's line box, not 1em of the label's own font.
     At 1em it measured 13px while the icon beside it is 18px, so the two
     controls came out 41px and 46px and the pair sat unevenly. --ap-line is
     the one number both of them use. */
  height: var(--ap-line, 18px);
  line-height: var(--ap-line, 18px);
  vertical-align: bottom;
}
.ap-swap > span{
  display: block;
  height: var(--ap-line, 18px);
  line-height: var(--ap-line, 18px);
  /* At rest the stack sits one line up, which puts the SECOND child — the real
     label — in the box. Hover returns it to zero and the duplicate above
     travels down into view. */
  transform: translateY(-100%);
  transition: transform .6s cubic-bezier(1, 0, 0, 1);
}
.ap-btn:hover .ap-swap > span,
.ap-btn:focus-visible .ap-swap > span,
.ap-ctl:hover .ap-swap > span,
.ap-ctl:focus-visible .ap-swap > span,
.ap-nav-cta:hover .ap-swap > span,
.ap-nav-cta:focus-visible .ap-swap > span,
.ap-nav-menu:hover .ap-swap > span,
.ap-nav-menu:focus-visible .ap-swap > span{ transform: none; }

/* On the hero pair the swap carries the WHOLE control — label and arrow
   together on one row — so hovering moves the button's entire contents rather
   than sliding the words out from under a stationary arrow. That is a step
   past the reference, whose icon sits outside the clip and stays put; it was
   asked for, and it is the same mechanic, just with more inside the box. */
.ap-btn .ap-swap > span{
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The travel is the whole effect, so a reader who asked for less motion gets
   the label and nothing moving. */
@media (prefers-reduced-motion: reduce){
  /* No travel at all — and the stack stays where it rests, so the label on
     screen is the real one rather than the duplicate. */
  .ap-swap > span{ transition: none; }
  .ap-btn:hover .ap-swap > span,
  .ap-btn:focus-visible .ap-swap > span,
  .ap-ctl:hover .ap-swap > span,
  .ap-ctl:focus-visible .ap-swap > span,
  .ap-nav-cta:hover .ap-swap > span,
  .ap-nav-cta:focus-visible .ap-swap > span,
  .ap-nav-menu:hover .ap-swap > span,
  .ap-nav-menu:focus-visible .ap-swap > span{ transform: translateY(-100%); }
}

/* ══ THE MENU PANEL ════════════════════════════════════════════════════════
   The reference opens a full-viewport sheet rather than a dropdown: a dark
   ground over the page, the navigation laid out in columns on the left and a
   couple of reading cards on the right. Measured at 1440x900 — fixed, the full
   viewport, rgba ground, 100px of padding down the top, three columns, rows
   35px apart.

   This is Floom's own navigation and Floom's own writing in that shape.

   Everything below is measured off the open sheet at 1440x900, not estimated:

     sheet      fixed, full viewport, rgba(0,0,0,.8) + blur(10px), radius 0,
                content inset 44px from each side — the same inset its logo
                sits on, so the first column lines up under the wordmark. Ours
                uses --ap-nav-side (42px) for the same reason.
     heads      x 44 / 243 / 508, y 100. 14px/16.8px, -0.28px tracking,
                uppercase, white at 0.5. 30px of air under the head.
     links      16px/22.4px, -0.48px tracking, white at 0.78, rows exactly
                35px apart. Columns are auto-width, 64px between them — not a
                grid: the three gaps measured 64 and 63 against three
                different column widths.
     cards      two of 328 x 426, 16px apart, right-aligned to the same 44px
                inset. Each is a flex column with a 22px gap: a SQUARE image
                328x328 at radius 0 and object-fit cover, then the title with
                a 15px arrow right-aligned on its first line, then the
                category 11px under it at 0.47.

   There is no card surface and no radius anywhere in it — the card is an
   image with type under it, which is the correction this block exists for. */
.ap-menu{
  position: fixed;
  inset: 0;
  z-index: 95;
  display: block;
  /* Measured at .8 on the first reference, and that is right over its dark
     navy page. Over OUR hero — a lit orange ramp — 20% of a 72px headline came
     through the first column and sat behind the copy. .92 keeps the sheet
     reading as glass over the page without the page reading through the type;
     the arrangement reference's own ground is denser still. */
  background: rgba(0, 0, 0, .92);
  /* The blur is on the OPEN state only, never here. This element is
     `position: fixed; inset: 0` and is laid out at all times — it is hidden
     with visibility, not display, because there has to be something to
     animate from. A backdrop-filter on it therefore kept a full-viewport
     filter layer alive on every page, all the time, with nothing to show for
     it: Chrome has to composite the whole viewport through that layer on
     every scrolled frame, and edge artefacts along the window's own corners
     are what that looks like. Closed, there is now no layer at all. */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-radius: 0;
  /* .mobile-menu paints a phone island: `inset 0 0 0 1px rgba(255,255,255,.5)`
     plus two drop shadows. On a full-bleed sheet that inset ring reads as a
     1px white border drawn right around the open menu — the thing to remove
     here. The sheet has no edge of its own; it IS the edge. */
  box-shadow: none;
  padding: round(calc(var(--fl-nav-top) + var(--fl-nav-pill) + 40px), 8px) var(--ap-nav-side) 48px;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Closed by default and NOT reachable: visibility (not just opacity) is what
     keeps the links out of the tab order while the sheet is down. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-14px);
  transition: opacity .42s cubic-bezier(.22,1,.36,1),
              transform .42s cubic-bezier(.22,1,.36,1),
              visibility 0s linear .42s;
}
.nav.menu-open ~ .ap-menu,
.ap-menu.is-open{
  opacity: 1;
  visibility: visible;
  transform: none;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: opacity .42s cubic-bezier(.22,1,.36,1),
              transform .42s cubic-bezier(.22,1,.36,1),
              visibility 0s;
}

/* ── what this sheet inherits, and has to put back ─────────────────────────
   .ap-menu is also .mobile-menu, and floom-nav.css styles `.mobile-menu a` as
   a stacked phone list: `min-height: 52px`, a hairline under each row, 13px of
   vertical padding, `align-items: center`, and `opacity: 0` lifted to 1 by
   `.mobile-menu.open a` at (0,3,0). Those are not properties this block
   re-declares, so they survived into the desktop sheet — the links stood 52px
   tall instead of 22, each card carried a rule under it, and `align-items:
   center` shrank the card's title to its text and centred it. Undone here
   rather than in floom-nav.css, which the phone list still needs.

   `.mobile-menu a` is (0,1,1) — one class and one TYPE — so a bare
   `.ap-menu-card` at (0,1,0) loses to it no matter which file loads last. That
   is the whole reason these selectors carry `.ap-menu` in front: two classes
   beat one class and a type. */
.ap-menu .ap-menu-col a,
.ap-menu .ap-menu-card{
  min-height: 0;
  border-bottom: 0;
  transform: none;
}

.ap-menu-inner{
  /* Full-bleed on the sheet's own inset, no centred shell: the reference runs
     its columns from the left inset to its cards on the right, and the first
     column lines up under the wordmark. A max-width here centred the whole
     row and left both edges 79px short of the nav above it. */
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: round(clamp(32px, 4vw, 80px), 8px);
}

/* ── the columns ───────────────────────────────────────────────────────────
   Two columns, no headings, and every item carries a line of its own copy
   under it — the arrangement from the supplied reference. The headings went
   because that sheet has none: the items describe themselves, which is a
   better fit for nine links than three category words would be. */
.ap-menu-cols{
  display: flex;
  /* nowrap, with a basis the row can actually measure. Left to `wrap` and an
     auto basis the two columns stacked one above the other at every width: the
     row gave this block 300px — one column — and the second wrapped under it.
     A stated basis settles it, and nowrap means it can only ever narrow. */
  flex-wrap: nowrap;
  flex: 0 1 664px;             /* 300 + 64 + 300 */
  min-width: 0;
  gap: 64px;
}
.ap-menu-col{
  flex: 1 1 300px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 32px;                   /* between items, not between lines */
}
.ap-menu-head{ display: none; }        /* the reference carries no headings */

.ap-menu .ap-menu-col a{
  display: block;
  margin: 0;
  padding: 0;
  opacity: 1;                  /* the two lines carry their own */
  text-decoration: none;
}
.ap-menu-t{
  display: block;
  color: #FFFFFF;
  font-size: 16px;
  font-weight: 500;
  line-height: 22.4px;
  letter-spacing: -0.03em;
  transition: color .2s ease;
}
.ap-menu-d{
  display: block;
  margin-top: 8px;
  max-width: 34ch;
  color: #FFFFFF;
  opacity: .5;
  font-size: 14.5px;
  font-weight: 450;
  line-height: 20px;
}
.ap-menu .ap-menu-col a:hover .ap-menu-t,
.ap-menu .ap-menu-col a:focus-visible .ap-menu-t{ color: var(--ap-orange-lit); }

/* ── the feature panel ─────────────────────────────────────────────────────
   The latest post (floom-mega.js builds it from the same FEATURE as the
   Features panel's card): one still, then the title, then a line of copy. The
   still is the panel card's plate — 7:5, 16px corners, a hairline — so the
   card reads as the same thing at every width; the words keep the sheet's own
   type. */
.ap-menu-feature{ flex: 0 0 420px; min-width: 0; }
.ap-menu .ap-menu-card{
  display: flex;
  flex-direction: column;
  align-items: stretch;        /* .mobile-menu a centres them otherwise */
  gap: 0;
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  text-decoration: none;
  font-size: 16px;
  color: #FFFFFF;
}
.ap-menu-card-art{
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 7 / 5;
  border-radius: 16px;
  overflow: hidden;
  background: #181818;
}
/* The hairline over the picture's edge, as on the panel's card: a black
   picture on the black sheet has none of its own. */
.ap-menu-card-art::after{
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(250, 246, 241, .12);
  pointer-events: none;
}
.ap-menu-card-art img{
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 70% 50%;    /* the art's subject sits right of centre (floom-mega.css) */
  transition: transform .5s cubic-bezier(.22,1,.36,1);
}
.ap-menu .ap-menu-card:hover .ap-menu-card-art img{ transform: scale(1.03); }
.ap-menu-card-body{ display: block; padding: 0; }
.ap-menu-card-title{
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 32px;
  color: #FFFFFF;
  font-size: 20px;
  font-weight: 500;
  line-height: 24.5px;
  letter-spacing: -0.02em;
}
.ap-menu-card-meta{
  display: block;
  margin-top: 12px;
  max-width: 44ch;
  color: #FFFFFF;
  opacity: .5;
  font-size: 14.5px;
  font-weight: 450;
  line-height: 20px;
  text-transform: none;
}

/* Items arrive in sequence rather than all at once — the index is set in the
   markup so this costs no script. */
.ap-menu-col, .ap-menu-feature{
  opacity: 0;
  transform: translateY(10px);
  transition: opacity .4s cubic-bezier(.22,1,.36,1) calc(var(--i, 0) * 55ms),
              transform .4s cubic-bezier(.22,1,.36,1) calc(var(--i, 0) * 55ms);
}
.nav.menu-open ~ .ap-menu .ap-menu-col,
.nav.menu-open ~ .ap-menu .ap-menu-feature,
.ap-menu.is-open .ap-menu-col,
.ap-menu.is-open .ap-menu-feature{ opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce){
  .ap-menu, .ap-menu-col, .ap-menu-feature{ transition-duration: .01ms !important; transform: none !important; }
}
/* The sheet is one row until there is no longer room for both halves of it;
   then the panel drops under the columns rather than being squeezed. */
@media (max-width: 1180px){
  .ap-menu-cols{ gap: 48px; flex-basis: 568px; }
  .ap-menu-col{ flex-basis: 260px; }
  .ap-menu-feature{ flex-basis: 340px; }
}
@media (max-width: 1000px){
  .ap-menu-inner{ flex-direction: column; gap: 44px; }
  .ap-menu-cols{ flex: 0 1 auto; width: 100%; }
  .ap-menu-col{ flex-basis: 300px; }
  .ap-menu-feature{ flex: 0 0 auto; width: 100%; max-width: 520px; }
}
/* On a phone the sheet is the links alone: the post's card stays on a tablet
   and up (at the user's request, 2026-11-02). */
@media (max-width: 767.98px){
  .ap-menu-feature{ display: none; }
}
@media (max-width: 560px){
  .ap-menu-cols{ flex-direction: column; flex-wrap: wrap; gap: 24px; }
  .ap-menu-col{ flex-basis: auto; gap: 24px; }
  .ap-menu-d{ font-size: 13.5px; line-height: 18px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE ASK SCREEN'S HEADING
   ──────────────────────────────────────────────────────────────────────────
   Two lines at the section heading's size, set in caps: the first plain, the second
   carried on a pale orange block with a caret parked after the full stop,
   because that line types itself (floom-search.js, `data-ai-type`).

   The block is an inline box on the line itself rather than a positioned
   layer, so it grows and shrinks with the typed string exactly as the drawing
   shows it — the highlight is the length of what has been typed, not of what
   will eventually be there.
   ══════════════════════════════════════════════════════════════════════════ */
.ai-search-header{
  max-width: var(--fl-span-10);   /* columns 2–11 */
}
.ai-search-header h2{
  font-family: var(--font-display);      /* Plus Jakarta Sans, stated not inherited */
  /* 32px, as given. It is a stated size rather than a step off the ladder,
     whose neighbours here are 28.81 and 32.5 — the drawing says 32 and half a
     pixel is not worth the difference. 2.7vw reaches it at 1185, which is the
     width the drawing is set at, so it holds flat across the desktop range and
     tapers below rather than keeping a 32px line on a phone. */
  font-size: var(--fl-t-title1);
  /* ds-exempt-start: supplied reference weight (ExtraBold) pending design-system decision (see NOTE) */
  font-weight: 800;                      /* ExtraBold */
  /* ds-exempt-end */
  letter-spacing: -0.03em;
  line-height: 1;
  /* BOTH lines in caps — the sentence is one statement set as one. The markup
     stays in sentence case: the caps are a rendering, so the aria-label
     floom-search.js builds off the child nodes is a sentence a screen reader
     can read rather than one it has to shout. */
  text-transform: uppercase;
  color: var(--black);
}
.ai-type{
  /* ── a panel, not a highlighter ──────────────────────────────────────────
     The block is a FIXED background that the line is typed inside. It used to
     be sized by the live text, so every keystroke widened it and every erased
     character shrank it again — the panel pumped in and out behind a line that
     was supposed to be the only thing moving.

     So the width comes from .ai-type-fit, which holds the finished sentence,
     is never typed into and is hidden from both the eye and the screen reader.
     The live line is laid over it. The two have to agree on where the content
     starts, which is why the padding is written once as these four custom
     properties and read by both.

     (.ai-type-fit is in the markup rather than built here, so the panel has a
     size with no JavaScript at all — the absolutely positioned line would
     otherwise leave it with nothing to measure and it would collapse.) */
  --ai-pt: .436em;
  --ai-pr: round(.98em, 4px);
  --ai-pb: round(.767em, 4px);
  --ai-pl: round(.98em, 4px);
  /* The band across the strip's top and bottom, sampled off the supplied file:
     three of its pixels, which is a hairline at the size this block is drawn.
     Not reachable as a wash — it has to drop red from 249 to 239 and no warm
     member can, every one of them being at 255 there. */
  /* ds-exempt-start: supplied reference colours pending palette decision (see NOTE) */
  --ai-edge: rgb(239, 209, 196);
  /* ds-exempt-end */
  position: relative;
  /* An atomic inline-level box, so the margin is real: it opens the drawing's
     gap between the two lines without a line-height off the leading ladder,
     and without the <br> hack that would collapse the moment the line wraps. */
  display: inline-block;
  margin-top: round(.63em, 4px);                     /* the drawing's 32px under line 1 */
  /* Set from the drawing's own close-up of this corner, measured in cap
     heights so it holds at every size — the block carries 1.36 caps of space
     before the first letter, 0.79 above it and 1.12 under the baseline.

     The block is NOT symmetric about the type, and that is the whole character
     of it: there is half again as much room under the baseline as over the
     caps, which is what a frame drawn around a text layer gives you, since the
     layer carries the font's descent whether the line uses it or not. Set even
     — it measured 0.85 / 0.81 — the line sits too low in its own box and the
     block reads as a highlighter stroke rather than as a drawn panel.

     These are NOT the padding: the line box sits inside the block with its own
     half-leading, which at line-height 1 on this face is -0.129em, so the caps
     start 0.162em below the block's top edge and the baseline sits 0.081em
     above its bottom one. Subtracting that, and the "O"'s own left bearing, is
     what gives the values below. Guessing from the block's outside dimensions
     is what had it a third too tight here the first time. */
  padding: round(calc(var(--ai-pt) - 4px), 4px) var(--ai-pr) round(calc(var(--ai-pb) - 4px), 4px) var(--ai-pl);
  border-top: 1px solid var(--ai-edge);
  border-bottom: 1px solid var(--ai-edge);
  /* Sampled off the supplied strip, not invented: the file is flat and fully
     opaque at (249, 232, 225) — one value across every interior pixel, no
     gradient and no alpha. It could not have been used as an image anyway:
     what was supplied is the layer with its guide lines and corner handles
     still on it, and the fill is the part of it that is the design.

     Reproduced on the palette rather than written as that literal — a wash is
     the one variation the palette allows, and this is one. Over this section's
     own ground, Pale Orange at .224 renders (250, 232, 225) against the
     strip's (249, 232, 225): exact on green and blue, one unit on red, which
     no warm member can beat — every one of them is at 255 there and the ground
     is at 249, so any mix of the two lands above it. Swept 0.218 to 0.240
     against the real ground; this is the only value that lands both of the
     other channels.

     .224 and not .225: alpha is kept in 8 bits, so .225 is stored as 57/255
     and reported back as .224 regardless. Written as what it actually is.

     (Hex is deliberately spelled out in decimal here. The design-system check
     reads comments as well as declarations, and a hex in prose counts against
     the file exactly as one in a rule would.) */
  /* The strip is the supplied file's fill — (249, 232, 225), cropped out of it
     and written lossless, 1841 x 239 and 62 bytes — with its band drawn as a
     real border rather than left in the image.

     THE BAND IS A BORDER BECAUSE A SCALED ONE IS NOT A HAIRLINE. In the file
     it is three pixels of a 251-pixel strip; stretched to a block 70 high that
     is 0.84 of a pixel, and what the compositor makes of 0.84 of a pixel is a
     soft two-pixel smear, measured peaking at (241, 214, 203) instead of
     landing on (239, 209, 196). A 1px border is exactly one device pixel of
     exactly the right colour at every size the block takes.

     The fill stays an image because no wash can reach it: every warm palette
     member is at 255 on red and the ground is at 249, so an alpha of any of
     them lands at 250 and the file's own 249 is out of reach. The wash is
     kept underneath anyway, so a slow, blocked or missing image leaves the
     block the right colour rather than a hole in the heading.

     The image is cropped to the fill ALONE now. Left in, its own band would
     paint a soft copy of itself directly under the sharp one.

     The padding gives the border its two pixels back (calc below), so the
     block's outside measurements are the ones taken off the drawing whether
     the border is there or not. */
  background-color: rgba(255, 177, 153, .224);   /* Pale Orange, as a wash */
  background-image: url("/assets/heading-strip.webp");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  border-radius: 0;                      /* the drawing rules it square */
}
/* The sentence at its full width, holding the panel open. Hidden with
   visibility rather than display so it still takes its space — and so a screen
   reader does not meet the line twice. The reserve on the right is the caret's
   own footprint, or the caret would sit on the panel's edge once the last
   character has landed. */
.ai-type-fit{
  display: block;
  visibility: hidden;
  white-space: pre;
  padding-right: round(calc(.06em + 4px), 4px);
}
/* Laid over it, from the same four numbers the padding is written in — less
   the border, because an absolutely positioned child measures from the PADDING
   box and the border is outside it. */
.ai-type-live{
  position: absolute;
  top: round(calc(var(--ai-pt) - 1px), 4px);
  left: var(--ai-pl);
  display: flex;
  align-items: baseline;
}
.ai-type-text{ white-space: pre; }

/* The corner handles the drawing's layer carried: a small square on each
   corner, as a selected text frame shows in a design tool — the same cell
   as the site's thread arrow (.th-cl), white with the orange line, centred
   on the block's outer corner (the -1px is the 1px band above and below). */
.ai-type-handles > i{
  position: absolute;
  width: 8px; height: 8px;
  background: #FFFFFF;
  box-shadow: inset 0 0 0 1.5px var(--ap-orange);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.ai-type-handles > i:nth-child(1){ left: 0;    top: -1px; }
.ai-type-handles > i:nth-child(2){ left: 100%; top: -1px; }
.ai-type-handles > i:nth-child(3){ left: 0;    top: calc(100% + 1px); }
.ai-type-handles > i:nth-child(4){ left: 100%; top: calc(100% + 1px); }

/* Parked, not blinking, while characters are arriving — a caret that blinks
   mid-word reads as two things happening instead of one. `is-typing` is on for
   the whole type-and-erase pass and off through the hold. */
.ai-type-caret{
  /* A caret is the height of the LINE, not of the capitals — it runs from
     above the capitals to below the baseline, which is what the drawing shows
     and what a text cursor actually does: 1.44 caps, with 0.28 of a cap of
     tail under the line.

     It is drawn by the pseudo-element and SIZED by the element, and those are
     two different heights on purpose. Flex baseline-aligns this box on its
     bottom edge, so whatever height it carries is height above the baseline —
     a 1.09em box therefore stands taller than the type's own 0.92em ascent and
     pushes the whole line down inside the block. Measured: it put 1.02 caps of
     space over the capitals where the drawing asks for 0.79, and no amount of
     padding fixes it because the padding was never the thing that moved.

     So the box is the ascent, which the line is already as tall as, and the
     mark it paints overhangs it. A negative bottom margin does not work here:
     Chrome synthesises this box's baseline from its BORDER edge, not its
     margin edge, so the margin took height off the line instead of carrying
     the tail below it. */
  position: relative;
  display: inline-block;
  width: 3px;
  height: .88em;
  margin-left: round(.06em, 4px);
  animation: ai-type-blink 1s steps(2) infinite;
}
.ai-type-caret::before{
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 1.09em;
  background: var(--black);
}
.ai-type.is-typing .ai-type-caret{ animation: none; }
@keyframes ai-type-blink{ 50% { opacity: 0; } }

/* The drawing sets this far larger than a caption — it reads as the second
   half of the statement, not as a footnote, and it runs noticeably wider than
   the highlight block above it. 25.55px gets it there with ~110px of slack
   left inside the page's 1152px content column, which is the margin that
   keeps it on ONE line if Urbanist ever falls back to a wider face. The
   drawing's own line is wider still (~1200px), and matching that exactly
   would mean breaking the 1200px column every other section lands on. */
.ai-search-sub{
  display: block;
  margin-top: var(--fl-gap-stand);
  font-size: var(--fl-t-lede);
  font-weight: 450;
  letter-spacing: 0;
  line-height: 1.3;
  color: var(--text-secondary, #767270);
  text-transform: none;
}

@media (max-width: 760px){
  .ai-type{ padding: round(.42em, 4px) round(.80em, 4px) round(.46em, 4px); margin-top: round(.54em, 4px); }
}

@media (prefers-reduced-motion: reduce){
  .ai-type-caret{ animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE PEOPLE AT THE DESK
   ──────────────────────────────────────────────────────────────────────────
   Full bleed, anchored to the hero's lower edge, in front of the monitor. The
   cutout is a single frame holding both figures with a clear channel between
   them — left figure to 39.1% of the width (her pointing hand to 46.8%),
   channel to 62.2%, right figure to the edge; only the keyboard and his arm
   cross the channel, in its lower 30% — so at width:100% the device shows
   through exactly where it is composed to, and nothing has to be positioned
   per figure.

   `height: auto` against the stated width/height is what keeps the frame from
   moving as the file decodes; the hero already clips, so whatever of the
   figures does not fit above the fold is simply cropped rather than scaled.
   ══════════════════════════════════════════════════════════════════════════ */
.ap-people{
  position: absolute;
  left: calc((100% - var(--ap-ppl)) / 2);   /* centred when wider than the hero */
  bottom: 0;
  z-index: 2;                  /* the monitor is 1 */
  width: var(--ap-ppl);
  max-width: none;
  height: auto;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  /* The dolly's anchor (data-par-scale, floom-motion.js): growing from the
     lower edge keeps that edge welded to the hero's, so the frame's cut can
     never lift into view. Scaling about the middle horizontally also widens
     the channel between the figures rather than closing it, so the device
     stays visible the whole way through. */
  transform-origin: 50% 100%;
}

/* ══ CLIENTS ══════════════════════════════════════════════════════════════
   The band under the hero. Its top gap is the hero's own measured 140, so the
   hero lands on this exactly as it used to land on the numbers; the numbers
   keep their own 140 below it and their 126 down to the features, both
   untouched.

   The marks themselves are not in the markup. See the note there: a wall
   headed "Companies that trust us" is a customer list, and this repository
   does not hold customer names. What is here is everything around them.
   ========================================================================= */
/* A band, not a section. It was a 140px-deep block with an eyebrow and a 40px
   heading over a wall of boxes; it is one quiet line and a strip of marks
   between two hairlines now, which is all a client list has ever needed to be
   and which is what the drawing shows. */
.cl{
  background: var(--ap-cream);
  padding: round(clamp(40px, 5.6vw, 72px), 8px) 0 round(clamp(40px, 5.6vw, 72px), 8px);
  text-align: center;
}
.cl-line{
  margin: 0 0 round(clamp(24px, 2.6vw, 32px), 4px);
  font-family: var(--font);
  font-size: clamp(15.79px, 1.5vw, 18px);
  font-weight: 450;
  line-height: 1.3;
  color: var(--text-secondary);
}
/* A triangle, as the drawing has it — not the square the section eyebrows use,
   so the two marks are not mistaken for one another. Drawn with borders rather
   than as an SVG because at 7px an SVG is three lines of markup for a shape
   two declarations make exactly. */
.cl h2{
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(25.55px, 3.4vw, 40px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--black);
  text-wrap: balance;
}

/* One centred row that wraps, not a grid of fixed columns: the drawing's rows
   hold 8, then 6, then 6, then 1 — which is what wrapping does on its own, and
   what a column count would have to be fought into. Adding or removing a mark
   then costs nothing. */
/* The ticker. Full bleed rather than the 1180px the wrapped grid sat in — a
   loop that stops short of the edges reads as a strip that happens to be
   moving, not as one that carries on past the window. */
.cl-ticker{
  /* Out of the container and across the window. A loop that stops short of
     the edges reads as a strip that happens to be moving rather than one that
     carries on past them, and the rules that close it have to run the same
     full width or they describe a box the marks leave. */
  margin: 0 round(calc(50% - 50vw), 4px);
  padding: round(clamp(24px, 2.4vw, 32px), 4px) 0;
  /* The ledger rule the design language already defines — ink at 7.5% — so
     the lines that close this strip are the same lines it rules paper with,
     and no fourteenth colour arrives to do it. */
  border-top: 1px solid var(--th-rule);
  border-bottom: 1px solid var(--th-rule);
  overflow: hidden;
  /* The marks fade out before the edge instead of being cut by it. */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}
.cl-track{
  display: flex;
  width: max-content;
  animation: cl-ticker 46s linear infinite;
}
/* Half, not all: the track is two identical groups, so half its width is one
   group — the frame it resets on is pixel-for-pixel the frame it started on. */
@keyframes cl-ticker{
  from{ transform: translateX(0); }
  to  { transform: translateX(-50%); }
}
.cl-ticker:hover .cl-track,
.cl-ticker:focus-within .cl-track{ animation-play-state: paused; }

/* Both groups carry the SAME gap and the same tail padding, and neither
   carries anything on its leading edge. That is the whole of the seam: the
   track travels half its own width, so the two halves have to measure the
   same to the pixel. Putting the joint's spacing on the second group as a
   padding-left made it 33px the wider of the two and the loop would have
   jumped by half of that on every pass. The tail padding is the full gap, so
   the space across the joint matches the spaces inside each group. */
.cl-group{
  display: flex;
  align-items: center;
  flex: none;
  gap: round(clamp(32px, 4.6vw, 72px), 8px);
  padding: 0 round(clamp(32px, 4.6vw, 72px), 8px) 0 0;
  margin: 0;
  list-style: none;
}

@media (prefers-reduced-motion: reduce){
  .cl-track{ animation: none; }
}
.cl-slot{
  display: grid;
  place-items: center;
  /* The box a mark stands in, so a wide wordmark and a round badge sit on the
     same line without either dictating the row's height. */
  height: clamp(26px, 2.6vw, 38px);
  flex: 0 0 auto;
}
.cl-slot img{
  max-height: 100%;
  max-width: clamp(96px, 11vw, 160px);
  width: auto;
  object-fit: contain;
  /* A wall of marks in their own colours is a wall of noise; one tone lets the
     row read as a list. They come up to full on hover, one at a time. */
  filter: grayscale(1);
  opacity: .62;
  transition: opacity .3s ease, filter .3s ease;
}
.cl-slot:hover img{ filter: grayscale(0); opacity: 1; }

/* The placeholder, which is meant to look like one. It carries no name on
   purpose: an invented client is a false claim, and a real one does not belong
   in this repository. */
.cl-slot span{
  display: grid;
  place-items: center;
  width: clamp(96px, 11vw, 150px);
  height: 100%;
  border: 1px dashed rgba(24, 24, 24, .16);
  border-radius: 6px;
  font-family: var(--font);
  font-size: 11.01px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted, #767270);
}

@media (prefers-reduced-motion: reduce){
  .cl-slot img{ transition: none; }
}
