/* =========================================================================
   Ruben V. — Personal IT Systems Portfolio
   Redesign direction: "Operational history"

   Content status: the identity, roles and capabilities are real (from Ruben's
   resume). What is still filler is listed under "Filler inventory" in
   README.md — keep that list, not this comment, as the source of truth.

   Palette, type, and the uptime-strip concept are deliberate design choices,
   documented in REDESIGN-PROPOSAL.md (§2, Project 1). The light/dark theme
   system is described in README.md under "Light and dark themes".
   ========================================================================= */

/* ---- Design tokens ------------------------------------------------------- */
/* Theme-independent tokens. */
:root {
  /* One colour per planet on the tech map's outer orbit. --kind-ink is the
     label tint, all ≥ 7:1 on the map background. The map stays dark in both
     themes, so these never change with the theme. */
  --kind-professional:  #e8a94f;  --kind-professional-ink: #f0cf98;
  --kind-personal:      #b59cf0;  --kind-personal-ink:     #d6c8f7;
  --kind-business:      #ee8a78;  --kind-business-ink:     #f6c0b6;

  /* Type */
  --font-sans:  "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-serif: var(--font-sans);   /* one text family: Plex Sans (was Source Serif 4) */
  --font-display: "Bricolage Grotesque", "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
  --font-mono:  "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* One type scale, ratio 1.25 (major third) on a 16px base. Every size on
     the page outside the map is one of these steps. step--1 (12.8px) is the
     smallest, above the 12px floor. */
  --step--1: 0.8rem;      /* 12.8px  labels, chips, data            */
  --step-0:  1rem;        /* 16px    body, UI, prose                */
  --step-1:  1.25rem;     /* 20px    h3, hero lead                  */
  --step-2:  1.5625rem;   /* 25px                                   */
  --step-3:  1.953rem;    /* 31.25px h2 on phones                   */
  --step-4:  2.441rem;    /* 39px    h2 on desktop                  */
  --step-5:  3.052rem;    /* 48.8px  h1 on phones                   */
  --step-6:  3.815rem;    /* 61px                                   */
  --step-7:  4.768rem;    /* 76.3px  h1 on desktop                  */

  /* Space + rhythm */
  --gutter:      clamp(1.25rem, 5vw, 4rem);
  --rail:        7.5rem;          /* width of the section-name rail on desktop */
  --measure:     62ch;            /* readable line length for prose            */
  --section-gap: clamp(3.5rem, 9vw, 7rem);

  --radius:      4px;
  --ring:        2px solid var(--ink);
  --ring-offset: 3px;

  /* Height of the sticky header, so anchor targets can clear it: a 44px
     control plus 0.5rem above and below, plus the 1px border. */
  --header-h:    62px;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ---- Theme tokens --------------------------------------------------------
   Light is the default. Dark applies when the visitor chose it
   (data-theme="dark", set by theme-init.js before paint or by the toggle),
   or when the system asks for dark and the visitor has not chosen light.
   The dark block appears twice, word for word, because CSS cannot share one
   declaration block between a media query and a selector. test.js checks
   that all three blocks define the same token names and that the two dark
   blocks match.

   Contrast (WCAG 2.x, measured):
     light  ink 12.9:1 on bg, muted 6.4:1 on bg, operational 5.2:1 on bg,
            incident-ink 5.5:1 on bg, line-strong 3.2:1 on bg / 3.4 on raised
     dark   ink 15.3:1 on bg, muted 7.6:1 on bg, operational 8.2:1 on bg,
            incident-ink 11.5:1 on bg, line-strong 3.6:1 on bg / 3.3 on paper
   --line is decorative only (~1.3–1.4:1); a control's only boundary uses
   --line-strong (WCAG 1.4.11). */
:root {
  color-scheme: light;

  /* A cool "architect's paper" ground with two status colours borrowed from
     status pages: pine = operational, amber = a role change (marked with a
     dark cap). */
  --bg:            #edf0f2;
  --paper:         #ffffff;   /* cards and panels                           */
  --raised:        #f6f8f9;   /* a surface inside a panel (rows, readout)   */
  --ink:           #1b2a33;   /* 12.9:1 on --bg, 14.7:1 on --paper          */
  --muted:         #4a5860;   /* 6.4:1 on --bg  — AA for body text          */
  --line:          #cbd4db;   /* hairlines and dividers — decorative only   */
  --line-strong:   #788895;   /* 3.2:1 on --bg, 3.65:1 on --paper           */
  --operational:   #2f6f5b;   /* 5.2:1 on --bg  — AA for text and UI        */
  --incident:      #c07a2e;   /* graphical only: 3.5:1 on --paper           */
  --incident-ink:  #8a5316;   /* 6.3:1 on --paper — AA for incident labels  */
  --cap:           #1b2a33;   /* the role-change cap: 4.3:1 on --incident   */

  /* The primary button: ink fill, paper text (14.7:1). */
  --button-bg:     #1b2a33;
  --button-ink:    #ffffff;

  /* Background dot-grid and the strip's pine glow. */
  --grid-dot:      rgba(27, 42, 51, 0.05);
  --glow:          0 30px 60px -35px rgba(47, 111, 91, 0.35);

  /* The tech map's outer edge. Its own dark ground already separates it
     from the light page. */
  --map-frame:     #1c3338;

  /* Elevation — one consistent scale, used instead of ad-hoc shadow values. */
  --shadow-sm: 0 1px 2px rgba(27, 42, 51, 0.06);
  --shadow-md: 0 8px 20px -6px rgba(27, 42, 51, 0.16);
  --shadow-lg: 0 20px 45px -12px rgba(27, 42, 51, 0.28);
}

/* Dark, following the system, unless the visitor picked light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg:            #0f1417;
    --paper:         #161d21;
    --raised:        #1d262b;
    --ink:           #e3ebe9;
    --muted:         #9aa9ad;
    --line:          #26323a;
    --line-strong:   #5d717b;
    --operational:   #4fbf9f;
    --incident:      #d99545;
    --incident-ink:  #f0c58a;
    --cap:           #0f1417;

    --button-bg:     #e3ebe9;
    --button-ink:    #161d21;

    --grid-dot:      transparent;
    --glow:          0 0 0 transparent;

    --map-frame:     #2c4e56;

    --shadow-sm: inset 0 1px 0 rgba(255, 255, 255, 0.04);
    --shadow-md: inset 0 1px 0 rgba(255, 255, 255, 0.05);
    --shadow-lg: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  }
}

/* Dark, chosen with the toggle. Keep identical to the block above. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:            #0f1417;
  --paper:         #161d21;
  --raised:        #1d262b;
  --ink:           #e3ebe9;
  --muted:         #9aa9ad;
  --line:          #26323a;
  --line-strong:   #5d717b;
  --operational:   #4fbf9f;
  --incident:      #d99545;
  --incident-ink:  #f0c58a;
  --cap:           #0f1417;

  --button-bg:     #e3ebe9;
  --button-ink:    #161d21;

  --grid-dot:      transparent;
  --glow:          0 0 0 transparent;

  --map-frame:     #2c4e56;

  --shadow-sm: inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --shadow-md: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  --shadow-lg: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

/* ---- Icons -------------------------------------------------------- */
.icon {
  width: 1em;
  height: 1em;
  flex: none;
  vertical-align: -0.15em;
  transition: transform 180ms var(--ease-out);
}
.icon--sm { width: 0.85em; height: 0.85em; }
a:hover > .icon, a.button:hover .icon { transform: translateX(3px); }
.link-underline:hover .icon--sm { transform: translate(2px, -2px); }

/* ---- Section reveal (one quiet fade as a section enters view) -----
   JS adds .reveal to each section on load and .is-visible when it enters
   the viewport. No per-card stagger: the strip's load-in is the page's one
   motion moment. No motion at all — and no layout shift — when the browser
   or the user asks for reduced motion, or if JS never runs.
   Uses `animation`, not `transition`, so it never fights a component's own
   hover transition for the same shorthand property. */
.reveal { opacity: 0; }
.reveal.is-visible { animation: reveal-in 360ms var(--ease-out) forwards; }
@keyframes reveal-in {
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; }
  .reveal.is-visible { animation: none; }
}

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

html { scroll-behavior: smooth; background: var(--bg); }

body {
  margin: 0;
  /* A faint schematic dot-grid — reads as graph paper / a monitoring board,
     never competes with content. Light theme only (--grid-dot is
     transparent in dark). */
  background:
    radial-gradient(circle, var(--grid-dot) 1px, transparent 1px)
      0 0 / 22px 22px fixed,
    var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; text-decoration: none; }

h1, h2, h3 {
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin: 0;
  text-wrap: balance;
}
/* The hero h1 breaks by hand (<br>), so it doesn't need balancing; `balance`
   on it was reported to crash Chromium 151 headless. */
h1 { text-wrap: wrap; }

/* Bricolage Grotesque is the display face for h1 and h2 only; h3, UI text and
   the map stay IBM Plex Sans. */
h1 { font-family: var(--font-display); font-weight: 700; font-size: clamp(var(--step-5), 6.4vw, var(--step-7)); letter-spacing: -0.035em; line-height: 1.02; }
h2 { font-family: var(--font-display); font-weight: 600; font-size: var(--step-3); letter-spacing: -0.025em; }
h3 { font-size: var(--step-1); letter-spacing: -0.01em; line-height: 1.25; }
@media (min-width: 900px) {
  h2 { font-size: var(--step-4); }
}

p { margin: 0; }

/* Prose paragraphs use the serif — it makes the "considered" positioning
   something you can feel rather than just read. */
.prose {
  font-family: var(--font-serif);
  font-size: var(--step-0);
  line-height: 1.65;
  color: var(--muted);
  max-width: var(--measure);
}
.prose + .prose { margin-top: 1rem; }

/* Mono labels and data. The smallest text on the page is --step--1 (12.8px). */
.eyebrow,
.rail-id,
.timeline time,
.legend,
.meta {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- Skip link -------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: 0.5rem;
  top: 0.5rem;
  transform: translateY(-150%);
  background: var(--ink);
  color: var(--paper);
  padding: 0.6rem 0.9rem;
  border-radius: var(--radius);
  font-size: var(--step-0);
  transition: transform 120ms ease;
  z-index: 20;
}
.skip-link:focus { transform: translateY(0); }

/* ---- Visible focus for keyboard users -------------------------------- */
:focus-visible {
  outline: var(--ring);
  outline-offset: var(--ring-offset);
  border-radius: 2px;
}
/* Don't show the ring for plain mouse clicks that also move focus. */
:focus:not(:focus-visible) { outline: none; }

/* <main> only takes focus as the skip link's destination. It is not a
   control, and ringing the whole page region is more confusing than helpful,
   so no indicator here — every actual control keeps its ring above. */
#main:focus,
#main:focus-visible { outline: none; }

/* Every in-page anchor target has to clear the sticky header, or following a
   nav link parks the section's heading underneath it. */
#top,
.block,
.hero,
.uptime {
  scroll-margin-top: calc(var(--header-h) + 1rem);
}

/* ---- Shell / layout -------------------------------------------------- */
.shell {
  max-width: 1180px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Each block is laid out as [ section-name rail | content ]. The rail is the
   section's one label on desktop. On phones it is dropped and the heading
   stands alone, rather than stacking two mono labels over every h2. */
.block {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  column-gap: clamp(1rem, 3vw, 2.5rem);
  padding-block: var(--section-gap);
  border-top: 1px solid var(--line);
}
.block > .rail-id {
  /* Lines the label up with the first line of the h2 beside it. */
  padding-top: 0.9rem;
  color: var(--operational);
}
.block__body > * + * { margin-top: 1.5rem; }

@media (max-width: 720px) {
  .block { grid-template-columns: minmax(0, 1fr); }
  .block > .rail-id { display: none; }
}

/* ---- Header --------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
@media (prefers-reduced-transparency: reduce) {
  .site-header { background: var(--bg); backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .site-header { background: var(--bg); backdrop-filter: none; border-bottom-color: var(--line-strong); }
}
.site-header__inner {
  max-width: 1180px;
  margin-inline: auto;
  padding: 0.5rem var(--gutter);
  display: flex;
  align-items: center;
  gap: 1rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  /* The monogram is only 30px, so the link's box was under the touch floor.
     The header row is already this tall, so this costs no layout. */
  min-height: 44px;
  font-weight: 600;
  letter-spacing: -0.01em;
  transition: color 140ms var(--ease-out);
}
.site-header .brand { margin-right: auto; }
.brand:hover { color: var(--operational); }
.brand__text { display: grid; line-height: 1.2; min-width: 0; }
.brand__title {
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--muted);
}
.brand__mark {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--step--1);
}

.nav {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2rem);
  font-size: var(--step-0);
}
.nav a { position: relative; padding: 0.25rem 0; color: var(--muted); }
.nav a:hover { color: var(--ink); }
.nav a[aria-current] {
  color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--operational);
}
/* An underline that draws in on hover/focus rather than snapping on. */
.nav a:not(.nav__cta)::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--operational);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 220ms var(--ease-out);
}
.nav a:not(.nav__cta):hover::after,
.nav a:not(.nav__cta):focus-visible::after { transform: scaleX(1); }
.nav a[aria-current]::after { display: none; }

.nav__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  padding: 0.55rem 0.9rem !important;
  color: var(--ink) !important;
  transition: background-color 160ms var(--ease-out), color 160ms var(--ease-out),
              box-shadow 160ms var(--ease-out), transform 160ms var(--ease-out);
}
.nav__cta:hover { background: var(--ink); color: var(--paper) !important; box-shadow: var(--shadow-sm); }
.nav__cta:active { background: var(--ink); color: var(--paper) !important; box-shadow: none; transform: scale(0.98); }

/* The light/dark switch. A 44px square, its border at --line-strong
   because the border is its only visual boundary (1.4.11). The icon shows
   the theme in use: sun in light, moon in dark. */
.theme-toggle {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 44px;
  height: 44px;
  padding: 0;
  background: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  color: var(--ink);
  font-size: var(--step-1);
  cursor: pointer;
  transition: border-color 160ms var(--ease-out), background-color 160ms var(--ease-out);
}
.theme-toggle[hidden] { display: none; }
.theme-toggle:hover { border-color: var(--ink); background: color-mix(in srgb, var(--ink) 6%, transparent); }
.theme-toggle:active { background: color-mix(in srgb, var(--ink) 12%, transparent); }
.theme-toggle > .icon { grid-area: 1 / 1; }
.theme-toggle__moon { display: none; }
.theme-toggle[aria-pressed="true"] .theme-toggle__sun { display: none; }
.theme-toggle[aria-pressed="true"] .theme-toggle__moon { display: block; }

.nav-toggle { display: none; }

@media (max-width: 720px) {
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    /* 44px min on both axes — it is the only way to reach the menu on a
       phone. */
    min-height: 44px;
    min-width: 44px;
    background: none;
    /* The border is this button's only visual boundary, so it has to clear
       3:1 (1.4.11). --line is 1.31:1 on --bg; --line-strong is 3.19:1. */
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    padding: 0.45rem 0.8rem;
    font: inherit;
    font-size: var(--step-0);
    color: var(--ink);
    cursor: pointer;
    transition: border-color 160ms var(--ease-out), background-color 160ms var(--ease-out);
  }
  .nav-toggle:hover { border-color: var(--ink); background: color-mix(in srgb, var(--ink) 4%, transparent); }
  .nav-toggle:active { background: color-mix(in srgb, var(--ink) 9%, transparent); }
  .site-header__inner { gap: 0.5rem; }
  .nav {
    position: absolute;
    inset: 100% 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
    padding: 0.5rem var(--gutter) 1rem;
  }
  .nav[hidden] { display: none; }
  .nav a { padding: 0.8rem 0; border-bottom: 1px solid var(--line); }
  .nav a[aria-current] { box-shadow: none; }
  .nav__cta { margin-top: 0.8rem; text-align: center; justify-content: center; }

  /* Without JS nothing can toggle the menu: the panel stayed permanently
     open, absolutely positioned over the hero, and the Menu button was a
     dead control. Drop both and let the links sit in a normal, non-sticky
     header instead. */
  html:not(.js) .site-header { position: static; }
  html:not(.js) .site-header__inner { flex-wrap: wrap; }
  html:not(.js) .nav-toggle { display: none; }
  html:not(.js) .nav {
    position: static;
    flex-basis: 100%;        /* wrap onto its own row under the brand */
    padding: 0.25rem 0 0.75rem;
    border-bottom: 0;
    background: none;
  }
}

/* ---- Hero console: intro, status panel, the uptime strip ------------ */
.hero {
  padding-block: clamp(2rem, 5vw, 3.5rem) var(--section-gap);
}

/* Headline and lead on the left, the status panel on the right; the strip
   runs the full width underneath, above the fold at 1440×900. */
.hero__top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(17rem, 20rem);
  align-items: start;
  gap: clamp(1.5rem, 4vw, 4rem);
}
.hero__intro { min-width: 0; }

.hero__status {
  display: inline-flex;
  align-items: flex-start;
  gap: 0.5rem;
  color: var(--operational);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
}
.hero__status::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  margin-top: 0.45em;
  border-radius: 50%;
  background: var(--operational);
}
.hero h1 { margin-top: 1rem; }
.hero h1 .accent { color: var(--operational); }
.hero__lead {
  margin-top: 1.25rem;
  font-family: var(--font-serif);
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--muted);
  max-width: 44ch;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  /* Row gap large enough that the two CTAs' expanded hit areas (see
     .link-underline below) can't overlap once they wrap. */
  gap: 1.5rem;
  align-items: center;
  margin-top: 1.75rem;
}

/* The status panel: the page's raised surface. */
.status-panel {
  position: relative;
  padding: 1.25rem 1.25rem 1.1rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
}
.status-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.status-panel__ping {
  position: relative;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--operational);
}
@media (prefers-reduced-motion: no-preference) {
  .status-panel__ping::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: var(--operational);
    animation: ping 2.4s cubic-bezier(0, 0, 0.2, 1) infinite;
  }
  @keyframes ping {
    0%   { transform: scale(1);   opacity: 0.6; }
    100% { transform: scale(2.6); opacity: 0; }
  }
}
.status-panel__role { font-size: var(--step-1); font-weight: 600; letter-spacing: -0.01em; line-height: 1.25; margin-top: 0.75rem; }
.status-panel__org { font-size: var(--step--1); color: var(--muted); margin-top: 0.3rem; }

.status-panel__facts { margin: 1rem 0 0; }
.status-panel__facts > div {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr);
  gap: 0.75rem;
  align-items: baseline;
  padding-block: 0.6rem;
  border-top: 1px solid var(--line);
}
.status-panel__facts dt {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  color: var(--muted);
}
.status-panel__facts dd { margin: 0; font-size: var(--step-0); }
.status-panel__open { color: var(--operational); font-weight: 600; }
.status-panel__facts .status-panel__certifications { grid-template-columns: minmax(0, 1fr); gap: 0.65rem; }

.badges {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(5.25rem, 1fr));
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.badges li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  line-height: 1.5;
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  padding: 0.65rem 0.35rem;
}
.badges img { display: block; width: 64px; height: 64px; object-fit: contain; background: #fff; border-radius: 2px; }

@media (max-width: 860px) {
  .hero__top { grid-template-columns: minmax(0, 1fr); }
}

/* The strip itself: one framed instrument holding the cells, the axis, the
   readout and the legend. */
.uptime {
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
}
.uptime__panel {
  padding: 1rem 1rem 0.9rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* A quiet ambient glow lifts the signature element off the page (light
     only; dark elevation comes from the lighter surface). */
  box-shadow: var(--shadow-md), var(--glow);
}
.uptime__panel > .eyebrow { margin-bottom: 0.75rem; }
.uptime__frame {
  display: flex;
  gap: 2px;
  align-items: stretch;
  padding-block: 0.25rem;
  overflow-x: auto;
  scrollbar-width: thin;
}
.uptime__cell {
  flex: 1 1 0;
  min-width: 3px;
  height: 64px;
  padding: 0;
  border: 0;
  border-radius: 1px;
  background: var(--operational);
  cursor: pointer;
  transition: transform 120ms ease, opacity 120ms ease;
}

/* Phone: nine two-year cells instead of 67 quarters, so each one is a real
   tap target (33.6px wide at 390px, 30.2px at 360px, 72px tall). script.js
   switches the data to match at the same 640px breakpoint. */
@media (max-width: 639px) {
  .uptime__frame { gap: 3px; }
  .uptime__cell { min-width: 0; height: 72px; }
}
.uptime__cell[data-state="incident"] {
  background: var(--incident);
  /* A shape cue as well as a colour: a dark cap on top, so a role change
     isn't told apart by hue alone (1.4.1). The legend swatch matches. The
     cap stays dark in both themes (--cap). */
  box-shadow: inset 0 6px 0 var(--cap);
}
/* Phone: nine two-year cells, four of them role changes, so a fully amber cell
   turned the strip nearly half amber and read as an incident. A role change is
   a green cell with a dark-and-amber cap instead: the strip stays mostly
   "operational" and the cap is still a shape cue, not hue alone. */
@media (max-width: 639px) {
  .uptime__cell[data-state="incident"] {
    background: linear-gradient(var(--cap) 0 6px, var(--incident) 6px 14px, var(--operational) 14px);
    box-shadow: none;
  }
}
.uptime__cell:hover,
.uptime__cell:focus-visible { transform: scaleY(1.12); }
/* The strip scrolls (overflow-x), which clips an outside ring, so the focus
   ring is drawn inside the cell. */
.uptime__cell:focus-visible { outline: var(--ring); outline-offset: -2px; position: relative; z-index: 1; }
.uptime__cell:active { transform: scaleY(1.04); opacity: 0.85; }
.uptime__cell[aria-pressed="true"] {
  outline: var(--ring);
  outline-offset: var(--ring-offset);
}

.uptime__axis {
  display: flex;
  justify-content: space-between;
  margin-top: 0.4rem;
}

/* The footer row of the frame: the readout, then the legend. */
.uptime__foot {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.75rem 1.5rem;
  align-items: center;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
}
@media (max-width: 720px) {
  .uptime__foot { grid-template-columns: minmax(0, 1fr); }
}

.uptime__readout {
  padding: 0.55rem 0.8rem;
  background: var(--raised);
  border-left: 3px solid var(--operational);
  border-radius: 2px;
  font-size: var(--step-0);
  line-height: 1.5;
  min-height: 4.1rem;
}
.uptime__readout[data-state="incident"] { border-left-color: var(--incident); }
.uptime__readout .rq {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  color: var(--muted);
  display: block;
  margin-bottom: 0.1rem;
}
.uptime__readout .tag-incident { color: var(--incident-ink); font-weight: 600; }
.uptime__readout .tenure {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.02em;
  color: var(--muted);
  white-space: nowrap;
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  text-transform: none;
  letter-spacing: 0.02em;
}
.legend span { display: inline-flex; align-items: center; gap: 0.45rem; }
.legend span::before {
  content: "";
  width: 12px;
  height: 12px;
  border-radius: 2px;
  background: var(--operational);
}
.legend .is-incident::before {
  background: var(--incident);
  box-shadow: inset 0 4px 0 var(--cap);
}
@media (max-width: 639px) {
  .legend .is-incident::before {
    background: linear-gradient(var(--cap) 0 3px, var(--incident) 3px 6px, var(--operational) 6px);
    box-shadow: none;
  }
}

/* ---- Buttons / links ---------------------------------------------- */
.button {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 44px;
  background: var(--button-bg);
  color: var(--button-ink);
  border: 1px solid var(--button-bg);
  border-radius: var(--radius);
  padding: 0.65rem 1.1rem;
  font: 600 var(--step-0) var(--font-sans);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background-color 160ms var(--ease-out), color 160ms var(--ease-out),
              box-shadow 160ms var(--ease-out), transform 160ms var(--ease-out);
}
.button:hover { background: color-mix(in srgb, var(--button-bg) 88%, var(--paper)); color: var(--button-ink); box-shadow: var(--shadow-md); transform: translateY(-1px); }
.button:active { transform: translateY(0) scale(0.98); box-shadow: var(--shadow-sm); }
.button:disabled { cursor: progress; opacity: 0.7; transform: none; }
.button[hidden] { display: none; }

/* The secondary button: outline only. Its border is its only boundary, so
   it is --line-strong (3:1+ in both themes). */
.button--quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--line-strong);
  box-shadow: none;
}
.button--quiet:hover { border-color: var(--ink); background: color-mix(in srgb, var(--ink) 6%, transparent); }

.link-underline {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
  font-weight: 600;
  font-size: var(--step-0);
  transition: color 140ms var(--ease-out);
}
/* These sit on their own as standalone calls to action, not inline in a
   sentence. The text box is ~28px tall; this overlay lifts the tappable area
   to ~46px without pushing the underline away from the words. */
.link-underline::before {
  content: "";
  position: absolute;
  inset: -9px 0;
}
.link-underline:hover { color: var(--operational); }
.link-underline:active { color: var(--ink); }

/* ---- About ------------------------------------------------------- */
/* Prose on the left, the proof column on the right. */
.about__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(15rem, 19rem);
  gap: 2rem clamp(2rem, 5vw, 4rem);
  align-items: start;
}
.block__body.about__body > * + * { margin-top: 0; }
.about__text > * + * { margin-top: 1.5rem; }
@media (max-width: 960px) {
  .about__body { grid-template-columns: minmax(0, 1fr); }
}

.signature {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--step-1);
  color: var(--ink);
}
.signature small {
  display: block;
  font: var(--step--1) var(--font-mono);
  font-style: normal;
  letter-spacing: 0.05em;
  color: var(--muted);
  margin-top: 0.3rem;
}

.proof {
  padding: 1.25rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-top: 3px solid var(--operational);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.proof__title {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.proof__list { list-style: none; margin: 0.75rem 0 0; padding: 0; }
.proof__list li {
  display: grid;
  gap: 0.15rem;
  padding-block: 0.75rem;
  border-top: 1px solid var(--line);
}
.proof__list li:last-child { padding-bottom: 0; }
.proof__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  color: var(--operational);
}
.proof__fact { font-weight: 500; line-height: 1.4; }

/* ---- Capabilities ---------------------------------------------------- */
/* Rows, not numbered: the six areas are not a sequence. The list starts
   well below the h2 with a rule of its own, so the heading no longer sits
   hard on the first item. */
.capabilities__list { list-style: none; margin: 0; padding: 0; }
.block__body > .capabilities__list,
.block__body > .timeline { margin-top: 2.25rem; }
.capabilities__list li {
  display: grid;
  grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr);
  gap: 0.6rem 2rem;
  padding: 1.5rem 0 1.5rem 0.9rem;
  border-top: 1px solid var(--line);
  border-left: 2px solid transparent;
  transition: border-color 200ms var(--ease-out), background-color 200ms var(--ease-out);
}
.capabilities__list li:hover {
  border-left-color: var(--operational);
  background: color-mix(in srgb, var(--operational) 5%, transparent);
}
.capabilities__list h3 { grid-row: span 2; }
.capabilities__list .prose { grid-column: 2; margin: 0; }
.capabilities__list .stack {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.5rem;
}
/* Chips: --line-strong border (3.2:1 light, 3.6:1 dark on --bg) so they read
   as labels, not disabled controls; ink text. */
.capabilities__list .stack span {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.02em;
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  padding: 0.1rem 0.45rem;
}

@media (max-width: 640px) {
  .capabilities__list li { grid-template-columns: minmax(0, 1fr); padding-left: 0.6rem; }
  .capabilities__list h3 { grid-row: auto; }
  .capabilities__list .prose,
  .capabilities__list .stack { grid-column: 1; }
}

/* ---- Experience timeline ------------------------------------------- */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li {
  display: grid;
  grid-template-columns: 10rem minmax(0, 1fr);
  gap: 0.3rem 1.5rem;
  padding: 1.5rem 0 1.5rem 0.9rem;
  border-top: 1px solid var(--line);
  border-left: 2px solid transparent;
  transition: border-color 200ms var(--ease-out);
}
.timeline li:hover { border-left-color: var(--operational); }
.timeline time { align-self: start; padding-top: 0.2rem; }
.timeline h3 small { font-size: var(--step-0); font-weight: 400; letter-spacing: 0; color: var(--muted); }
.timeline .prose { margin-top: 0.5rem; }

@media (max-width: 560px) {
  .timeline li { grid-template-columns: minmax(0, 1fr); padding-left: 0.6rem; }
}

/* ---- Principles -------------------------------------------------- */
/* Three principles, not steps, so no numbers: a short pine rule marks each. */
.principles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1.5rem;
}
.block__body > .principles { margin-top: 2.25rem; }
.principle {
  padding-top: 1rem;
  border-top: 2px solid var(--operational);
}
.principle h3 { margin-bottom: 0.5rem; }
.principle .prose { margin: 0; }

/* ---- Contact --------------------------------------------------- */
.contact__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.block__body > .contact__actions { margin-top: 2rem; }
/* script.js is what turns the Email me anchor into a real mailto: link (the
   address is deliberately kept out of the HTML source). Without JS it has no
   href, so hide it rather than present a control that does nothing — the
   <noscript> note in index.html explains the situation instead. */
html:not(.js) .contact__actions { display: none; }
.block__body > .contact__note {
  margin-top: 0.8rem;
  min-height: 1.6rem;
  font-size: var(--step-0);
  color: var(--muted);
}

/* ---- Footer ------------------------------------------------------ */
.site-footer {
  border-top: 1px solid var(--line);
  padding: 2.5rem var(--gutter);
  max-width: 1180px;
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  /* Row gap > the 9px .link-underline hit-area overlay, so when these wrap
     on a phone the overlay can't cover the edge of the item above it. */
  gap: 1.5rem 2rem;
  align-items: center;
  justify-content: space-between;
}
.site-footer .copyright {
  flex-basis: 100%;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.03em;
  color: var(--muted);
}

/* ---- Motion preferences ---------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* The load-in reveal for the strip is opt-in: only when motion is welcome.

   Both rules are scoped to :not(.is-settled), and script.js sets .is-settled
   once the reveal has finished. Without that, `transform: scaleY(1)` here is
   four classes deep and permanently outranks `.uptime__cell:hover` and
   `:focus-visible` (one class each) — so the strip's hover and focus feedback
   was being silently cancelled for the life of the page. Letting these rules
   drop out of the cascade when they are done is what frees those states. */
@media (prefers-reduced-motion: no-preference) {
  .js .uptime:not(.is-settled) .uptime__cell { opacity: 0; transform: scaleY(0.4); }
  .js .uptime.is-revealed:not(.is-settled) .uptime__cell {
    opacity: 1;
    transform: scaleY(1);
  }
}

/* The tech map is styled in map.css, linked after this file. It stays dark
   in both themes; only its outer edge (--map-frame) follows the theme. */

/* The hidden SVG icon sheet at the top of the page (kept out of inline
   styles so the Content-Security-Policy in .htaccess can forbid them). */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---- Tech map: item kinds ------------------------------------------- */
/* Shared by map.css and the page. --kind / --kind-ink are tuned for the
   dark map panel. */
.tm-kind--professional { --kind: var(--kind-professional); --kind-ink: var(--kind-professional-ink); }
.tm-kind--personal     { --kind: var(--kind-personal);     --kind-ink: var(--kind-personal-ink); }
.tm-kind--business     { --kind: var(--kind-business);     --kind-ink: var(--kind-business-ink); }
