/* =========================================================================
   ABOUT v2 — Figma LrUdy5WZmi3n2gkRz9SEC2, node 2864:1702 (1440x894).
   Stand-alone: only about-v2.html loads this file, and this file edits nothing
   that any other page reads.

   EVERY NUMBER BELOW IS THE NODE'S, and is named here once so the layout has a
   single source rather than a scatter of literals:

       mark          2864:1714/1715   left 48, top 75, 45.254 x 84.095
       right column  2864:1742        left 807.41, top 74.82, width 527.402,
                                      column gap 12
       title         2864:1744        40px, tracking 3.2, caps, #141d1f
       bio           2864:1745        16px / 24px line, tracking 1.28, #141d1f
       "Shoon"       2864:1708        left 192.5, 360px, centre at y 673
       "BROWNE"      2864:1711        left 192.5 + 10 pad, top 808, 40px / 3.2
       surface       grey/surface     #f9f9f9

   THE COLUMN'S RIGHT INSET IS DERIVED, NOT PICKED: the node puts the column at
   807.41 and it is 527.402 wide, so its right edge is 1334.812 and the gap to
   the 1440 frame edge is 105.188. Spending that as a PERCENTAGE right margin
   reproduces left 807.41 EXACTLY at 1440 while letting the column slide left as
   the viewport narrows — which a hardcoded left 807.41 would not do.
   ========================================================================= */

/* Self-hosted Inter, the same file and the same declaration index.html and
   about.html use. Declared here rather than imported so this page carries no
   dependency on a shared sheet that other pages are free to change. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;              /* variable weight axis */
  font-display: swap;
  src: url('../assets/fonts/inter-latin.woff2') format('woff2');
}

:root {
  --av-surface:    #f9f9f9;          /* grey/surface */
  --av-ink:        #141d1f;

  /* THE GUTTER IS NOW THE SHARED PAGE MARGIN (user, 2026-10-11). It was 48/16
     from the node; it is --page-margin from css/page-margins.css, which both
     this page and the Recent work overlay load. The name is kept because six
     rules spend it, and keeping it means one line changes rather than six. */
  --av-gutter:     var(--page-margin);
  /* TOP PADDING LADDER (user, 2026-10-11): 32 desktop / 24 tablet / 16 mobile.
     This supersedes the node's 75px (2864:1714 top) — flagged, not dropped
     quietly. The two narrow steps are set in the blocks at the foot of the file,
     which must stay in max-width order so the narrower one wins. */
  /* ---- THE RECENT WORK OVERLAY IS THE REFERENCE (user, 2026-10-11) ---------
     SUPERSEDES the previous pass, which placed this block on the corner axis so
     the title lined up with the close button and the mark. That instruction is
     withdrawn: the title and bio now move DOWN, as one unit with their internal
     spacing untouched, until the About title's CAP TOP equals the cap top of
     the "Recent work" title in the overlay on index.html.

     ONE TOKEN carries that position — --av-rw-cap-top below — and --av-top is
     solved from it. Nothing else about the block changes.

     WHY THE TOKEN IS DEFINED HERE RATHER THAN SHARED LITERALLY: the overlay's
     sheet is css/pellmell-intro-recentwork.css, which index.html loads and this
     page does not; the two pages have no stylesheet in common, and putting one
     there would mean editing index.html, which is out of scope. The overlay is
     NOT modified — it is the reference. The token is read off its rules and
     carries them by name so the dependency is visible. */

  /* THE OVERLAY TITLE'S CAP TOP, from css/pellmell-intro-recentwork.css:
         .rw-overlay    padding: 48px 31px      -> 32px 20px at max-width 1180
         .rw-title-row  padding: 10px
         .rw-title      32px / 28px             -> 24px / 28px at the same step
     and its cap sits centred in its own line box. That last step is MEASURED in
     the running overlay rather than assumed: cap centre 71.94 against a line-box
     centre of 72.00 at 1440, and 56.08 against 56.00 at 768 — 0.08px at the
     worst. So the cap top is
         overlay padding-top + row padding + (line box − cap height) / 2
     with Bricolage Grotesque's cap height measured off the face at 0.66em
     (21.12 at 32px, 15.84 at 24px — the same ratio at both, as it must be).
     The narrow value is set in the max-width:1180 block below, which is the
     overlay's OWN breakpoint, not this sheet's 767 one. */
  --av-rw-cap-top: calc(var(--page-top) + 10px + (28px - 0.66 * 32px) / 2);

  /* --av-top is that cap top, less everything between the top of .av-top's
     content box and the title's cap:
         the title's own padding-top, and
         0.24745em of lead-in — Inter's normal-line-height ascent (0.975em)
         less the cap height of P (0.72755em), BOTH MEASURED in this page: the
         40px title's baseline lands 39.00px under its content box and canvas
         reports its P at 29.102px of ink.
     Solving for the row top rather than for the axis is the same move the last
     pass made; only the line it solves against has changed. */
  --av-top:        calc(var(--av-rw-cap-top) - var(--av-title-pad)
                        - 0.24745 * var(--av-title-size));
  /* The title's own 10px padding-top, named because .av-mark's baseline offset
     spends it too — the mark's placement would drift if this changed there. */
  --av-title-pad:  10px;

  /* The mark's box: Inter's cap ratio applied to the title size, then the node's
     45.254/84.095 proportion. Tokens rather than em because .av-mark-slot has to
     reserve the same box from outside the mark's em context. */
  /* ---- THE CORNER CONTROLS ARE about.html's, COPIED EXACTLY ----------------
     Values lifted from css/about.css (.ab-close, .ab-mark and the --ab-mark-*
     tokens it spends); that sheet is not modified and is not loaded here.

         inset   14px        --mark-inset, which about.css notes it takes from
                             --mark-inset in css/pellmell-intro.css, so the
                             homepage and About already share it
         mark    22.627 x 42.047   --ab-mark-w / --ab-mark-h
         close   40 x 40     the icon's own viewBox
         0.059 / 0.373       about.css's ink corrections: the logo asset has
                             transparent edges, so the BOX is offset by that much
                             to put 14px of INK in the corner. Carried verbatim —
                             dropping them would move the ink, which is the thing
                             the eye measures. */
  /* RETIRED 2026-10-11: the 14px corner is replaced by --page-top /
     --page-inset from css/page-margins.css. Kept as a comment, not a token, so
     nothing can quietly start spending it again. */
  /* --av-corner: 14px; */
  --av-close-size: 40px;
  --av-mark-w:     22.627px;
  --av-mark-h:     42.047px;
  /* THE CONTROLS ARE BACK AT THE LIVE PAGE'S CORNERS (user, 2026-10-11),
     restored from fe33594 — close at --av-corner, mark at --av-corner less its
     0.059 ink correction. The dependency is now INVERTED: the axis is read off
     the controls, and the text is placed to meet it.

     THE AXIS IS THE MIDPOINT OF THE TWO CONTROL CENTRES, because they do not
     share one. Both sit at 14px from the top, but the close is 40 tall and the
     mark 42.047, so their centres are 34.00 and 34.96 — 0.96px apart, which no
     single line can satisfy exactly. The midpoint puts the title within 0.48 of
     each instead of 0.00 of one and 0.96 of the other, and the whole spread
     stays inside the brief's 1px. */
  /* BOTH CONTROLS NOW SIT ON --page-top (user, 2026-10-11), not on the old
     14px corner. The 0.059 ink correction is kept: the logo asset has
     transparent edges, and dropping it would put the BOX on the line while
     leaving the INK 0.059px off it — which is the thing the eye measures.
     It is 6/100ths of a pixel, well inside the brief's +-1. */
  --av-logo-top:   calc(var(--page-top) - 0.059px);
  /* The axis is still the midpoint of the two control centres, because they
     still do not share one: both start at --page-top but the close is 40 tall
     and the mark 42.047. Only the term they are measured from has changed. */
  --av-axis:       calc(var(--page-top)
                        + (var(--av-close-size) + var(--av-mark-h)) / 4
                        - 0.0295px);
  /* Unit 2 now opens with the PARAGRAPH, not a heading — the node's INTRO
     (2871:1757) drops it — so the landing aligns the paragraph's first line to
     the corner axis. Its cap CENTRE, so it sits on the same axis the controls
     do, and by the same derivation:
         (lh − 1.211111·fs)/2 + 0.600794·fs
     which is --av-axis's own 0.600794, applied at the paragraph's size. */
  --av-desc-size:  16px;             /* node 2871:1749 / 2880:1839 */
  --av-desc-lh:    24px;
  --av-desc-track: 1.28px;
  --av-desc-color: #000;             /* node 2871:1749's `text-black` */
  --av-desc-cap-c: calc((var(--av-desc-lh) - 1.211111 * var(--av-desc-size)) / 2
                        + 0.600794 * var(--av-desc-size));
  /* THE CAP TOP, not the cap centre. Same half-leading term, but the second one
     is ascent − cap rather than ascent − cap/2:
         0.969444 − 0.7373 = 0.232144   (Inter, measured off the face; the T of
                                         "The most recent example" is a flat cap)
     At 16/24 that is 2.3111 + 3.7143 = 6.0254px from the box top. */
  --av-desc-cap-t: calc((var(--av-desc-lh) - 1.211111 * var(--av-desc-size)) / 2
                        + 0.232144 * var(--av-desc-size));

  /* THE BOTTOM EDGE OF THE LOWER ICON. The two controls start on the same line
     but are not the same height, so "icon bottom" is the mark's, not the close
     button's: the close ends at --page-top + 40, the mark at --page-top +
     41.988 (42.047 tall, lifted 0.059 by its ink correction). Taking the lower
     of the two is what makes the clearance below hold for BOTH. */
  --av-icons-bottom: calc(var(--av-logo-top) + var(--av-mark-h));

  /* THE BASELINE'S DISTANCE TO THE BOTTOM OF ITS OWN LINE BOX — the mirror of
     --av-desc-cap-t above, and the other half of what it takes to measure a
     line by its INK rather than by its box:
         line height − (half-leading + ascent)
     At 16/24: 24 − (2.31111 + 15.51110) = 6.17779px.
     Inter's ascent is 0.969444, read off the face. */
  --av-desc-base-b: calc(var(--av-desc-lh)
                         - ((var(--av-desc-lh) - 1.211111 * var(--av-desc-size)) / 2
                            + 0.969444 * var(--av-desc-size)));

  /* ---- THE FOOTER'S ONE GAP (user, 2026-10-11) -------------------------
     The space above the links and the space below them are the SAME token, so
     they cannot drift. It is an INK measurement at both ends: rule to the
     glyphs' cap top above, glyphs' baseline to the document end below.

     ITS VALUE IS THE GAP THE PAGE ALREADY HAD, not a new number. The node's
     spacing put the links' LINE BOX 28px under the rule (18 margin + 10
     padding); the cap top is --av-desc-cap-t further down again, so the gap the
     eye actually saw was 28 + 6.02541 = 34.02541. That is what both ends now
     spend, which is why the footer's top half looks unchanged and only the
     bottom half moves.

     THE 10px ROW PADDING IS GONE, and it cost nothing: the 44x44 tap target is
     on .av-foot__link::after, on the anchor itself, not on this row. */
  --av-foot-gap:   calc(28px + var(--av-desc-cap-t));

  /* THE NODE'S COLUMN IS 527.402 AND STARTS AT 807.41. IT IS WIDENED TO 615 AND
     MOVED LEFT (user, 2026-10-11), and the two are the same change: the approved
     title is longer than the node's "UX PRODUCT DESIGNER" and must hold one line
     at >=1024. MEASURED at the node's own type spec (Inter 700 / 40px / 3.2px),
     "Principal Product Designer" sets in 604.7px, so 615 holds it with 10px to
     spare — and 615 is also what lands the column's left edge on 719.81 at 1440,
     i.e. the requested ~50% of the frame, WITHOUT touching the node's 105.188
     right inset. One number satisfies both halves of the brief.
     MEASURE: 615px is 62 characters per line at 16px/1.28px — inside the 60-75
     the brief asks for (560 would be 57, 720 would be 73). */
  /* 615 -> 660 (2026-10-11), FORCED BY THE MARGIN SYSTEM AND MEASURED.
     With the column's right edge on the right margin line and the mark's right
     edge on the SAME line, the two now share a vertical band -- the title's cap
     top is --page-top and the mark spans --page-top to +42.047. At 615 the
     title's 604.7px of ink ended 10.3px short of the column edge while the mark
     starts 22.627px short of it, so the last word ran 12.3px UNDER the mark at
     every width. 660 puts the ink's end 55.3px short of the line, a 32.7px
     channel beside the mark, and keeps the title on ONE line at >=1024.
     THE MEASURE SURVIVES: 660px at 16px/1.28px is ~66 characters, inside the
     60-75 the earlier brief set (615 was 62). */
  --av-col-w:      660px;            /* node 2864:1742 was 527.402; 615 until 2026-10-11 */
  --av-col-gap:    12px;

  --av-title-size: 40px;             /* node 2864:1744 */
  --av-track:      3.2px;
  --av-body-size:  16px;             /* node 2864:1745 */
  --av-body-lh:    24px;
  --av-body-track: 1.28px;

  /* The lockup's three node numbers — 360px, the 192.5 left and BROWNE's 10px
     inset — are NOT tokens here. They are ratios of --av-lock, declared on
     .av-wordmark itself so the unit has exactly one number to change. */
}

*, *::before, *::after { box-sizing: border-box; }

html { background: var(--av-surface); }

body {
  margin: 0;
  background: var(--av-surface);
  color: var(--av-ink);
  font-family: 'Inter', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ---- THE FRAME ----------------------------------------------------------
   The node is a fixed 1440x894. On screen the page is centred and capped at
   1440 so it cannot stretch past the composition, and is at least a viewport
   tall so the wordmark can sit at the bottom the way the node does. */
/* THE PAGE CARRIES NO HORIZONTAL PADDING, and that is load-bearing rather than
   tidy. The node's three left edges — mark 48, column 807.41, wordmark 192.5 —
   are measured from the FRAME edge, so each block takes its own inset from the
   page box. Had the page padded itself, every percentage below would resolve
   against the padded box and none of the three would land on its number. */
.av-page {
  max-width: 1440px;
  margin-inline: auto;
}

/* UNIT 1 — A FIXED FULL-VIEWPORT LAYER (user, 2026-10-11), the shape Be-ACK's
   sections have: 100dvh, stacked, revealed and hidden by opacity.

   dvh, NOT svh, and that supersedes the earlier svh call: dvh tracks the live
   viewport, so the layer is exactly as tall as what is actually visible and
   BROWNE stays flush with the real bottom edge whether the mobile browser's bars
   are showing or not. svh would have left a gap once they retracted.

   OPAQUE, because this layer COVERS unit 2 rather than sitting beside it. A
   transparent one would show the card through the bio.

   CENTRED BY PADDING, NOT BY A TRANSFORM. Above 1440 the composition has to stay
   in the middle, and `translateX(-50%)` is the obvious way — but a transformed
   ancestor becomes the containing block for a position:fixed descendant, which
   is the exact bug that dragged the logo down the page earlier. Symmetric
   padding does the same centring and creates no containing block. */
.av-screen {
  position: fixed;
  inset: 0;
  height: 100dvh;
  z-index: 2;
  padding-inline: max(0px, calc((100% - 1440px) / 2));
  background: var(--av-surface);
  display: flex;
  flex-direction: column;
}
/* ======================================================================
   FLOW MODE — THE PAGE GROWS INSTEAD OF OVERLAPPING (user, 2026-10-11).

   THE BUG THIS FIXES: unit 1 is a FIXED layer of exactly 100dvh with the
   lockup pinned to its bottom edge, so the title and bio can run past the
   lockup and there is nowhere for them to go — a fixed layer cannot scroll.
   MEASURED before the fix: the bio overlapped "Shoon" by 52.7px at 375x667
   and by 213.1px at 320x568. 390x844 and every larger size were clear.

   THE RULE, FROM THE BRIEF: BROWNE is flush at the bottom ONLY WHEN THE
   CONTENT FITS. When it does not, the page grows and scrolls and the lockup
   moves BELOW the bio. So there are two states, and which one applies is a
   question about CONTENT HEIGHT, which CSS cannot ask — the bio's height
   depends on how it wraps, which depends on width, so no (max-height) or
   (max-width) query can stand in for it without being wrong somewhere.
   js/about-v2.js measures it and sets .av-flow on <html>; this block is the
   other state.

   THE LANDING IS OFF IN THIS STATE, and that is deliberate rather than a
   casualty: the cross-fade exists to reveal unit 2 from under a fixed cover,
   and in flow mode there is no cover to fade — unit 2 simply follows unit 1
   down the document and the browser's own scroll reaches it. js/about-v2.js
   bails out of the fade for the same reason.
   ====================================================================== */
/* ======================================================================
   THE ICON BAND (user, 2026-10-11).

   The rest position above clears the icons, but SCROLLING PAST IT does not:
   the icons are fixed and everything else moves, so without this the card, the
   category rows and the footer would all pass through them. This is a solid
   band of the page's own background, full width, fixed, exactly as tall as the
   clearance the rest position spends — icon bottom + --page-margin — so what
   scrolls disappears UNDER it instead of colliding with the icons.

   IT IS NOT PRESENT AT THE TOP, and that is the whole subtlety. In the top
   state unit 1's title has its cap top at --page-top + ~13, well inside this
   band's height, so a band that was always on would cover the title. The same
   is true of flow mode's title at scroll 0. So js/about-v2.js adds .av-scrolled
   to <html> the moment scrollY leaves 0, and the band is keyed to that — at
   rest there is nothing to hide, because nothing is behind the icons yet.

   z-index 15: above everything that scrolls (unit 1 is 2, unit 2 is auto) and
   below the two controls, which stay on 20 and are not touched by any of this.
   pointer-events:none so it can never take a click from them.
   ====================================================================== */
.av-band {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: calc(var(--av-icons-bottom) + var(--page-margin));
  background: var(--av-surface);
  z-index: 15;
  pointer-events: none;
}
.av-scrolled .av-band { display: block; }

.av-flow .av-screen {
  position: relative;
  height: auto;
  min-height: 100dvh;
}
/* In flow mode there is no cover and no "rest position" -- unit 2 simply
   follows unit 1 down the document, so the offset above would read as a large
   dead gap rather than an alignment. One margin is the right separation here. */
.av-flow .av-site { margin-top: var(--page-margin); }
.av-flow .av-wordmark {
  /* Out of the corner and back into the flow, as the last flex child of
     .av-screen — which is what puts it under the bio instead of over it. */
  position: static;
  margin-top: 32px;
  margin-inline: var(--page-margin);
  padding-bottom: var(--page-margin);
}

/* Set by js/about-v2.js once the layer has faded out, so the card underneath is
   clickable. Mirrors Be-ACK clearing pointer-events on the panel it retires. */
.av-screen.is-gone { pointer-events: none; }

/* ---- TOP BLOCK ---------------------------------------------------------- */
.av-top {
  padding: var(--av-top) var(--page-margin) 0;
  display: flex;
  align-items: flex-start;
  gap: var(--av-gutter);
}

/* THE MARK IS SIZED OFF THE TITLE, IN em (user, 2026-10-11) — it no longer
   carries the node's fixed 45.254 x 84.095.

   font-size IS THE MECHANISM. The mark is not inside the title, so an `em` here
   would otherwise resolve against the inherited 16px body size. Setting its own
   font-size to the title token makes every em below mean "of the title's size",
   and the mark then tracks the title at every breakpoint with no second rule —
   the mobile block changes --av-title-size and the mark follows.

   WIDTH CARRIES THE NODE'S RATIO rather than a re-guessed number: the node's
   mark is 45.254 x 84.095, so width is height x 45.254/84.095 and the
   proportions are exact at any size.

   HEIGHT IS THE TITLE'S CAP HEIGHT (user, 2026-10-11), not its line height.
   0.7675em is Inter's ACTUAL cap ratio, measured off the face at both title
   sizes (30.70/40 and 21.49/28 both give 0.7675) — not the brief's "around
   0.7em", which would miss the cap line by 2.7px at a 40px title and break the
   very alignment the same instruction asks for.

   THAT MAKES THE ALIGNMENT EXACT, BY CONSTRUCTION. The title's baseline sits
   (padding-top + Inter's 0.96944em ascent) below the row top, so a mark of
   cap-height placed at (padding-top + ascent − cap-height) ends on the baseline;
   and because its height IS the cap height, its top necessarily lands on the cap
   line. Both halves of the brief hold at once at this size — at 1.2em they could
   not, which is why the previous pass could only satisfy one.

   Both ratios are Inter's own metrics (ascent 349/360 off the face), not
   estimates. Mobile overrides the offset — see the narrow block, where the grid
   centres the mark on the title instead. */
/* THE MARK — about.css's .ab-mark, verbatim geometry. FIXED rather than its
   `absolute`: the live sheet notes "absolute, NOT fixed ... it scrolls away with
   the page", but the brief requires both controls to stay put while scrolling,
   so that one property differs by instruction. Everything else is its. */
.av-mark {
  position: fixed;
  top:   var(--av-logo-top);                      /* --page-top − 0.059 ink */
  right: calc(var(--page-inset) - 0.373px);       /* margin line − 0.373 ink */
  display: block;
  width:  var(--av-mark-w);
  height: var(--av-mark-h);
  z-index: 20;                                    /* about.css's layer */
}
/* THE ASSET SHIPS WHITE — its single path is fill="white", which is why both
   existing consumers (.hero-mark img, .ab-mark img) force it dark with this same
   one-word filter. Reusing the asset means reusing its treatment. */
.av-mark img { display: block; width: 100%; height: 100%; filter: brightness(0); }

/* THE CLOSE — about.css's .ab-close, verbatim: 40x40 at 14/14, z-index 20.
   `fixed` for the same reason the mark is. The two corners mirror because both
   spend --av-corner from their own edge, exactly as the live page does. */
.av-close {
  position: fixed;
  top:  var(--page-top);             /* the shared margin system, 2026-10-11 */
  left: var(--page-inset);
  display: block;
  width:  var(--av-close-size);
  height: var(--av-close-size);
  z-index: 20;
}
.av-close img { display: block; width: 100%; height: 100%; }

/* 44x44 hit area. The icon is 40x40, so the min-* below do bind. Empty, out of
   flow, unpainted — and nothing on the path clips it. */
.av-close::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
}
/* Keyboard users get a ring; pointer users do not — the same :focus-visible
   split about.css uses on its own controls. Offset so it clears the disc. */
.av-close:focus-visible {
  outline: 2px solid var(--av-ink);
  outline-offset: 3px;
  border-radius: 50%;
}

/* The in-flow reservation. Same box, paints nothing, holds the flex gap open on
   desktop and the first grid column on mobile. */
/* The slot reserves whatever fixed control sits at the row's LEFT — which is now
   the close button, not the mark. Same job: keep the 48px flex gap to the text
   column and the mobile header's first grid column measuring a real box. */
.av-mark-slot {
  flex: none;
  display: block;
  width: var(--av-close-size);
  height: var(--av-close-size);
}
/* 44x44 HIT AREA — and at cap height it is now LOAD-BEARING AT EVERY WIDTH, not
   just mobile: the visible mark is 30.7 x 16.5 at a 40px title and 21.5 x 11.6 at
   28px, both well under 44 in each axis. The brief allows exactly this ("an
   invisible hit area can extend beyond the visible mark"). Declared once,
   unconditionally, so the guarantee does not depend on remembering to re-state
   it per breakpoint.
   Empty, out of flow and unpainted: it changes no layout and nothing on screen.
   Nothing clips it — .av-mark and its ancestors set no overflow or clip-path,
   which is what would otherwise silently cut the hit area back to the box. */
.av-mark::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
}
/* THE ASSET SHIPS WHITE — its single path is fill="white", which is why BOTH
   existing consumers (.hero-mark img in pellmell-intro.css and .ab-mark img in
   about.css) force it dark with the same one-word filter. Reusing the asset means
   reusing its treatment; without this the mark renders invisible on #f9f9f9. */
.av-mark img { display: block; width: 100%; height: 100%; filter: brightness(0); }

.av-col {
  width: min(var(--av-col-w), 100%);
  max-width: 100%;
  margin-left: auto;                 /* pushed right; the inset below places it */
  /* 1440 − (807.41 + 527.402) = 105.188 of right inset, expressed against
     .av-top's content box (1440 − 48 = 1392) so it SCALES: 105.188/1392.
     At 1440 this lands the column on 807.41 exactly; narrower, it slides left
     with the viewport instead of being pinned to a number that only works once. */
  /* 7.5567% is the node's 105.188 right inset expressed against .av-top's
     content box, and it governs from about 1170 up. Below that the percentage
     shrinks faster than the mark does, and the column starts running under the
     mark now that the mark sits at the right edge — MEASURED at 768, the column
     reached 713.6 against a mark starting at 703.5. The max() floor keeps a
     24px channel beside the mark at every width and is inert at 1440, where the
     percentage still wins (105.19 against 88.5), so the 50% column start is
     untouched. */
  /* RIGHT EDGE ON THE RIGHT MARGIN LINE (user, 2026-10-11). The old inset held
     the column clear of the fixed mark; the margin line does that job now, and
     the clearance moved into --av-col-w instead -- see the note there. */
  margin-right: 0;
  display: flex;
  flex-direction: column;
  gap: var(--av-col-gap);
}

/* The node wraps the title in a box with `pr-10 py-10`; the 10px top is what
   sets the title's baseline against the mark, so it is kept rather than
   collapsed into the column gap. */
.av-title {
  margin: 0;
  /* The node's box is `pr-10 py-10`. The 10px RIGHT is dropped: on a nowrap single
     line it constrains nothing visually, but it does come straight off the width
     the title has to fit in (604.7 of 615 is a 10.3px budget). The 10px top/bottom
     stay — they are what set the title against the mark. */
  padding: var(--av-title-pad) 0;
  font-size: var(--av-title-size);
  font-weight: 700;
  line-height: normal;
  letter-spacing: var(--av-track);
  /* NEVER MID-WORD, at any width. Below 1024 the title wraps between words; these
     are the computed defaults already, stated so an inherited `overflow-wrap:
     anywhere` could not later start splitting "Principal" across two lines. */
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
  -webkit-hyphens: none;
  /* NO text-transform (user, 2026-10-11). The node's own string is typed in caps
     and its style sheet carries no transform, so the caps were the CONTENT, not a
     rule — the approved title is set in title case and rendered as written. The
     node's font, size, weight and 3.2px tracking are unchanged. */
  color: var(--av-ink);
}

.av-bio { display: flex; flex-direction: column; }
.av-bio p {
  margin: 0 0 var(--av-body-lh);     /* the node's empty 24px line between paras */
  font-size: var(--av-body-size);
  font-weight: 400;
  line-height: var(--av-body-lh);
  letter-spacing: var(--av-body-track);
  color: var(--av-ink);
}
.av-bio p:last-child { margin-bottom: 0; }

/* ---- THE LOCKUP --------------------------------------------------------
   "Shoon" AND "BROWNE" ARE ONE UNIT (user, 2026-10-11). Both derive from a
   single length, --av-lock, which IS the "Shoon" size — so their relative size,
   their inset and the space between them are fixed ratios of one number and
   cannot drift apart at any width. The ratios are the node's own:

       BROWNE size   40 / 360          = --av-lock / 9
       BROWNE inset  10 / 360          = --av-lock / 36   (node 2864:1710's pad)
       BROWNE track  3.2 / 40  = 0.08em of BROWNE's own size
       ink gap       ~28 / 360         = --av-lock * 0.0778

   ONE SIZE RULE AT EVERY WIDTH. min(25vw, 360px) is 360 at exactly 1440 — the
   node's size — and below that the unit tracks the viewport. Checked against the
   measured advance (3.0688em, letter-spacing included) at the inset each
   breakpoint uses: 1440 -> 1297 of 1440, 1024 -> 923 of 1009, 768 -> 692 of 768,
   320 -> 262 of 289. It fits everywhere with room, so the word never needs to
   break and never needs a second rule.

   FLUSH TO THE BOTTOM, NO GAP (user): margin-top:auto drops it to the foot of a
   full-height page and there is no bottom padding, so BROWNE's line box ends on
   the viewport's bottom edge. This SUPERSEDES the node's 18px (BROWNE box bottom
   876 of 894) — flagged rather than silently dropped.

   padding-top IS THE MINIMUM GAP, and it works precisely because margin-top is
   auto: free space lands in the margin, the padding is always there, so the bio
   -> lockup distance is (leftover + 48) and never less than 48. When the content
   outgrows the viewport the auto margin goes to 0, the page grows past 100svh and
   the two still cannot touch. */
.av-wordmark {
  /* 41dvh IS THE REGRESSION FIX'S OTHER HALF. The first two terms size the
     lockup by WIDTH, which is why a short-and-wide window broke it: at
     1440x700 the layer is hard-capped at 100dvh while the content wanted
     713.1px, so the column overflowed and BROWNE sat 13.1px BELOW the viewport
     — unreachable, because a fixed layer cannot scroll. 41dvh is chosen to be
     inert at the design height (41% of 894 is 366.5, so the 360px cap still
     wins and 1440x894 is untouched) and to bind only as the window shortens,
     which keeps the lockup clear of the bio down to about 620px of height. */
  --av-lock: min(25vw, 360px, 41dvh);

  /* PINNED TO THE LAYER'S BOTTOM, not flowed to it. margin-top:auto only reaches
     the bottom while the content FITS; once it overflows, the end of the column
     is below the fold and BROWNE goes with it. Positioned, the lockup's bottom
     edge is the viewport's bottom edge at every height, which is what "flush,
     fully visible" has to mean on a fixed layer. */
  position: absolute;
  left: var(--page-margin);
  right: var(--page-margin);
  bottom: 0;
  /* NO BOTTOM PADDING (user, 2026-10-11 — this OVERRIDES the earlier instruction
     that mirrored the top padding here). BROWNE's baseline has to land on the
     viewport's bottom edge at 0, so nothing may sit under it: not padding here,
     not margin, and not the line box's own space below the baseline — see
     .av-browne, which is where that last one is removed. */
  /* 192.5 / 1440 = 13.3681% of the page box — exact at the reference width and
     proportional below it. A FIXED 192.5 WOULD OVERFLOW: at 768 it leaves only
     527px for a word whose advance at 25vw is 589, so the giant line would run
     off the right edge. Measured, not assumed. */
  /* ON THE LEFT MARGIN LINE (user, 2026-10-11). It was 13.3681% -- the node's
     192.5/1440 -- which is not a margin line at all, just where the comp put it.
     left/right both spend the margin so the lockup can never reach past either. */
  margin-left: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* THE GAP IS INK-TO-INK, DERIVED (user, 2026-10-11: 16px at desktop, ~0.04em
     of the Shoon size so it scales). The flex gap is a BOX gap, and the brief's
     measurement is from Shoon's BASELINE to BROWNE's CAP TOP, so the two leftover
     slivers of line box between them have to come out of it. Both are Inter's own
     metrics (ascent 0.969444, descent 0.241667, cap 0.7675 — read off the face):

       Shoon box bottom below its baseline, at line-height 0.75:
           0.75 − (((0.75 − 1.211111) / 2) + 0.969444)  = 0.0111115em
       BROWNE cap top below its box top, at line-height 1, in Shoon ems:
           (((1 − 1.211111) / 2) + 0.969444 − 0.7675) / 9 = 0.0107098em

       gap = 0.044444 − 0.0111115 − 0.0107098        = 0.0226227em

     0.044444 is 16/360 — the brief's 16px expressed against the node's Shoon
     size, so it holds at every width rather than only at 1440. */
  gap: calc(var(--av-lock) * 0.0226227);
  font-size: var(--av-lock);         /* the unit both children measure against */
}

.av-shoon {
  display: block;
  font-size: 1em;                    /* = --av-lock */
  font-weight: 800;
  /* TRIMMED TO THE GLYPHS (user, 2026-10-11). `normal` leading would add ~0.21em
     of dead space top and bottom, and that dead space is exactly what was opening
     the gap under the baseline. "Shoon" measures 0.737222em of ink above the
     baseline and 0.010278em below (the round letters' overshoot) = 0.7475em in
     all, so 0.75 wraps it with ~0.3px of slack at 360 — tight enough to control
     the gap below, loose enough that the entrance mask never touches a glyph.
     Lower than this and `overflow: hidden` starts clipping the "S" and the "h". */
  line-height: 0.75;
  letter-spacing: -0.02em;
  color: var(--av-ink);
  /* ONE WORD, NEVER BROKEN (brief). On its own `word-break: normal` would not
     break it — but js/about-v2.js splits it into per-character inline-block
     spans for the entrance, and the line-breaking algorithm MAY break between
     any two adjacent inline-level boxes. That is a real defect this site has
     already been bitten by on about.html, where "Shoon Browne" broke as
     "Shoon Bro" / "wne". The splitter therefore nests the letters inside
     .av-word, and the nowrap below is what actually forbids the break. */
  white-space: nowrap;
  overflow: hidden;                  /* the entrance mask */
}
.av-word { display: inline-block; white-space: nowrap; }
.av-char { display: inline-block; will-change: transform; }

.av-browne {
  display: block;
  /* max() IS THE 12px FLOOR, and it is the one place the lockup stops being
     strictly proportional. --av-lock/9 falls under 12px once --av-lock is below
     108, i.e. below a 432px viewport, so at 320-430 BROWNE holds at 12 while
     "Shoon" keeps shrinking. The floor is an explicit requirement and it wins;
     the alternative is 10.4px type at 320. */
  font-size: max(12px, calc(var(--av-lock) / 9));
  padding-left: calc(var(--av-lock) / 36);   /* node 2864:1710's 10px at 360 */
  font-weight: 700;
  /* 1, not `normal` (1.211): the 0.21em of leading above the caps was the other
     half of the gap. The derivation in .av-wordmark's `gap` assumes this value —
     change it and the ink gap moves with it. */
  line-height: 1;
  letter-spacing: 0.08em;            /* = the node's 3.2px at 40px */
  color: var(--av-ink);

  /* FLUSH TO THE VIEWPORT'S BOTTOM EDGE (user, 2026-10-11).
     line-height: 1 still leaves the font's descender space under the baseline —
     with Inter's 0.969444 ascent, the baseline sits at 0.8638885em from the box
     top, so 0.1361115em of empty box hangs below it. That space is what was
     holding BROWNE off the edge, and it has to come out.

     THE FALLBACK IS THE DERIVED NEGATIVE MARGIN; the trim below supersedes it
     wherever the property exists (it does in current Chrome — checked with
     CSS.supports before writing this). Only one of the two is ever active, so
     they cannot double-count.

     em, NOT the lockup's size: BROWNE is clamped to a 12px floor under a 432px
     viewport, so below that its own size stops being --av-lock/9. Measuring the
     trim in BROWNE's own em keeps it exact on both sides of that clamp.

     THE 'O' STILL OVERHANGS, BY DESIGN AND BY 0.0098em — round caps overshoot
     the baseline (0.39px at 40px, 0.12px at 12px). The brief defines the target
     as the BASELINE of the caps, and nothing is clipped, so the overshoot is
     left to render below the line rather than shifted up to hide it. */
  margin-bottom: -0.1361115em;       /* = 1em − 0.8638885em (baseline from top) */
}
@supports (text-box: trim-end cap alphabetic) {
  .av-browne {
    text-box: trim-end cap alphabetic;
    /* NOT 0 — THE ROUND CAPS OVERSHOOT. The trim ends the box on the alphabetic
       baseline, and Inter's 'O' dips 0.009766em below it, so a box flush with
       the viewport edge leaves that sliver outside. The brief now asks for the
       GLYPH bottom at 0 and "fully visible", not the baseline, so the box is
       lifted by exactly the overshoot. */
    margin-bottom: 0.009766em;
  }
}

/* ---- ENTRANCE (pre-paint hiding) ----------------------------------------
   Present only while motion is allowed; js/about-v2.js removes the flag once
   GSAP owns these as inline styles. Under prefers-reduced-motion .av-anim is
   never added, so none of this applies and the page renders at rest. */
.av-anim .av-mark,
.av-anim .av-title,
.av-anim .av-bio p,
.av-anim .av-browne { opacity: 0; }
/* "Shoon" is masked, not faded: the letters drop in from outside the box, so
   the box clips them rather than the opacity hiding them. */
.av-anim .av-shoon { visibility: hidden; }
.av-shoon { overflow: hidden; }

/* =========================================================================
   NARROW VIEWPORTS — not in the node, which is a single 1440 frame.

   THE RULE THE BRIEF SETS: below ~768 the right column moves ABOVE the
   wordmark at full width. The desktop layout already stacks top-block then
   wordmark, so the move is simply the top block going from a row to a column —
   the mark leads, the text follows, and the wordmark stays last.
   ========================================================================= */
/* TABLET — the 768-1023 step is GONE. It pinned --av-top to 24px, and --av-top
   is now derived from the corner axis rather than chosen per breakpoint; leaving
   it would have held the title 24px down and broken the alignment it is there to
   make. The row layout and the 40px title were always the desktop ones here, so
   nothing else in that block was doing anything. */

/* THE OVERLAY'S OWN BREAKPOINT, NOT THIS SHEET'S. css/pellmell-intro-recentwork.css
   steps .rw-overlay's padding to 32px and .rw-title to 24px at max-width 1180, which
   moves the line this page is matching. This block exists only to follow it, and it
   is placed ahead of the 767 block so the narrower one still wins everything else. */
@media (max-width: 1180px) {
  :root {
    --av-rw-cap-top: calc(var(--page-top) + 10px + (28px - 0.66 * 24px) / 2);
  }
}

@media (max-width: 767px) {
  :root {
    /* --av-gutter is NOT set here any more: it is var(--page-margin), whose
       clamp already lands on 16px at 320 and ~24px at 390. */
    /* --av-top is NOT set here any more: it is derived from the axis, and this
       block's own --av-title-size step is what moves it (7.5px at 28px type). */
    /* THE TITLE SIZE DROPS AT THE TOKEN, NOT ON .av-title — .av-mark's height is
       1.2em of this, so overriding the rule instead of the token would leave the
       mark at its desktop 48px next to a 28px title. Measured when it did: the
       mark came out 1.7143em of the title rather than 1.2. */
    --av-title-size: 28px;
    --av-track:      2.2px;
  }

  /* THE HEADER IS A ROW, NOT A STACK (user, 2026-10-11): mark left, title beside
     it, bio below at full width.

     `display: contents` ON .av-col IS WHAT MAKES THAT POSSIBLE WITHOUT TOUCHING
     THE MARKUP. The title and the bio are children of .av-col, so as a grid of
     .av-top they would be one cell between them and could not split across two
     rows. Dropping .av-col's own box promotes BOTH to grid items of .av-top, and
     each can then be placed independently — the title beside the mark, the bio
     spanning underneath. The desktop layout, where .av-col IS the right column,
     is untouched. (.av-col carries no background, border or padding, so there is
     no painted box being discarded here.) */
  .av-top {
    padding: var(--av-top) var(--page-margin) 0;
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-columns: var(--av-close-size) 1fr;
    column-gap: 16px;                /* user: 16px between logo and title */
    row-gap: 28px;
    /* START, NOT CENTRE — a consequence of the mark becoming `fixed`. Centring
       aligned the mark to the MIDDLE of the row, which moves when the title wraps
       to a second line (it does at 390); a fixed element cannot follow that. The
       cap-line rule holds at any line count, and matches desktop, so the mark sits
       on the title's first-line caps at every width instead. */
    align-items: start;
  }
  .av-col { display: contents; }

  /* Size still comes from .av-mark's 1.2em of --av-title-size, which this block
     drops to 28px — so the mark follows the title down to 33.6 x 18.1 with no
     size rule here at all. Only the vertical placement changes: the grid's
     align-items:center owns it, so the desktop baseline offset is cleared. */
  .av-mark-slot { grid-column: 1; grid-row: 1; }
  /* The mark is fixed at the right edge and out of flow, so nothing in the grid
     knows it is there. The title is the one thing that reaches that far, so it
     takes the clearance: the mark's width plus the column gap. */
  .av-title { padding-right: calc(var(--av-mark-w) + 16px); }
  .av-title { grid-column: 2; grid-row: 1; }
  .av-bio   { grid-column: 1 / -1; grid-row: 2; }

  /* The 192.5 inset is a 1440 composition; at phone widths the lockup takes the
     page gutter instead. Size, ratio and the flush bottom are unchanged — they
     are the lockup's own rules and do not vary by breakpoint. */
  .av-wordmark { margin-left: 0; }
}

/* The title is 40px at the node; the narrow block above drops the TOKEN to 28px
   (it is the page's one piece of display type in the text column, and at 320 it
   would otherwise eat the screen). 12px is the label floor; this stays far above
   it, and the mark follows the token down with it. */

/* ONE LINE FROM 1024 UP (user, 2026-10-11). The column is sized to hold it
   (604.7 of 615), so this asserts the intent rather than forcing an overflow:
   if the measure ever stops being wide enough the line will overrun visibly
   instead of silently re-wrapping, which is the failure you want to see.
   BELOW 1024 IT IS DELIBERATELY ABSENT — the brief allows wrapping between words
   there, and the guards on .av-title keep that from ever landing mid-word. */
@media (min-width: 1024px) {
  .av-title { white-space: nowrap; }
}


/* =========================================================================
   THIS SITE — node 2871:1722.

   WIDTH AND LEFT EDGE. The node's column (2871:1760) is 1093 wide; the brief
   pins its LEFT to the lockup's edge rather than the node's centring, so it
   takes the lockup's own 13.3681% and caps at 1093. At 1440 that is 192.5 ->
   1285.5. (The node centres the same column at 173.5; the 19px difference is
   the brief's call, and it is what keeps the card flush under "Shoon".)

   SPACING IS THE NODE'S: 48 between the lockup and this section (2871:1760's
   gap) and 24 between the description and the card (2871:1759's gap).

   WHAT IS **NOT** THE NODE'S, and why — flagged rather than silently resolved:
     - The node sets the card's headings at 140px with py-48 and a 10px row gap.
       The copied component sizes its card FROM the heading (--ab-cat-size 100px,
       with padding and row height derived off it at pellmell's 118/110/200/150
       over 140 ratios). Taking the node's three numbers in isolation would break
       that derivation and the row rhythm the open/close tween measures against,
       so the component's own proportions stand and the node's type size is noted.
     - The copied sheet drops --ab-body-size to 15px under 1140. It is pinned at
       16 here: the brief's mobile floor for body copy is 16px, and 15 would fail
       it. This is the one value deliberately changed in the copy.
   ========================================================================= */
.av-site {
  /* MARGIN LINE TO MARGIN LINE (user, 2026-10-11). It was 13.3681% wide by
     min(1093px, ...), i.e. the node's column. Now it is the page box, capped and
     centred exactly as .av-screen is, with the margins as padding -- so the
     paragraph, the card, the footer rule and the footer links all START on the
     left line and the rule and card END on the right one, with no per-element
     number anywhere. */
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--page-margin);
  /* UNIT 2 IS THE WHOLE DOCUMENT NOW — unit 1 is fixed and out of flow, so this
     starts at the top of the page. The offset puts the "This site" CAP on the
     logo's top line at scroll 0, which is where the brief wants it when the
     layer above fades away: logo-top, less the cap's own inset inside its box.

     THE min-height FILLER IS GONE (user, 2026-10-11). It existed to buy enough
     document height for a SCROLLED landing to reach this alignment; with the
     cross-fade the alignment is static, needs no scroll at all, and the filler
     was leaving 566px of blank tail at 768 and 349px at 390. Unit 2 is now only
     as tall as its content. */
  /* BELOW THE ICONS, NOT ON THEIR AXIS (user, 2026-10-11). This used to be
     --av-axis − --av-desc-cap-c, which put the paragraph's cap CENTRE on the
     line the close button and the mark are centred on — so at rest the first
     line sat in the same band as two fixed icons and was covered by them. The
     alignment to the icon axis is WITHDRAWN. The rule now is clearance:

         paragraph cap top = icon bottom + --page-margin

     so the gap below the icons is the same measure as the gap beside them, and
     it scales with the margin system rather than being a number.
     The offset is the cap TOP's inset inside the paragraph's first line box,
     which is why --av-desc-cap-t exists above and --av-desc-cap-c no longer
     appears here. */
  margin-top: calc(var(--av-icons-bottom) + var(--page-margin)
                   - var(--av-desc-cap-t));
  /* THE TAIL IS ONE MARGIN (user, 2026-10-11). It was a flat 96px, and at 1440
     that left ~46px of blank page under the footer links that nothing accounted
     for. It is NOT scroll room for the landing, despite looking like it: the
     landing's rest position is scroll 0 -- unit 2 is placed by its margin-TOP,
     above the content -- so the landing needs no document height below the
     footer and never did. The 96 predates the landing entirely.
     IT IS NOW --av-foot-gap, NOT --page-margin (user, 2026-10-11): the space
     below the links has to equal the space above them, and the space above them
     is the footer's own gap, not the page margin. Because the row above is
     trimmed to its baseline, this measures from the INK to the document end. */
  padding-bottom: var(--av-foot-gap);
  display: flex;
  flex-direction: column;
  gap: 16px;                                  /* node 2871:1759 gap (was 24) */
}
/* node 2871:1749 — 16px / 24px line / 1.28 tracking. Black, as the node sets it. */
.av-site__desc {
  margin: 0;
  font-size: var(--av-desc-size);
  line-height: var(--av-desc-lh);
  letter-spacing: var(--av-desc-track);
  color: var(--av-desc-color);
}
.av-site .ab-h2 { text-align: left; }

@media (max-width: 767px) {
  .av-site {
    margin-left: 0;
    width: 100%;
    /* margin-top is the alignment offset and is NOT overridden here — it is
       derived from the tokens, which this block already steps. */
    /* padding-bottom is NOT stepped here any more: --page-margin's own ramp
       lands on 16px at this width, which is the value this block existed to set. */
  }
}

/* ---- COPIED VERBATIM FROM css/about.css ---------------------------------
   The categories card: its @property registration, its rules and its
   reduced-motion block. Only the tokens it reads are re-declared below (the
   source sheet keeps them among the About page's own, which this page does not
   want). Nothing here is edited except where the header above says so. */
:root {
  --ab-black:      #000000;
  --ab-pink:       #FF3567;          /* === --pi-pink, css/pellmell-intro.css */
  --ab-cat-bg:     #f2f2f2;          /* pellmell --color-offwhite-2 */
  /* DARKENED 2026-10-11 (user): #959697 measured 2.65:1 on --ab-cat-bg and these
     labels are large (100px / 48px), so the target is 3:1. #8B8C8D is the nearest
     value that meets it with hue and saturation held. === --pi-grey-text-lg in
     css/pellmell-intro.css; a LITERAL here because this page loads no other sheet. */
  --ab-cat-fg:     #8B8C8D;          /* was pellmell --color-grey-3 #959697 */
  /* --ab-num-fg WAS MISSING FROM THIS SHEET. css/about.css defines it (#FF3567) and
     this copy did not, so `color: var(--ab-num-fg)` on .ab-cat__num was invalid at
     computed-value time and the digits INHERITED the label grey instead of being
     pink. Restored here, and at the accessible value: the digits are 12px on a white
     pill, which is normal text, so they take the passing pink rather than #FF3567's
     3.53:1. === --pi-pink-text. */
  --ab-num-fg:     #D92D58;
  /* --ab-ink WAS MISSING TOO (found 2026-10-15), the same copy gap as --ab-num-fg above.
     css/about.css defines it (#191919, "the expanded panel") and .ab-cat__panel below reads
     it, so `background: var(--ab-ink)` was invalid at computed-value time at EVERY width
     and the open panel was transparent: white text on the #f2f2f2 card. Restored at the
     source's value, which is also the Recent Work Matey box (#191919,
     css/pellmell-intro-recentwork.css). */
  --ab-ink:        #191919;
  --ab-title-wt:   700;
  --ab-title-lh:   24px;
  --ab-h2-size:    32px;             /* = the source's --ab-role-size at >=1141 */
  --ab-h2-lh:      28px;
  --ab-body-size:  16px;
  --ab-body-lh:    26px;
  --ab-gap-work:   24px;
  /* --ab-cat-size IS THE ONLY FREE NUMBER; the rest re-solve from it. */
  --ab-cat-size:   100px;
  --ab-cat-h:      calc(var(--ab-cat-size) * 150 / 140);
  --ab-cat-gap:    0px;
  --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-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 */
}

.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,
/* HOVER STAYS --ab-pink (#FF3567): it lands on --ab-cat-bg at 3.15:1 and these
   labels are large, so its target is 3:1 and it passes. Not darkened. */
.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);
}

/* The source sheet's narrow step, with --ab-body-size HELD AT 16 (see the header:
   15px would fail the brief's body-copy floor). --ab-h2-size follows the source's
   --ab-role-size to 28. */
@media (max-width: 1140px) {
  :root {
    --ab-cat-size: 34px;
    --ab-h2-size:  28px;
    --ab-body-lh:  24px;
  }
}

/* =========================================================================
   FOOTER — node 2880:1836.

   THE RULE IS THE NODE'S OWN PATH, read from its SVG rather than eyeballed:
   `<path d="M10 10H1103" stroke="black"/>` inside a 1113x20 box. So it is 1px,
   black, and 1093 wide — the content column exactly, which is why it is a
   border on this column rather than a full-bleed line — sitting at the vertical
   centre of a 20px row, with 10px of air either side.

   THE 28s ARE DERIVED FROM THAT ROW, not chosen. The node stacks: card, 17.84
   to the footer box, then 10 to the line (half the rule row); and below the
   line, 10 back out of the row, the footer's own 8px gap, and the links row's
   10px padding. That is 27.84 above and 28.00 below — symmetric, taken as 28
   both ways.
   ========================================================================= */
.av-foot {
  /* 28 TO THE LINE, MINUS THE COLUMN'S OWN GAP. .av-site is a flex column with
     a 16px gap, which is already between the card and this footer — adding 28
     on top of it put the rule 44px down. Written as the subtraction so the two
     cannot drift if the node's gap changes. */
  margin-top: calc(28px - 16px);
  width: 100%;
}
.av-foot__rule {
  border-top: 1px solid #000;        /* the node's stroke, at its own weight */
}
.av-foot__links {
  /* TRIMMED TO THE INK AT BOTH ENDS. The box's own edges are half-leading away
     from the glyphs, so a margin alone cannot make the two gaps match — the top
     margin is short by the cap inset and the bottom one is long by the
     baseline-to-box-bottom slack. Both are taken out here:

       margin-top     --av-foot-gap − --av-desc-cap-t   (28.00 at 16/24)
                      so the CAP TOP, not the box top, lands --av-foot-gap
                      below the rule.
       margin-bottom  −--av-desc-base-b                 (−6.17779)
                      pulls the box's bottom edge up onto the BASELINE, so
                      .av-site's padding-bottom then measures from the ink.

     NOT text-box-trim, which would be the obvious tool: this row is a FLEX
     container, and text-box-trim acts on a block container's first and last
     LINE BOXES, which a flex container does not have. The metrics are the same
     ones text-box would use; they are just applied by hand.
     Negative margin-bottom is safe here — .av-foot is a flex item and so
     establishes its own formatting context; nothing collapses through it. */
  margin: calc(var(--av-foot-gap) - var(--av-desc-cap-t)) 0
          calc(-1 * var(--av-desc-base-b));
  padding: 0;
  display: flex;
  align-items: center;
  gap: 10px;                         /* node 2880:1839 */
  font-size: var(--av-desc-size);
  line-height: var(--av-desc-lh);
  letter-spacing: var(--av-desc-track);
  color: #000;
  /* The node sets whitespace-nowrap on this row. Kept — the two links and the
     pipe are one short line, and it also means nothing can break mid-word. */
  white-space: nowrap;
}
.av-foot__pipe { color: #000; }

/* ---- THE LINKS -----------------------------------------------------------
   SET AS THE "This site" PARAGRAPH IS (user, 2026-10-11). The row above already
   spends --av-desc-size / -lh / -track / -color, which are that paragraph's own
   tokens, and family and weight both inherit from body — so the links restate
   nothing and cannot drift from it at any breakpoint. What is left here is only
   the removal of the browser's link chrome.

   THE ROLL IS GONE with the underline it carried: the hover is now an underline
   and nothing else, so there is no second copy to mask and no clip to keep off
   the anchor.

   :visited AND :active ARE LISTED EXPLICITLY. `color: inherit` on the base rule
   alone does not stop the visited colour in every engine, and a purple
   shoonb@gmail.com after one click is exactly what the brief rules out. */
.av-foot__link,
.av-foot__link:visited,
.av-foot__link:active {
  color: inherit;
  text-decoration: none;
}
.av-foot__link {
  position: relative;
  display: inline-block;
}
/* The resting look is untouched; the underline is the whole hover cue. Gated on
   a real pointer so a tap does not leave it stuck underlined. */
@media (hover: hover) and (pointer: fine) {
  .av-foot__link:hover { text-decoration: underline; }
}
.av-foot__link:focus-visible {
  outline: 2px solid var(--av-ink);
  outline-offset: 3px;
}
/* 44x44 hit area. The text line is 24px tall, so the min-* genuinely bind, and
   nothing on the anchor's path clips it. */
.av-foot__link::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
}

/* ---- LARGE MONITOR: RECENT WORK'S CORNERS, AND NO SCROLLBAR SHIFT (user, 2026-10-15) ----
   From 1920px wide the close and the mark leave the centred column and sit where the Recent
   Work overlay puts its own: the close's top-left corner on the page margins
   (css/pellmell-intro-recentwork.css .rw-close: top/left var(--page-margin)) and the mark's
   top-right corner on them (.rw-mark). The mark keeps About's own size (22.6 x 42.0); only
   its position follows. --av-logo-top is NOT edited, so the column's clearance under the
   icons (--av-icons-bottom) is untouched.
   scrollbar-gutter: stable reserves the scrollbar's width even while the page fits, so
   opening or closing a description can no longer add a scrollbar and slide everything
   centred 7.5px sideways. It also makes `vw` exclude that gutter, which is what the
   homepage's always-present scrollbar does, so the margins come out the same number there
   and here. Below 1920 none of this applies. */
@media (min-width: 1920px) {
  html { scrollbar-gutter: stable; }
  .av-close { left: var(--page-margin); }
  .av-mark  { top: var(--page-top); right: var(--page-margin); }
  /* THE HOMEPAGE MARK'S SIZE, AND THE CLOSE AT ITS HEIGHT (user, 2026-10-15). The mark keeps
     --av-mark-w/-h (22.627 x 42.047) — the same pair as the homepage .hero-mark's
     --mark-w/-h, Figma 1480:1482 — and the close becomes a 42.047 square so the two are one
     height, identical to Recent Work's. On the element, NOT on --av-close-size, which also
     feeds the axis maths above. Its top-left corner stays on the page margins. */
  .av-close { width: var(--av-mark-h); height: var(--av-mark-h); }
}
