/* ==========================================================================
   Personal landing page — structure and components
   Built in the Harbour language. Structure: the label column is the spine of
   the whole page, not just of individual sections. Left edge carries the
   argument; the column beside it carries the proof.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Contrast correction — a deliberate, documented deviation.
   Harbour assigns --mist to "quiet chrome: secondary text, tag fills,
   hairlines". As a TEXT colour on the fog ground, mist measures ~2.6:1 and
   fails WCAG AA. Mist is kept exactly as specified for hairlines, tag fills
   and rules; text that a reader must actually finish uses these darkened
   derivations instead. The hue and role are unchanged; only legibility moves.
   -------------------------------------------------------------------------- */
:root {
  --text-meta: color-mix(in srgb, var(--mist) 40%, var(--ink));
}
.harbour-night {
  --text-meta: color-mix(in srgb, var(--mist) 45%, var(--sky));
}

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

html {
  -webkit-font-smoothing: antialiased;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--text-body);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: var(--w-display);
  letter-spacing: var(--track-heading);
  line-height: var(--lh-heading);
  color: var(--text-heading);
  margin: 0;
  text-wrap: balance;
}

p { margin: 0; max-width: var(--measure-body); }

a {
  color: var(--text-body);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: color var(--dur) var(--ease),
              text-decoration-color var(--dur) var(--ease);
}
a:hover {
  color: var(--accent-press);
  text-decoration-color: var(--accent-press);
  text-decoration-thickness: 2px;
}

/* The page had no rule for `code` at all: it rendered in the browser's
   default monospace at full body size, outside the type system on a design
   system whose whole premise is that mono annotates.
   The wrapping properties matter more. A channel pattern like
   mission.*.{areas,entities,geofencing,layers} contains no character a normal
   break opportunity applies to, so it overran the right gutter on a phone and
   was clipped mid-word. `anywhere` also lets the containing block shrink,
   which `break-word` alone does not. */
code, kbd {
  font-family: var(--font-mono);
  font-size: 0.92em;
  overflow-wrap: anywhere;
  word-break: break-word;
}

::selection { background: var(--accent); color: var(--on-accent); }

/* The outline carries the colour; the box-shadow lays a 1px ink edge just
   outside it. On the fog ground the ring alone is legible, but on the night
   section and against the accent fill a warm ring on a warm field loses its
   boundary — the hairline guarantees one on every surface the page has. */
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
  box-shadow: 0 0 0 calc(var(--focus-offset) + 2px) var(--surface-page),
              0 0 0 calc(var(--focus-offset) + 3px) var(--ink);
}
.harbour-night :focus-visible {
  box-shadow: 0 0 0 calc(var(--focus-offset) + 2px) var(--surface-page),
              0 0 0 calc(var(--focus-offset) + 3px) var(--sky-head);
}

.skip {
  position: absolute;
  left: -9999px;
  top: var(--s-3);
  background: var(--ink);
  color: var(--fog);
  padding: var(--s-3) var(--s-5);
  font-family: var(--font-display);
  font-size: var(--size-small);
  z-index: 10;
}
.skip:focus { left: var(--s-3); }

.shell {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 var(--page-gutter);
}

/* --------------------------------------------------------------------------
   The ledger — the page's structural spine.
   Left: the claim, set as a Harbour label. Right: the evidence for it.
   -------------------------------------------------------------------------- */
.row {
  display: grid;
  grid-template-columns: var(--label-col) minmax(0, 1fr);
  column-gap: var(--s-8);
  row-gap: var(--s-5);
  border-top: var(--border-rule);
  padding: var(--s-7) 0 var(--s-9);
}

.row__label {
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-meta);
  margin: 0;
  position: sticky;
  top: var(--s-6);
  align-self: start;
}

/* A claim in the label column carries weight the plain labels do not.
   Sized above the case headings it governs: the spine states the argument,
   the evidence answers it, and a child heading larger than its parent
   reversed that relationship on every width. */
.row__claim {
  font-family: var(--font-display);
  font-weight: var(--w-display);
  font-size: var(--size-h2);
  letter-spacing: var(--track-heading);
  line-height: var(--lh-heading);
  text-transform: none;
  color: var(--text-heading);
}

.row__body > * + * { margin-top: var(--s-5); }

.stack   { display: flex; flex-direction: column; gap: var(--s-5); }
.stack-s { display: flex; flex-direction: column; gap: var(--s-3); }
.inline  { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-5); }

.lead {
  font-size: var(--size-lead);
  line-height: var(--lh-body);
  max-width: var(--measure-lead);
}

.meta {
  font-family: var(--font-mono);
  font-size: var(--size-mono);
  letter-spacing: var(--track-mono);
  line-height: 1.9;
  color: var(--text-meta);
  margin: 0;
}
.meta a { color: inherit; }

.quiet { color: var(--text-meta); }

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  padding: var(--s-6) 0;
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  text-decoration: none;
  color: var(--text-heading);
}
.brand__name {
  font-family: var(--font-display);
  font-weight: var(--w-display);
  font-size: var(--size-h3);
  letter-spacing: var(--track-display);
  line-height: 1.1;
}
.brand__role {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--size-mono);
  letter-spacing: var(--track-mono);
  color: var(--text-meta);
  font-weight: 400;
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-6);
}
.site-nav a {
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-meta);
  text-decoration: none;
}
.site-nav a:hover { color: var(--accent); }

/* --------------------------------------------------------------------------
   Hero — every fact a recruiter scans for, above the fold.
   -------------------------------------------------------------------------- */
.hero { padding: var(--s-7) 0 var(--s-8); }

.hero h1 {
  font-size: clamp(38px, 6vw, var(--size-display));
  letter-spacing: var(--track-display);
  line-height: var(--lh-display);
  /* Tuned to the face, not inherited from the mockup: Bricolage sets narrower
     per character than Space Grotesk, so 17ch left the headline wrapping a
     line early and pushed the fact row — the recruiter's payload — down the
     first viewport. */
  max-width: 20ch;
}

/* Both hidden until the 700px breakpoint switches them on. */
.hero__strip { display: none; }
.mobile-bar { display: none; }

/* The hero portrait sits beside the argument on wide screens only. On a phone
   it would push the fact grid back below the fold, which is precisely what the
   mobile work exists to prevent — so the face is a desktop affordance and the
   facts keep the first viewport. */
.hero__portrait { display: none; margin: 0; }

@media (min-width: 900px) {
  .hero__main {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: var(--s-8);
    align-items: end;
  }
  .hero__portrait { display: block; }
  .hero__portrait .plate { aspect-ratio: 4 / 5; }
}

.hero__lead {
  font-size: var(--size-lead);
  max-width: 54ch;
  margin-top: var(--s-6);
}

.case__nda {
  font-family: var(--font-mono);
  font-size: var(--size-mono);
  letter-spacing: var(--track-mono);
  line-height: 1.7;
  color: var(--text-meta);
  margin: 0 0 var(--s-5);
  max-width: var(--measure-body);
}

.hero__facts {
  border-top: var(--border-rule);
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--s-5) var(--s-8);
}
.hero__fact dt {
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-meta);
  margin-bottom: var(--s-2);
}
.hero__fact dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--size-small);
  line-height: 1.6;
  color: var(--text-body);
}
.hero__fact dd .tag { margin-bottom: var(--s-2); }

/* --------------------------------------------------------------------------
   Controls
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 var(--s-6);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-small);
  letter-spacing: 0.02em;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover { background: var(--accent-press); color: var(--on-accent); }

.btn--text {
  background: none;
  color: var(--text-body);
  padding: 0;
  min-height: 44px;
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-underline-offset: 4px;
}
.btn--text:hover {
  background: none;
  color: var(--accent-press);
  text-decoration-color: var(--accent-press);
  text-decoration-thickness: 2px;
}

.tag {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 0 var(--s-3);
  background: var(--tag-bg);
  color: var(--tag-text);
  border-radius: var(--radius);
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
}
.tag--sun { background: var(--accent); color: var(--on-accent); }

.tags { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* --------------------------------------------------------------------------
   Placeholder — content the author must supply. Deliberately conspicuous:
   this must never be mistaken for finished copy or ship unnoticed.
   -------------------------------------------------------------------------- */
.todo {
  border: 1px dashed var(--accent);
  padding: var(--s-5);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  max-width: var(--measure-body);
}
.todo__label {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  /* --accent-press alone measured 3.52:1 against the tinted fill. Pulled
     toward ink until it clears AA; still reads as rust, not as body text. */
  color: color-mix(in srgb, var(--accent-press) 45%, var(--ink));
  margin-bottom: var(--s-3);
}
.todo p { font-size: var(--size-small); color: var(--text-body); }
.todo p + p { margin-top: var(--s-3); }

/* --------------------------------------------------------------------------
   Case studies
   -------------------------------------------------------------------------- */
.case + .case { margin-top: var(--s-9); }

.case__head { max-width: var(--measure-body); }
.case__head h3 {
  font-size: var(--size-h3);
  max-width: 26ch;
}
.case__summary { margin-top: var(--s-4); font-size: var(--size-lead); }

.case__facts {
  font-family: var(--font-mono);
  font-size: var(--size-mono);
  letter-spacing: var(--track-mono);
  color: var(--text-meta);
  margin-bottom: var(--s-4);
}

/* Beat label sits beside its content rather than above it, so the labels
   read as a scannable rail down the case — the same move the row grid
   already makes one level up, and the pattern the CV uses for its own
   entries. Collapses to a stacked block on narrow viewports below. */
.beat {
  margin-top: var(--s-6);
  display: grid;
  grid-template-columns: var(--beat-label-col) minmax(0, 1fr);
  column-gap: var(--s-6);
  align-items: start;
}
.beat > h4 {
  grid-column: 1;
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-meta);
  margin: 0;
}
.beat > :not(h4) { grid-column: 2; }
.beat p + p { margin-top: var(--s-4); }

.decisions { display: flex; flex-direction: column; gap: var(--s-5); }
.decisions > li { list-style: none; }
.decisions p:first-child {
  font-family: var(--font-display);
  font-weight: var(--w-body-strong);
  color: var(--text-heading);
  margin-bottom: var(--s-2);
}
.decisions ol, .decisions ul { margin: 0; padding: 0; }

/* --------------------------------------------------------------------------
   Work index — an in-section contents rail for the three case studies,
   sharing the row label's sticky column. Marks are the brand mark's own
   circle and square, in the brand mark's own colours — the exact pairing
   the CV uses beside its own section labels ("○■ SELECTED PROJECT"), not a
   monochrome recolour of it. aria-current is set by assets/js/work-index.js
   as the reader scrolls: the ring fills solid to read as "you are here",
   the same filled/outline logic the brand mark's own square already uses
   for weight. Progressive enhancement — the links are plain #id anchors and
   work without the script.
   -------------------------------------------------------------------------- */
#work .row__label { position: static; }
.work-nav {
  grid-column: 1;
  position: sticky;
  top: var(--s-6);
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: var(--s-7);
}
.work-index { display: flex; flex-direction: column; gap: var(--s-4); }
.work-index__item {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 32px;
  text-decoration: none;
}
.work-index__mark { flex: none; }
.work-index__ring {
  fill: none;
  stroke: var(--text-heading);
  transition: fill var(--dur) var(--ease);
}
/* --accent-press, not --accent: --sun against --fog clears 2.96:1, under the
   3:1 floor for a non-text indicator — see --focus-ring's note in
   tokens.css. --sun-deep (--accent-press) is what actually clears it here. */
.work-index__dot { fill: var(--accent-press); }
.work-index__label {
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-meta);
  transition: color var(--dur) var(--ease);
}
.work-index__item:hover .work-index__label,
.work-index__item:focus-visible .work-index__label { color: var(--text-body); }
/* Current case: the ring fills solid and the label goes full ink and bold —
   two independent signals changing together, not one subtle colour step. */
.work-index__item[aria-current="true"] .work-index__ring { fill: var(--text-heading); }
.work-index__item[aria-current="true"] .work-index__label {
  color: var(--text-heading);
  font-weight: var(--w-body-strong);
}

/* --------------------------------------------------------------------------
   Two-ink plate — Harbour's imagery treatment, rendered as the bare register
   texture. No photography exists; this is the system's own stand-in.
   -------------------------------------------------------------------------- */
.plate {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--fog);
  border: var(--border-rule);
  overflow: hidden;
}
.plate::before,
.plate::after {
  content: "";
  position: absolute;
  inset: -6px;
  background-image: radial-gradient(circle, currentColor 34%, transparent 36%);
  background-size: var(--plate-dot) var(--plate-dot);
}
.plate::before { color: var(--plate-ink-a); opacity: 0.5; transform: translate(-2px, -1px); }
.plate::after  { color: var(--plate-ink-b); opacity: 0.42; transform: translate(2px, 2px);
                 background-position: 1px 1px; }

/* Photograph as two misregistered ink plates — the system's imagery rule.
   Each plate takes the image through an SVG filter that maps luminance to
   alpha and floods a single ink, then masks it with a halftone dot grid and
   multiplies it over the ground. The two plates use different grid phases and
   opposite translations, so they sit a few pixels out of register the way a
   two-colour press does. Never full colour, never a single grey plate. */
/* Image sources live here, not in the markup. A url() inside a custom property
   resolves relative to the stylesheet that declares it, so a document-relative
   path written in an HTML style attribute ends up looked up under assets/css/.
   Declaring them in this file keeps the paths relative — and correct. */
.plate--portrait-a { --plate-src: url("../portrait-a.jpg"); }
.plate--portrait-b { --plate-src: url("../portrait-b.jpg"); }

.plate--photo { background: var(--surface-page); }
.plate--photo::before,
.plate--photo::after {
  inset: 0;
  background-image: none;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  mix-blend-mode: multiply;
}
.plate--photo::before,
.plate--photo::after {
  background-image: var(--plate-src);
  -webkit-mask-image: radial-gradient(circle, #000 58%, transparent 60%);
  mask-image: radial-gradient(circle, #000 58%, transparent 60%);
  -webkit-mask-size: var(--plate-dot) var(--plate-dot);
  mask-size: var(--plate-dot) var(--plate-dot);
}
.plate--photo::before { transform: translate(-0.5px, -0.5px); }
.plate--photo::after  { transform: translate(0.5px, 0.5px); }

.harbour-night .plate--photo { background: var(--night); }

/* Portrait A (hero) — original two-ink treatment: sun and ink plates near
   equal strength, and both plates carry over to a dark ground. Sky carries
   the structure where the image is bright; sun-lift is the warm second
   plate, held back so it tints the highlights rather than flooding them. */
.plate--portrait-a.plate--photo::before { opacity: 0.6; filter: url(#plate-sun-a); }
.plate--portrait-a.plate--photo::after  { opacity: 0.9; filter: url(#plate-ink-a); }
.harbour-night .plate--portrait-a.plate--photo::before {
  filter: url(#plate-sky-night-a);
  mix-blend-mode: screen;
  opacity: 1;
}
.harbour-night .plate--portrait-a.plate--photo::after {
  display: block;
  filter: url(#plate-warm-night-a);
  mix-blend-mode: screen;
  opacity: 0.5;
}

/* Portrait B (contact) — sun plate held well back so the ink plate carries
   the structure. On the night ground the plate screens instead of
   multiplying, and the alpha map is mirrored — see plate-sun-night-b in
   index.html. The ink plate is hidden rather than mirrored alongside it:
   screening its shadow-favouring map over a dark ground printed a
   photographic negative under it, so the night ground collapses to one
   plate. */
.plate--portrait-b.plate--photo::before { opacity: 0.4; filter: url(#plate-sun-b); }
.plate--portrait-b.plate--photo::after  { opacity: 0.9; filter: url(#plate-ink-b); }
.harbour-night .plate--portrait-b.plate--photo::before {
  filter: url(#plate-sun-night-b);
  mix-blend-mode: screen;
  opacity: 1;
}
.harbour-night .plate--portrait-b.plate--photo::after {
  display: none;
}

.plate__caption {
  font-family: var(--font-mono);
  font-size: var(--size-mono);
  letter-spacing: var(--track-mono);
  color: var(--text-meta);
  margin-top: var(--s-3);
}

/* --------------------------------------------------------------------------
   Credentials table
   -------------------------------------------------------------------------- */
.table { width: 100%; border-collapse: collapse; }
.table caption {
  caption-side: bottom;
  text-align: left;
  font-family: var(--font-mono);
  font-size: var(--size-mono);
  letter-spacing: var(--track-mono);
  color: var(--text-meta);
  padding-top: var(--s-4);
}
.table th {
  text-align: left;
  font-family: var(--font-display);
  font-weight: var(--w-label);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-meta);
  padding: 0 var(--s-5) var(--s-3) 0;
  border-bottom: var(--border-rule);
}
.table td {
  padding: var(--s-4) var(--s-5) var(--s-4) 0;
  border-bottom: var(--border-rule);
  vertical-align: top;
  font-size: var(--size-small);
}
.table td.mono {
  font-family: var(--font-mono);
  font-size: var(--size-mono);
  letter-spacing: var(--track-mono);
  color: var(--text-meta);
  white-space: nowrap;
}
.table tbody tr { transition: background var(--dur) var(--ease); }
.table tbody tr:hover { background: color-mix(in srgb, var(--mist) 12%, transparent); }

/* --------------------------------------------------------------------------
   Contact — the night ground
   -------------------------------------------------------------------------- */
.contact { background: var(--night); margin-top: var(--s-9); }
.contact .shell { padding-top: var(--s-9); padding-bottom: var(--s-9); }
.contact h2 {
  font-size: clamp(30px, 4.5vw, var(--size-h1));
  letter-spacing: var(--track-display);
  max-width: 19ch;
}
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--s-8);
  align-items: start;
}
.contact__portrait { margin: 0; }
.contact__portrait .plate { aspect-ratio: 4 / 5; }
.contact__sign {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--s-6);
  margin-top: var(--s-9);
  border-top: 1px solid var(--rule);
  padding-top: var(--s-5);
}

/* --------------------------------------------------------------------------
   Responsive — mobile is a primary case, not a fallback.
   68% of first views are on a phone; the ledger collapses to a single
   readable column and the label becomes a heading rather than a margin note.
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  :root { --page-gutter: var(--s-5); }

  .row {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--s-4);
    padding: var(--s-6) 0 var(--s-7);
  }
  .row__label { position: static; }
  /* The spine holds its size on mobile. Collapsing it to --size-h3 made the
     argument typographically identical to the evidence, which is the one
     distinction the whole structure exists to draw. */
  .row__claim { font-size: var(--size-h2); }

  .contact__grid { grid-template-columns: minmax(0, 1fr); }

  .hero { padding: var(--s-5) 0 var(--s-6); }
  .hero__facts { gap: var(--s-5); }

  .case + .case { margin-top: var(--s-7); }
  .case__head h3 { font-size: var(--size-h3); }

  /* The work index is a sticky-column device; there is no sticky column at
     this width to hang it on, and a fourth screen's worth of nothing but
     three links is not worth the space it would take from the case itself. */
  .work-nav { position: static; gap: var(--s-4); }
  .work-index { display: none; }

  .beat { display: block; }
  .beat > h4 { margin: 0 0 var(--s-3); }

  .site-header { padding: var(--s-5) 0; gap: var(--s-4); }
  .site-nav { gap: var(--s-4); }

  /* The section nav is dropped below tablet width. Five wrapped links pushed
     the wordmark out of the first viewport, which is the one thing a phone
     reader must not lose — and on a single scrolling page the nav is a
     convenience, not the only route to anything. */
  @media (max-width: 700px) {
    .site-nav { display: none; }

    /* The three answers a recruiter needs, above the headline. Mono, because
       this is metadata in Harbour's terms — the same role the fact grid uses
       further down, just compressed to what fits before the fold. */
    .hero__strip {
      display: block;
      font-family: var(--font-mono);
      font-size: var(--size-mono);
      letter-spacing: var(--track-mono);
      line-height: 1.8;
      color: var(--text-meta);
      margin: 0 0 var(--s-4);
      max-width: none;
    }
    .hero__strip b {
      font-weight: 500;
      /* Pulled toward ink so it clears AA at 12px; --accent-press alone is
         3.80:1 on fog, which is fine for a focus ring and not for text. */
      color: color-mix(in srgb, var(--accent-press) 45%, var(--ink));
    }

    /* The tenure sentence is carried by the fact grid and the record section;
       on a phone it costs three lines of the opening viewport. */
    .lead__tail { display: none; }

    /* Persistent contact. `.harbour-night` on the element re-scopes the
       tokens, so the button and rule colours come from the night ground
       without a single hard-coded value. */
    .mobile-bar {
      position: fixed;
      inset: auto 0 0 0;
      z-index: 20;
      display: flex;
      align-items: center;
      gap: var(--s-4);
      padding: var(--s-3) var(--page-gutter);
      padding-bottom: calc(var(--s-3) + env(safe-area-inset-bottom, 0px));
      border-top: 1px solid var(--rule);
    }
    .mobile-bar__state {
      font-family: var(--font-mono);
      font-size: var(--size-mono);
      letter-spacing: var(--track-mono);
      color: var(--text-body);
    }
    /* Beats the full-width `.btn` rule at 560px on specificity, not order. */
    .mobile-bar .btn {
      margin-left: auto;
      width: auto;
      padding: 0 var(--s-5);
    }

    /* Clear the fixed bar so it never covers the end of the document. */
    body { padding-bottom: 92px; }
  }

  /* The credentials table stops being a table on a phone.
     `caption` must be in this list. Left as `display: table-caption` inside a
     now-block parent it shrink-wraps to ~73px wide and 12 lines tall, which
     destroyed the one sentence explaining why the employer column is empty.
     `caption-side: bottom` stops applying once it is a block, so it now reads
     above the rows — which is the better order anyway: the reader learns the
     aliasing convention before meeting the blanks. */
  .table, .table caption, .table tbody, .table tr, .table td { display: block; width: 100%; }
  .table caption { padding: 0 0 var(--s-4); }
  .table thead { display: none; }
  .table tr { border-bottom: var(--border-rule); padding: var(--s-4) 0; }
  .table td { border: 0; padding: 0 0 var(--s-2); }
  .table td.mono { white-space: normal; }
}

@media (max-width: 560px) {
  .hero h1 { max-width: 100%; }
  .inline { gap: var(--s-4); }
  /* The primary action goes full width; the text button must not, or the row
     reads as two competing buttons. */
  .btn { width: 100%; }
  .btn--text { width: auto; }
  .hero__fact dd .tag { margin-bottom: var(--s-2); }
}

/* --------------------------------------------------------------------------
   Print — the page should survive being sent to paper by a recruiter.
   -------------------------------------------------------------------------- */
@media print {
  /* .btn is NOT hidden. Hiding it removed both email buttons from the
     printed page, so a printout carried no way to make contact — and the
     a::after href rule below never fired because the element was gone. */
  .site-nav, .mobile-bar { display: none; }
  .hero__strip, .lead__tail { display: block; }
  /* Backgrounds are routinely dropped by print settings, so the button is
     given an ink border and ink text rather than relying on the accent fill. */
  .btn {
    display: inline-block;
    background: none;
    color: #000;
    border: 1pt solid #000;
    padding: 3pt 7pt;
    min-height: 0;
  }
  body { background: #fff; color: #000; font-size: 11pt; }
  .row { break-inside: avoid; border-top: 1px solid #999; }
  .contact { background: none; }
  .contact h2, .contact p { color: #000; }
  a::after { content: " (" attr(href) ")"; font-size: 9pt; color: #444; }
}
