/* ============================================================================
   ElectroTek Consultants — shared craft layer
   ----------------------------------------------------------------------------
   The token contract. Every color, easing, duration, radius and shadow on this
   site resolves from here. Components must never carry literals — retrofitting
   tokens onto literals costs 5-10x, which is the state this file replaced.

   Brand derivation (hex -> OKLCH, computed not eyeballed):
     copper       #B87333  ->  oklch(61.8% 0.117 60.4)
     copper light #C9854A  ->  oklch(67.5% 0.112 60.0)
     navy         #0C1118  ->  oklch(17.6% 0.017 256.8)
     cream        #FAF8F5  ->  oklch(98.0% 0.005 78.3)
   ========================================================================== */

:root {
  /* ---- Color ------------------------------------------------------------ */
  --color-brand:            oklch(61.8% 0.117 60.4);
  --color-brand-light:      oklch(67.5% 0.112 60.0);
  --color-brand-muted:      color-mix(in oklch, var(--color-brand) 30%, transparent);
  --color-brand-faint:      color-mix(in oklch, var(--color-brand) 10%, transparent);

  /* Light page surfaces. Never pure #fff — a faint warm cast ties the
     neutrals to the copper so the whole surface family shares a temperature. */
  --color-background:       oklch(99.4% 0.002 78);
  --color-surface-1:        oklch(98.0% 0.005 78);
  --color-surface-2:        color-mix(in oklch, var(--color-surface-1), var(--color-brand) 5%);

  --color-foreground:       oklch(22.0% 0.008 258);
  --color-foreground-muted: oklch(52.0% 0.012 258);
  --color-foreground-faint: oklch(66.0% 0.010 258);

  /* Perceptual, never a hardcoded gray — these follow --color-foreground, so
     the dark-surface scope below inverts them for free. */
  --color-hairline:         color-mix(in oklch, var(--color-foreground) 12%, transparent);
  --color-hairline-strong:  color-mix(in oklch, var(--color-foreground) 22%, transparent);

  /* Ink surfaces, used by the hero and footer. */
  --color-ink:              oklch(17.6% 0.017 257);
  --color-ink-1:            oklch(25.8% 0.030 259);
  --color-ink-2:            oklch(32.3% 0.032 260);

  /* ---- Typography ------------------------------------------------------- */
  --font-display: 'DM Serif Display', 'DM Serif Fallback', Georgia, serif;
  --font-body:    'IBM Plex Sans', 'IBM Plex Fallback', system-ui, sans-serif;

  /* ---- Motion physics ---------------------------------------------------
     Asymmetric beziers approximating spring tension. Nothing in nature moves
     linearly, and the stock symmetric curves are the commonest motion tell. */
  --ease-spring:   cubic-bezier(0.32, 0.72, 0, 1);
  --ease-exit:     cubic-bezier(0.16, 1, 0.3, 1);
  --duration-fast: 150ms;
  --duration-ui:   250ms;
  --duration-enter: 600ms;

  /* ---- Geometry ---------------------------------------------------------
     Deliberately tight. A forensic engineering practice should read closer to
     a technical drawing than to a consumer app; this is the one geometry
     decision that keeps the brand from looking like the last one. */
  --radius-sm: 3px;
  --radius-md: 5px;
  --radius-lg: 8px;

  /* ---- Depth -------------------------------------------------------------
     Two recipes, not interchangeable. Layered falloff reads as ambient
     occlusion; a single drop shadow reads as a component-library default.
     Tinted with the ink hue — pure black goes muddy. */
  --shadow-elevation:
    0 1px 2px  color-mix(in oklch, var(--color-ink) 8%, transparent),
    0 2px 4px  color-mix(in oklch, var(--color-ink) 6%, transparent),
    0 4px 8px  color-mix(in oklch, var(--color-ink) 5%, transparent),
    0 8px 16px color-mix(in oklch, var(--color-ink) 4%, transparent);
  --shadow-highlight: inset 0 1px 0 color-mix(in oklch, white 8%, transparent);

  /* ---- Layout ----------------------------------------------------------- */
  --measure: 68ch;
}

/* Dark sections redefine the foreground, and every hairline, surface and
   muted tone re-derives from it. This is the whole point of the color-mix
   contract: one declaration flips the region. */
[data-surface='ink'] {
  --color-background:       var(--color-ink);
  --color-surface-1:        var(--color-ink-1);
  --color-surface-2:        var(--color-ink-2);
  --color-foreground:       oklch(97% 0.004 78);
  --color-foreground-muted: oklch(74% 0.010 258);
  --color-foreground-faint: oklch(58% 0.012 258);
  color: var(--color-foreground);
}

/* ============================================================================
   Metric-matched fallbacks
   ----------------------------------------------------------------------------
   Values computed from the actual font binaries (head/OS/2/hhea tables) against
   the macOS system fallbacks — not copied from another project. Text occupies
   its final box on first paint, so nothing reflows when the webfont resolves.
   ========================================================================== */

@font-face {
  font-family: 'IBM Plex Fallback';
  src: local('Arial'), local('Helvetica Neue'), local('Liberation Sans');
  size-adjust: 120.75%;
  ascent-override: 84.89%;
  descent-override: 22.77%;
  line-gap-override: 0%;
}

@font-face {
  font-family: 'DM Serif Fallback';
  src: local('Georgia'), local('Times New Roman'), local('Liberation Serif');
  size-adjust: 127.52%;
  ascent-override: 81.24%;
  descent-override: 26.27%;
  line-gap-override: 0%;
}

/* ============================================================================
   Base layer
   ========================================================================== */

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

html {
  scroll-behavior: smooth;
  /* Stops the page jumping sideways between a scrolling and non-scrolling route. */
  scrollbar-gutter: stable;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Fluid base: 15px at 320px viewport to 18px at 1440px. */
  font-size: clamp(0.9375rem, 0.85rem + 0.27vw, 1.125rem);
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-kerning: normal;
  font-variant-ligatures: common-ligatures;
  color: var(--color-foreground);
  background: var(--color-background);
  line-height: 1.6;
}

/* Rag control. The default text engine optimises for speed, not typesetting. */
h1, h2, h3, h4 { text-wrap: balance; }
p, li          { text-wrap: pretty; }

/* Numerals as designed objects. NOTE: Google's subset of IBM Plex Sans ships
   no `tnum` feature, so this is currently inert — it takes effect only if the
   full family is ever self-hosted. Declared because the intent is correct and
   the cost is zero. */
.tabular, .stat-value, table, th, td { font-variant-numeric: tabular-nums; }

/* Selection is a designed state. Browser-default blue on a themed page is an
   immersion break. */
::selection {
  background-color: var(--color-brand);
  color: oklch(99% 0 0);
}

/* Chrome is not content — stop accidental drag-select on controls. Never
   applied to prose, addresses or phone numbers, which people copy. */
button, [role='button'], summary, label, nav a, .btn {
  -webkit-user-select: none;
  user-select: none;
}

/* Accessibility as a visual feature, not a degraded compliance state.
   :focus-visible, never :focus — the latter fires on mouse click. */
:focus-visible {
  outline: 2px solid var(--color-brand);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
[data-surface='ink'] :focus-visible { outline-color: var(--color-brand-light); }

a { color: inherit; }

/* Icons are an extension of typography, so their stroke must optically match
   the body copy beside them. A raw weight of 2 in a 24-unit viewBox renders
   1.33px at 16px and 3.33px at 40px — a 3x inconsistency on one page.
   This pins the rendered weight regardless of icon size. */
svg[stroke]:not([stroke='none']) :is(path, line, polyline, circle, rect, polygon, ellipse) {
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ============================================================================
   Material
   ========================================================================== */

/* Procedural grain. Displays render gradients with mathematical perfection,
   which bands; noise dithers the ramp and adds material. One fixed tiled
   data-URI layer — not a live feTurbulence, which is a real GPU cost. */
.grain {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: 0.4;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0.12 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

.hairline   { border-color: var(--color-hairline); }
.rule       { height: 1px; background: var(--color-hairline); border: 0; }
.rule-fade  { height: 1px; border: 0; background: linear-gradient(90deg, var(--color-hairline-strong), transparent); }
.rule-brand { width: 3rem; height: 1px; border: 0; background: var(--color-brand); }

/* ============================================================================
   Typography scale
   ========================================================================== */

.fluid-hero    { font-size: clamp(2.5rem, 1.4rem + 5.2vw, 5rem); letter-spacing: -0.025em; line-height: 1.04; }
.fluid-h2      { font-size: clamp(1.875rem, 1.25rem + 2.8vw, 3rem); letter-spacing: -0.02em; line-height: 1.1; }
.fluid-h3      { font-size: clamp(1.15rem, 1rem + 1vw, 1.65rem); letter-spacing: -0.01em; }
.fluid-body-lg { font-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem); }
.fluid-stat    { font-size: clamp(1.75rem, 1.25rem + 2.5vw, 2.75rem); }
/* The ranked credential. Roughly 1.6x the secondary stats — enough that the
   hierarchy is legible from across the room rather than on inspection. */
.fluid-stat-lead { font-size: clamp(2.75rem, 1.6rem + 4.4vw, 4.5rem); }
.fluid-quote   { font-size: clamp(1.35rem, 1rem + 1.8vw, 2.25rem); }

.font-display { font-family: var(--font-display); font-weight: 400; }
.font-body    { font-family: var(--font-body); }

/* The small caps kicker that opens each section. */
.kicker {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--color-brand);
}

/* Rail variant. A copper hairline leading into the kicker gives the left-aligned
   hero a hard vertical edge to start from — the same drafting-sheet cue the
   service hairlines use, rather than type floating in space. */
.kicker--rail {
  display: flex;
  align-items: center;
  gap: 0.875rem;
}
.kicker--rail::before {
  content: '';
  width: clamp(1.75rem, 4vw, 3.25rem);
  height: 1px;
  flex: none;
  background: linear-gradient(90deg, var(--color-brand), var(--color-brand-muted));
}

/* Tinted brand text. These replace `text-copper/60` and `text-copper/80`:
   a Tailwind opacity modifier only works on a theme color carrying the
   <alpha-value> placeholder, and a bare var() cannot. The modifier therefore
   emitted a declaration the browser discarded, and the text fell back to the
   inherited colour instead of copper. color-mix does the job in the token
   layer, where the rest of the contract already lives. */
.text-brand-60 { color: color-mix(in oklch, var(--color-brand) 60%, transparent); }
.text-brand-80 { color: color-mix(in oklch, var(--color-brand) 80%, transparent); }

.prose { max-width: var(--measure); }

/* Vertical rhythm. Was 6rem top and bottom, so consecutive sections put 12rem
   of dead air between two short blocks of copy. Interior pages are mostly short
   blocks, so that read as emptiness rather than generosity. */
.fluid-section  { padding-block: clamp(2.75rem, 1.9rem + 2.8vw, 4.5rem); }
.fluid-hero-pad { padding-top: clamp(3.5rem, 2rem + 5vw, 6.5rem); padding-bottom: clamp(4rem, 3rem + 5vw, 7.5rem); }

/* Interior heroes are a title and one line, not a landing statement. The
   homepage keeps the full measure; these sit tighter. */
.hero--page .fluid-hero-pad {
  padding-top: clamp(2.5rem, 1.75rem + 2.4vw, 3.75rem);
  padding-bottom: clamp(2.75rem, 2rem + 2.6vw, 4.25rem);
}

/* ============================================================================
   Controls
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.875rem 1.75rem;
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--duration-ui) var(--ease-spring),
    border-color     var(--duration-ui) var(--ease-spring),
    color            var(--duration-ui) var(--ease-spring),
    transform        var(--duration-fast) var(--ease-spring);
}

/* Physical confirmation of the click. Scale down, never up, and inversely to
   surface area — a full-width element scaled 0.97 reads as a glitch. */
.btn:active { transform: scale(0.975); }

.btn--primary {
  background: var(--color-brand);
  color: oklch(99% 0 0);
  box-shadow: var(--shadow-highlight);
}
.btn--primary:hover { background: var(--color-brand-light); }

.btn--ghost {
  color: var(--color-foreground);
  border-color: var(--color-hairline-strong);
}
.btn--ghost:hover {
  border-color: var(--color-brand);
  color: var(--color-brand);
}

/* The inline "Full biography ->" affordance. The arrow advances on hover,
   which is motion explaining a destination rather than decoration. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-brand);
  text-decoration: none;
  transition: color var(--duration-ui) var(--ease-spring);
}
.link-arrow svg { transition: transform var(--duration-ui) var(--ease-spring); }
.link-arrow:hover { color: var(--color-brand-light); }
.link-arrow:active { transform: scale(0.99); }

.nav-link {
  color: var(--color-foreground-muted);
  text-decoration: none;
  font-weight: 500;
  transition: color var(--duration-fast) var(--ease-spring);
}
.nav-link:hover { color: var(--color-foreground); }

@supports (corner-shape: squircle) {
  .btn, .card { corner-shape: squircle; border-radius: calc(var(--radius-sm) * 1.5); }
}

/* Hover is a pointer capability, not a universal one. Without this, emulated
   hover latches after a tap and the site feels broken on touch. */
@media (hover: none) {
  .btn:hover, .card:hover, .svc-row:hover { transform: none; }
}

/* ============================================================================
   Entrances
   ----------------------------------------------------------------------------
   Staggered above the fold to establish hierarchy. Below the fold content is
   simply present — animating every section on scroll is scroll-jacking.
   ========================================================================== */

@keyframes slide-enter {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

[data-animate] {
  opacity: 0;
  animation: slide-enter var(--duration-enter) var(--ease-exit) forwards;
  animation-delay: calc(var(--stagger-step, 0) * 80ms);
}

/* Below-the-fold reveal: crossfade only, no travel, triggered by observer. */
.reveal {
  opacity: 0;
  transition: opacity var(--duration-enter) var(--ease-exit);
}
.reveal.is-visible { opacity: 1; }

/* A user with a vestibular disorder still deserves a designed transition:
   remove the travel, keep the fade. The critical part is resetting entrance
   elements to their END state — the common failure is killing the animation
   and stranding the content at opacity 0, permanently invisible. */
@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  [data-animate], .reveal {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
}

/* ============================================================================
   Site chrome
   ========================================================================== */

.site-nav {
  position: sticky;
  top: 0;
  z-index: 300;
  background: color-mix(in oklch, var(--color-background) 88%, transparent);
  /* The one blurred surface on the site. It genuinely floats over scrolling
     content; every other panel is solid with a hairline. */
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  backdrop-filter: blur(12px) saturate(1.4);
  border-bottom: 1px solid var(--color-hairline);
}

.svc-dropdown {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  padding-top: 0.5rem;
  min-width: 14rem;
  z-index: 310;
}
.svc-trigger:hover .svc-dropdown,
.svc-trigger:focus-within .svc-dropdown { display: block; }

.svc-dropdown-panel {
  background: var(--color-background);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-elevation);
  padding-block: 0.375rem;
}
.svc-dropdown-panel a {
  display: block;
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
  color: var(--color-foreground-muted);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-spring),
              background-color var(--duration-fast) var(--ease-spring);
}
.svc-dropdown-panel a:hover {
  color: var(--color-foreground);
  background: var(--color-surface-1);
}

.mob-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: color-mix(in oklch, var(--color-ink) 97%, transparent);
  z-index: 400;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2rem;
}
.mob-overlay.is-open { display: flex; }

/* One hero. Pages vary it through two custom properties rather than each
   carrying its own copy of the rule — which is what the eight duplicated
   <style> blocks were doing before. */
.hero {
  --hero-art: url('hero-fire-v1.png');
  --hero-art-opacity: 0.44;
  position: relative;
  overflow: hidden;
  background: var(--color-ink);
}

/* The fire plate is pushed right and masked off the left half so it acts as a
   compositional counterweight to the headline instead of a flat wash behind it.
   Nearly doubling the opacity is only safe because the horizontal scrim below
   darkens the copy side specifically — the plate gets brighter exactly where no
   type sits over it. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--hero-art) right center / cover no-repeat;
  opacity: var(--hero-art-opacity);
  pointer-events: none;
  -webkit-mask-image: linear-gradient(90deg, transparent 4%, black 48%);
  mask-image: linear-gradient(90deg, transparent 4%, black 48%);
}

/* Interior pages: the headline steps down so the homepage keeps the top of
   the type scale, and the art sits back further behind the copy. */
.hero--page { --hero-art-opacity: 0.3; }
.hero--page .fluid-hero { font-size: clamp(2rem, 1.25rem + 3.5vw, 3.5rem); }

/* Two scrims doing separate jobs: the horizontal one buys type contrast over
   the plate, the vertical one seats the section into the credential ribbon. */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(90deg,
      var(--color-ink) 0%,
      color-mix(in oklch, var(--color-ink) 84%, transparent) 40%,
      color-mix(in oklch, var(--color-ink) 34%, transparent) 76%,
      color-mix(in oklch, var(--color-ink) 58%, transparent) 100%),
    linear-gradient(180deg,
      color-mix(in oklch, var(--color-ink) 42%, transparent) 0%,
      transparent 34%,
      color-mix(in oklch, var(--color-ink) 68%, transparent) 84%,
      var(--color-ink) 100%);
}

/* Below the breakpoint the headline spans the full column, so a horizontal mask
   would drop type straight onto the brightest part of the plate. Revert to a
   centred, dimmer wash and let the vertical scrim carry it alone. */
@media (max-width: 767px) {
  .hero { --hero-art-opacity: 0.24; }
  .hero::before {
    background-position: center;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .hero::after {
    background: linear-gradient(
      180deg,
      color-mix(in oklch, var(--color-ink) 52%, transparent) 0%,
      color-mix(in oklch, var(--color-ink) 78%, transparent) 68%,
      var(--color-ink) 100%
    );
  }
}

.cred-divider {
  width: 1px;
  height: 2.5rem;
  background: linear-gradient(180deg, transparent, var(--color-brand-muted), transparent);
}

/* Credential captions. Tighter tracking than .kicker because these sit beside a
   large numeral and have to read as its label, not as a section opener. */
.cred-label {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  line-height: 1.35;
  text-transform: uppercase;
  color: var(--color-foreground-muted);
  text-wrap: balance;
}

/* Service rows. Hairlines define the regions; the watermark is masked so it
   dissolves rather than sitting in a box. */
.svc-row {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--color-hairline);
  transition: background-color var(--duration-ui) var(--ease-spring);
}
.svc-row::after {
  content: '';
  position: absolute;
  top: -10%;
  bottom: -10%;
  width: 60%;
  background-size: cover;
  background-position: center;
  opacity: 0.35;
  pointer-events: none;
  filter: grayscale(0.2);
  transition: opacity var(--duration-ui) var(--ease-spring);
}
.svc-row:hover { background: var(--color-surface-1); }
.svc-row:hover::after { opacity: 0.5; }
.svc-row:hover .svc-num { color: var(--color-brand); }
.svc-row:active { transform: scale(0.997); }

.svc-num {
  font-family: var(--font-display);
  font-size: 1.5rem;
  color: var(--color-foreground-faint);
  transition: color var(--duration-ui) var(--ease-spring);
}

/* Alternating placement so the four watermarks do not stack into a column. */
.svc-01::after {
  right: 0; width: 55%;
  -webkit-mask-image: linear-gradient(90deg, transparent, black 40%);
  mask-image: linear-gradient(90deg, transparent, black 40%);
  background-image: url('svc-origin.png');
}
.svc-02::after {
  left: 15%; right: 15%; width: 70%;
  -webkit-mask-image: linear-gradient(90deg, transparent, black 25%, black 75%, transparent);
  mask-image: linear-gradient(90deg, transparent, black 25%, black 75%, transparent);
  background-image: url('svc-product.png');
}
.svc-03::after {
  right: -3%; width: 50%;
  background-position: center 70%;
  -webkit-mask-image: linear-gradient(90deg, transparent 5%, black 45%);
  mask-image: linear-gradient(90deg, transparent 5%, black 45%);
  background-image: url('svc-vehicle.png');
}
.svc-04::after {
  left: 10%; right: 10%; width: 80%;
  background-position: center 40%;
  -webkit-mask-image: linear-gradient(90deg, transparent 5%, black 20%, black 80%, transparent 95%);
  mask-image: linear-gradient(90deg, transparent 5%, black 20%, black 80%, transparent 95%);
  background-image: url('svc-testimony.png');
}

/* ============================================================================
   Cards and portraits
   ========================================================================== */

.card {
  background: var(--color-background);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
}

/* ---- Method tiles --------------------------------------------------------
   These carried `from-gray-900/90` behind text set in --color-foreground, which
   on a light page is near-black — near-black type on a near-black scrim. The
   tile now declares data-surface="ink" so the whole token family inverts for
   the region, which is what the contract exists to do, and the scrim derives
   from --color-ink so it shares material with the hero instead of Tailwind's
   stock gray. The min-height gives the plate behind it room to read as a
   photograph rather than a smear behind two lines of copy. */
.tile {
  position: relative;
  display: flex;
  overflow: hidden;
  min-height: clamp(15rem, 12rem + 9vw, 19rem);
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-md);
  background: var(--color-ink);
}
.tile__art {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  transform: scale(1.02);
  transition: transform var(--duration-enter) var(--ease-spring);
}
.tile:hover .tile__art { transform: scale(1.06); }
.tile__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    color-mix(in oklch, var(--color-ink) 30%, transparent) 0%,
    color-mix(in oklch, var(--color-ink) 68%, transparent) 52%,
    color-mix(in oklch, var(--color-ink) 93%, transparent) 100%
  );
}
.tile__body {
  position: relative;
  margin-top: auto;
  padding: clamp(1.375rem, 1rem + 1.4vw, 2rem);
}

/* ---- Label / content split -----------------------------------------------
   Was md:w-1/3 + md:w-2/3 with a 4rem gap, which left a tall empty column
   beside a two-line heading. The hairline turns that space into a drafting
   rule rather than a hole, the rail is narrower, and it sticks so the label
   tracks the copy it belongs to instead of stranding at the top. */
.split {
  display: grid;
  gap: clamp(1.25rem, 0.75rem + 1.8vw, 2.5rem);
  border-top: 1px solid var(--color-hairline);
  padding-top: clamp(1.25rem, 0.9rem + 1.4vw, 2.25rem);
}
@media (min-width: 768px) {
  .split { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); align-items: start; }
  .split__label { position: sticky; top: 5.5rem; }
}

/* Portrait frame. The copper baseline is a deliberate reference to the
   credential rule used elsewhere on the site. */
.portrait {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: linear-gradient(145deg, var(--color-ink-1), var(--color-ink-2));
  box-shadow: var(--shadow-elevation);
}
.portrait::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2px;
  background: var(--color-brand);
}
.portrait picture {
  display: block;
  width: 100%;
  height: 100%;
}
.portrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Reserves the box before the image decodes, so nothing reflows. */
  aspect-ratio: 1 / 1;
}
/* Credential chip. Sized off the body scale so it reads as a typographic
   element rather than a UI badge. */
.chip {
  display: inline-flex;
  align-items: center;
  padding: 0.5rem 1rem;
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  background: var(--color-brand-faint);
  border: 1px solid var(--color-brand-muted);
  color: var(--color-brand-light);
}

/* Credential/education row: label left, year right, hairline between.
   Tabular figures keep the year column from jittering down the list. */
.entry-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--color-hairline);
}
.entry-row:last-child { border-bottom: 0; padding-bottom: 0; }
.entry-row .year {
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  color: var(--color-foreground-faint);
}

/* A quoted/attributed block marked by a copper rule rather than a filled box. */
.marked {
  border-left: 2px solid var(--color-brand);
  padding-left: 1.5rem;
}

.portrait--mono {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  color: color-mix(in oklch, white 80%, transparent);
  user-select: none;
}
