/* ================================================================
   BRUTAL PARTS — an experiment layer

   Ports three components from the v2 "brutalist" fork onto the live
   site: the header strip, the footer rule, and the controls.

   Deliberately additive. It loads after every other stylesheet and
   restyles the classes v1 already uses, so nothing in the markup
   changes and the whole experiment is removed by deleting one <link>
   per page. Nothing here is a token rename, so style.css stays the
   single source for colour.

   Both versions already share --bg (#1C1A19) and the orange accent,
   so the port needs no recolouring. On the light pages (cases, blog
   posts) v1's accent is blue, and the inverting states pick that up
   through var(--accent) rather than hardcoding orange.
   ================================================================ */

:root {
  /* Declared here rather than patched at each call site: --nav-h is
     read by case-study.css, blog.css, the hero padding and two scroll
     offsets in main.js, and all of them follow from this one line.
     80px, between v1's original 88 and the fork's 54. */
  --nav-h: 80px;

  /* Component geometry, lifted from brutalist.css. Prefixed so none of
     it collides with a v1 token. */
  --b-pad: clamp(18px, 4vw, 56px);
  --b-track-label: 0.085em;
  --b-track-ui: 0.005em;
  --b-size: 12px;
  --b-dur: 0.12s;
  /* One section rhythm for the whole page. Every band pads to this top
     and bottom, so the gap between any two is always twice it. Matched
     to the fork's own beat padding, which is what gives the brutalist
     layout its air: the rules need room either side or they crowd the
     content they are meant to separate. */
  --b-sec-pad: clamp(72px, 13vh, 150px);

  /* A control's border is its own boundary, so WCAG 1.4.11 asks for
     3:1 against the page rather than the laxer reading that covers a
     decorative divider. v2's --rule (0.34 alpha) measures 2.82:1 and
     would not clear it here, so these are a step stronger: 3.95:1 on
     the dark ground, 3.89:1 on white. */
  --b-rule: rgba(240, 235, 228, 0.45);
  --b-quiet: #8d8982;          /* 4.98:1 on --bg, AA at 12px */

  /* Dividers are a separate token from control borders, because the two
     answer to different rules. A section rule is decorative and 1.4.11
     exempts it, so it can sit quietly at 2.17:1. A button's border is
     the control's own boundary and may not go under 3:1, which is why
     --b-rule above stays where it is rather than following this down. */
  --b-divider: rgba(240, 235, 228, 0.26);
  /* Placeholder, a step quieter than the label. Both had been
     --text-4, which is why they read as the same thing. 4.72:1 on the
     field's fill, so it still clears AA. */
  --b-placeholder: #8d8982;
}

body.theme-light {
  --b-rule: rgba(28, 26, 25, 0.55);
  --b-quiet: #5d5a55;          /* 6.86:1 on white */
  --b-divider: rgba(28, 26, 25, 0.30);
  /* On a light field, quieter means lighter rather than darker, so
     this goes the other way from the dark theme's value. 4.68:1 on
     the light fill, against the label's 6.05:1. */
  --b-placeholder: #6b6f74;
}

@media (max-width: 768px) {
  /* style.css drops --nav-h to 70px here for the taller mobile bar.
     The strip is one height in both orientations, so it holds. */
  :root { --nav-h: 80px; }
}

/* Wide screens get 24px more on every band, top and bottom.

   Added to the clamp rather than raising its ceiling: --b-sec-pad is
   driven by viewport height, so on a 900px-tall screen it resolves to
   117px and never reaches the 150px cap. Lifting the cap would have
   changed nothing on the screens this is for. calc guarantees the 24
   wherever the clamp lands. */
@media (min-width: 1200px) {
  :root {
    --b-sec-pad: calc(clamp(72px, 13vh, 150px) + 24px);
  }
}


/* ── Header: one strip of cells, edge to edge ──────────────────────
   main.js fades the nav's background, blur and border in on scroll by
   writing inline styles, which outrank a stylesheet. The strip is
   opaque and ruled from the first pixel and carries no blur, so those
   declarations have to be forced. The border is the reason the shorthand
   is used rather than border-bottom-width: main.js writes borderColor,
   which sets all four colour longhands, and only an important
   declaration on the same longhand holds the rule steady through the
   scroll fade. Nothing else in this file needs it.

   box-sizing is border-box sitewide, so the rule is drawn inside
   --nav-h rather than adding a pixel to it, and every offset that
   reads that token still lands flush under the strip. */
.nav {
  height: var(--nav-h);
  background: var(--bg) !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  border-bottom: 1px solid var(--b-divider) !important;
}

/* The strip spans the viewport, so the container stops constraining it
   and the cells carry their own padding instead. */
.nav > .container {
  max-width: none;
  width: 100%;
  padding: 0;
}

.nav__inner {
  align-items: stretch;
  justify-content: flex-start;
}

/* Logo cell. It inverts to white rather than to the accent: the mark is
   the identity, not an action, so it should not borrow the colour the
   nav cells use for navigation. */
.nav__logo {
  display: flex;
  align-items: center;
  padding: 10px var(--b-pad);
  transition: background var(--b-dur);
}
.nav__logo img { height: 26px; }

/* The mark ships as solid white and cannot follow currentColor through
   an <img>, so the two inverted states are reached with a filter:
   brightness(0) blackens the white paths and leaves the transparent
   ground alone. On the light pages the mark is already inverted to
   near-black by style.css, so dropping that filter returns it to white
   over the dark cell. */
.nav__logo:hover,
.nav__logo:focus-visible { background: #ffffff; outline: 0; }
.nav__logo:hover img,
.nav__logo:focus-visible img { filter: brightness(0); }

body.theme-light .nav__logo:hover,
body.theme-light .nav__logo:focus-visible { background: var(--text); }
body.theme-light .nav__logo:hover img,
body.theme-light .nav__logo:focus-visible img { filter: none; }

/* Nav cells butt against each other: the gap goes, and the hit area is
   the whole cell rather than the text inside it. */
.nav__links {
  gap: 0;
  margin-left: auto;
  align-items: stretch;
}
.nav__links li { display: flex; }

.nav__links a {
  display: flex;
  align-items: center;
  padding: 10px 18px;
  /* Back to Inter in caps. Pangea at 16/300 in sentence case matched
     the mobile drawer, but the drawer is a full-screen list where the
     display face has room; in a 80px strip the caps hold the line
     better and sit with the legends and controls, which are all set
     this way. The tracking belongs to the caps. */
  font-size: var(--b-size);
  letter-spacing: var(--b-track-label);
  text-transform: uppercase;
  /* Same as the ghost button's label. The menu had been --b-quiet,
     which made it read as secondary to controls sitting further down
     the page. */
  color: var(--text);
  /* A divider on the leading edge of every cell, so the dividers fall
     between cells and one marks where the menu starts. The trailing
     edge is left alone: a border there would sit a hairline inside the
     viewport edge rather than on it, which reads as a mistake. */
  border-left: 1px solid var(--b-divider);
  transition: background var(--b-dur), color var(--b-dur), border-color var(--b-dur);
}

/* Padding widens from 14px to 18px with the dividers in: cells that are
   only separated by space can sit closer than cells with a line between
   them, which needs air on both sides to read as a division rather than
   as an underline on the text. */

/* The underline swipe is replaced by the cell fill, so the pseudo
   element goes rather than animating underneath it. */
.nav__links a::after { display: none !important; }

.nav__links a:hover,
.nav__links a:focus-visible {
  background: var(--accent);
  color: var(--accent-ink);
  /* The divider goes accent too, so a lit cell is one solid block
     rather than a block with a grey line down its left edge. */
  border-color: var(--accent);
  outline: 0;
}

/* The cell to the right of a lit one keeps its own divider, which would
   draw a grey line against the accent fill. Matching it to the accent
   closes the seam. */
.nav__links li:has(> a:hover) + li > a,
.nav__links li:has(> a:focus-visible) + li > a { border-left-color: var(--accent); }

/* Current page reads as the brightest label, not a dimmed one: against
   cells that light up on hover, 0.55 opacity made the page you are on
   look disabled. */
/* With every item at --text, colour can no longer mark the current
   page, so the cell carries a rule instead. aria-current already
   says it to a screen reader; this says it to everyone else. */
.nav__links a.is-current,
.nav__links a[aria-current="page"] {
  color: var(--text);
  opacity: 1;
  box-shadow: inset 0 -2px 0 0 var(--accent);
}
/* The fill covers the marker on hover, which is correct: the cell is
   showing its hover state, not its page state. */
.nav__links a.is-current:hover,
.nav__links a[aria-current="page"]:hover,
.nav__links a.is-current:focus-visible,
.nav__links a[aria-current="page"]:focus-visible {
  box-shadow: none;
}

/* The hamburger and its drawer are kept as they are. Five cells split
   across a 375px screen leaves about 60px each, too tight to read at
   this size, so below the breakpoint the tested mobile menu still
   runs. It only needs pushing to the strip's right edge, since the
   inner row no longer uses space-between. */
.nav__hamburger {
  margin-left: auto;
  padding: 6px var(--b-pad);
  align-self: center;
}

/* The drawer hangs off the strip, which is now shorter. */
.nav__mobile { top: var(--nav-h); }


/* ── Footer: a rule and a quiet line ──────────────────────────────── */
footer {
  border-top: 1px solid var(--b-divider);
  padding-block: 36px;
}
footer > .container {
  max-width: none;
  padding-inline: var(--b-pad);
}
.footer__copy {
  font-size: var(--b-size);
  letter-spacing: var(--b-track-ui);
  color: var(--b-quiet);
}


/* ── Controls: hard boxes that invert ─────────────────────────────── */
/* All six of v1's control classes collapse onto one component, so a
   "view" action reads the same whether it sits in the hero, a project
   card, the archive or the masterclass block. */
.btn-primary,
.btn-ghost,
.btn-outline,
.mentorship__btn,
.project-card__link,
.other-card__link,
.case-back,
.case-site,
.post-back,
.form-submit {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font);
  font-size: var(--b-size);
  letter-spacing: var(--b-track-label);
  text-transform: uppercase;
  color: var(--text);
  text-decoration: none;
  background: none;
  border: 1px solid var(--b-rule);
  border-radius: 0;
  padding: 10px 15px;
  cursor: pointer;
  transition: background var(--b-dur), color var(--b-dur), border-color var(--b-dur);
}

.btn-primary:hover,
.btn-primary:focus-visible,
.btn-ghost:hover,
.btn-ghost:focus-visible,
.btn-outline:hover,
.btn-outline:focus-visible,
.mentorship__btn:hover,
.mentorship__btn:focus-visible,
.project-card__link:hover,
.project-card__link:focus-visible,
.other-card__link:hover,
.other-card__link:focus-visible,
.case-back:hover,
.case-back:focus-visible,
.case-site:hover,
.case-site:focus-visible,
.post-back:hover,
.post-back:focus-visible,
.form-submit:hover,
.form-submit:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  outline: 0;
}

/* Anything sitting on the inverted fill has to inherit it. The hero
   button carries an arrow on currentColor, which follows on its own,
   but a nested span with a colour of its own would drop out. */
.btn-primary:hover *, .btn-primary:focus-visible *,
.btn-ghost:hover *, .btn-ghost:focus-visible *,
.btn-outline:hover *, .btn-outline:focus-visible *,
.mentorship__btn:hover *, .mentorship__btn:focus-visible *,
.project-card__link:hover *, .project-card__link:focus-visible *,
.other-card__link:hover *, .other-card__link:focus-visible *,
.case-back:hover *, .case-back:focus-visible *,
.case-site:hover *, .case-site:focus-visible *,
.post-back:hover *, .post-back:focus-visible *,
.form-submit:hover *, .form-submit:focus-visible * { color: inherit; }

/* The solid variant. The hero's primary and the form's Send are the
   two places that should carry weight before being touched, so they
   start filled and empty out, the reverse of every other control. */
.btn-primary,
.form-submit {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.btn-primary:hover,
.btn-primary:focus-visible,
.form-submit:hover,
.form-submit:focus-visible {
  background: transparent;
  border-color: var(--accent);
  color: var(--accent);
}

/* style.css lets the parent card drive the link's border and colour,
   which would fight the cell invert. The card hover now leaves the
   control at rest and the control answers for itself. */
.project-card:hover .project-card__link,
.project-card--featured:hover .project-card__link {
  color: var(--text);
  border-color: var(--b-rule);
}
.project-card:hover .project-card__link:hover,
.project-card--featured .project-card__link:hover,
.project-card--featured .project-card__link:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* The inverting controls draw their own focus state, so the global
   ring would double up on them. Removing it is only safe because the
   state above is a full fill change, not a colour shift. */
.btn-primary:focus-visible,
.btn-ghost:focus-visible,
.btn-outline:focus-visible,
.mentorship__btn:focus-visible,
.project-card__link:focus-visible,
.other-card__link:focus-visible,
.case-back:focus-visible,
.case-site:focus-visible,
.post-back:focus-visible,
.form-submit:focus-visible,
.nav__links a:focus-visible,
.nav__logo:focus-visible { outline: 0; }

@media (prefers-reduced-motion: reduce) {
  .nav__logo,
  .nav__links a,
  .btn-primary, .btn-ghost, .btn-outline, .mentorship__btn,
  .project-card__link, .other-card__link, .form-submit,
  .case-back, .case-site, .post-back { transition: none; }
}


/* ── Page frame: one wide measure, aligned to the strip ───────────── */
/* v1's container capped at 1160px while the strip above it runs edge to
   edge, so content sat in a column noticeably narrower than its own
   header. Raising the cap and matching the gutter to the strip's
   padding lines the two up: the logo and the first word of every
   section now share a left edge. */
/* The gutter is global: it lines every page's content up with the
   strip above it, and it barely moves from what was there before. */
:root {
  --gutter: var(--b-pad);
}

/* One width for the whole site. Prose is unaffected either way, since
   case and blog copy carry their own 680px measure. */
:root {
  --container: 1440px;
}

/* What the wider frame does break: of the 54 case images, 46 are 1440
   or wider and simply fill more of the page, but 8 are narrower than
   the new container and would be stretched past their own pixels.
   Each is capped at its intrinsic width and centred, so it renders
   sharp at its real size rather than soft at 1440.

   Listed by file rather than by a blanket rule, because the cap has to
   be each image's own width. Re-exporting any of these at 1440 or
   wider lets it fill the frame, and its line here can then go. */
.case-image {
  margin-inline: auto;
}
.case-image[src$="hero.webp"],
.case-image[src$="ld-offsite.webp"],
.case-image[src$="ld-workshop-2.webp"],
.case-image[src$="mc-brand-hero.webp"]          { max-width: 1200px; }
.case-image[src$="td-workshop-cards.webp"]      { max-width: 1380px; }
.case-image[src$="td-platform-transition.webp"] { max-width: 1048px; }
.case-image[src$="td-workshop-diamond.webp"]    { max-width:  999px; }
.case-image[src$="ld-onsite.webp"]              { max-width:  900px; }


/* ── Section rules ────────────────────────────────────────────────── */
/* Every band gets the rule across its top edge, as the beats do on the
   fork. Full-bleed rather than inside the container, so the line reads
   as the page being divided rather than as a border on a box.

   The hero is excluded: it sits directly under the strip, whose own
   bottom rule already draws that line, and two would stack. .showcase
   is excluded because it is decorative and aria-hidden, so a rule there
   would announce a section that is not one. */
.quote,
.stats,
.projects,
.other-projects,
.about,
.contact {
  border-top: 1px solid var(--b-divider);
}




/* ── Section legends ──────────────────────────────────────────────── */
/* The title moves to where the fork puts its beat meta: small, upper
   case and right aligned, tight under the section's own rule. Kept in
   normal flow rather than absolutely positioned, because #ai-projects
   already has a positioned container for its canvas and an absolute
   legend would anchor to that in one section and to the band in the
   others. In flow it lines up with the content at every width.

   It stays an <h2>, so the document outline is unchanged. */
.section-header {
  display: block;
  margin: 0 0 clamp(40px, 7vh, 80px);
}

/* The two .projects sections wrap their header in a space-between flex
   row, so aligning inside .section-header aligned it within a
   shrink-to-fit child rather than within the band. That is why two
   legends sat left and two sat right. The wrapper goes back to block
   flow and every legend starts at the content edge. */
.projects__header {
  display: block;
}

/* One rule for all five legends, the Currently label included, so the
   two cannot drift apart. */
.section-title,
.quote__meta {
  display: block;
  font-family: var(--font);
  font-size: var(--b-size);
  font-weight: 400;
  letter-spacing: var(--b-track-label);
  text-transform: uppercase;
  color: var(--b-quiet);
  line-height: 1;
  text-align: left;
}
/* The old rule under the title was the divider; the band's own top
   border does that job now. */
.section-divider { display: none; }


/* ── One vertical rhythm ──────────────────────────────────────────── */
/* v1 had grown four different answers: the quote band at 80 top and 32
   bottom over a 460px floor, stats at 56 with its own outer margins,
   the rest at 72, and the showcase adding 192 of margin on each side.
   With rules now drawn on every join, those differences stopped being
   invisible. Each band gets the same padding and no outer margin, so
   the space between any two rules is twice --b-sec-pad everywhere.

   Last in the file deliberately: these share specificity with the
   rules they replace, so source order is what settles it. */
.quote,
.stats,
.projects,
.other-projects,
.about,
.contact {
  padding-top: var(--b-sec-pad);
  padding-bottom: var(--b-sec-pad);
  margin-top: 0;
  margin-bottom: 0;
}

/* The band was given a 460px floor to hold space while the quote typed
   itself in. The copy is static now, so it sizes to its content like
   every other section. */
.quote { min-height: 0; }

/* Decorative, and between two bands that already pad themselves. Its
   own margins would make that one join wider than all the others. */
.showcase {
  margin-top: 0;
  margin-bottom: 0;
}

/* No pulling the legend up out of the padding. Every legend is simply
   the first thing inside its band, so all five sit at --b-sec-pad
   below their rule and at the content's left edge, identical by
   construction rather than by five separate offsets. The earlier
   negative-margin trick put four of them under the rule but could not
   reach the Currently label, which lives in a centred flex block.

   That band is top-aligned here for the same reason: centred, its
   legend floated to wherever the content height put it. */
.quote {
  align-items: flex-start;
}
.quote__meta {
  margin-bottom: clamp(40px, 7vh, 80px);
}


/* ── Stats cards lose their frame ─────────────────────────────────── */
/* The grid drew its own box and ruled between the cells, which made
   sense when the band had nothing else marking its edges. The section
   rule does that now, and a framed grid inside a ruled band reads as
   two boxes nested rather than one row of numbers.

   The .is-ready gate in main.js still runs and still toggles a border
   colour that no longer resolves to anything visible. Left alone
   rather than torn out, so putting the frame back is one block. */
.stats__grid,
html.js .stats__grid,
html.js .stats__grid.is-ready {
  border: 0;
  border-radius: 0;
}
.stat,
.stat + .stat,
.stat:nth-child(n + 3),
.stat:nth-child(4n + 1) {
  border: 0;
}

/* The gutter moves to the grid rather than living as padding on each
   cell. Cell padding only separates columns, so at the 900px break,
   where the row folds to two columns, the second row ran straight into
   the first with nothing between them: the original grid sets gap: 0
   and relied on the cell borders to do the separating.

   Row gap is the larger of the two on purpose. Columns are told apart
   by alignment, but stacked rows only have the space itself, and a
   description here can run to three lines before the next number. */
.stats__grid {
  gap: clamp(40px, 6vh, 64px) clamp(24px, 3vw, 48px);
}
.stat {
  padding: 0;
}


/* ── The one band that outranked the rhythm ───────────────────────── */
/* #ai-projects sets padding-block twice by ID, 160px and 112px, which
   is (1,0,0) and beats the class rule above. That is why Building &
   Coding sat its legend much further from its rule than the others:
   the gap under a rule is the section's top padding, so one section
   keeping its own padding was one legend out of line.

   Matched on specificity and placed last rather than reached for with
   !important, so the next person can still override it normally. */
#ai-projects {
  padding-top: var(--b-sec-pad);
  padding-bottom: var(--b-sec-pad);
  margin-top: 0;
  margin-bottom: 0;
}


/* ── Diagrams ─────────────────────────────────────────────────────── */
/* No border, no radius, no ground. The frame was competing with the
   strokes inside it, which are the drawing, and the figure now sits
   straight on the page.

   Transparent is also what was happening already, by accident:
   case-study.css sets background to var(--bg-card), and --bg-card is
   not defined in any stylesheet, so the declaration has never
   resolved to anything. Stating it means the next person reading the
   rule is not misled. The same dead token is still used by
   .case-section--tinted and .case-output-col, left alone rather than
   changed in passing.

   Tags are deliberately left alone. They had been squared with
   everything else, but a tag is not a thing you press, and a pill
   says that better than a box does. style.css keeps them at 999px. */
.case-diagram {
  border: 0;
  border-radius: 0;
  background: transparent;
  /* The padding was insetting the drawing from the frame. With no
     frame it is just up to 44px of dead space on every side, and on a
     narrow screen it was eating width the figure needed. */
  padding: 0;
  /* It had none of its own, relying on the frame to separate it from
     the copy either side. */
  margin-block: clamp(28px, 4vh, 44px);
}

/* case-study.css already gives the wrapper overflow-x: auto and touch
   scrolling, so a figure wider than the screen scrolls rather than
   squashing. The 820px floor on the svg is what triggers that, and it
   is a legibility floor rather than an arbitrary one: the viewBox is
   960 wide with 13.5px labels, so at 820 they render around 11.5px,
   and anything narrower starts dropping under 10. Phones therefore
   scroll a 820px figure instead of shrinking it to 354px, where the
   labels would be about 5px. */
.case-diagram svg {
  min-width: 820px;
}

/* Fields take the same hairline the header's menu cells use, so the
   form and the strip are drawn with one line weight. The fill stays
   as it was.

   --b-divider rather than --b-rule is the header's value, and it is
   the softer of the two at 2.17:1. That is the right call for a
   divider, which 1.4.11 exempts, but a field's border is the
   control's own boundary and the same clause asks 3:1 of it. Matched
   to the header deliberately; --b-rule is the one-word change if the
   fields should clear it on their own.

   Focus turns the whole edge orange, border and an inset ring
   together so it reads as 2px without the box growing. Accent on the
   page is 6.21:1. */
.form-group input,
.form-group textarea {
  border: 1px solid var(--b-divider);
  border-radius: 0;
}
.form-group input::placeholder,
.form-group textarea::placeholder {
  color: var(--b-placeholder);
  opacity: 1;    /* Firefox dims placeholders by default */
}
.form-group input:focus,
.form-group textarea:focus {
  outline: 0;
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}


/* ── Bento: a ruled block, not floating cards ─────────────────────── */
/* The band read as hidden for two reasons at once. Every tile carried a
   30% black veil on top of an already dimmed image, and the rounded
   corners with 12px of gap and padding made the work look like cards
   resting on the page rather than part of it.

   Brutalist treatment: square corners, hairline seams, no outer
   padding, and the veil cut to the point where it still unifies the
   set without burying it. The band also takes rules top and bottom,
   so it joins the page's rhythm instead of floating between two
   sections that have them. */
.showcase {
  border-top: 1px solid var(--b-divider);
  border-bottom: 1px solid var(--b-divider);
}

/* Edge to edge, tiles butted against each other. The 1px seam is the
   same hairline the sections use, so the grid reads as the page
   divided rather than as a gallery with its own margins. */
.showcase__stage--bento {
  gap: 1px;
  padding: 0;
  background: var(--b-divider);
}
.showcase__col {
  gap: 1px;
}

/* Square, and no radius left at the small breakpoints either, where
   the original stepped down to 10px. */
.showcase__tile {
  border-radius: 0;
}

/* The veil sits at 0.22, between the original 0.3, which buried the
   work, and the 0.12 that let the tiles glare against a page whose
   darkest element is otherwise a hairline. Enough to hold the set
   together, not enough to hide it. */
.showcase__tile::after {
  background: rgba(0, 0, 0, 0.22);
}
body.theme-light .showcase__tile::after {
  background: rgba(255, 255, 255, 0.10);
}

/* The crossfade fallback, for no-JS and before the grid is built,
   tracks the tiles so the two paths look the same. */
.showcase__img {
  filter: brightness(0.86);
}
.showcase__stage::after {
  background: rgba(0, 0, 0, 0.24);
}
body.theme-light .showcase__img {
  filter: brightness(0.96);
}
body.theme-light .showcase__stage::after {
  background: rgba(0, 0, 0, 0.10);
}


/* ── Hero foot ────────────────────────────────────────────────────── */
/* 96px under the buttons was set when the next thing down was a band
   with no edge of its own. The section below now draws a rule across
   the full width, and a rule reads as a harder stop than empty space
   does, so the hero needs more room before it to avoid looking cut
   off. +50 at both sizes. */
.hero {
  padding-bottom: 146px;
}
@media (max-width: 768px) {
  .hero {
    padding-bottom: 114px;
  }
}


/* ── Motion, taken from the fork ──────────────────────────────────── */
/* v1 and v2 reveal the same way, an IntersectionObserver adding a
   class, so this is a retune rather than new machinery. The fork's
   .rise is the reference:

     opacity   .34s linear                       .16s delay
     transform .34s cubic-bezier(.2,.9,.2,1)     .16s delay
     distance  10px

   Against v1's 420ms, 18px and cubic-bezier(.16,1,.3,1), the fork
   travels a shorter distance, arrives sooner and decelerates less
   abruptly. The shared delay is what gives it its character: nothing
   moves for the first sixth of a second after the element crosses the
   line, so the reveal reads as deliberate instead of reactive. */
html.js .reveal {
  transform: translateY(10px);
  transition: opacity 0.34s linear 0.16s,
              transform 0.34s cubic-bezier(0.2, 0.9, 0.2, 1) 0.16s;
}

/* The fork reveals a whole beat at once and has no sibling stagger.
   v1's cascade is worth keeping, but 120ms steps were paced against
   the slower transition: by the sixth card that was 600ms of waiting
   on top of the delay. 60ms keeps the cascade at the new tempo. */
html.js .stagger > .reveal:nth-child(1) { transition-delay: 0.16s; }
html.js .stagger > .reveal:nth-child(2) { transition-delay: 0.22s; }
html.js .stagger > .reveal:nth-child(3) { transition-delay: 0.28s; }
html.js .stagger > .reveal:nth-child(4) { transition-delay: 0.34s; }
html.js .stagger > .reveal:nth-child(5) { transition-delay: 0.40s; }
html.js .stagger > .reveal:nth-child(6) { transition-delay: 0.46s; }

/* The bento tiles ran on 900ms and 28px, from before any of this. */
html.js .showcase__tile {
  transform: translateY(10px);
  transition: opacity 0.34s linear 0.16s,
              transform 0.34s cubic-bezier(0.2, 0.9, 0.2, 1) 0.16s;
  transition-delay: calc(0.16s + var(--reveal-i, 0) * 0.06s);
}
.showcase__stage--bento.is-visible .showcase__tile {
  transform: none;
}

/* style.css already silences all of this under reduced motion, but
   those rules sit earlier in the cascade and the ones above would
   otherwise reinstate the transitions they turn off. Restated here,
   last, so the preference still wins. */
@media (prefers-reduced-motion: reduce) {
  html.js .reveal,
  html.js .showcase__tile,
  html.js .stagger > .reveal:nth-child(1),
  html.js .stagger > .reveal:nth-child(2),
  html.js .stagger > .reveal:nth-child(3),
  html.js .stagger > .reveal:nth-child(4),
  html.js .stagger > .reveal:nth-child(5),
  html.js .stagger > .reveal:nth-child(6) {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s;
  }
}


/* ── The last rounded corners ─────────────────────────────────────── */
/* The case previews on the home and the masterclass card. Both are a
   rounded wrapper with overflow:hidden doing the clipping, so the
   image inside is squared too rather than relying on the parent. */
.case-card__media,
.case-card__img,
.mentorship__card,
.mentorship__card-img {
  border-radius: 0;
}


/* ── Stacked copy and controls ────────────────────────────────────── */
/* .archive-card__actions carried margin-top: 4px and no bottom margin,
   which was fine while it was always the last thing in its column. In
   the Leading section it now sits between two paragraphs, so the
   button nearly touched the copy above it and the paragraph below ran
   straight into it.

   A control is a heavier object than a line of text, so it needs more
   room than the 20px that separates two paragraphs, and the same
   amount on both sides. Where it is still the last child the bottom
   margin does nothing, since the band's own padding follows. */
.archive-card__actions {
  margin-top: 36px;
  margin-bottom: 36px;
}

/* 36px between paragraphs. 20px was set when the copy sat on its own;
   against 32px of space around the buttons it read as cramped, and
   the two stacked items in this column need the copy inside each one
   to breathe at roughly the same rate as the controls between them. */
.about__text {
  margin-bottom: 36px;
}

/* A title needs the gap above it as well, or the space between two
   items is only whatever the thing above happened to leave. Adjacent
   margins collapse to the larger of the two, so with every value in
   this column set to the same 36 the gap is 36 wherever two elements
   meet, regardless of which pair it is. Flat, not a clamp: a clamp
   would make the measurement drift with viewport height. */
.about__lede {
  margin-top: 36px;
  /* 12 below, not 36. A title belongs to the copy under it, so the
     gap that separates the two items has to be larger than the gap
     inside one. The 36 above still does the separating. */
  margin-bottom: 12px;
}
/* Except at the top of a column, where that margin would push the
   whole block down rather than separate anything. */
.about__lede:first-child {
  margin-top: 0;
}


/* ── Heading wipe ─────────────────────────────────────────────────── */
/* The fork's typographic move: each word clipped to nothing and
   uncovered upward, 30ms apart. Values are the fork's own, .44s on
   the same curve the reveal uses.

   102% rather than 100% on the closed state, because a clip at
   exactly the box edge leaves a hairline of antialiasing showing on
   some renderers. -10% on the open state gives descenders room to sit
   outside the box rather than being shaved. */
html.js [data-split] .w {
  display: inline-block;
  clip-path: inset(0 0 102% 0);
  transform: translateY(0.1em);
  transition: clip-path 0.44s cubic-bezier(0.2, 0.9, 0.2, 1),
              transform 0.44s cubic-bezier(0.2, 0.9, 0.2, 1);
}
html.js [data-split].visible .w {
  /* Generous on every side, and well outside the box. The fork uses
     -10% at the bottom, which is fine for its headings; these run at
     line-height 0.92, so the word's box is shorter than the glyphs
     and a descender hangs roughly 22% of the box height below it.
     -10% sliced the tail off every y, g and p. */
  clip-path: inset(-30% -10% -40% -10%);
  transform: none;
}

/* These headings are .reveal as well, so without this the block would
   fade and rise while its own words were wiping in: two animations
   doing the same job at different speeds. The words carry it alone. */
html.js [data-split].reveal {
  opacity: 1;
  transform: none;
  transition: none;
}

/* Belt and braces. The script returns before splitting under reduced
   motion so no .w exists to match, but if one ever did, this stops it
   animating. */
@media (prefers-reduced-motion: reduce) {
  html.js [data-split] .w {
    clip-path: none;
    transform: none;
    transition: none;
  }
}


/* ── Case card tags ───────────────────────────────────────────────── */
/* The row had been running the full width of the card while the
   description above it stops at 52ch, so the tags reached past the
   text they belong to. Same measure now, and flowing from the left,
   so the first tag starts on the same edge as the first word of the
   description and the row wraps where the text wraps. */
.case-card__tags {
  max-width: 52ch;
  justify-content: flex-start;
}


/* ── Leading, two columns ─────────────────────────────────────────── */
/* 120px between the copy and the masterclass block on wide screens.
   style.css runs 80px from 1025 up and 48px in the single-column
   layout below that, so this only lifts the top band: at 1025 the two
   columns are about 430px each and a 120px channel between them would
   take more than it gave. From 1200 they are roughly 490px and can
   carry it. */
@media (min-width: 1200px) {
  .about__inner {
    gap: 120px;
  }
}


/* ── Lately band ──────────────────────────────────────────────────── */
/* The block was pinned at 900px while the container around it runs to
   1440, so on a wide screen it sat in a column noticeably narrower
   than everything else on the page, and the type stopped growing at
   1333px even though the band kept getting wider.

   The cap goes entirely, so the block fills the container like every
   other band on the page rather than sitting in a column of its own.

   The type is deliberately left at its original clamp. Scaling it
   with the width held the line length constant, but the ask was for
   the paragraph to occupy more of the page, not to shout louder, and
   those are opposite moves: bigger type in a wider block changes
   nothing about how much room the words take. */
.quote__block {
  max-width: none;
}
