/* ==========================================================================
   NUVORM — layout.css (Phase 1b — §34 client-feedback revision, 2026-10-14)
   Page skeleton: header, menu overlay, hero, about (merged intro), the
   §34.11 quad grid (WORK + NEWS as one 4-col × 4-row vertical unit), the
   CONTACT (black, contains the footer). Mockups are the visual authority
   for STRUCTURE; §34 directives + §2.3/§12–§33 surviving laws govern CRAFT.
   §34.11 SUPERSESSION: the §31/§32/§33 horizontal band mechanics (band
   scrollers, edge arrows, edge-pan on the main page) are RETIRED — the main
   page scrolls vertically only; side-scrolling lives ONLY in the lightbox
   (§34.13).
   ========================================================================== */

/* --------------------------------------------------------------------------
   Header — fixed, transparent. §35.2 AMENDED: the header NEVER hides
   wholesale any more (the §34 .site-header.is-hidden translate/visibility
   mechanic is RETIRED, with its :focus-within/menu-open returns) — the menu
   control stays reachable at every scroll position. Only the LOGO leaves:
   app.js toggles #logo-wordmark.logo-hidden on scroll-through; the CSS
   guarantees below return it while the menu is open or any focus sits in
   the header. html.header-on-dark is SCROLL-driven over #contact now (JS
   sets it while the dark field is under the header) + the focus-driven
   state carries — it swaps the wordmark to its white twin AND whitens the
   menu button (currentColor glyph, below).
   -------------------------------------------------------------------------- */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* §35.1: the enlarged 180px mark renders 28.3px tall (the 2478×390 master
     ratio) — the 88px header still holds it uncrowded (verified in capture;
     the anticipated ~104px raise was NOT needed). */
  min-height: 88px;
  padding: 0 clamp(20px, 2.5vw, 36px);
}
/* §35.2 the logo's own hide state (240ms, small lift — reads as the mark
   receding, not the chrome dropping). §38.1: the menu-open/focus-within
   re-show guarantees are RETIRED with the [data-header] re-show law — the
   wordmark features in the HERO only and never returns past it (the menu
   control's visibility never depended on these rules; it is never hidden). */
.logo-link.logo-hidden {
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: opacity 240ms var(--ease), transform 240ms var(--ease);
}

.logo-link {
  position: relative; /* seats the white legibility twin (below) */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin: -1px;
}
/* §34.4 AS AMENDED §34.19.1, §35.1 — the +5% law history: 122 → 128px
   (§34.19.1); §35.1 raises the header mark to the FOOTER scale: width
   var(--mark-w) = 180px, the exact slot the footer wordmark occupies
   (client: "bigger, to match the footer logo's scale"). The mark's height
   is 28.3px — see the header note above. .logo-white twin matches EXACTLY
   (law carried). Trigger geometry untouched; header legibility state
   (html.header-on-dark) untouched. */
.wordmark { width: var(--mark-w); height: auto; }
/* §30.4 colour-legibility twin — the white wordmark identity, swapped in by
   html.header-on-dark (app.js toggles it while keyboard focus sits inside the
   dark contact field; a focus-driven state, NOT the retired §30.4 mechanic).
   Both identities are stacked in .logo-link so the swap is pixel-exact. */
.logo-white {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: var(--mark-w); /* §34.19.1 + §35.1: twin matches the wordmark exactly */
  height: auto;
  opacity: 0;
  pointer-events: none;
}
.wordmark, .logo-white { transition: opacity 160ms ease; }
html.header-on-dark .wordmark { opacity: 0; }
html.header-on-dark .logo-white { opacity: 1; }

/* §34.3 menu button AS AMENDED §35.2/§35.3 — ALWAYS visible (the header no
   longer hides; the logo's hide state never touches the button). 44px hit
   area + ARIA/keyboard contract; radius 0 per §34.8. The svg glyph and its
   §35.3 morph live in components.css (the old 27.5×27 geometry rules are
   retired there too). html.header-on-dark whitens the button — the glyph is
   currentColor: */
.menu-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: -1px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--black);
  cursor: pointer;
}
html.header-on-dark .menu-button { color: var(--white); }
/* §38.2 MENU-OPEN CONTRAST AT DARK SECTIONS — once the menu is open the
   trigger glyph (stripes + nu, ONE currentColor svg) renders in the PANEL's
   ink (dark) on the white panel, regardless of the section beneath; the
   CLOSED trigger keeps section-adaptive contrast (white-on-black at
   #contact stays). There is no colour transition on the button, so the swap
   is instant at menu-open: the morph mid-state (stripes fading out + nu
   fading in) is already dark over the white panel — it can never flash
   invisible. Light sections are unchanged (they were dark-on-white too). */
html.menu-open .menu-button { color: var(--black); }

/* --------------------------------------------------------------------------
   §29.2 Menu overlay — full-height right panel, white, links right-aligned
   (mockup 2). Hidden by default (display:none); html.menu-open (set by JS)
   reveals it with the staggered entrance. Scrim: translucent white over the
   page. ESC/backdrop/focus-trap = motion's app.js; this is structure + state.
   JS-off: overlay never opens — the .menu-fallback row in #contact keeps every
   target reachable (progressive enhancement law, §8-A7).
   -------------------------------------------------------------------------- */
.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: none;
}
.menu-overlay::before { /* scrim over the page */
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.6);
}
.menu-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(420px, 86vw);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  /* §35.10 (B9 pass): top 128→144, bottom 48→56 — one conservative step of
     air on the panel. */
  padding: 144px clamp(24px, 3vw, 44px) 56px;
  background: var(--white);
  border-left: 1px solid var(--black);
}
.menu-list {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: clamp(20px, 4vh, 34px);
  margin: 0;
  padding: 0;
  list-style: none;
}
.menu-link {
  font-family: var(--font-display);
  font-size: clamp(24px, 3vw, 34px);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.1;
  text-decoration: none;
}
.menu-link:hover { text-decoration: underline; text-underline-offset: 6px; }

/* Entrance: display flip + keyframes (animations restart when .menu-open
   flips display — reliable without transition bookkeeping). .js-gated.
   NOTE: .menu-open lives ON <html> (set by app.js) — compound selector
   html.js.menu-open, not the descendant form (motion-designer blocker fix).
   §34.5 MENU ANIMATION SLOWER ~+50%: panel-in 420→600ms, scrub 280→400ms,
   link-in 380→530ms, link stagger 80/150/220/290→110/205/300/395ms (+40%).
   Reduced-motion: instant (base.css kill-switch) — untouched law. */
html.js.menu-open .menu-overlay { display: block; }
html.js.menu-open .menu-panel { animation: menu-panel-in 600ms var(--ease) both; }
html.js.menu-open .menu-overlay::before { animation: menu-scrub-in 400ms ease both; }
html.js.menu-open .menu-link {
  opacity: 0;
  transform: translateX(24px);
  animation: menu-link-in 530ms var(--ease) both;
}
html.js.menu-open .menu-list li:nth-child(1) .menu-link { animation-delay: 110ms; }
html.js.menu-open .menu-list li:nth-child(2) .menu-link { animation-delay: 205ms; }
html.js.menu-open .menu-list li:nth-child(3) .menu-link { animation-delay: 300ms; }
html.js.menu-open .menu-list li:nth-child(4) .menu-link { animation-delay: 395ms; }
@keyframes menu-panel-in { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes menu-scrub-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes menu-link-in { to { opacity: 1; transform: translateX(0); } }

/* JS-off fallback row lives in #contact (see layout below) */
.menu-fallback { display: flex; }
html.js .menu-fallback { display: none; }
.menu-fallback a {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  text-underline-offset: 4px;
}
.menu-fallback a:hover { text-decoration: underline; }
.contact-section .menu-fallback { gap: 28px; padding: 96px var(--edge) 0; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   §29.3 Hero — full-viewport edge-to-edge carousel. Slides are STACKED
   (crossfade-ready for motion's app.js). Slide 1 renders in every state;
   slides 2–5 render only with .is-active (JS) — JS-off/pre-init shows slide 1
   only. §34.2: the dot nav is BARE CIRCLES on the photo (the white plate is
   retired) — white ring/fill + soft shadow for photo legibility; dots STAY
   circles (the named §34.8 circle exemption).
   §35.4 CYCLE-2 ADJUDICATION: this stacked block is AGAIN the single hero
   layout — the cycle-1 .hero--drift flex-strip below is RETIRED; §35.4
   continuity now rides as a continuous ken-burns pan INSIDE the active
   slide's img (components.css §35.4 block: @keyframes hero-pan / hero-pan-rev,
   6800ms linear). The pan never pauses (no :hover/:focus hook exists).
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  height: 100svh;
  min-height: 560px;
  overflow: hidden;
  background: var(--black); /* pre-paint field behind the first image */
}
.hero-track { position: relative; height: 100%; margin: 0; }
.hero-slide {
  position: absolute;
  inset: 0;
  display: none;
  margin: 0;
}
.hero-slide:first-child,
.hero-slide.is-active { display: block; }
.hero-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
/* §35.4 CYCLE-2 RETIRED RULE — the cycle-1 .hero--drift block is REMOVED
   (adjudication, §35 critic cycle-1 fixNow 1): the flex-strip drift read as
   an image strip with clipped captions and is superseded. The stacked
   crossfade carousel above stands as the single hero layout; §35.4's
   never-pause continuity is preserved as the ken-burns pan on the active
   slide's img — see components.css (hero-pan / hero-pan-rev keyframes).
   JS-off: .is-active never applies, so slide 1 renders static via
   :first-child — static-safe by construction. Reduced-motion: components.css
   reduce block zeroes the pan (animation: none !important on .hero-slide img). */
/* §34.1 hero caption — BARE TEXT ON THE PHOTO: the white chip (background/
   border/padding/radius) is RETIRED; never a box again. White + a soft
   legibility shadow; the evidence-tag string (capability — place — project)
   is unchanged (§2.9.1 tagging law). */
.hero-tag {
  position: absolute;
  bottom: 18px;
  left: 18px;
  z-index: 2;
  max-width: min(55%, 360px);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.3;
  color: var(--white);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55), 0 0 14px rgba(0, 0, 0, 0.28);
}
/* §34.2 bare dot nav — no plate: the circles sit directly on the photo. */
.hero-dots {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  display: none; /* revealed under .js (JS manages the carousel) */
  gap: 10px;
  padding: 0;
  background: transparent;
  border-radius: 0;
}
html.js .hero-dots { display: flex; }
/* cycle-1 fixNow 1 (WCAG 2.2 pointer-target): the BUTTON is the hit area —
   44×44 CSS px (2.2's 24×24 floor cleared, the preferred 44×44 met); the
   visible 12px circle is the ::before ring below, still centred. The hit
   padding between button box and visible circle is fully transparent — no
   plate, no edge — the §34.2 bare-on-photo look is unchanged. */
.hero-dot {
  position: relative;
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 50%; /* §34.8 named circle exemption */
  cursor: pointer;
}
/* white ring + soft shadow = photo legibility (§34.2); active = white fill */
.hero-dot::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 12px;
  height: 12px;
  transform: translate(-50%, -50%);
  border: 1.5px solid var(--white);
  border-radius: 50%;
  background: transparent;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.hero-dot[aria-current="true"]::before { background: var(--white); }

/* --------------------------------------------------------------------------
   §34.9 ABOUT — ONE ≥100svh white section (client: "the intro section should
   be the same dimensions as the hero, and fill the screen with white — the
   hero before and tiles after should not be visible"): the giant draw-on
   wordmark + about copy + the positioning block all live inside it. The
   opaque white background + min-height 100svh guarantee the edge-to-edge
   white screen (no overlap tricks); the composition is budgeted to fit
   1440×900 and 1920×1080 uncrowded (§34.19.2: the wordmark is BACK at its
   pre-§34 width cap min(100%, 1280px) — the §34 fullscreen composition's
   1600px read "a little too giant"; the fixNow-2 block rhythm below STAYS).
   Mobile = natural height, same order, same laws. §33.3: the copy centres
   horizontally under the wordmark. §35.8 FLOOR EXCEPTION (FLAGGED DEVIATION,
   client-sanctioned): #about MAY exceed 100svh — the §35.6 client copy + the
   five §35.7 disciplines own the honest length; min-height stays the floor
   and the section grows to its natural height (the overflow guard IS the
   natural growth: nothing clipped, no inner scroll — min-content floor =
   natural height). The §34.21/§34.22 pinned paddings below stay EXACT —
   growth comes from the new content, not re-doubled air.
   -------------------------------------------------------------------------- */
/* cycle-1 fixNow 2 — the vertical rhythm is REDISTRIBUTED so the whole
   composition (wordmark → copy → CTA → heading → the four stacked
   disciplines) fits the viewport at the natural intro position: every
   discipline box fully inside 100svh at 1440×900 AND 1920×1080, uncrowded
   (measured air ≈59px / ≈127px below the last discipline), 1920×1080 clean.
   The §34.7 in-copy spacing laws are untouched (cities gap stays 28px,
   lede 1.7 line-height); what moved is the BLOCK rhythm: padBottom 64→48,
   wordmark→copy 48→36, cities→CTA 36→28, CTA→heading 48→32,
   heading→disciplines 36→24 + list gap 14→12, and the h1's unreset
   default margin (21.4px top+bottom of dead space) is zeroed. */
.about {
  display: flex;
  flex-direction: column;
  justify-content: space-between; /* §36.12: distribute only spare viewport height within the intro so its top/bottom spacing units stay equal. */
  min-height: 100vh;
  min-height: 100svh;
  /* §36.8: the hero→intro-logo air HALVES — the §34.21 176px client pin
     becomes var(--intro-gap) = 88px (Dillon's directive supersedes the
     §34.21 doubling). §36.12 SPACING SYMMETRY: the SAME unit governs the
     arrangement→work distance below the intro block; space-between uses
     any spare 100svh height between the interior flex children, never at
     the outer edges, so both measured edge gaps remain equal. */
  --intro-gap: 88px;
  /* §37.3.1 PARAGRAPH↔WORDMARK EDGE ALIGNMENT — the intro's content width
     is ONE token: the giant wordmark and the paragraph blocks (.about-copy,
     the panel/stacked discipline paragraphs) all resolve their edges from
     var(--intro-w), so the paragraph edges line up with the wordmark's
     edges at every breakpoint (±2px verified at 1440/768/390). No
     per-paragraph values remain. */
  --intro-w: min(100%, 1280px);
  padding: var(--intro-gap) var(--edge) var(--intro-gap);
  background: var(--white);
}
.wordmark-hero {
  display: block;
  width: var(--intro-w); /* §37.3.1 token (§34.19.2: reverted from the §34 1600px cap) */
  margin: 0 auto; /* §33.3 centring; block margins 0 — the h1 default
                     margin-block (21.4px) was dead space at both ends
                     (fixNow-2 zeroing STAYS, §34.19.2) */
}
.wordmark-hero svg { display: block; width: 100%; height: auto; fill: currentColor; }
/* §33.3: the intro copy centres horizontally under the (centred) nuvorm
   wordmark — auto inline margins. §36.1: the block widened 62ch →
   min(1160px, 88vw) so the two paragraphs could sit SIDE BY SIDE. §37.3.1
   SUPERSEDES the 1160px/88vw measure: the block now spans the SAME token
   as the wordmark (var(--intro-w)) — the paragraph edges sit exactly on
   the wordmark's edges (Dillon: "align edges of paragraphs to line up with
   the edges of the nuvorm logo"), still centred (the §33.3 law). The
   §34.21 72px margin-top pin is untouched. */
.about-copy { max-width: var(--intro-w); margin: 72px auto 0; } /* §34.21: 36→72 (fixNow 2: 48→36); §37.3.1: block width = the wordmark token */
/* §36.1 TWO-COLUMN PARAGRAPHS — .about-cols wraps the two .about-lede;
   two equal columns, gutter clamp(32px, 4vw, 64px) (Dillon's ~4vw register).
   Both paragraphs are the verbatim client copy (COPY-ONLY law). */
.about-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(32px, 4vw, 64px);
  align-items: start; /* columns top-align (the paragraphs differ in length) */
}
/* the 28px stacked-paragraph register applies to the pre-§36.1 adjacency
   (and the ≤820px single-column stack below) — inside the two-column grid
   the gutter owns the separation, so the second column's top margin is zero */
.about-cols .about-lede + .about-lede { margin-top: 0; }
/* ≤820px: single column stack (parity of comprehension, not of effect —
   the §2.4 adaptive law); the 28px paragraph register returns. */
@media (max-width: 820px) {
  .about-cols { display: block; }
  .about-cols .about-lede + .about-lede { margin-top: 28px; }
}
/* §34.7 intro text — "slightly smaller, better spaced": lede down 1–2px
   (18→16–17px effective), line-height 1.55→1.7, paragraph/cities spacing
   20→28px. (Inter woff2 is variable wght 100–900 — verified; no faux weights.)
   §35.6: the copy is now TWO verbatim paragraphs — the second one was butting
   the first (both carry margin 0 from the §34 single-paragraph grammar); the
   28px paragraph register (§34.7's own spacing law) now separates them (B9). */
.about-copy .about-lede {
  margin: 0;
  font-family: var(--font-body);
  font-size: clamp(15px, 1.15vw, 17px);
  line-height: 1.7;
}
.about-lede + .about-lede { margin-top: 28px; }
/* §36.1: the tail line (based-statement + contact detail — §36.10 merges
   them onto ONE line; §36.9 retires the name story, the nu echo lives only
   in the menu icon §35.3) sits CENTERED below the two columns — the intro
   is one centred composition (§33.3: wordmark, copy block, positioning all
   centred); with the block widened to min(1160px, 88vw) a left-rag tail
   would orphan at the far left edge. */
.about-cities {
  margin: 28px 0 0;
  text-align: center;
  font-family: var(--font-body);
  font-size: clamp(15px, 1.15vw, 17px);
  line-height: 1.7;
}
.about-cities strong { font-weight: 700; }
/* §36.10: the contact detail rides the SAME line as the based-statement —
   inline, the CTA register preserved (display face, 600, the standing
   arrow); the old flex .about-cta block retires with the merge.
   §39.3 (Dillon 2026-10-23): the intro's reveal mechanic RETIRES — the
   line is now a PLAIN ANCHOR to #contact. The landing reuses the
   §36.13.1 settle path natively: html scroll-behavior smooth + the
   scroll-margin-top: 0 flush law (base.css) → the same designed rest as
   the menu links; the scroll-driven header-on-dark engages on the glide.
   Reduced-motion: the kill-switch sets scroll-behavior auto → instant.
   JS-off: a plain anchor, still reaches #contact. The affordance follows
   the §36.4 discipline-name register (underline on hover/focus only). */
.about-contact {
  font-family: var(--font-display);
  font-weight: 600;
  white-space: nowrap;
  color: inherit;
  text-decoration: none;
}
.about-contact:hover,
.about-contact:focus-visible { text-decoration: underline; text-underline-offset: 6px; }
.about-contact .cta-arrow { font-weight: 400; }

/* --------------------------------------------------------------------------
   §34.10 AS AMENDED §35.7, REBUILT BY §36.2/§36.4 (inside #about) — "FROM
   FRAMEWORK TO BUILDING" + the FIVE disciplines (client's numbered order
   01–05, §35.C) as BUTTONS in the inline row; the §35.7 details/dropdown
   machinery is RETIRED (§36.4 shared centred panel — see the block below).
   The SEQUENTIAL reveal after the heading is KEPT and extended to five
   beats (delays in components.css §34.10 block; the shared panel joins at
   the sixth beat). Reduced-motion/JS-off: static/stacked instantly.
   Mobile: the li stack (row-gap 16px at ≤700 — see the block below).
   Rhythm values are the §34.19.2 numbers — heading→list 24px, row gap
   12px — client-pinned, untouched.
   -------------------------------------------------------------------------- */
.positioning {
  margin-top: 64px; /* §34.21: 32→64 (fixNow 2: 48→32) */
  text-align: center;
}
/* §38.5 (Dillon, mid-loop): the positioning KICKER is RETIRED entirely —
   the .positioning-kicker rule (§35.8/§35.7 quiet index register, §36.13
   sentence case) is removed with its markup; the positioning title stands
   alone. No dead hooks remain (the construct-beat delay hook went with it
   in components.css). */
/* §34.7 positioning title — TEXT heading (giant words are UNCHANGED brand
   glyphs): clamp cap down ~11% (64→57), weight 700→600 (Inter variable —
   real weights, never faux). §35.7: the all-caps TEXT renders as typed —
   no text-transform (all pinned values unchanged). */
.positioning-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(30px, 4.5vw, 57px);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.08;
}
/* §36.2/§36.4 DISCIPLINES — REBUILT. The 01–05 numbering (.discipline-num)
   and the per-item dropdown machinery (details.discipline +
   .discipline-drop, the native-marker hides) are RETIRED. Each li holds
   button.discipline-name (the name — hover/focus/tap/click swap the panel)
   + p.discipline-text (the verbatim write-up): the JS-off baseline shows
   it — every li stacks name + full write-up, all five visible (parity of
   comprehension, §5 AC8); html.js hides the per-li text and hands the copy
   to the ONE shared centred panel below the row (the html.js .tile-info
   gate pattern). */
.discipline-name {
  display: inline-block;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  text-align: inherit; /* centred in the row (inherited from .positioning) */
}
/* the affordance: underline on hover AND focus-visible AND the persistent
   active mark (§36.4: the active state = the existing underline register);
   focus adds the 3px outline (black on the white about field) */
.discipline-name:hover,
.discipline-name:focus-visible,
.discipline-name[data-active] { text-decoration: underline; text-underline-offset: 6px; }
.discipline-name:focus-visible { outline: 3px solid var(--black); outline-offset: 2px; }
.discipline-text {
  /* §37.3.1: the stacked (JS-off) write-up's box spans the intro token —
     its edges sit on the wordmark's edges (was max-width 62ch + auto
     margins, which stepped the paragraph 60px inside the wordmark at
     1440). Left-aligned text on the shared left edge; the ragged right is
     the text's own. */
  margin: 10px 0 0;
  text-align: left;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
}
html.panel-on .discipline-text { display: none; } /* the app.js-execution gate (§36.4): with the panel system live the copy lives in the shared panel */

/* §36.4 the SHARED CENTRED PANEL — in-flow, full width of the positioning
   block, centred text; the swap is a quiet 180ms opacity reveal (discrete,
   no continuous animation). display:none without JS (the per-li write-ups
   carry the copy then). The panel's min-height is sized to the LONGEST
   write-up (02's) at each breakpoint so a swap can never reflow the page —
   values below are MEASURED (see the §36 report for the per-breakpoint
   numbers). */
.discipline-panel { display: none; }
html.panel-on .discipline-panel {
  display: block;
  margin-top: 24px;
  min-height: 76px; /* MEASURED (§36): sized to the longest write-up so swaps never reflow the page. §37.3.1: the panel text now wraps at the full intro token (fewer lines) — the longest write-up measures 2 lines (48px) at 1280px, so the 76px floor still owns the box height and the fixed-spot law holds unchanged. */
  text-align: center;
}
.discipline-panel p {
  /* §37.3.1: box spans the intro token (edges on the wordmark's edges);
     the text itself stays centred (the §36.4 panel register) — the removal
     of max-width/auto margins changes the box, not the rendered words. */
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  opacity: 1;
  transition: opacity 180ms ease; /* the quiet reveal (§36.4 (d)) */
}

.capability-line {
  display: flex;
  flex-direction: column; /* JS-off baseline: the stacked name + write-up index (§36.4 (b)) */
  align-items: stretch;
  row-gap: 12px; /* fixNow 2: 14→12 — the tightened rhythm STAYS */
  margin: 24px 0 0; /* fixNow 2: 36→24 */
  padding: 0;
  list-style: none;
  font-family: var(--font-display);
  /* §36.4 REGISTER QUESTION RESOLVED: the 18px single-line cap (and the
     ≥1360 nowrap guard) existed ONLY to kill the per-item-dropdown
     collision; with the dropdowns retired and the panel in-flow BELOW the
     row, the approved §34.7 register clamp(15px, 1.6vw, 22px) is RESTORED
     and the nowrap guard is DROPPED — the row wraps freely (wrap is now
     safe: no overlay exists). The rendered row state at 1440 and the
     #about height impact are recorded in the §36 report. */
  font-size: clamp(15px, 1.6vw, 22px);
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.3;
}
/* app.js running: the inline row returns (the §34.19.3 client-approved
   composition); it wraps naturally at every width — no overlay to collide
   with any more. Gated to html.panel-on (the app.js-execution class — see
   the panel block above): JS-off keeps the stacked index. */
html.panel-on .capability-line {
  flex-direction: row;
  flex-wrap: wrap; /* ≤1026 keeps the free wrap — §39.1: 3+2 at 768 measured */
  justify-content: center;
  align-items: baseline;
  column-gap: clamp(24px, 3.5vw, 48px);
}
/* §39.1 (Dillon 2026-10-23): DESKTOP SINGLE-LINE LAW — at ≥1027 the five
   disciplines sit on ONE line. MEASURED first (39w1 probes): after the
   §39.2 rename the row's natural width is 50.885px per font-px (sum at a
   100px reference = 5088.5px at -0.01em) + 4 gaps. REGISTER CONSEQUENCE
   (FLAGGED per §39.1 — recorded with numbers, never silent): the pinned
   §34.7 register clamp(15px, 1.6vw, 22px) cannot hold one line across the
   full desktop band even with tightened gaps — at 1366 the 1.6vw register
   (21.86px) needs 1223px vs the 1206px token; at 1150 18.4px needs 1149px
   vs 990px. The 22px CAP and 15px FLOOR are kept; between them the size
   YIELDS TO FIT via the measured law below (gaps tightened 3.5vw→2vw,
   tracking -0.005em→-0.01em). Rendered register table (fit law vs pinned):
     1027 → 15px  (pinned 16.4)   1150 → 17.5px (pinned 18.4)
     1366 → 21.5px (pinned 21.9)  1440 → 22px  (pinned — EXACT, cap holds)
     1920/2560 → 22px (pinned — exact)
   The 22px register law holds from ~1400px up; the flagged consequence is
   confined to the 1027–1400 band. FLAG on Dillon's desk: accept the fit
   register, or the row returns to 2 lines at those widths.
   Tablet (≤1026) keeps the free wrap (3+2 at 768 re-verified after the
   rename); mobile ≤700 keeps the stacked column (§36.4 parity law). */
@media (min-width: 1027px) {
  html.panel-on .capability-line {
    --row-gap: clamp(24px, 2vw, 40px);
    flex-wrap: nowrap;
    column-gap: var(--row-gap);
    letter-spacing: -0.01em;
    /* fit law: one line must hold — the size yields to fit UNDER the
       pinned clamp (the 50.9 divisor is the measured post-rename
       coefficient + ~0.03% rounding safety); min() keeps the pinned
       register wherever it fits (22px cap from ~1400px up) */
    font-size: min(clamp(15px, 1.6vw, 22px),
                   calc((100vw - 2 * var(--edge) - 4 * var(--row-gap)) / 50.9));
  }
  html.panel-on .capability-line li { white-space: nowrap; }
}
/* Settled construct-targets drop their transform: retained as settled-state
   hygiene (a settled translateY(0) is still a transform → a needless
   stacking context). The dropdown-collision rationale is retired with the
   dropdowns (§36.4) — the rule now only keeps settled targets at a clean
   identity transform. The tile rule (scale(1)) is separate and keeps its
   context by design. */
.js [data-construct].is-constructed .construct-target { transform: none; }

/* --------------------------------------------------------------------------
   §34.11 QUAD GRID AS RE-DERIVED BY §35.8 — DYNAMIC VIEWPORT GRID, ONE
   VIEWPORT PER SECTION. WORK and NEWS still form the 4-col × 4-row unit
   (MIRROR LAW kept: WORK head LEFT cols 1–2, NEWS head RIGHT cols 3–4; six
   EXACTLY-4:5 tiles each; uniform --band-gap; display:contents lists), but
   each section now occupies EXACTLY ONE VIEWPORT: height 100svh, its own
   --vw-pad air, contents centred (justify/align-content center).

   VIEWPORT-DERIVED TILE SIZE — the tile must satisfy BOTH budgets of the
   100svh box (box-sizing: border-box via the global reset; padding inside):
   · width  (4-col row): (100vw − 2·edge − 3·gap) / 4
   · height (2 rows):    (100svh − 2·vw-pad − gap) / 2 → the row height; the
     4:5 tile's width is that × 0.8.
   --tile-w = min of the two → the tiles always fit the viewport budget and
   the 4:5 law never distorts. The tile ROWS still compute to --tile-w × 1.25
   exactly: rows are AUTO and the li is exactly --tile-w wide, so the
   .tile-media aspect-ratio (components.css) yields that height natively.

   HEAD ROW = AUTO + §35.8 FLOOR EXCEPTION (FLAGGED DEVIATION, §35.8's own
   feasibility clause — "apply a min-content floor + overflow guard and flag"):
   measured at 1440×900 the head's honest content (the full-seat giant word
   198.5px block + band-gap + the §34.7-pinned-register write-up 224px) needs
   444px but the uniform tile row offers 379.8px. The contract's fallback
   (reduce write-up font/line) cannot reach fit in viable steps (−64px needed;
   registers pinned), and a height-budgeted word falls below its own 96px
   floor at 1280×800. So: rows are AUTO (the head row self-sizes to its
   content — overflow is impossible BY CONSTRUCTION, no inner scroll, JS-off
   captions included) and the section height is a MIN-HEIGHT floor: every
   section is ≥ one viewport (the §35.8 law), growing only where the head's
   honest length demands it. Measured overshoot: +53.6px @1440×900 (953.6 total,
   106%), +1.7px @1920×1080 (≈exact), fits fully ≤2560×1440. The seam and the
   centered-air read are unaffected (consecutive ≥-viewports).

   GIANT-WORD SEAT (§34.7 seat-derived law re-applied): size = the 2-col seat
   ÷ the word's measured box (WORK 3.1695em, NEWS 3.0788em, Inter 800 incl.
   Chromium trailing tracking — see the components.css provenance comments).
   DIVISOR CORRECTION vs the contracted numbers (FLAGGED): the contract wrote
   (2·tile-w + gap) ÷ 6.34 — but 6.34/6.16 are 2× the word boxes, the FULL-ROW
   era's divisors ((4 cols) ÷ 6.34 = 2 cols of word). Paired with the actual
   2-col seat expression they render the word ONE TILE wide (≈315px @1440 vs
   the previous ≈614px — and 55px font on a 390px phone, below the 96px
   floor's protection). The standing components.css law is "seat ÷ word box"
   and the contract's own prose is "the word fills its 2-col seat" — so the
   divisor here is the word box itself (3.1695 / 3.0788). Escalated in the
   report for art-director confirmation.
   -------------------------------------------------------------------------- */
.work,
.news {
  display: grid;
  min-height: 100svh; /* §35.8 one-viewport law as the floor (see the head-row note above) */
  box-sizing: border-box;
  padding: var(--vw-pad) var(--edge);
  /* §38.3: FIVE tracks at the same page width — the width budget term
     re-derives (4 gutters, ÷5); the height budget term is unchanged (the
     unit is still two tile rows). */
  --tile-w: min(
    calc((100vw - 2 * var(--edge) - 4 * var(--band-gap)) / 5),
    calc((100svh - 2 * var(--vw-pad) - var(--band-gap)) / 2 * 0.8)
  );
  grid-template-columns: repeat(5, var(--tile-w));
  /* rows AUTO: tile rows compute to --tile-w × 1.25 via the 4:5 media box
     (li width = the track); the head row grows to its honest content —
     the §35.8 floor exception. Fixed rows were tried and MEASURED overflowing
     (head 444 in a 379.8 row @1440 — the write-up clipped into the tile row). */
  grid-template-rows: auto;
  justify-content: center;
  align-content: center;
  gap: var(--band-gap); /* uniform gutter, rows AND columns */
}
/* §35.8 WORK↔NEWS SEAM RE-DERIVED — the old .work { padding-bottom:
   var(--band-gap) } seam rule is REMOVED: inside the fixed 100svh box it
   double-counted. With both sections exactly one viewport and their grids
   centred, the seam IS the centered air (each section's content floats in
   its own viewport of white) — the sections read as consecutive viewports.
   -------------------------------------------------------------------------- */
/* §37.6: WORK's word box grew by the evened W→O kern (0.0327em — see
   components.css) → the seat divisor re-derived 3.1695 → 3.2 (the §34.11
   carry: seat ÷ word box; the word still fills the 2-col seat exactly,
   ending flush against the tile column). */
.work {
  --head-f: min(clamp(96px, 17.5vw, 320px), calc((2 * var(--tile-w) + var(--band-gap)) / 3.2));
}
.news {
  --head-f: min(clamp(96px, 17.5vw, 320px), calc((2 * var(--tile-w) + var(--band-gap)) / 3.0788));
}
/* §37.5 CONTACT HEADING SCALE — the contact heading joins the WORK/NEWS
   register: .contact-section carries the SAME --head-f derivation as .work
   (identical tile budget + the same seat divisor 3.2, §37.6's re-derived
   box) so .giant-contact renders at the WORK giant-word size exactly
   (±0.5px at 1440; Dillon: "contact heading text is much to big, should be
   the size of news and work"). The old .contact-head standalone clamp
   register is retired — with the §31.5 companion mark gone (§34.14) the
   freed trailing air no longer buys it its own divisor. KONTAK roll laws
   (§31.3/§32.2) are size-relative and unaffected. Mobile containment
   override lives in the ≤700px block. */
.contact-section {
  /* §38.3: the contact token mirrors the work budget (§37.5 law — the
     heading rides the WORK register), so its width term re-derives with
     the five-track unit too. */
  --tile-w: min(
    calc((100vw - 2 * var(--edge) - 4 * var(--band-gap)) / 5),
    calc((100svh - 2 * var(--vw-pad) - var(--band-gap)) / 2 * 0.8)
  );
  /* third min() term = §31.4 CONTAINMENT: CONTACT's measured 5.113em word
     must end inside the right margin. FLAGGED (report §37.5): the parity
     AC is satisfiable at desktop widths only — below ≈1027px the word
     cannot render at the work register without overflowing, so the
     containment term binds (at 390×844: fs 67.3px vs the work register's
     96px). Stated tolerance, never silent. */
  --head-f: min(clamp(96px, 17.5vw, 320px), calc((2 * var(--tile-w) + var(--band-gap)) / 3.2),
                calc((100vw - 2 * var(--edge)) / 5.2));
}

/* §36.5 HEAD-CELL ALIGNMENT LAW (part 1: the cell constraint + flushness)
   — desktop grid rows ONLY (where an image sits BESIDE the word); at
   ≤1026px the head is full-width ABOVE the tiles and keeps its natural
   composition (the base rules govern there). The head cell is CONSTRAINED
   to the tile row height (--tile-w × 1.25):
   · the giant word's INK top is lifted flush with the row-1 tile top —
     the negative margin = Inter's line-height:1 cap offset (--baseline-gap,
     0.136em, the base.css-measured metric); the glyph svg IS its ink box,
     so the flushness is verified by measuring the svg box against the
     tile top (measured Δ +0.12px — §36 report numbers);
   · the write-up below must close ABOVE the image bottom — the intro
     register tune lives in part 2 BELOW the base intro rules (source
     order: the tune must override the base register).
   min-height: max-content keeps the E1 honest-length floor: a head whose
   write-up genuinely cannot fit the tile-height row GROWS instead of
   clipping (the §35.8 floor+guard pattern; measured residuals recorded in
   the §36 report). The row is still auto in the base rule — with the head
   capped, the tile rows and the head row compute identically, so the
   sections measure exactly 100svh wherever the math closes (retirement
   numbers in the §36 report). */
@media (min-width: 1027px) {
  .work-head,
  .news-head { height: calc(var(--tile-w) * 1.25); min-height: max-content; }
  .work-head .giant-word,
  .news-head .giant-word { margin-top: -0.136em; /* ink-top flush with the tile top */ }
  /* §36.5 integration fix (art-director, measured): the word's line-box
     carries dead leading BELOW the ink — the intro parked under it with the
     full band-gap pushed honest text past the image bottom at 1100×900
     (+61.4px) and 1440×820 (+17.1px). Internal gap tightens 6px and the
     intro pulls up into that leading — the word/intro relationship stays
     legible (intro sits just under the ink, never touching), the text
     returns inside the image line at every desktop width (§36.5 law). */
  .work-head,
  .news-head { gap: calc(var(--band-gap) - 6px); }
  .work-head .work-intro,
  .news-head .news-copy { margin-top: calc(var(--head-f) * -0.055); }
  /* flex-shrink:0 — the intro is a flex item in a definite-height cell; the
     default shrink squeezed its BOX under its text (silent last-line bleed
     past the image bottom, measured @1100×900). Zero shrink = the cell grows
     to true max-content: whole lines, nothing clipped, nothing distorted —
     the E1 floor+guard in its clean form (§35.8 feasibility clause, carried
     by §36.5's "if honest content still exceeds"). The residual excess is
     recorded in the §36 report and flagged for Dillon. */
  .work-head .work-intro,
  .news-head .news-copy { flex-shrink: 0; }
}

/* (JS-off parity note, §8-A7/§5: with AUTO rows the JS-off caption below
   each photo simply grows its row — no separate release rule needed; the
   static page reads as the same composition, naturally taller.) */

/* Head seats — the word + write-up compose one flex block per section. */
.work-head,
.news-head {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: var(--band-gap);
  min-width: 0;
}
.work-head { grid-column: 1 / 3; grid-row: 1; }
/* §38.3: the news head holds RIGHT — cols 4–5 of the five-track unit
   (was 3 / 5 in the four-track unit). */
.news-head { grid-column: 4 / 6; grid-row: 1; }

/* Grid lists dissolve — the li are the grid items (list semantics kept). */
.work-grid,
.news-grid {
  display: contents;
  margin: 0;
  padding: 0;
  list-style: none;
}
.work-grid li,
.news-grid li { min-width: 0; margin: 0; padding: 0; }

/* §34.11 explicit placements — deterministic in every engine. §38.3 FIVE
   tracks, MIRRORED +1 column: work = original 4 + new RIGHTMOST (head
   cols 1–2 LEFT; row 1 = head + tiles 1–3 (cols 3–5); row 2 = tiles 4–8
   (cols 1–5); the new seats are the rightmost column, rows 1–2).
   News mirrors: row 1 = tiles 1–3 (cols 1–3 — the NEW leftmost column
   carries seats 7–8 at col 1) + head (cols 4–5 RIGHT); row 2 = tiles 4–8
   (cols 1–5). Existing tiles keep their relative order and markup; the
   new seats are §34.12 placeholders. (Local rows: work row 1–2 = unit
   rows 1–2; news = unit rows 3–4 — the sections' zero vertical padding
   makes them read continuous.) */
#work-grid li:nth-child(1) { grid-column: 3; grid-row: 1; }
#work-grid li:nth-child(2) { grid-column: 4; grid-row: 1; }
/* §44.1 (Dillon, mid-loop decision: "as long as the layout doesn't change,
   just the order of the displayed tiles; the write-up and heading must keep
   their current positions"): the work seats remap to the CHRONOLOGICAL
   row-major reading (Gordon's 1–8) on BOTH breakpoints — placement-only,
   the §38.3 geometry (row A = head + 3 tiles, row B = 5 tiles, mirrored
   +1 column) and the head cell (cols 1–2, row 1) are UNTOUCHED. Row A
   reads [head, 1, 2, 3], row B reads [4, 5, 6, 7, 8]. The ≤1026/≤700
   blocks already map row-major 1–8 (verified) and are untouched. */
#work-grid li:nth-child(3) { grid-column: 5; grid-row: 1; }
#work-grid li:nth-child(4) { grid-column: 1; grid-row: 2; }
#work-grid li:nth-child(5) { grid-column: 2; grid-row: 2; }
#work-grid li:nth-child(6) { grid-column: 3; grid-row: 2; }
#work-grid li:nth-child(7) { grid-column: 4; grid-row: 2; }
#work-grid li:nth-child(8) { grid-column: 5; grid-row: 2; }
/* §44.2 (Dillon, mid-loop clarification — amends the §38.3 NEWS-side
   mirroring by explicit client directive): the feed fills seats
   newest→oldest (posts[0] = latest at seat 1), so the news seats must
   read CHRONOLOGICALLY row-major at desktop: seat 1 (the LATEST post) is
   the TOP-LEFT cell, reading left→right, top→bottom, seat 8 (the earliest
   post shown) bottom-right. PLACEMENT-ONLY remap — the five-track
   template and the cols 4–5 head cell are untouched. The WORK grid's
   seats carry the SAME §44.1 chronological row-major map (Dillon's
   decision on the measured §44 conflict). The ≤1026/≤700 blocks already
   map row-major chronological (verified) and are untouched. */
#news-grid li:nth-child(1) { grid-column: 1; grid-row: 1; }
#news-grid li:nth-child(2) { grid-column: 2; grid-row: 1; }
#news-grid li:nth-child(3) { grid-column: 3; grid-row: 1; }
#news-grid li:nth-child(4) { grid-column: 1; grid-row: 2; }
#news-grid li:nth-child(5) { grid-column: 2; grid-row: 2; }
#news-grid li:nth-child(6) { grid-column: 3; grid-row: 2; }
#news-grid li:nth-child(7) { grid-column: 4; grid-row: 2; }
#news-grid li:nth-child(8) { grid-column: 5; grid-row: 2; }

/* write-up paragraph grammar — the seats under each word (§34.7: lede-strong
   register eased one step to 500; body unchanged). */
.work-intro,
.news-copy { margin: 0; max-width: 52ch; }
.work-intro .lede-strong,
.news-copy .lede-strong {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(17px, 1.4vw, 20px);
  font-weight: 500; /* §34.7: eased one step (was 600) — Inter variable 500 */
  letter-spacing: -0.005em;
  line-height: 1.4;
}
.work-intro .lede-body,
.news-copy .lede-body {
  margin: 16px 0 0;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
}
.news-follow { font-weight: 600; text-underline-offset: 4px; }

/* §36.5 HEAD-CELL ALIGNMENT LAW (part 2: the write-up fit) — declared AFTER
   the base intro register so the tune wins by source order. The intro must
   fit the head cell's remaining height (the tile-height row minus the word
   and the gutter) at the pinned breakpoints (1440×900, 1920×1080) — the E1
   root-cause shave: max-width 52ch → 100% of the 2-col seat (a ch measure
   shrinks as the font shrinks and re-wraps; the seat-relative measure stays
   put), lede-strong line-height 1.4 → 1.35, lede-body 16/1.55 → 15/1.5 —
   sizes/line-heights only, the type register shape (Inter/Figtree, the
   clamps' floors/caps) is FIXED. If a write-up still exceeds the row at
   some width, part 1's min-height: max-content grows the head honestly
   (E1 floor+guard — flagged with numbers, never clipped). */
@media (min-width: 1027px) {
  .work-intro,
  .news-copy { max-width: 100%; }
  .work-intro .lede-strong,
  .news-copy .lede-strong { line-height: 1.35; }
  .work-intro .lede-body,
  .news-copy .lede-body { font-size: 15px; line-height: 1.5; }
}
/* §36.5 integration fix: the step-down applies on TWO honest axes — the
   narrow 1027–1359 band (2-col seat at its narrowest) AND any viewport
   shorter than 880px (1366×768 / 1440×820-class laptops, where the
   height-budget term starves the cell). Both were measured overflowing at
   the step-1 register; both fit at this one. ≥1360w × ≥880h keeps step-1
   (fits: −37.5px @1440×900, −106.4px @1920×1080). */
@media (min-width: 1027px) and (max-width: 1359px),
       (min-width: 1360px) and (max-height: 879px) {
  .work-intro .lede-strong,
  .news-copy .lede-strong { font-size: clamp(15px, 1.25vw, 17px); line-height: 1.3; }
  .work-intro .lede-body,
  .news-copy .lede-body { font-size: 14px; line-height: 1.45; }
}

/* --------------------------------------------------------------------------
   CONTACT — black section containing the footer. §31.4: the giant word is
   CONTAINED within the page margins at every breakpoint; the word starts at
   the left margin edge and ends inside the right margin. §34.14: the nu
   companion is REMOVED — the freed width is trailing air to the margin, and
   the containment measure (kept at /6.3) preserves the §34.7 GIANT-WORDS-
   UNCHANGED size exactly (the word ends inside the margins; the mark's old
   seat is simply air).
   Two office columns, each with address block + reveal slots + map panel.
   Bottom bar: wordmark master · copyright · socials.
   -------------------------------------------------------------------------- */
.contact-section {
  position: relative;
  /* §35.10: top 120 → clamp(120px, 14vh, 176px) (viewport-derived air);
     bottom 48 → 64. Natural height (the §35.8 one-viewport law does not
     apply to the contact/footer section). */
  padding: clamp(120px, 14vh, 176px) 0 64px;
  color: var(--white);
  background: var(--black);
  overflow: hidden;
}
.contact-head {
  position: relative;
  padding: 0 var(--edge);
  /* §37.5: the heading renders at the WORK register (var(--head-f), the
     .contact-section derivation above) — the §31.4 standalone clamp divisor
     (6.3) is retired with it. Containment (the word ends inside the right
     margin) re-verified at the new register: at 1440 the word (5.113em
     measured) ends ≈1054px, ~306px of trailing air to the margin. The
     ≤700px block re-derives --head-f with a containment term (see below).
     Floors/caps from the old clamp are carried by the shared register's
     own clamp(96px, …, 320px). */
  font-size: var(--head-f, clamp(40px, calc((100vw - 2 * var(--edge)) / 6.3), 380px));
}
.contact-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 56px 64px; /* §35.10 B9: 48/56 → 56/64, one step */
  padding: 96px var(--edge) 0;
}
.contact-col {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 40px; /* §35.10 B9: 32 → 40, one step */
  align-items: start;
}
/* §34.7 contact city — TEXT heading: 20/700 → 18/600. */
.contact-city {
  margin: 0 0 18px;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.005em;
}
.contact-addr {
  margin: 0;
  font-style: normal;
  font-family: var(--font-body);
  font-size: 15.5px;
  line-height: 1.65;
}
.contact-addr .addr-line { display: block; }
.contact-slots { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; margin: 22px 0 0; }

.map-block { min-width: 0; }
.map-panel {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 0; /* §34.8 */
  background:
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.07) 0 1px, transparent 1px 28px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.07) 0 1px, transparent 1px 28px),
    var(--tint-inverse);
  overflow: hidden;
}
/* §34.15/§34.16 PLACE EMBED — keyless standard maps (no API key, never add
   one) + §34.16 MONOCHROME LIGHT FILTER on the iframe surface: grayscale +
   brightness/contrast tuned to sit quietly in the white page (the client
   asked for the recolouring — supersedes the §31.6 no-filter amendment).
   The filter tints Google's attribution WITH the surface — it is never
   obscured or covered (embed terms). The tinted/grid background above is
   only the loading/offline state. */
.map-embed {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  filter: grayscale(1) brightness(1.06) contrast(0.95);
}
.map-link {
  display: inline-block;
  margin-top: 12px;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  padding: 0; /* §34.24-b (Dillon, 2026-10-16): pill border removed — plain underlined text link */
  color: var(--white);
  text-underline-offset: 4px;
  transition: text-decoration-thickness 200ms ease;
}
.map-link:hover,
.map-link:focus-visible { text-decoration-thickness: 2px; }

/* footer bottom bar: wordmark master · copyright · socials */
.footer-bottom {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: end;
  gap: 24px;
  padding: 128px var(--edge) 0; /* §35.10: 120 → 128 (one 8px step) */
}
.footer-mark { position: relative; min-width: var(--mark-w); }
.footer-mark svg { display: block; width: var(--mark-w); height: auto; fill: var(--white); }
/* §34.6 brand line: "© 2026 nuvorm studio" (client answer 4) — lowercase,
   matching the mockup and the lowercase brand identity. */
.footer-copyright {
  justify-self: center;
  margin: 0;
  font-family: var(--font-body);
  font-size: 13.5px;
}
.footer-socials {
  display: flex;
  gap: 20px;
  align-items: center;
  margin: 0;
}
.footer-socials a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 24px;
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
}
.footer-socials a:hover { text-decoration: underline; text-underline-offset: 4px; }
.footer-socials svg { flex: none; }

/* --------------------------------------------------------------------------
   Responsive — mobile 390 is a designed composition (§2.3 mobile law):
   20px margins, 8px gutters. §34.11 mobile: the quad grids become 2-col
   tile grids with the word + write-up spanning the full width (art-director
   decision 4); mirror law holds per section; uniform gutter unchanged.
   -------------------------------------------------------------------------- */
@media (max-width: 1026px) {
  /* §35.8 MOBILE VARIATION OF THE VIEWPORT GRID (≤1026, floor exception
     FLAGGED — the mobile/tablet sections' honest length exceeds one viewport
     at 2-per-row: height stays auto/min-height from the desktop rule, rows
     stay auto): tiles go 2-per-row at the same viewport-derived --tile-w
     (2-col width budget vs the svh height budget); the head spans the full
     2-col seat. The placements are RE-PINNED for 2 columns (carried from
     §34.11 — the desktop pins would otherwise create implicit 3rd/4th
     columns). */
  .work,
  .news {
    --tile-w: min(
      calc((100vw - 2 * var(--edge) - var(--band-gap)) / 2),
      calc((100svh - 2 * var(--vw-pad) - var(--band-gap)) / 2 * 0.8)
    );
    grid-template-columns: repeat(2, var(--tile-w));
  }
  .work-head,
  .news-head { grid-column: 1 / -1; grid-row: 1; }
  #work-grid li:nth-child(1) { grid-column: 1; grid-row: 2; }
  #work-grid li:nth-child(2) { grid-column: 2; grid-row: 2; }
  #work-grid li:nth-child(3) { grid-column: 1; grid-row: 3; }
  #work-grid li:nth-child(4) { grid-column: 2; grid-row: 3; }
  #work-grid li:nth-child(5) { grid-column: 1; grid-row: 4; }
  #work-grid li:nth-child(6) { grid-column: 2; grid-row: 4; }
  #news-grid li:nth-child(1) { grid-column: 1; grid-row: 2; }
  #news-grid li:nth-child(2) { grid-column: 2; grid-row: 2; }
  #news-grid li:nth-child(3) { grid-column: 1; grid-row: 3; }
  #news-grid li:nth-child(4) { grid-column: 2; grid-row: 3; }
  #news-grid li:nth-child(5) { grid-column: 1; grid-row: 4; }
  #news-grid li:nth-child(6) { grid-column: 2; grid-row: 4; }
  /* §38.3: the two §34.12 placeholder seats join the 2-col stack (row 5). */
  #work-grid li:nth-child(7) { grid-column: 1; grid-row: 5; }
  #work-grid li:nth-child(8) { grid-column: 2; grid-row: 5; }
  #news-grid li:nth-child(7) { grid-column: 1; grid-row: 5; }
  #news-grid li:nth-child(8) { grid-column: 2; grid-row: 5; }
  /* word spans the FULL 2-col seat (2·tile-w + gap) — the §35.8 seat law:
     seat ÷ word box (see the desktop block's divisor note) */
  .work { --head-f: min(clamp(96px, 24vw, 320px), calc((2 * var(--tile-w) + var(--band-gap)) / 3.2)); }
  .news { --head-f: min(clamp(96px, 24vw, 320px), calc((2 * var(--tile-w) + var(--band-gap)) / 3.0788)); }
  /* §37.5: the contact section mirrors the ≤1026 work register (same
     tokens, same divisor) so parity holds below the desktop breakpoint. */
  .contact-section {
    --tile-w: min(
      calc((100vw - 2 * var(--edge) - var(--band-gap)) / 2),
      calc((100svh - 2 * var(--vw-pad) - var(--band-gap)) / 2 * 0.8)
    );
    /* §31.4 containment term included (see the base block's §37.5 flag):
       binds below ≈1027px; desktop parity unaffected. */
    --head-f: min(clamp(96px, 24vw, 320px), calc((2 * var(--tile-w) + var(--band-gap)) / 3.2),
                  calc((100vw - 2 * var(--edge)) / 5.2));
  }

  /* §34.2 dot lift ≤1026: the bare caption (bottom-left) and the centred
     bare dots share the hero's bottom band — the dots lift to their own
     row (≥10px air above the caption seat) at every width where the two
     could meet (measured worst case ≈701px). Bare-caption tier drops 1px
     (the §31 chip tiers retire with the plate). */
  .hero-dots { bottom: 58px; }
  .hero-tag { font-size: 11px; }
}

@media (max-width: 700px) {
  :root { --edge: 20px; }

  .site-header { min-height: 72px; }
  /* §43 REVISION: Retired §35 Mobile Menu Plate (Dillon Report).
     The previous opaque background was removed due to visibility asymmetry
     (the 'white chip' artifact observed over sky/photography). Restoring the
     desktop-approved §34.3 bare control on mobile (<700px).
     This supersedes the §35 collision case. The §38.2 open state remains unchanged. */
  /* §34.19.1 → §35.1 mobile: the header mark matches the FOOTER mark's
     mobile slot exactly (found 150px in this block's .footer-mark rule) —
     footer-scale parity carried to mobile. */
  .wordmark { width: var(--mark-w-m); }
  .logo-white { width: var(--mark-w-m); }
  .menu-panel { width: min(420px, 86vw); padding-top: 104px; }

  /* §36.4 mobile — the li stack: JS-off gets it from the base rules; the
     html.js row rule is re-overridden here so JS-on stacks too (parity of
     comprehension, not of effect). The shared panel sits directly below
     the stacked list, full width; its min-height is re-measured for the
     350px mobile measure. */
  html.panel-on .capability-line { flex-direction: column; align-items: stretch; row-gap: 16px; }
  .capability-line li { width: 100%; }
  html.js .discipline-panel { margin-top: 28px; min-height: 120px; } /* MEASURED: longest write-up = 96px (4 lines) @390; 120px covers the ≤700px band's worst case @320 (5 lines) */

  /* §35.8 mobile grid — same 2-col variation as ≤1026, at the mobile
     padding register (--vw-pad-m) and the 20px edge (measured @390×844:
     tiles 169px, rows 211.3px, uniform 12px gutter; the section runs
     1140px — floor exception, flagged). */
  .work,
  .news {
    --tile-w: min(
      calc((100vw - 2 * var(--edge) - var(--band-gap)) / 2),
      calc((100svh - 2 * var(--vw-pad-m) - var(--band-gap)) / 2 * 0.8)
    );
  }

  .about { min-height: 0; --intro-gap: 72px; padding: var(--intro-gap) 20px var(--intro-gap); } /* §36.8: hero air halved (§34.21 144→72); §36.12 same unit bottom */
  .about-copy { margin-top: 72px; } /* §34.21: 36→72 */
  .positioning { margin-top: 80px; } /* §34.21: 40→80 */
  .capability-line { margin-top: 28px; }

  .contact-section { padding-top: 88px; }
  .contact-cols { grid-template-columns: 1fr; gap: 56px; padding-top: 64px; }
  .contact-col { grid-template-columns: 1fr; gap: 24px; }

  .footer-bottom {
    grid-template-columns: 1fr;
    align-items: start;
    gap: 32px;
    padding-top: 88px;
  }
  .footer-mark { min-width: 0; }
  .footer-mark svg { width: 150px; }
  .footer-copyright { justify-self: start; }
}

/* Tablet guard: the §35.8 viewport composition is breakpoint-continuous —
   the 4-col one-viewport grid + seat-derived word sizes hold above 1026px;
   below it the 2-col mobile variation takes over (see the block above). The
   giant words keep their seat-derived clamp sizing 701–1920+. */
