/* ==========================================================================
   angelosathanasakis.com
   Design tokens, then components. No framework, no build step, no requests
   to anything but this origin.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   THEMES

   Night: dark blue-charcoal, four stacked surface tiers, orange accent.
   Day:   warm neutral greys rather than white, white only for raised cards,
           same orange darkened for contrast on light.

   Both carry the same texture vocabulary, tuned per theme:
     --hairline  a 1px top-edge highlight, so a card reads as a raised plane
     --dot       the dot-grid on banded sections
     --grain     film-grain overlay opacity (see body::after)
     --shadow    elevation
   --------------------------------------------------------------------------- */

:root {
  /* Night is the default. Day values live in the media query and the
     [data-theme] overrides below, so the toggle wins in both directions. */
  --bg:          #0d1420;
  --bg-alt:      #101d33;
  --surface:     #16202f;
  --surface-2:   #1c2839;
  --surface-3:   #243247;
  --border:      #2e3d55;
  --border-soft: #222e42;

  /* Measured on --bg #0d1420: text 15.8:1, text-dim 10.3:1, muted 6.0:1,
     faint 5.1:1, accent 8.9:1. --faint also has to clear AA against a raised
     card (#16202f), where it lands at 4.5:1, and that second surface is what
     ruled out the darker grey this started with. */
  --text:        #e9eef6;
  --text-dim:    #b5c3d6;
  --muted:       #8395ad;
  --faint:       #7488a2;

  /* AWS orange #F59800, stepped down one notch for the night theme so it sits
     on the dark surfaces without glaring. 7.4:1 on the page, 6.6:1 on a card.
     The day theme uses the same hue much darker, because #F59800 itself only
     manages about 2:1 on a light background. */
  --accent:      #e89000;
  --accent-2:    #f5b74d;

  /* --accent is for text and borders sitting on the page, so it has to clear
     contrast against the background. --brand is the actual brand orange, used
     only where it is a solid fill with its own ink on top: buttons, the
     progress bar, step badges, connectors. That distinction is why the button
     used to come out brown in the light theme. */
  --brand:       #f59800;
  --brand-ink:   #1a1206;

  /* One colour per certification tier, so the wall of chips reads as a
     hierarchy instead of a list. Used on borders, tints and the verify arrow,
     never on the label, which stays at full text contrast. */
  --c-pro:       #f59800;
  --c-spec:      #b07cf0;
  --c-assoc:     #3ecfb2;
  --c-found:     #5aa9f0;
  --accent-soft: rgba(245, 152, 0, 0.13);
  --accent-line: rgba(245, 152, 0, 0.34);

  --hairline: rgba(255, 255, 255, 0.055);
  --dot:      rgba(255, 255, 255, 0.05);
  --grain:    0.055;
  --shadow:   0 1px 2px rgba(0, 0, 0, .38), 0 10px 26px -14px rgba(0, 0, 0, .7);

  --radius:    0.75rem;
  --radius-sm: 0.45rem;

  --maxw: 70rem;
  --pad:  clamp(1.25rem, 4vw, 2rem);

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;

  color-scheme: dark;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg:          #f3f5f7;
    --bg-alt:      #e3e8ef;
    --surface:     #ffffff;
    --surface-2:   #f6f8fa;
    --surface-3:   #edf0f4;
    --border:      #d2d8e1;
    --border-soft: #e2e6ed;

    /* Measured, not eyeballed. On --bg #f3f5f7: text 15.5:1, text-dim 8.8:1,
       muted, faint and accent all clear AA against BOTH the page and the
       darker banded sections. Deepening the bands pushed the previous values
       under 4.5 on the bands only, which is the case that is easy to miss. */
    --text:        #151d29;
    --text-dim:    #3a4655;
    --muted:       #586475;
    --faint:       #5a6878;

    /* AWS orange hue, darkened to 4.77:1 on this background and 5.21:1 on a
       white card. #FF9900 itself would be unreadable here. */
    --accent:      #8f5a00;
    --accent-2:    #7a4d00;

    /* Same brand orange as the night theme. It is a fill here, not text. */
    --brand:       #f59800;
    --brand-ink:   #1a1206;

    /* Tier colours darkened for a light background. */
    --c-pro:       #a06200;
    --c-spec:      #7c3aed;
    --c-assoc:     #0d8a76;
    --c-found:     #2563eb;
    --accent-soft: rgba(143, 90, 0, 0.09);
    --accent-line: rgba(143, 90, 0, 0.30);

    --hairline: rgba(255, 255, 255, 0.9);
    --dot:      rgba(28, 44, 66, 0.06);
    --grain:    0.03;
    --shadow:   0 1px 2px rgba(20, 30, 45, .06), 0 10px 22px -16px rgba(20, 30, 45, .3);

    color-scheme: light;
  }
}

/* Explicit toggle overrides the system preference in both directions. */
:root[data-theme="dark"] {
  --bg: #0d1420; --bg-alt: #101d33; --surface: #16202f; --surface-2: #1c2839;
  --surface-3: #243247; --border: #2e3d55; --border-soft: #222e42;
  --text: #e9eef6; --text-dim: #b5c3d6; --muted: #8395ad; --faint: #7488a2;
  --accent: #e89000; --accent-2: #f5b74d; --brand: #f59800; --brand-ink: #1a1206;
  --c-pro: #f59800; --c-spec: #b07cf0; --c-assoc: #3ecfb2; --c-found: #5aa9f0;
  --accent-soft: rgba(245,152,0,.13); --accent-line: rgba(245,152,0,.34);
  --hairline: rgba(255,255,255,.055); --dot: rgba(255,255,255,.05); --grain: .055;
  --shadow: 0 1px 2px rgba(0,0,0,.38), 0 10px 26px -14px rgba(0,0,0,.7);
  color-scheme: dark;
}

:root[data-theme="light"] {
  --bg: #f3f5f7; --bg-alt: #e3e8ef; --surface: #ffffff; --surface-2: #f6f8fa;
  --surface-3: #edf0f4; --border: #d2d8e1; --border-soft: #e2e6ed;
  --text: #151d29; --text-dim: #3a4655; --muted: #586475; --faint: #5a6878;
  --accent: #8f5a00; --accent-2: #6b4300; --brand: #f59800; --brand-ink: #1a1206;
  --c-pro: #a06200; --c-spec: #7c3aed; --c-assoc: #0d8a76; --c-found: #2563eb;
  --accent-soft: rgba(143,90,0,.09); --accent-line: rgba(143,90,0,.30);
  --hairline: rgba(255,255,255,.9); --dot: rgba(28,44,66,.06); --grain: .03;
  --shadow: 0 1px 2px rgba(20,30,45,.06), 0 10px 22px -16px rgba(20,30,45,.3);
  color-scheme: light;
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 5rem; /* clears the sticky nav on anchor jumps */
}

/* --------------------------------------------------------------------------
   Cross-document view transitions

   The language switch and every internal link stay real navigations to real
   URLs, which is what keeps both languages indexable. This only removes the
   white flash between them: the browser crossfades the old page into the new
   one instead.

   Swapping the text with JavaScript would have been the other way to avoid a
   reload, and it would have cost the Greek pages their place in search
   entirely. Progressive enhancement, so browsers without support simply
   navigate as before.
   -------------------------------------------------------------------------- */

@view-transition { navigation: auto; }

/* Chrome pins the nav and the back-to-top button so they hold still while the
   page body crossfades underneath them. Named elements are matched across the
   two documents by name. */
.nav   { view-transition-name: site-nav; }
.totop { view-transition-name: site-totop; }

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: .22s;
  animation-timing-function: cubic-bezier(.2, .7, .3, 1);
}

::view-transition-old(site-nav),
::view-transition-new(site-nav) { animation: none; }

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.65 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* Scrollbar in the accent. Firefox takes the standard properties; WebKit and
   Blink need the pseudo-elements, so both are declared. */
html {
  scrollbar-color: var(--brand) var(--bg-alt);
  scrollbar-width: auto;
}

::-webkit-scrollbar { width: 16px; height: 16px; }
::-webkit-scrollbar-track { background: var(--bg-alt); }

::-webkit-scrollbar-thumb {
  background: var(--brand);
  border-radius: 8px;
  border: 4px solid var(--bg-alt);
}

::-webkit-scrollbar-thumb:hover { background: var(--accent-2); }
::-webkit-scrollbar-corner { background: var(--bg-alt); }

/* One step up on desktop. A wider container with the same 16px type just
   leaves more empty margin; larger type uses the room and reads better at
   arm's length. */
@media (min-width: 68rem) {
  body { font-size: 17px; }
}

h1, h2, h3, h4 { margin: 0; line-height: 1.2; letter-spacing: -0.02em; font-weight: 700; }
p { margin: 0; }

a { color: var(--accent); text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding-inline: var(--pad);
}

/* Wider container for the four-across grids. 64rem split four ways leaves
   ~240px per card, which is tight once an image and a paragraph are in it.
   Prose stays at the narrower default, because long measure hurts reading. */
.wrap.wrap-wide { max-width: 76rem; }

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: 1rem; top: 1rem;
  z-index: 100;
  padding: .6rem 1rem;
  background: var(--accent);
  color: #0b0f16;
  border-radius: var(--radius-sm);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Navigation
   -------------------------------------------------------------------------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color .2s;
}

.nav[data-scrolled="true"] { border-bottom-color: var(--border-soft); }

.nav-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: .85rem var(--pad);
  display: flex;
  align-items: center;
  gap: 1rem;
}

.nav-mark {
  position: relative;
  font-weight: 700;
  font-size: .95rem;
  color: var(--text);
  letter-spacing: -0.01em;
  margin-right: auto;
  transition: letter-spacing .28s cubic-bezier(.2, .7, .3, 1);
}

/* The full stop after the name doubles as the hover target: it slides right
   and swells slightly, and the name opens up a fraction to make room. Small
   enough to feel like the mark is alive rather than like an animation. */
.nav-mark span {
  display: inline-block;
  color: var(--brand);
  transition: transform .28s cubic-bezier(.2, .7, .3, 1), color .2s;
}

/* A thin brand rule that wipes in beneath the name. */
.nav-mark::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -3px;
  height: 1.5px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .3s cubic-bezier(.2, .7, .3, 1);
}

.nav-links {
  display: flex;
  gap: 1.4rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-links a {
  position: relative;
  color: var(--muted);
  font-size: .9rem;
  font-weight: 500;
  padding-block: .2rem;
  transition: color .15s;
}

/* Underline wipes in from the left rather than fading. scaleX keeps it on the
   compositor, so it stays smooth while the page is scrolling. */
.nav-links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 1.5px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .24s cubic-bezier(.2, .7, .3, 1);
}

.nav-links a[aria-current="true"] { color: var(--text); }
.nav-links a[aria-current="true"]::after { transform: scaleX(1); }

@media (max-width: 44rem) { .nav-links { display: none; } }

/* Language switcher: real links to real URLs, not a runtime toggle.
   A JS swap would leave one URL carrying one language in the served HTML,
   so only that language is ever indexed and the translation earns nothing. */
.lang {
  display: flex;
  align-items: center;
  gap: .1rem;
  padding: .18rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .03em;
}

.lang a {
  padding: .3rem .5rem;
  border-radius: calc(var(--radius-sm) - .18rem);
  color: var(--muted);
  line-height: 1;
}
.lang a:hover { color: var(--text); }
.lang a[aria-current="true"] {
  background: var(--accent-soft);
  color: var(--accent);
}

.theme-btn {
  display: grid;
  place-items: center;
  width: 2.1rem; height: 2.1rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.theme-btn:hover { color: var(--accent); border-color: var(--accent-line); }
.theme-btn svg { width: 1rem; height: 1rem; }

/* One icon shows per theme. */
.theme-btn .i-sun  { display: none; }
:root[data-theme="light"] .theme-btn .i-sun  { display: block; }
:root[data-theme="light"] .theme-btn .i-moon { display: none; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .theme-btn .i-sun  { display: block; }
  :root:not([data-theme]) .theme-btn .i-moon { display: none; }
}

/* --------------------------------------------------------------------------
   Sections
   -------------------------------------------------------------------------- */

section { padding-block: clamp(3.5rem, 9vw, 6rem); }
section.alt { background: var(--bg-alt); border-block: 1px solid var(--border-soft); }

.eyebrow {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: .9rem;
}

.h2 { font-size: clamp(1.55rem, 4vw, 2.15rem); margin-bottom: .8rem; }

.section-lede {
  color: var(--muted);
  max-width: 44rem;
  margin-bottom: 2.5rem;
}

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  padding-block: clamp(3.5rem, 11vh, 6.5rem) clamp(3rem, 8vw, 5rem);
  overflow: hidden;
}

.hero::before {
  content: "";
  position: absolute;
  inset: -30% 0 auto -10%;
  height: 42rem;
  background: radial-gradient(38rem 26rem at 22% 30%, var(--accent-soft), transparent 72%);
  pointer-events: none;
}

.hero-inner { position: relative; }

.hero h1 {
  font-size: clamp(2.3rem, 7.5vw, 4rem);
  margin-bottom: .7rem;
}

.hero-role {
  font-size: clamp(1rem, 2.8vw, 1.3rem);
  font-weight: 600;
  color: var(--accent);
  margin-bottom: 1.8rem;
}

.hero-lede {
  font-size: 1.075rem;
  color: var(--text-dim);
  max-width: 38rem;
  margin-bottom: 1rem;
}
.hero-lede:last-of-type { color: var(--muted); }
.hero-lede strong { color: var(--text); font-weight: 600; }

.hero-actions {
  margin-top: 2.2rem;
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .7rem 1.25rem;
  border-radius: var(--radius-sm);
  font-size: .92rem;
  font-weight: 600;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform .12s, background .15s, border-color .15s, color .15s;
}
.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--accent); color: #120c02; }
.btn-primary:hover { filter: brightness(1.08); }

.btn-ghost { border-color: var(--border); color: var(--text); background: transparent; }
.btn-ghost:hover { border-color: var(--accent-line); color: var(--accent); }

/* Stat strip */
.stats {
  margin-top: 3.2rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1px;
  background: var(--border-soft);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  overflow: hidden;
}

.stat { background: var(--surface); padding: 1.15rem 1.25rem; }

.stat-n {
  font-size: clamp(1.4rem, 3.4vw, 1.85rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.stat-l {
  font-size: .78rem;
  color: var(--muted);
  margin-top: .2rem;
  line-height: 1.4;
}

/* --------------------------------------------------------------------------
   Case studies
   -------------------------------------------------------------------------- */

.case {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--surface);
  padding: clamp(1.35rem, 3.5vw, 2.1rem);
  margin-bottom: 1.25rem;
}

.case-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .6rem 1rem;
  margin-bottom: 1rem;
}

.case h3 { font-size: clamp(1.15rem, 3vw, 1.4rem); flex: 1 1 20rem; }

.chip {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: .28rem .6rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--muted);
  white-space: nowrap;
}
.chip.live { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }

.case-meta {
  font-size: .82rem;
  color: var(--faint);
  margin-bottom: 1.2rem;
  font-family: var(--mono);
}

.case p { color: var(--text-dim); margin-bottom: 1rem; }
.case p:last-of-type { margin-bottom: 0; }
.case strong { color: var(--text); font-weight: 600; }

.case-h4 {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 1.8rem 0 .85rem;
}

/* Outcome callout */
.outcome {
  margin-top: 1.5rem;
  padding: 1rem 1.15rem;
  border-left: 2.5px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.outcome p { color: var(--text); margin: 0; }

/* Decision list */
.decisions { margin: 0; padding-left: 1.15rem; color: var(--text-dim); }
.decisions li { margin-bottom: .6rem; }
.decisions li::marker { color: var(--accent); font-weight: 700; }
.decisions li:last-child { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   Flow diagram: CSS, not SVG, so it reflows on mobile and themes for free
   -------------------------------------------------------------------------- */

.flow {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  gap: .5rem;
  padding: 1.15rem;
  background: var(--surface-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  overflow-x: auto;
}

.node {
  flex: 1 1 7rem;
  min-width: 6.5rem;
  padding: .7rem .8rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  text-align: center;
}

.node-svc {
  display: block;
  font-family: var(--mono);
  font-size: .78rem;
  font-weight: 600;
  color: var(--accent);
  margin-bottom: .15rem;
}

.node-lbl {
  display: block;
  font-size: .7rem;
  color: var(--muted);
  line-height: 1.35;
}

.arrow {
  flex: 0 0 auto;
  align-self: center;
  color: var(--faint);
  font-size: .9rem;
  padding-inline: .1rem;
  user-select: none;
}

@media (max-width: 34rem) {
  .flow { flex-direction: column; }
  .node { flex: 1 1 auto; }
  .arrow { transform: rotate(90deg); align-self: center; }
}

/* --------------------------------------------------------------------------
   Capability / stack grid
   -------------------------------------------------------------------------- */

.stack-grid {
  display: grid;
  gap: 1rem;
}

.stack-card {
  padding: 1.35rem 1.4rem;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
}

.stack-card h3 { font-size: .95rem; margin-bottom: .8rem; }

.tags { display: flex; flex-wrap: wrap; gap: .38rem; }

.tag {
  font-family: var(--mono);
  font-size: .74rem;
  padding: .26rem .55rem;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border-soft);
  color: var(--text-dim);
}

/* --------------------------------------------------------------------------
   Certifications
   -------------------------------------------------------------------------- */

.cert-group { margin-bottom: 1.6rem; }
.cert-group:last-child { margin-bottom: 0; }

.cert-head {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-bottom: .75rem;
}

.cert-head h3 {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.cert-head::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-soft);
}

.cert-list { display: flex; flex-wrap: wrap; gap: .5rem; }

.cert {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .8rem;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  font-size: .86rem;
  color: var(--text-dim);
}

/* Declared once on the group. Heading, rule and every chip inside inherit it,
   so a tier is one class in the markup and one line here. */
.cert-group.tier-pro   { --tier: var(--c-pro); }
.cert-group.tier-spec  { --tier: var(--c-spec); }
.cert-group.tier-assoc { --tier: var(--c-assoc); }
.cert-group.tier-found { --tier: var(--c-found); }

.cert-group.tier-pro .cert { font-weight: 600; }

/* Heading and its rule take the tier colour too. */
.cert-group[class*="tier-"] .cert-head h3 { color: var(--tier); }
.cert-group[class*="tier-"] .cert-head::after {
  background: color-mix(in srgb, var(--tier) 38%, var(--border-soft));
}

.cert-group[class*="tier-"] .cert {
  color: var(--text);
  border-color: color-mix(in srgb, var(--tier) 42%, var(--border));
  background-image: linear-gradient(180deg, color-mix(in srgb, var(--tier) 12%, var(--surface)), var(--surface) 78%);
}

/* A short colour bar at the leading edge, which is what actually makes the
   tiers scannable at a glance. */
.cert-group[class*="tier-"] .cert::before {
  content: "";
  width: 3px;
  height: 1.05em;
  margin-right: .1rem;
  border-radius: 2px;
  background: var(--tier);
  flex: 0 0 auto;
}

/* Verifiable certifications are anchors; the rest stay as plain spans. The
   arrow is the only signal that one is checkable, which is exactly the
   distinction worth showing: a badge you can verify is worth more than one
   you can only read.

   The arrow is an SVG mask rather than the ↗ character, because the glyph
   renders thin and inconsistently across fonts. A mask lets the stroke weight
   be set deliberately, and painting it with currentColor means it inherits
   the link colour for free. The CSP already allows img-src data:. */
a.cert {
  --arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17 17 7M8.5 7H17v8.5'/%3E%3C/svg%3E");
  transition: border-color .16s, color .16s, transform .16s, box-shadow .16s, background-color .16s;
}

a.cert::after {
  content: "";
  flex: 0 0 auto;
  width: .78em;
  height: .78em;
  margin-left: -.18rem;
  opacity: .5;
  background-color: currentColor;
  -webkit-mask: var(--arrow) center / contain no-repeat;
  mask: var(--arrow) center / contain no-repeat;
  transition: transform .2s cubic-bezier(.2, .7, .3, 1), opacity .18s, background-color .18s;
}

.cert-head h3,
.cert-head::after { transition: color .22s, background-color .28s; }

/* --------------------------------------------------------------------------
   Timeline
   -------------------------------------------------------------------------- */

.timeline {
  position: relative;
  margin: 0;
  padding: 0 0 0 1.6rem;
  list-style: none;
}

.timeline::before {
  content: "";
  position: absolute;
  left: .3rem; top: .4rem; bottom: .4rem;
  width: 1px;
  background: var(--border);
}

.tl-item { position: relative; padding-bottom: 1.9rem; }
.tl-item:last-child { padding-bottom: 0; }

.tl-item::before {
  content: "";
  position: absolute;
  left: calc(-1.6rem + .3rem - 3.5px);
  top: .45rem;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--border);
}

.tl-item.now::before { border-color: var(--brand); background: var(--brand); }

.tl-when {
  font-family: var(--mono);
  font-size: .76rem;
  color: var(--faint);
  margin-bottom: .15rem;
}

.tl-what { font-size: 1rem; font-weight: 600; margin-bottom: .3rem; }
.tl-what span { color: var(--accent); font-weight: 500; }
.tl-desc { font-size: .92rem; color: var(--muted); }

/* --------------------------------------------------------------------------
   Contact
   -------------------------------------------------------------------------- */

.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .75rem;
  margin-top: 2rem;
}

.contact-card {
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: 1.05rem 1.15rem;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  transition: border-color .15s, transform .12s;
}
.contact-card:hover { border-color: var(--accent-line); transform: translateY(-2px); }

.contact-card svg { width: 1.1rem; height: 1.1rem; color: var(--accent); flex: 0 0 auto; }

.contact-k { display: block; font-size: .72rem; color: var(--muted); }
.contact-v { display: block; font-size: .9rem; color: var(--text); font-weight: 500; word-break: break-all; }

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

footer {
  padding: 2.25rem 0 3rem;
  border-top: 1px solid var(--border-soft);
  color: var(--faint);
  font-size: .84rem;
}

.footer-inner {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.5rem;
  justify-content: space-between;
}

/* --------------------------------------------------------------------------
   Reveal on scroll: opt-in, and inert when motion is reduced
   -------------------------------------------------------------------------- */

.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .5s ease, transform .5s ease;
}
.reveal.in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
}

/* No JS? Show everything. */
.no-js .reveal { opacity: 1; transform: none; }

/* ==========================================================================
   MULTI-PAGE COMPONENTS
   Everything below is for the sub-pages: case studies, work index, about.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Asset placeholders: swap for real files, then delete the .ph markup
   -------------------------------------------------------------------------- */

.ph {
  display: grid;
  place-content: center;
  gap: .3rem;
  text-align: center;
  padding: 1.5rem;
  background: var(--surface-2);
  border: 1.5px dashed var(--border);
  border-radius: var(--radius-sm);
  color: var(--muted);
}

.ph span { font-size: .9rem; font-weight: 600; color: var(--text-dim); }
.ph small { font-family: var(--mono); font-size: .72rem; color: var(--faint); }

.ph-wide     { aspect-ratio: 16 / 9; }
.ph-diagram  { aspect-ratio: 16 / 8; }
.ph-square   { aspect-ratio: 1 / 1; }
.ph-portrait { aspect-ratio: 4 / 5; max-width: 18rem; }

.ph-icon {
  aspect-ratio: 1 / 1;
  width: 2.75rem;
  padding: .4rem;
  border-radius: var(--radius-sm);
}
.ph-icon span { font-size: .6rem; }

/* --------------------------------------------------------------------------
   Architecture diagrams

   Inline SVG rather than an exported file, because an <img> cannot see the
   page's custom properties. Referencing them here means one diagram serves
   both themes and re-colours with the toggle, which an exported asset can
   never do. Also stays sharp at any zoom and adds no request.
   -------------------------------------------------------------------------- */

.diagram {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  background-color: var(--surface-2);
  background-image: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0);
  background-size: 18px 18px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .12);
}

.diagram .zone      { fill: none; stroke: var(--border); stroke-width: 1.25; stroke-dasharray: 7 5; }
.diagram .box       { fill: var(--surface); stroke: var(--border); stroke-width: 1.25; }
.diagram .box-accent{ fill: var(--accent-soft); stroke: var(--accent-line); stroke-width: 1.5; }
.diagram .svc       { fill: var(--accent); font-family: var(--mono); font-size: 13px; font-weight: 600; }
.diagram .lbl       { fill: var(--muted); font-size: 11.5px; }
.diagram .zlbl      { fill: var(--faint); font-family: var(--mono); font-size: 11px; letter-spacing: .06em; }
.diagram .wire      { stroke: var(--border); stroke-width: 1.5; fill: none; }
.diagram .flow      { stroke: var(--accent); stroke-width: 1.75; fill: none; }
.diagram .head      { fill: var(--accent); }
.diagram text       { font-family: var(--sans); }

@media (prefers-reduced-motion: no-preference) {
  .diagram .pulse { animation: live-pulse 2.6s ease-out infinite; }
}

/* --------------------------------------------------------------------------
   Card artwork

   Replaces the photo slot on the case-study cards. A stock image would say
   nothing; the services a system is built from, plus its headline number, say
   what it is at a glance. Costs no request beyond the icons already loaded.
   -------------------------------------------------------------------------- */

.card-art {
  position: relative;
  aspect-ratio: 16 / 9;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: .6rem;
  padding: 1rem;
  border-bottom: 1px solid var(--border-soft);
  background-color: var(--surface-2);
  background-image:
    radial-gradient(22rem 12rem at 50% -20%, var(--accent-soft), transparent 70%),
    radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0);
  background-size: auto, 16px 16px;
  overflow: hidden;
}

.card-art-icons {
  display: flex;
  align-items: center;
  gap: .45rem;
}

.card-art-icons img {
  width: 2.2rem;
  height: 2.2rem;
  padding: .34rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: .4rem;
  box-shadow: inset 0 1px 0 var(--hairline);
  transition: transform .2s cubic-bezier(.2, .7, .3, 1);
}

.card-art-stat {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
}

.card-art-stat b {
  display: block;
  font-size: 1.15rem;
  letter-spacing: -.02em;
  text-transform: none;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Icons fan apart slightly when the card is hovered. */
@media (hover: hover) {
  .card:hover .card-art-icons img:first-child { transform: translateX(-3px) translateY(-2px); }
  .card:hover .card-art-icons img:last-child  { transform: translateX(3px)  translateY(-2px); }
}

/* --------------------------------------------------------------------------
   Portrait slot

   Holds the space with a monogram until a real photograph exists. Reads as a
   design decision rather than a missing image, which a dashed box never does.
   -------------------------------------------------------------------------- */

.portrait {
  position: relative;
  aspect-ratio: 4 / 5;
  max-width: 18rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background-color: var(--surface);
  background-image:
    radial-gradient(16rem 14rem at 50% 12%, var(--accent-soft), transparent 72%),
    radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0);
  background-size: auto, 18px 18px;
  box-shadow: inset 0 1px 0 var(--hairline), var(--shadow);
  overflow: hidden;
}

.portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }

.portrait-mark {
  font-size: clamp(3rem, 9vw, 4.5rem);
  font-weight: 700;
  letter-spacing: -.05em;
  color: var(--accent);
  line-height: 1;
  opacity: .9;
}

.portrait-mark + span {
  position: absolute;
  bottom: .9rem;
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--faint);
}

/* --------------------------------------------------------------------------
   Architecture diagrams, HTML flavour

   Built from real elements rather than one SVG canvas, because an SVG diagram
   on a 380px phone only scales down until it is unreadable. This reflows:
   zones stack, rows wrap, everything stays legible.

   Sequence is carried by numbered badges and a legend underneath instead of a
   web of connectors. Arrows between non-adjacent boxes are what make
   architecture diagrams unreadable, and they are the first thing that breaks
   when the layout reflows.

   AWS icons keep their own colours in both themes. They are trademarked
   assets and must not be recoloured, so the frame themes around them.
   -------------------------------------------------------------------------- */

.dgm {
  padding: clamp(1rem, 2.5vw, 1.5rem);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  background-color: var(--surface-2);
  background-image: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0);
  background-size: 18px 18px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .12);
}

.dgm-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .85rem;
}

/* Compact variant, for a diagram sitting in a sidebar next to body copy
   rather than spanning the page. Single column, everything a notch smaller. */
.dgm.is-compact { padding: .8rem; }
.dgm.is-compact .dgm-cols      { grid-template-columns: minmax(0, 1fr); gap: .5rem; }
.dgm.is-compact .dgm-zone      { padding: .7rem .7rem .75rem; }
.dgm.is-compact .dgm-zone-head { margin-bottom: .6rem; gap: .35rem; }
.dgm.is-compact .dgm-zone-head span { font-size: .66rem; letter-spacing: .05em; }
.dgm.is-compact .dgm-stack     { gap: .35rem; }
.dgm.is-compact .dgm-node      { padding: .45rem .55rem; gap: .5rem; }
.dgm.is-compact .dgm-icon      { width: 1.5rem; height: 1.5rem; }
.dgm.is-compact .dgm-icon svg  { width: 1.2rem; height: 1.2rem; }
.dgm.is-compact .dgm-svc       { font-size: .78rem; }
.dgm.is-compact .dgm-sub       { font-size: .69rem; margin-top: .05rem; }
.dgm.is-compact .dgm-link      { padding-block: 0; gap: .1rem; }
.dgm.is-compact .dgm-link i    { height: 9px; }
.dgm.is-compact .dgm-link span { font-size: .63rem; }
.dgm.is-compact .dgm-step {
  width: 1rem; height: 1rem;
  font-size: .56rem;
  top: -.35rem; left: -.35rem;
}

/* Inside a split it sits beside the text, so it should not add the top margin
   a standalone figure gets. */
.split > .figure { margin: 0; }
.split > .figure figcaption { text-align: left; font-size: .78rem; }

@media (min-width: 46rem) {
  .dgm-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Account / VPC boundary */
.dgm-zone {
  padding: .9rem .9rem 1rem;
  border: 1.25px dashed var(--border);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--surface) 55%, transparent);
}

.dgm-zone-head {
  display: flex;
  align-items: center;
  gap: .45rem;
  margin-bottom: .85rem;
}

.dgm-zone-head img { width: 1.05rem; height: 1.05rem; flex: 0 0 auto; }

.dgm-zone-head span {
  font-family: var(--mono);
  font-size: .76rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--faint);
}

.dgm-stack { display: grid; gap: .5rem; }

.dgm-row {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
}

/* A service box */
.dgm-node {
  position: relative;
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .55rem .7rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: inset 0 1px 0 var(--hairline);
  transition: border-color .16s, transform .16s;
}

.dgm-node img { width: 1.85rem; height: 1.85rem; flex: 0 0 auto; }

/* Line icons for diagrams that have nothing to do with AWS. Same weight as
   the rest of the icon set, tinted with the accent rather than brand colours. */
.dgm-icon {
  display: grid;
  place-items: center;
  width: 1.85rem;
  height: 1.85rem;
  flex: 0 0 auto;
  color: var(--accent);
}

.dgm-icon svg { width: 1.5rem; height: 1.5rem; }

.dgm-node > div { min-width: 0; }

.dgm-svc {
  display: block;
  font-family: var(--mono);
  font-size: .88rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
}

.dgm-sub {
  display: block;
  font-size: .8rem;
  color: var(--muted);
  line-height: 1.35;
  margin-top: .15rem;
}

/* Compact chips for "the things being watched" */
.dgm-node.is-chip { padding: .45rem .65rem; gap: .45rem; flex: 1 1 auto; }
.dgm-node.is-chip img { width: 1.5rem; height: 1.5rem; }
.dgm-node.is-chip .dgm-svc { font-size: .82rem; }

/* Step number */
.dgm-step {
  position: absolute;
  top: -.5rem;
  left: -.5rem;
  width: 1.2rem;
  height: 1.2rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--brand);
  color: var(--brand-ink);
  font-size: .64rem;
  font-weight: 700;
  font-family: var(--mono);
}



/* Downward connector inside a stack */
.dgm-link {
  display: grid;
  justify-items: center;
  gap: .2rem;
  padding-block: .1rem;
}

.dgm-link i {
  width: 1.5px;
  height: 14px;
  background: var(--brand);
  position: relative;
}

.dgm-link i::after {
  content: "";
  position: absolute;
  bottom: -1px;
  left: 50%;
  transform: translateX(-50%);
  border-left: 4.5px solid transparent;
  border-right: 4.5px solid transparent;
  border-top: 7px solid var(--brand);
}

.dgm-link span {
  font-family: var(--mono);
  font-size: .74rem;
  color: var(--muted);
  text-align: center;
}

/* Numbered legend under the diagram, which carries the sequence */
.dgm-legend {
  margin: .9rem 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: .3rem;
  counter-reset: dgm;
}

.dgm-legend li {
  display: grid;
  grid-template-columns: 1.35rem minmax(0, 1fr);
  gap: .6rem;
  align-items: start;
  font-size: .92rem;
  line-height: 1.55;
  color: var(--text-dim);
}

.dgm-legend li::before {
  counter-increment: dgm;
  content: counter(dgm);
  display: grid;
  place-items: center;
  width: 1.35rem;
  height: 1.35rem;
  margin-top: .1rem;
  border-radius: 50%;
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font-family: var(--mono);
  font-size: .72rem;
  font-weight: 700;
}

@media (hover: hover) {
  .dgm-node:hover { border-color: var(--accent-line); transform: translateY(-1px); }
}

.figure { margin: 1.75rem 0; }
.figure figcaption {
  margin-top: .6rem;
  font-size: .82rem;
  color: var(--muted);
  text-align: center;
}

/* --------------------------------------------------------------------------
   Sub-page header
   -------------------------------------------------------------------------- */

.page-head {
  position: relative;
  padding-block: clamp(2.5rem, 7vw, 4rem) clamp(2rem, 5vw, 3rem);
  border-bottom: 1px solid var(--border-soft);
  overflow: hidden;
}

.page-head::before {
  content: "";
  position: absolute;
  inset: -40% 0 auto -5%;
  height: 30rem;
  background: radial-gradient(30rem 20rem at 18% 40%, var(--accent-soft), transparent 70%);
  pointer-events: none;
}

.page-head > .wrap { position: relative; }

.breadcrumb {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: .82rem;
  color: var(--faint);
  margin-bottom: 1.1rem;
  font-family: var(--mono);
}
.breadcrumb a { color: var(--muted); }
.breadcrumb a:hover { color: var(--accent); }

.page-head h1 {
  font-size: clamp(1.8rem, 5vw, 2.9rem);
  max-width: 30ch;
  margin-bottom: .9rem;
}

.page-sub {
  font-size: 1.05rem;
  color: var(--text-dim);
  max-width: 50rem;
}

/* Project fact bar */
/* Three-up variant for the about-page sidebar, where auto-fit was wrapping
   3 cells as 2 + 1 and leaving a dead square. */
.meta-bar.is-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.meta-bar.is-3 .meta-cell { padding: .8rem .75rem; }
.meta-bar.is-3 .meta-k    { font-size: .62rem; letter-spacing: .06em; }
.meta-bar.is-3 .meta-v    { font-size: .82rem; }

.meta-bar {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1px;
  margin-top: 2.2rem;
  background: var(--border-soft);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  overflow: hidden;
}

.meta-cell { background: var(--surface); padding: .9rem 1.05rem; }
.meta-k {
  display: block;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: .25rem;
}
.meta-v { display: block; font-size: .9rem; color: var(--text); font-weight: 500; }
.meta-v.is-live { color: var(--accent); font-weight: 700; }

/* --------------------------------------------------------------------------
   Article prose
   -------------------------------------------------------------------------- */

/* The article runs the full width of the container and the *text inside it*
   is what gets measured. Capping the container instead was squeezing the stat
   rows, tag lists and diagrams down to paragraph width, which wasted most of
   the screen. Now those use the whole column and only the prose is limited,
   because past roughly 90 characters a line gets hard to track back to. */
.prose { max-width: none; }

.prose > p,
.prose > h2,
.prose > h3,
.prose > ul,
.prose > ol,
.prose > .pull,
.prose > .todo { max-width: 52rem; }

.prose > * + * { margin-top: 1.1rem; }
.prose p { color: var(--text-dim); }
.prose strong { color: var(--text); font-weight: 600; }
.prose h2 { font-size: clamp(1.25rem, 3.2vw, 1.6rem); margin-top: 2.75rem; }
.prose h3 { font-size: 1.05rem; margin-top: 2rem; }
.prose ul, .prose ol { padding-left: 1.2rem; color: var(--text-dim); }
.prose li + li { margin-top: .5rem; }
.prose li::marker { color: var(--accent); }

.prose .wide { max-width: none; }

.pull {
  margin: 2rem 0;
  padding-left: 1.25rem;
  border-left: 2.5px solid var(--accent);
  font-size: 1.1rem;
  line-height: 1.55;
  color: var(--text);
}

/* Editorial note: visible only while drafting. Delete before launch. */
.todo {
  margin: 1.25rem 0;
  padding: .85rem 1rem;
  border: 1.5px dashed var(--accent-line);
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  font-family: var(--mono);
  font-size: .8rem;
  color: var(--text-dim);
}
.todo b { color: var(--accent); }

/* --------------------------------------------------------------------------
   Case-study cards (home + work index)
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Four-across grids
   Each of these holds exactly four items. auto-fit with a min track width
   wrapped them 3 + 1 and left an orphan on its own row, so the columns are
   declared explicitly per breakpoint instead of being inferred.
   -------------------------------------------------------------------------- */

.card-grid {
  display: grid;
  gap: 1rem;
}

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

@media (min-width: 34rem) {
  .card-grid,
  .stack-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 62rem) {
  .card-grid,
  .stack-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: .85rem;
  }

  /* Narrower columns need less internal padding, or the text measure gets
     squeezed to nothing. */
  .card-body  { padding: 1.05rem 1.1rem 1.25rem; }
  .stack-card { padding: 1.15rem 1.2rem; }
  .card h3    { font-size: 1rem; }
}

.card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color .15s, transform .12s;
}
.card:hover { border-color: var(--accent-line); transform: translateY(-3px); }

.card .ph { border: 0; border-bottom: 1px solid var(--border-soft); border-radius: 0; }

.card-body { padding: 1.25rem 1.35rem 1.4rem; display: flex; flex-direction: column; flex: 1; }

.card-status {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: .6rem;
}
.card-status.live { color: var(--accent); }
.card-status .dot {
  width: .45rem; height: .45rem;
  border-radius: 50%;
  background: currentColor;
}

.card h3 { font-size: 1.05rem; margin-bottom: .55rem; }
.card p { font-size: .92rem; color: var(--muted); margin-bottom: 1rem; }

.card-link {
  margin-top: auto;
  font-size: .88rem;
  font-weight: 600;
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: .35rem;
}
.card:hover .card-link { gap: .6rem; }
.card-link::after { content: "→"; transition: none; }

/* Whole card clickable, keyboard-accessible via the inner link */
.card-a { color: inherit; display: contents; }

/* --------------------------------------------------------------------------
   Key numbers row
   -------------------------------------------------------------------------- */

.numbers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1rem;
  margin: 2rem 0;
}

.number {
  padding: 1.1rem 1.25rem;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-left: 2.5px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.number b {
  display: block;
  font-size: 1.5rem;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.number span { font-size: .8rem; color: var(--muted); line-height: 1.4; display: block; margin-top: .15rem; }

/* --------------------------------------------------------------------------
   Next / previous case study
   -------------------------------------------------------------------------- */

.pager {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1rem;
  margin-top: 3.5rem;
  padding-top: 2.25rem;
  border-top: 1px solid var(--border-soft);
}

.pager a {
  padding: 1.05rem 1.2rem;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  transition: border-color .15s;
}
.pager a:hover { border-color: var(--accent-line); }
.pager .pk {
  display: block;
  font-size: .7rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: .3rem;
}
.pager .pv { display: block; font-size: .95rem; font-weight: 600; color: var(--text); }

/* --------------------------------------------------------------------------
   CTA band
   -------------------------------------------------------------------------- */

.cta {
  margin-top: 0;
  padding-block: clamp(3rem, 7vw, 4.5rem);
  background: var(--bg-alt);
  border-top: 1px solid var(--border-soft);
  text-align: center;
}
.cta h2 { font-size: clamp(1.4rem, 3.6vw, 1.9rem); margin-bottom: .7rem; }
.cta p { color: var(--muted); max-width: 34rem; margin: 0 auto 1.7rem; }
.cta .hero-actions { justify-content: center; }

/* --------------------------------------------------------------------------
   Portrait block (about page / hero)
   -------------------------------------------------------------------------- */

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem;
  align-items: start;
}

@media (min-width: 52rem) {
  .split { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 3rem; }
}

/* --------------------------------------------------------------------------
   Service icon row
   -------------------------------------------------------------------------- */

.icon-row {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  align-items: center;
  margin-top: 1rem;
}

/* --------------------------------------------------------------------------
   Principle cards ("How I work")
   Exists so the markup carries no inline style=. The deployed CSP is
   style-src 'self' with no unsafe-inline, and an inline style renders
   unstyled in production while looking perfect locally.
   -------------------------------------------------------------------------- */

.principle h3 { font-size: .98rem; margin-top: .9rem; margin-bottom: .5rem; }
.principle p  { font-size: .92rem; color: var(--muted); }

/* Drawn in the same line weight as the contact and theme icons, so the set
   reads as one family rather than as clip art collected from three places. */
.principle-icon {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  box-shadow: inset 0 1px 0 var(--hairline);
  color: var(--accent);
  transition: border-color .18s, transform .18s;
}

.principle-icon svg { width: 1.15rem; height: 1.15rem; }

@media (hover: hover) {
  .principle:hover .principle-icon {
    border-color: var(--accent-line);
    transform: translateY(-2px);
  }
}

/* --------------------------------------------------------------------------
   Touch devices: lift-on-hover sticks after a tap
   @media (hover: none) rather than a width query, though note this does not
   fire when emulating mobile by narrowing a desktop window.
   -------------------------------------------------------------------------- */

@media (hover: none) {
  .card:hover,
  .contact-card:hover,
  a.cert:hover { transform: none; }
}

/* --------------------------------------------------------------------------
   404
   Styled from here rather than inline, because the deployed CSP forbids
   inline styles. If this stylesheet ever fails to load the page is plain
   but still readable, an acceptable trade for a page nobody should reach.
   -------------------------------------------------------------------------- */

.notfound {
  min-height: 72vh;
  display: grid;
  place-content: center;
  text-align: center;
  padding: 2rem;
}
.notfound h1 {
  font-size: clamp(3rem, 12vw, 5rem);
  margin-bottom: .35rem;
  letter-spacing: -.04em;
}
.notfound p { color: var(--muted); margin-bottom: 1.75rem; }

/* ==========================================================================
   TEXTURE & ELEVATION

   Loaded after the components so these win on equal specificity. Everything
   here is generated: gradients, a dot grid and an inline SVG noise filter as
   a data: URI. No image requests, and the CSP already allows `img-src data:`.
   ========================================================================== */

/* Film grain over the whole viewport. Low opacity, tuned per theme: dark
   surfaces need more grain than light ones to read as textured rather than
   flat. Sits above the sticky nav (z-index 50) but below the skip link (100),
   and never intercepts a click. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
  opacity: var(--grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}

@media print { body::after { display: none; } }

/* --------------------------------------------------------------------------
   Banded sections get a dot grid
   The shorthand `background: var(--bg-alt)` earlier clears background-image,
   so this has to come afterwards to layer on top of the colour.
   -------------------------------------------------------------------------- */

section.alt {
  background-image: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0);
  background-size: 22px 22px;
}

.cta {
  background-image: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0);
  background-size: 22px 22px;
}

/* --------------------------------------------------------------------------
   Raised planes: gradient body, 1px top highlight, elevation shadow
   -------------------------------------------------------------------------- */

.stack-card,
.card,
.case,
.contact-card,
.pager a,
.number,
.principle {
  background-image: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 58%);
  box-shadow: inset 0 1px 0 var(--hairline), var(--shadow);
}

/* Seamless grids: hairline only, no shadow, or the 1px gaps go muddy. */
.stat,
.meta-cell {
  background-image: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 70%);
  box-shadow: inset 0 1px 0 var(--hairline);
}

.cert {
  background-image: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 65%);
  box-shadow: inset 0 1px 0 var(--hairline);
}

.cert.tier-pro {
  background-image: linear-gradient(180deg, var(--accent-soft), transparent);
}

/* --------------------------------------------------------------------------
   Recessed panels: the opposite treatment, so diagrams read as inset
   -------------------------------------------------------------------------- */

.flow,
.ph {
  background-image: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0);
  background-size: 18px 18px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .12);
}

.node {
  background-image: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 60%);
  box-shadow: inset 0 1px 0 var(--hairline), 0 1px 2px rgba(0, 0, 0, .18);
}

/* --------------------------------------------------------------------------
   Chrome
   -------------------------------------------------------------------------- */

.nav[data-scrolled="true"] {
  box-shadow: 0 1px 0 var(--hairline), 0 6px 20px -18px rgba(0, 0, 0, .8);
}

.theme-btn {
  background-image: linear-gradient(180deg, var(--surface-2), var(--surface));
  box-shadow: inset 0 1px 0 var(--hairline);
}

.tag {
  background-image: linear-gradient(180deg, var(--surface-3) 0%, var(--surface-2) 70%);
  box-shadow: inset 0 1px 0 var(--hairline);
}

/* --------------------------------------------------------------------------
   Accent surfaces
   -------------------------------------------------------------------------- */

.btn-primary {
  background-image: linear-gradient(180deg, color-mix(in srgb, var(--brand) 88%, #fff), var(--brand));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), var(--shadow);
  color: var(--brand-ink);
  /* Orange against a light page is only about 2:1, so the fill alone does not
     give the control a defined edge. A darker rim of the same colour does. */
  border-color: color-mix(in srgb, var(--brand) 72%, #000);
}

/* No per-theme ink override any more: the fill is the same orange in both
   themes, so one ink colour works for both. */

.outcome,
.todo {
  background-image: linear-gradient(180deg, var(--accent-soft), transparent 85%);
}

/* --------------------------------------------------------------------------
   Ambient light: two offset washes rather than one, so the glow has depth
   -------------------------------------------------------------------------- */

.hero::before {
  background:
    radial-gradient(34rem 22rem at 18% 22%, var(--accent-soft), transparent 70%),
    radial-gradient(30rem 24rem at 82% 8%, var(--surface-3), transparent 72%);
}

.page-head::before {
  background:
    radial-gradient(26rem 16rem at 14% 34%, var(--accent-soft), transparent 70%),
    radial-gradient(24rem 18rem at 88% 0%, var(--surface-3), transparent 74%);
}

/* ==========================================================================
   MOTION

   Three rules govern everything below:

   1. Hover effects live inside @media (hover: hover), or they latch on after
      a tap and stay until the next tap somewhere else.
   2. Anything that moves uses transform or opacity only, so it stays on the
      compositor and never triggers layout.
   3. prefers-reduced-motion collapses all of it, handled globally near the
      top of this file, which is why nothing here repeats the query.
   ========================================================================== */

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* A ring that expands and fades. Used on "live" status dots, where a static
   dot claims the same thing but doesn't convince anyone. */
@keyframes live-pulse {
  0%   { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
  70%  { box-shadow: 0 0 0 5px transparent; opacity: .75; }
  100% { box-shadow: 0 0 0 0 transparent; opacity: 1; }
}

/* --------------------------------------------------------------------------
   Entrance: hero and sub-page headers, on load
   backwards fill so the element is already hidden before its delay elapses,
   instead of flashing at full opacity first.
   -------------------------------------------------------------------------- */

.hero h1,
.hero .hero-role,
.hero .hero-lede,
.hero .hero-actions,
.hero .stats,
.page-head .breadcrumb,
.page-head h1,
.page-head .page-sub,
.page-head .meta-bar {
  animation: rise .55s cubic-bezier(.2, .7, .3, 1) backwards;
}

.hero .hero-role                { animation-delay: .06s; }
.hero .hero-lede:nth-of-type(1) { animation-delay: .12s; }
.hero .hero-lede:nth-of-type(2) { animation-delay: .18s; }
.hero .hero-actions             { animation-delay: .24s; }
.hero .stats                    { animation-delay: .30s; }

.page-head h1         { animation-delay: .06s; }
.page-head .page-sub  { animation-delay: .12s; }
.page-head .meta-bar  { animation-delay: .18s; }

/* --------------------------------------------------------------------------
   Staggered reveal
   Grid children arrive in sequence rather than as a block. Delays are set by
   position, so no inline style attribute is needed, which matters, because
   the CSP forbids one.
   -------------------------------------------------------------------------- */

.card-grid  > .reveal:nth-child(2),
.stack-grid > .reveal:nth-child(2) { transition-delay: .07s; }
.card-grid  > .reveal:nth-child(3),
.stack-grid > .reveal:nth-child(3) { transition-delay: .14s; }
.card-grid  > .reveal:nth-child(4),
.stack-grid > .reveal:nth-child(4) { transition-delay: .21s; }

.timeline .tl-item:nth-child(2) { transition-delay: .06s; }
.timeline .tl-item:nth-child(3) { transition-delay: .12s; }
.timeline .tl-item:nth-child(4) { transition-delay: .18s; }
.timeline .tl-item:nth-child(5) { transition-delay: .24s; }
.timeline .tl-item:nth-child(6) { transition-delay: .30s; }
.timeline .tl-item:nth-child(7) { transition-delay: .36s; }

/* --------------------------------------------------------------------------
   Live indicators
   -------------------------------------------------------------------------- */

.card-status.live .dot,
.tl-item.now::before {
  animation: live-pulse 2.6s ease-out infinite;
}

/* --------------------------------------------------------------------------
   Scroll progress: element is created by app.js, so it simply does not
   exist without JavaScript. Decorative only.
   -------------------------------------------------------------------------- */

.progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 3px;
  z-index: 60;
  background: linear-gradient(90deg, var(--brand), var(--accent-2));
  transform: scaleX(0);
  transform-origin: left;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Back to top
   Built by app.js, so it does not sit inert in the DOM without JavaScript.
   -------------------------------------------------------------------------- */

.totop {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 70;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--brand) 72%, #000);
  border-radius: .85rem;
  background: var(--brand);
  color: var(--brand-ink);
  cursor: pointer;
  box-shadow: 0 6px 20px -8px rgba(0, 0, 0, .6);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px) scale(.94);
  transition: opacity .28s, transform .28s, visibility .28s, filter .15s;
}

.totop svg { width: 1.5rem; height: 1.5rem; }

.totop[data-show="true"] {
  opacity: 1;
  visibility: visible;
  transform: none;
}

@media (hover: hover) {
  .totop:hover { filter: brightness(1.08); transform: translateY(-2px); }
}

.totop:active { transform: translateY(1px); }

@media print { .totop { display: none; } }

/* --------------------------------------------------------------------------
   Hover
   -------------------------------------------------------------------------- */

@media (hover: hover) {

  .nav-links a:hover { color: var(--text); }
  .nav-links a:hover::after { transform: scaleX(1); }

  .nav-mark:hover { letter-spacing: .01em; }
  .nav-mark:hover span { transform: translateX(3px) scale(1.35); }
  .nav-mark:hover::after { transform: scaleX(1); }

  /* Cards lift further and the shadow deepens with them, so the movement
     reads as height rather than as sliding. */
  .card:hover {
    transform: translateY(-4px);
    border-color: var(--accent-line);
    box-shadow: inset 0 1px 0 var(--hairline), 0 16px 34px -18px rgba(0, 0, 0, .75);
  }
  .card:hover .card-link { color: var(--accent); }
  .card:hover h3 { color: var(--accent); }
  .card h3 { transition: color .18s; }

  .stat { transition: background-color .18s; }
  .stat:hover { background-color: var(--surface-2); }
  .stat:hover .stat-n { color: var(--accent); }
  .stat-n { transition: color .18s; }

  .tag { transition: border-color .15s, color .15s, transform .12s; }
  .tag:hover {
    border-color: var(--accent-line);
    color: var(--text);
    transform: translateY(-1px);
  }

  .node { transition: border-color .18s, transform .15s; }
  .node:hover { border-color: var(--accent-line); transform: translateY(-2px); }

  .contact-card:hover { transform: translateY(-3px); }
  .contact-card svg { transition: transform .2s; }
  .contact-card:hover svg { transform: scale(1.12); }

  .pager a { transition: border-color .15s, transform .15s; }
  .pager a:hover { transform: translateY(-2px); }
  .pager a:hover .pv { color: var(--accent); }
  .pager .pv { transition: color .18s; }

  .tl-item { transition: transform .2s; }
  .tl-item:hover { transform: translateX(3px); }

  /* ---- certifications ---------------------------------------------------
     Hovering a group lights its heading and rule, so the tiers read as
     groups rather than as one undifferentiated wall of chips. */
  .cert-group:hover .cert-head h3   { color: var(--accent); }
  .cert-group:hover .cert-head::after { background-color: var(--accent-line); }

  /* Verifiable badges lift and warm. */
  a.cert:hover {
    transform: translateY(-2px);
    border-color: var(--accent-line);
    background-color: var(--accent-soft);
    color: var(--text);
    box-shadow: 0 8px 18px -12px rgba(0, 0, 0, .7);
  }

  /* The arrow travels the direction it points. */
  a.cert:hover::after {
    opacity: 1;
    background-color: var(--tier, var(--accent));
    transform: translate(2px, -2px);
  }

  a.cert:focus-visible::after { opacity: 1; background-color: var(--accent); }

  /* Professional is the headline credential, so it gets an accent-tinted
     shadow the others do not. */
  .cert-group.tier-pro a.cert:hover { box-shadow: 0 10px 24px -12px var(--c-pro); }

  /* Non-verifiable ones acknowledge the pointer without pretending to be
     clickable: colour shift only, no lift, no arrow. */
  span.cert { transition: border-color .16s, color .16s; }
  span.cert:hover { border-color: var(--border); color: var(--text); }

  .btn { transition: transform .14s, filter .15s, border-color .15s, color .15s; }
  .btn:hover { transform: translateY(-2px); }
  .btn-primary:hover { filter: brightness(1.06); }

  .theme-btn { transition: transform .15s, color .15s, border-color .15s; }
  .theme-btn:hover { transform: rotate(-12deg); }

  .breadcrumb a { transition: color .15s; }
}

/* :active after every :hover, or equal specificity means source order decides
   and the press feedback gets eaten by the lift. */
.btn:active { transform: translateY(1px); }
.card:active { transform: translateY(-2px); }

/* --------------------------------------------------------------------------
   Print: someone will print a CV page or a case study
   -------------------------------------------------------------------------- */

@media print {
  :root {
    --bg: #fff; --bg-alt: #fff; --surface: #fff; --surface-2: #fff;
    --border: #bbb; --border-soft: #ddd;
    --text: #000; --text-dim: #222; --muted: #444; --faint: #666;
    --accent: #000; --accent-soft: transparent; --accent-line: #000;
  }

  .nav, .theme-btn, .skip, .cta, .pager, .hero-actions, .ph, .progress { display: none !important; }

  body { background: #fff; font-size: 11pt; }
  section { padding-block: 1rem; break-inside: avoid; }
  .hero::before, .page-head::before { display: none; }
  .case, .stack-card, .card, .number { break-inside: avoid; border-color: #ccc; }
  .reveal { opacity: 1 !important; transform: none !important; }

  /* Print the destination of real links, but not internal anchors. */
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #555; }
}
