/* =========================================================================
   ABOUT PAGE — isolated sheet.  ABOUT ONLY.  v=36  (2026-09-01)

   *** SUPERSEDES EVERYTHING COMMITTED FOR THIS PAGE EARLIER TODAY. *** Third
   direction in one session: node 2493-1951 (Lexend, then Bricolage) is retired,
   and this is built from Figma LrUdy5WZmi3n2gkRz9SEC2 nodes
     2500-2077  ABOUT, default state
     2501-2159  ABOUT-01, "The problem with a capable agent" expanded
   plus pellmell.fr's SliceHomeCategories section as the interaction reference.

   THE STRUCTURAL CHANGE, not just a type change: the six subsections are no longer
   headed paragraphs. They are a CATEGORIES LIST — six 60px grey headings on an
   #f2f2f2 card — and each one's prose lives in an EXPANDED STATE that replaces it.
   All 878 words are still on the page; they are just behind the interaction now.

   ── THE REFERENCE, MEASURED (source: live pellmell.fr, inline component CSS) ──
   The section is .SliceHomeCategories (NOT .AppProjectCategories, which is the
   small 13px pill component and a different thing entirely):
       .SliceHomeCategories-list        background var(--color-offwhite-2, #f2f2f2)
                                        border-radius 10px, overflow hidden
       .SliceHomeCategories-listItemName color var(--color-grey-3, #959697)
                                        font-size var(--text-140, 14rem)
       .SliceHomeCategories-listItem    height 150px; will-change: height
       .SliceHomeCategories-listItemHover clip-path: inset(0 0 100% round 10px)
   Their root is FLUID (font-size: calc(1000/1440 * 1vw)), so 1rem = 10px at 1440
   and --text-140 is literally 140px. The user's measured 140 / rgb(149,150,151) /
   rgb(242,242,242) are exactly --text-140 / --color-grey-3 / --color-offwhite-2.

   TYPE IS SCALED DOWN TO OUR CONTAINER, NOT COPIED AT 140 (user instruction). The
   node already did the scaling: 60px on a 1093px container against their 140px on
   a full-bleed one. 60 is the node's number, not an interpolation of mine.

   ── MEDIUM (2026-07-10 lock) ─────────────────────────────────────────────────
   GSAP tweens on pointer enter/leave driving a CLIP-PATH REVEAL plus a HEIGHT
   tween. Not opacity, not a CSS transition.
   WHY GSAP WHEN THE REFERENCE HAS NONE: pellmell.fr loads no GSAP — grepped live,
   `lenis` x10, `gsap` x0. Its Categories hover is CSS transitions carrying
   GSAP-NAMED easing tokens (--ease-power3-out), and its scroll motion is a
   JS-written custom property (--px-progress) read by CSS transforms. Lenis does
   the SCROLL SETTLE ONLY — the 2026-08-25 lock's point, restated: their page
   settles because the transport settles, not because anything animates.
   So the MECHANISM is theirs (height + clip reveal) and the TOOLCHAIN is ours:
   js/pellmell-intro-recent.js already tweens hover with GSAP on this site, and the
   Matey hero already runs a clip-path reveal. Matching the portfolio's motion
   character is the instruction; matching their bundle is not.
   ========================================================================= */

/* Bricolage Grotesque — HEADINGS. Already in the project, variable on all three
   axes the nodes pin (opsz 12-96, wght 200-800, wdth 75-100). */
/* --ab-cat-h CARRIES A calc() AND IS READ BY JS, SO IT MUST BE REGISTERED.
   js/about.js does parseFloat(getComputedStyle(...).getPropertyValue('--ab-cat-h')).
   An UNREGISTERED custom property returns its calc() as an unevaluated STRING and
   parseFloat gives NaN — the exact trap Docs/RULES.md records against --gap-3. The
   registration makes it compute to a real length before JS ever sees it. */
@property --ab-cat-h {
  syntax: "<length>";
  inherits: true;
  initial-value: 107.142857px;      /* 100 * 150/140, the derived default */
}

@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 200 800;
  font-stretch: 75% 100%;
  font-display: swap;
  src: url('../assets/fonts/bricolage-grotesque-latin.woff2') format('woff2');
}
/* Inter — BODY (user instruction, 2026-09-01). *** THIS DEPARTS FROM BOTH NODES,
   WHICH SET BODY IN BRICOLAGE LIGHT. *** The instruction is explicit ("Bricolage
   Grotesque for headings, Inter for body") and is the site's own body face, so it
   wins over the node here. Flagged rather than silently resolved. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../assets/fonts/inter-latin.woff2') format('woff2');
}

:root {
  --ab-black:  #000000;
  --ab-ink:    #191919;        /* the expanded panel */
  --ab-role:   #5f5f62;        /* "Sr. Product Designer" (node) */
  --ab-cat-bg: #f2f2f2;        /* pellmell --color-offwhite-2 = rgb(242,242,242) */
  /* THE SITE PINK, RESTATED HERE BECAUSE THE TOKEN DOES NOT REACH THIS PAGE.
     --pi-pink lives on :root in css/pellmell-intro.css, and about.html loads ONE sheet
     (css/about.css) -- it does not load the intro sheet at all, so var(--pi-pink) would
     resolve to nothing here. This is the same literal, deliberately duplicated, and it is
     LOGGED in Docs/TODO.md rather than hidden: the real fix is a shared token sheet both
     pages link, which is a separate pass. If #FF3567 ever moves, it moves in two places. */
  --ab-pink: #FF3567;          /* === --pi-pink, css/pellmell-intro.css */
  --ab-cat-fg: #959697;        /* pellmell --color-grey-3   = rgb(149,150,151) */
  /* THE PINK IS THE DIGITS, NOT THE PILL (user, 2026-09-01). The pill reverts to
     the node's white fill; #FF3567 — the site's own red, the same value the
     portfolio's .ht-recent-sep bullet spends — moves to the numeral.
     *** CONTRAST FAILS AA AND THAT IS FLAGGED, NOT FIXED. *** The digits are 12px at
     weight 300, so AA wants 4.5:1, and #FF3567 on #ffffff measures 3.53:1. It is the
     same 3.53 the white-on-red arrangement gave — inverting a pair does not change
     its ratio, which is why the previous arrangement kept BLACK digits.
     Nearest passing variants of the same hue, if this should be corrected:
         #D92D58  4.70:1   #CC2A52  5.22:1   #BF284D  5.79:1
     Left at #FF3567 because it is the site's exact red and the instruction was
     explicit; changing the hue to pass is a design call, not mine. */
  --ab-num-fg: #FF3567;
  --ab-ground: #ffffff;

  --ab-shell-w:    1093px;     /* node 2501:2158 */
  --ab-shell-top:  68px;
  --ab-gap-top:    64px;       /* header block -> content */
  --ab-gap-content:32px;
  --ab-gap-about:  16px;
  --ab-gap-work:   24px;

  --ab-name-size:  90px;       /* node 2500:2082, Bricolage Bold */
  /* BYLINE — SUBORDINATED (user, 2026-09-01: "lighter weight and smaller … it
     should sit clearly subordinate to the name", values my call). Was the node's
     40px Regular 400.
     NEITHER NUMBER IS INVENTED — both are steps this page already spends:
       24px  == --ab-h1-size and --ab-body-size
       300   == the body weight (and Bricolage is variable 200-800, so it is a real
                instance, not synthesised)
     WHY 24 AND NOT 32: the ratio against the 90px name is what carries the
     subordination. 40 gave 1:2.25 and 32 (--ab-h2-size) only reaches 1:2.8, which
     still reads as a second title. 24 gives 1:3.75 and the line reads as a caption
     to the name, which is what was asked for.
     COLOUR IS UNCHANGED at the node's #5f5f62 — the instruction was weight and
     size; making it paler too would have been a third change nobody asked for. */
  /* node 2501:2166, re-read 2026-09-01: 40 -> 32. Face, weight and colour are
     unchanged (Bricolage Regular 400, #5f5f62, leading 28) — only the size moved.
     History on this line: 40 (node) -> 24 Light (CC's subordination call) -> 40
     (node re-read, overruling that call) -> 32 (node again). It is the node's
     number every time except the 24; do not re-derive it. */
  --ab-role-size:  32px;
  /* SECTION TITLES AND BODY ARE NOW THE SAME SIZE (user, 2026-09-01: "Body 16px /
     Section titles 16px, heavier weight"). Was 24 (h1) / 32 (h2) / 24 (body).
     At equal size the WEIGHT is the only separator, so it has to be a real step:
     700 against the body's 300. "About" was Medium 500 and gets genuinely heavier;
     the h2 was already 700 and only changes size.
     Leading is not carried over flat — 40px on 16px type would be 2.5. Body keeps
     the node's ~1.667 ratio (16 x 1.667 = 26.67 -> 26) and titles take 24 (1.5),
     tighter than body as a title should be. */
  --ab-h1-size:    16px;
  /* THE h2 — "This site" as of 2026-09-01, "The fifth project" before that. The
     node (2501:2182) reads 48px / lh 28.
     *** THIS BREAKS THE "SECTION TITLES ARE ONE SIZE" MODEL. *** The user's earlier
     brief set BOTH section titles to 16 at a heavier weight, and h1 "About" still
     holds that. The node has now moved this one alone to 48, so the two titles are
     no longer a pair and h2 needs its own leading as well as its own size — 28 is
     the node's, and it is TIGHTER THAN THE TYPE (28 on 48), which is this design's
     habit on display type: the 60px category labels run lh 28 too.
     The equal-space fix above still holds: that arithmetic is the title wrapper's
     padding plus the two gaps (10+24 either side), none of which is the text's
     height, so 34/34 survives the size change. */
  /* MATCHED TO THE BYLINE (user, 2026-09-01) — h2 spends --ab-role-size rather than
     a number of its own, so the two cannot drift apart if the byline moves again.
     That byline has already moved four times today (40 -> 24 -> 40 -> 32), which is
     exactly why this is a reference and not a copied 32.
     SUPERSEDES the node's 48 (2501:2182), read minutes earlier. */
  --ab-h2-size:    var(--ab-role-size);
  --ab-h2-lh:      28px;
  --ab-title-lh:   24px;       /* h1 + the expanded panel title */
  --ab-title-wt:   700;
  --ab-body-size:  16px;
  --ab-body-lh:    26px;
  /* ── THE LIST DERIVES FROM ONE NUMBER ────────────────────────────────────
     User, 2026-09-01: "Keep the ratio, not the absolute values — 150px row against
     140px type is 1.07. My labels are smaller, so scale the row height with them."
     The same reasoning governs the paddings, which were measured on the same
     component and are absolute px against THEIR 140px type.

     DEVTOOLS, pellmell.fr .SliceHomeCategories-list (user):
         listPadding  118 / 110 / 200      (top / inline / bottom)
         itemHeights  150
         gaps         0        <- rows BUTT TOGETHER; this is the "tighter" list
         itemMargin 0, itemPadding 0, listGap normal
     Their type on that component is 140, so as ratios: row 1.0714, pad-t 0.8429,
     pad-x 0.7857, pad-b 1.4286.

     WHY RATIOS AND NOT THE MEASURED PX. Their card is far wider than our 1093 shell,
     so 110px of inline padding is a small fraction there and 20% of the width here.
     Spending the absolute number would have cut our inner width to 873 and forced
     the type down anyway — the ratio does the same job without the collision.

     --ab-cat-size IS THE ONLY FREE NUMBER. Everything else re-solves from it, so
     changing the type re-proportions the whole list. 100 is chosen so the longest
     label clears with room: at 100 the inner width is 1093 - 157.1 = 935.9 and
     "Why it matters" runs 693.7, i.e. 74% — against 97% at the node's 140, which is
     what wrapped. */
  --ab-cat-size:   100px;
  --ab-cat-h:      calc(var(--ab-cat-size) * 150 / 140);       /* the 1.0714 ratio */
  --ab-cat-gap:    0px;                                        /* measured: 0 */
  --ab-cat-pad-t:  calc(var(--ab-cat-size) * 118 / 140);
  --ab-cat-pad-x:  calc(var(--ab-cat-size) * 110 / 140);
  --ab-cat-pad-b:  calc(var(--ab-cat-size) * 200 / 140);
  --ab-panel-title: 16px;

  /* the portfolio's --mark-w / --mark-h, not the node's 29.584 x 54.976 */
  --ab-mark-w: 22.627px;
  --ab-mark-h: 42.047px;

  /* Motion. power3.out is the shared character: pellmell's Categories transitions
     are .5s/.7s var(--ease-power3-out), and this project's EF fold is 0.70s
     power3.out. 0.6 sits between the reference's two. */
  --ab-ease: power3.out;
}

html, body { margin: 0; padding: 0; }
html { scrollbar-gutter: stable; }
body {
  position: relative;                /* containing block for the page-level mark */
  background: var(--ab-ground);
  color: var(--ab-black);
  font-family: 'Inter', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}
/* Headings take Bricolage with the nodes' axis pinning, set once. */
.ab-name, .ab-role, .ab-h1, .ab-h2, .ab-cat__label, .ab-cat__title, .ab-cat__num {
  font-family: 'Bricolage Grotesque', 'Inter', system-ui, sans-serif;
  font-variation-settings: "opsz" 14, "wdth" 100;
}

/* ---- ENTRANCE ------------------------------------------------------------
   .ab-anim is set on <html> before first paint (see the head of about.html) and is
   removed by js/about.js once it has written the from-state as inline styles. The
   handover order matters: class hides -> inline hides -> class removed, so there is
   never a frame where the element is unstyled and visible.
   Under prefers-reduced-motion the class is never added, so none of this applies and
   the page simply renders. */
/* .ab-name is NOT in this list. Its reveal is a MASK, not a fade: the chars start
   below their word's clip and slide up, so the element is already visually empty at
   rest and hiding it would only make the first frame blank twice over. Everything
   else here does fade, so it must be hidden before first paint. */
.ab-anim .ab-role,
.ab-anim .ab-h1,
.ab-anim .ab-bio p,
.ab-anim .ab-lede,
.ab-anim .ab-h2,
.ab-anim .ab-cat,
.ab-anim .ab-mark { opacity: 0; }

/* Character spans injected by the splitter in js/about.js. showLogo has no word
   grouping — the CONTAINER masks (see .ab-name's overflow below) and letters drop in
   from outside it. inline-block is load-bearing: a transform does nothing to an
   inline box. */
.ab-char { display: inline-block; will-change: transform; }

/* ---- SHELL — CENTRED at 1093 (the node uses left-1/2 + -translate-x-1/2) --- */
.ab-shell {
  /* EQUAL SPACE ABOVE AND BELOW THE h2 ("This site") (user, 2026-09-01).
     The h2 sits in .ab-work__title, which carries `padding: 10px`, and .ab-work
     spends --ab-gap-work (24) BELOW it. The shell had no gap, so the two sides read
     10 above against 34 below — the node's own asymmetry (2501:2160 sets no gap).
     Spending the SAME token on the shell makes them equal by construction rather
     than by a matched pair of numbers that could drift:
         above = shell gap 24 + title padding-top 10    = 34
         below = title padding-bottom 10 + work gap 24  = 34
     Only two children sit in this shell (.ab-top and .ab-work), so this adds
     exactly one gap and nothing else moves. */
  gap: var(--ab-gap-work);
  width: var(--ab-shell-w);
  max-width: calc(100% - 48px);
  margin-inline: auto;
  padding-top: var(--ab-shell-top);
  padding-bottom: 120px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.ab-top { display: flex; flex-direction: column; gap: var(--ab-gap-top); width: 100%; }

/* ---- MASTHEAD ------------------------------------------------------------
   STICKY IS DROPPED. The node stacks a 90px name over a 40px role inside the
   scrolling column, and a ~150px sticky bar would sit over the Categories card
   for the whole page. Sticky was right for an 18px label and is not right for
   this masthead; it is removed deliberately rather than carried forward. */
/* HEADER — node 2501-2159 as re-read 2026-09-01. A centred column: the BYLINE
   first, full-width and centred; the NAME second, right-aligned. No gap between
   them (the node's column sets none). This supersedes both the mark-on-the-name-
   line alignment and the 24px/Light byline — see about.html. */
.ab-header { display: flex; flex-direction: column; align-items: center; width: 100%; }
.ab-name {
  margin: 0;
  /* CENTRED (user, 2026-09-01: "Centre 'Shoon Browne' and 'Sr. Product Designer'
     horizontally in the page"). SUPERSEDES THE NODE, whose 2503:2208 is
     `justify-end` — that right-alignment is what pushed the block to the edge.
     Both lines are now centred on the same axis: .ab-header is `align-items:
     center` and the shell is `margin-inline: auto`, so centring here centres in the
     PAGE, not merely within the header. */
  align-self: center;
  text-align: center;
  font-weight: 700;
  font-size: var(--ab-name-size); line-height: normal; color: var(--ab-black);
  /* THE MASK. pellmell's showLogo sets overflow:hidden on the wordmark container and
     drops each letter in from far above it, so the letters are invisible until they
     enter the box. Without this they simply fly down the page. */
  overflow: hidden;
}
.ab-role {
  margin: 0;
  width: 100%; text-align: center;   /* 2501:2163/2164 already centre this one */
  font-weight: 400;                  /* node: Bricolage Regular */
  font-size: var(--ab-role-size); line-height: 28px; color: var(--ab-role);
}
/* THE MARK TAKES THE PORTFOLIO'S OWN POSITION (user, 2026-09-01: "same position as
   the portfolio homepage (top right)"). This supersedes BOTH the earlier
   align-to-the-name-line call AND the node, which puts it at frame left 1377 /
   top 58 (2501:2167) — a looser inset than the portfolio's.

   VALUES ARE THE PORTFOLIO'S, NOT RE-DERIVED. css/pellmell-intro.css .hero-mark:
       --mark-w 22.627  --mark-h 42.047  --mark-inset 14
       top   = --mark-inset - --mark-ink-top   (0.059)
       right = --mark-inset - --mark-ink-right (0.373)
   The two ink corrections are the whole point: the asset carries trailing viewBox
   space, so positioning the BOX at 14 lands the INK at 14.06 / 14.37. Spending them
   here is what makes the mark occupy the SAME PIXEL on both pages, which is what
   "same position" has to mean if it is not to appear to jump on navigation.

   THE BOX SIZE COMES WITH IT (22.627 x 42.047, not the node's 29.584 x 54.976).
   The ink corrections above are absolute px derived for THAT box; keeping the
   node's larger box would need them rescaled by 1.30746 and would still paint a
   visibly bigger mark than the portfolio's. Same position and a different size is
   not "the same". Flagged: if only the inset was meant, restore --ab-mark-w/h to
   the node's pair and scale the two corrections.

   `absolute`, NOT `fixed`. The portfolio's mark is effectively fixed only because
   its .hero is `position: fixed` — it is an absolute box inside a pinned stage.
   About is an ordinary scrolling document, so absolute reproduces the node's
   frame-level box and lets it scroll away rather than hovering over the copy. */
.ab-mark {
  position: absolute;
  top:   calc(14px - 0.059px);
  right: calc(14px - 0.373px);
  display: block; width: var(--ab-mark-w); height: var(--ab-mark-h);
  z-index: 20;
}
.ab-mark img { display: block; width: 100%; height: 100%; filter: brightness(0); }

/* ---- CLOSE CONTROL, TOP LEFT ----------------------------------------------------
   INSET IS 14px, MIRRORING .ab-mark (user, 2026-09-02). It was 48/31, borrowed from the
   overlay's padding, which left the two top corners visibly unmatched. 14 is the SITE's
   mark inset, not this page's invention: --mark-inset in css/pellmell-intro.css drives
   .hero-mark on the homepage and .ab-mark restates it here, so two of the three places
   the mark appears already used it. The overlay's 48/31 is documented in its own sheet as
   "named as new because it does not derive" — a free layout number — so it is the one
   that moved.
   .ab-mark IS UNCHANGED. It already resolves to 14px of INK inset: its 0.059/0.373
   subtractions compensate for the logo's transparent edge. The close asset needs no such
   correction — its ink fills its box (one path, a filled disc with the X knocked out),
   measured, so a plain 14 puts its ink at 14 too. The two therefore differ by at most
   0.373px, which is below a device pixel.
   `absolute`, NOT `fixed`, matching .ab-mark: it scrolls away with the page, which is
   what that element's own note says the node calls for.
   MEASURED CLEAR: at 1440 the box spans x 14-54, y 14-54. .ab-shell starts at x 166, so
   there is 112px of clearance, and elementsFromPoint across the box returns nothing but
   the root. .ab-mark is at the opposite corner. */
.ab-close {
  position: absolute;
  top: 14px;
  left: 14px;
  display: block; width: 40px; height: 40px;
  z-index: 20;                 /* the mark's layer */
}
.ab-close img { display: block; width: 100%; height: 100%; }

/* ---- ABOUT BLOCK ---------------------------------------------------------- */
.ab-content { display: flex; flex-direction: column; gap: var(--ab-gap-content); width: 100%; }
.ab-about   { display: flex; flex-direction: column; gap: var(--ab-gap-about); width: 100%; }
.ab-h1 { margin: 0; font-weight: var(--ab-title-wt); font-size: var(--ab-h1-size); line-height: var(--ab-title-lh); color: var(--ab-black); }
.ab-bio p {
  margin: 0; font-weight: 300;
  font-size: var(--ab-body-size); line-height: var(--ab-body-lh); color: var(--ab-black);
}
/* The node puts a blank 40px line between the bio's two paragraphs (a
   zero-width-space paragraph); reproduced as a margin rather than an empty node. */
.ab-bio p + p { margin-top: var(--ab-body-lh); }

/* The lede — restored into the nodes' empty Title placeholder (see about.html).
   Body size, so it reads as the last line of the About block rather than as a
   heading for the Categories card below it. */
.ab-lede {
  margin: 0; font-weight: 300;
  font-size: var(--ab-body-size); line-height: var(--ab-body-lh); color: var(--ab-black);
}

/* ---- WORK / CATEGORIES ---------------------------------------------------- */
.ab-work { display: flex; flex-direction: column; gap: var(--ab-gap-work); width: 100%; }
.ab-work__title { display: flex; align-items: center; justify-content: center; padding: 10px; width: 100%; }
.ab-h2 { margin: 0; font-weight: var(--ab-title-wt); font-size: var(--ab-h2-size); line-height: var(--ab-h2-lh); color: var(--ab-black); }

/* The card. overflow:hidden matches the reference's list and keeps the expanding
   panel's corners inside the 8px radius while it opens. */
.ab-cats {
  margin: 0; list-style: none;
  padding: var(--ab-cat-pad-t) var(--ab-cat-pad-x) var(--ab-cat-pad-b);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--ab-cat-gap);
  width: 100%; box-sizing: border-box;
  background: var(--ab-cat-bg);
  border-radius: 8px;
  overflow: hidden;
}
/* One row. `contain: layout` + `will-change: height` are the reference's own hints
   on .SliceHomeCategories-listItem — the height is what animates. */
.ab-cat {
  position: relative;
  width: 100%;
  height: var(--ab-cat-h);
  contain: layout;
  will-change: height;
}
/* COLLAPSED — the 60px grey heading, centred. A <button> because it drives an
   expansion; the UA reset is here, and it fills the row so the whole band is the
   target rather than just the glyphs. */
/* ---- INTERACTIVE AFFORDANCE (2026-09-02) ---------------------------------------
   Opening became click-only, and nothing then said the rows were interactive: the label
   sat at one flat grey whatever the pointer did. This is the signal.

   CONTRAST, MEASURED AGAINST THE REAL BACKGROUND. The brief quoted 3.53:1 for #FF3567 on
   WHITE, but this list is not on white -- it is on #f2f2f2, which is darker, so the true
   figure is lower:
       #FF3567 on #ffffff   3.53:1
       #FF3567 on #f2f2f2   3.15:1   <- the one that applies
   WCAG 1.4.3 asks 4.5:1 for normal text and 3:1 for LARGE text (>=24px, or >=18.66px
   bold). Every size this label takes is large: 100px, and 34px under the 1140 breakpoint. So 3.15:1 PASSES -- but on
   5% of headroom, which is why the number is written down rather than left implied. The
   STYLEGUIDE's passing variant #D92D58 is NOT needed here (it measures 4.20:1 on this
   same #f2f2f2, so it would not reach 4.5 either -- it only clears normal-text AA on
   white).

   FOCUS MATCHES HOVER, and is not carried by colour alone: nothing in this sheet sets
   `outline`, so the UA focus ring is intact and the pink is an addition to it, not a
   replacement. :focus-visible rather than :focus so a mouse click does not leave the
   label stuck pink after the pointer has gone.

   The transition is a plain colour fade -- 0.2s, FIXED, not derived: a pointer cue has to
   feel immediate, and the sheet's other durations (0.6s panel) would lag the cursor. The
   curve is the site's power3.out character as a bezier. */
.ab-cat__head {
  appearance: none; -webkit-appearance: none;
  border: 0; padding: 0; margin: 0; background: none; cursor: pointer;
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  width: 100%;
  color: var(--ab-cat-fg);
  text-align: center;
  transition: color 0.2s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.ab-cat__head:hover,
.ab-cat__head:focus-visible { color: var(--ab-pink); }
.ab-cat__label {
  /* NEVER WRAP. Under line-height 28px at this size a second line does not sit
     below the first — it lands ON it. That was the "wraps and overlaps" report.
     With this guard, outgrowing the row degrades to a clean clip instead. */
  white-space: nowrap;

  /* ── WEIGHT: 600 ─────────────────────────────────────────────────────────
     Instruction (user, 2026-09-01): "Bricolage reads lighter than their Season face
     at the same number, so match apparent weight rather than the 400 figure." This
     DELIBERATELY SUPERSEDES the DevTools 400 read off pellmell's own component. That
     number was correct; the point is that a weight figure is only comparable WITHIN
     one typeface — 400 in Season and 400 in Bricolage are not the same colour on the
     page. The reference is the look, not the integer.

     THE LADDER, so nobody re-walks it:
         400  read light   (the measured figure)
         600  current      <- trying this before 500, per instruction
         500  next         if 600 is still too heavy
     Bricolage is variable 200-800, so 550 and 650 are real instances too.

     THE FIT NEVER CONSTRAINS THIS DIAL. At 100px against the 935.9 inner width,
     "Why it matters" runs 693.7 at weight 400 and 723.7 at 700 — the whole axis
     costs 3 percentage points. Choose on colour alone.

     THIS ELEMENT IS NOT THE PAGE'S 700s. Those are --ab-title-wt (.ab-h1, .ab-h2,
     .ab-cat__title) and .ab-name. A "too heavy" report about the masthead or the
     expanded panel's heading is one of those, not this.

     The DevTools reading still stands for its other two values: #959697 here and
     #F2F2F2 on the card, both already correct. */
  font-weight: 600;

  font-size: var(--ab-cat-size);
  line-height: 28px;                         /* the node's own tight leading */
  color: inherit;
}
/* EXPANDED — the dark panel. Starts fully clipped from the bottom, exactly the
   reference's `clip-path: inset(0 0 100% round 10px)`; GSAP opens the inset.
   pointer-events is off while shut so a clipped panel can never take the pointer
   away from the row underneath it. */
.ab-cat__panel {
  /* top/left/width, NOT inset:0 — the panel's height must be CONTENT-DRIVEN so the
     row can tween its own height to it. inset:0 would tie the panel to the collapsed
     139.777 and there would be nothing to measure. clip-path does not affect layout,
     so offsetHeight reads the true natural height even while shut. */
  position: absolute; top: 0; left: 0; width: 100%;
  box-sizing: border-box;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 24px;
  padding: 24px;
  background: var(--ab-ink);
  border-radius: 8px;
  /* SHUT STATE — clipped to a CENTRE LINE, not to the bottom edge. This is the
     reference's own start value (inset(50% 0% 50% 0% round 10px), pellmell's
     SliceHomeCategories enter handler) and it is the single biggest reason their
     reveal reads as a panel ARRIVING rather than a mask sliding down. A bottom-up
     wipe — what this used to be — is what "clippy" was describing. */
  clip-path: inset(50% 0 50% 0 round 8px);
  opacity: 0;
  transform: scale(0.8);
  transform-origin: center;
  pointer-events: none;
  will-change: clip-path, transform, opacity;
}
.ab-cat.is-open .ab-cat__panel { pointer-events: auto; }
.ab-cat__row { display: flex; align-items: center; gap: 12px; width: 100%; }
.ab-cat__title {
  margin: 0; flex: 1 0 0; min-width: 0;
  font-weight: var(--ab-title-wt); font-size: var(--ab-panel-title); line-height: var(--ab-title-lh);
  color: #ffffff; text-align: right;
}
/* The numbered pill — node 2501:2198/2199. Fixed 40px wide, pill radius. */
.ab-cat__num {
  flex: none;
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 26.707px;
  background: #ffffff; border-radius: 48px;   /* the node's fill, restored */
  font-weight: 300; font-size: 12px; line-height: 1; color: var(--ab-num-fg);
}
/* The prose inside the panel. Inter, per the user instruction — the nodes set
   Bricolage Light here and the instruction overrides them. */
.ab-cat__prose { width: 100%; color: #ffffff; }
.ab-cat__prose p, .ab-cat__prose li {
  margin: 0;
  font-weight: 300; font-size: var(--ab-body-size); line-height: var(--ab-body-lh);
  color: #ffffff;
}
.ab-cat__prose p + p, .ab-cat__prose ul { margin-top: var(--ab-body-lh); }
.ab-cat__prose ul { padding-left: 24px; list-style: disc; }
.ab-cat__prose li + li { margin-top: 12px; }
.ab-cat__prose b { font-weight: 500; }
/* The rule lines keep the treatment locked earlier today — grey, italic, indent
   only, no bar (user exception). White-on-dark here, so the grey is lifted to a
   translucent white rather than #828282, which would vanish on #191919. */
.ab-cat__rule {
  display: block;
  margin-top: 12px; padding-left: 26px;
  font-style: italic;
  font-size: var(--ab-body-size); line-height: var(--ab-body-lh);
  color: rgba(255, 255, 255, 0.66);
}

/* ---- NO-JS / REDUCED MOTION ----------------------------------------------
   The prose must not be unreachable if the tween never runs. With no JS the
   panels stay shut and the headings stay readable; `.ab-cat__head` is a real
   button, so Enter and Space still open a row via the same click handler. (Focus no
   longer opens a row on its own -- the trigger became click-only on 2026-09-02.) */
@media (prefers-reduced-motion: reduce) {
  .ab-cat__panel { transition: none; }
  /* The colour still CHANGES -- the affordance must survive reduced motion, it is
     information, not decoration. Only the fade between the two states goes. */
  .ab-cat__head  { transition: none; }
}

/* ---- NARROW VIEWPORTS — not in the nodes (single 1440 frame) --------------- */
@media (max-width: 1140px) {
  :root {
    --ab-name-size: 56px; --ab-role-size: 28px;
    --ab-cat-size: 34px;
    --ab-body-size: 15px; --ab-body-lh: 24px;
  }
}
