/* ==========================================================================
   Maleesha Fonseka · portfolio stylesheet
   One stylesheet for every page.

   1. Tokens        5. Landing page
   2. Base          6. Projects index
   3. Layout        7. Case study (project detail pages)
   4. Components    8. About page
                    9. Responsive + reduced motion

   To retheme the site, edit the tokens in section 1 only.
   ========================================================================== */


/* 1. Tokens
   ========================================================================== */

:root {
  color-scheme: dark;

  /* Surfaces: matte black to dark grey */
  --bg:         #0b0d0e;
  --bg-1:       #101315;
  --bg-2:       #151a1c;
  --bg-media:   #0d1011;

  /* Text */
  --text:       #d5dadd;
  --text-head:  #e7ebed;
  --text-body:  #c3c9cc;
  --text-dim:   #9aa3a8;
  --text-faint: #788187;

  /* Silver hairlines */
  --silver:     #b3bbc1;
  --line:       rgba(179, 187, 193, 0.13);
  --line-2:     rgba(179, 187, 193, 0.26);
  --line-3:     rgba(179, 187, 193, 0.48);

  /* Dark teal */
  --teal:       #3d9f96;
  --teal-hi:    #66c2b8;
  --teal-lo:    #1d4f4b;
  --teal-fill:  #143936;
  --teal-wash:  rgba(61, 159, 150, 0.08);

  /* Project areas: the small square beside each area on the projects page */
  --area-ai:       #9d8fdc;
  --area-data:     #78b37a;
  --area-software: #6f9ad6;
  --area-research: #cf9a5b;
  --area-other:    #8a9399;

  /* Type: Fira Code, weight 450 matches the "Retina" cut */
  --font: "Fira Code", ui-monospace, "Cascadia Code", "SF Mono", Menlo, Consolas, monospace;
  --fs-xs:   0.72rem;
  --fs-sm:   0.82rem;
  --fs-base: 0.9375rem;
  --fs-md:   1.04rem;

  /* Layout */
  --maxw:     1120px;
  --gutter:   clamp(16px, 4vw, 40px);
  --header-h: 60px;
  --footer-h: 78px;   /* the footer's height: 28px padding × 2, one line, 1px border */
  --ease:     cubic-bezier(0.2, 0.7, 0.2, 1);

  /* Corners and elevation: components float on soft shadows, not outlines.
     On a near-black page a plain black shadow barely shows, so each one pairs
     a deep, soft drop with a faint highlight along the top edge. */
  --radius-sm: 6px;     /* buttons, chips, small labels */
  --radius-md: 10px;    /* cards, figures, panels */
  --radius-lg: 14px;    /* the hero card and the closing card */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5), 0 4px 10px -2px rgba(0, 0, 0, 0.5);
  --shadow-md: inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 2px 4px rgba(0, 0, 0, 0.45), 0 14px 30px -10px rgba(0, 0, 0, 0.8);
  --shadow-lg: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 4px 10px rgba(0, 0, 0, 0.5), 0 26px 50px -14px rgba(0, 0, 0, 0.9);
}


/* 2. Base
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 24px);
}

body {
  min-height: 100vh;
  -webkit-user-select: none;   /* text can't be selected on any page */
  user-select: none;
  display: flex;
  flex-direction: column;
  font-family: var(--font);
  font-size: var(--fs-base);
  font-weight: 450;
  line-height: 1.75;
  color: var(--text);
  background-color: var(--bg);
  /* Fine grain for a matte finish */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 .045 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* Faint drafting grid, fading out down the page */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 48px 48px;
  opacity: 0.35;
  -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 0%, transparent 75%);
          mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 0%, transparent 75%);
}

main { flex: 1 0 auto; }

img, svg, video { display: block; max-width: 100%; height: auto; }

a {
  color: var(--teal-hi);
  text-decoration: underline;
  text-decoration-color: rgba(102, 194, 184, 0.35);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color 0.2s, text-decoration-color 0.2s;
}
a:hover { color: #8fd8cf; text-decoration-color: currentColor; }

:focus-visible { outline: 1px solid var(--teal-hi); outline-offset: 3px; }

h1, h2, h3, h4 {
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--text-head);
  text-wrap: balance;
}

p { text-wrap: pretty; }
ul[class], ol[class] { list-style: none; padding: 0; }
[hidden] { display: none !important; }
code, kbd, pre { font-family: inherit; }
hr { border: 0; border-top: 1px solid var(--line); }

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

.skip {
  position: absolute;
  left: 16px; top: -48px;
  z-index: 100;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  background: var(--teal-fill);
  color: var(--text-head);
  box-shadow: var(--shadow-md);
  text-decoration: none;
  font-size: var(--fs-sm);
}
.skip:focus { top: 12px; }


/* 3. Layout: container, header, footer
   ========================================================================== */

.container {
  width: 100%;
  max-width: calc(var(--maxw) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The header and footer reach the screen edges (one gutter in) at every
   width. Page content stays in the centred column above. */
.site-header .container,
.site-footer .container { max-width: none; }

.section { padding-block: clamp(56px, 9vw, 104px); }
.section--tight,
.section + .section { padding-top: 0; }

/* Full-screen section (home and about pages): fills the viewport below the
   header and centres its content vertically. .screen--last leaves room for
   the footer, so the final section and the footer share one screen. */
.screen {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
}
.screen--last {
  min-height: calc(100vh - var(--header-h) - var(--footer-h));
  min-height: calc(100svh - var(--header-h) - var(--footer-h));
}

/* Section snapping, opted into per page with <html class="snap">.
   Only the home page uses it; every other page scrolls freely.
   The footer snap point keeps it reachable even if it wraps to two lines. */
html.snap {
  scroll-snap-type: y mandatory;
  scroll-padding-top: var(--header-h);
}
html.snap .screen { scroll-snap-align: start; }
html.snap .site-footer { scroll-snap-align: end; }

/* Header */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(11, 13, 14, 0.9);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  /* Bottom hairline as a shadow, not a border, so the header is exactly
     --header-h tall and snapped sections line up under it */
  box-shadow: 0 1px 0 var(--line);
}

.site-header__inner {
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: var(--text);
  text-decoration: none;
  font-size: var(--fs-sm);
}
.brand:hover { color: var(--text); }

.brand__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 1px solid var(--line-3);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--silver);
  transition: border-color 0.2s, color 0.2s;
}
.brand__mark::after {
  content: "";
  position: absolute;
  right: -4px;
  bottom: -4px;
  width: 6px;
  height: 6px;
  background: var(--teal);
}
.brand:hover .brand__mark { border-color: var(--teal); color: var(--teal-hi); }

.brand__name { color: var(--text-dim); }
.brand__name b { color: var(--teal); font-weight: inherit; }

.site-nav {
  display: flex;
  gap: clamp(14px, 3vw, 32px);
  font-size: var(--fs-sm);
}
.site-nav a {
  position: relative;
  padding-block: 4px;
  color: var(--text-dim);
  text-decoration: none;
}
.site-nav a::before { content: "/"; color: var(--text-faint); margin-right: 1px; }
.site-nav a:hover { color: var(--text); }
.site-nav a[aria-current="page"] { color: var(--teal-hi); }
/* Teal bar that sits on the header's bottom hairline */
.site-nav a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc((100% - var(--header-h)) / 2 - 1px);
  height: 1px;
  background: var(--teal);
}

/* Footer: copyright and links. Its height must match --footer-h exactly,
   or the last full-screen section can't scroll fully into place. */
.site-footer {
  min-height: var(--footer-h);
  margin-top: auto;
  border-top: 1px solid var(--line);
  padding-block: 28px;
  font-size: var(--fs-xs);
  color: var(--text-faint);
}

.site-footer__bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 12px 24px;
}
.footer-links { display: flex; flex-wrap: wrap; gap: 8px 20px; }
.footer-links a { color: var(--text-dim); text-decoration: none; }
.footer-links a:hover { color: var(--teal-hi); }


/* 4. Components (shared by every page)
   ========================================================================== */

/* Section heading on a ruler line */
.sec-head {
  position: relative;
  padding-top: 20px;
  margin-bottom: clamp(28px, 4vw, 44px);
  border-top: 1px solid var(--line-2);
}
.sec-head::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 10px;
  background:
    repeating-linear-gradient(90deg, var(--line-3) 0 1px, transparent 1px 120px) top left / 100% 9px no-repeat,
    repeating-linear-gradient(90deg, var(--line-2) 0 1px, transparent 1px 24px) top left / 100% 5px no-repeat;
  -webkit-mask-image: linear-gradient(90deg, #000 30%, transparent 90%);
          mask-image: linear-gradient(90deg, #000 30%, transparent 90%);
}
.sec-head__meta {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 10px;
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sec-head__tag { color: var(--teal); }
.sec-head__tag::before { content: "// "; color: var(--text-faint); }
.sec-head h2 { font-size: clamp(1.45rem, 3vw, 2rem); }
.sec-head p { margin-top: 10px; max-width: 62ch; color: var(--text-dim); }

/* Page heading for inner pages */
.page-head { padding-block: clamp(48px, 8vw, 88px) clamp(32px, 5vw, 56px); }
.page-head h1 { font-size: clamp(2.2rem, 6vw, 3.6rem); letter-spacing: -0.035em; }
.page-head__lede { margin-top: 18px; max-width: 62ch; color: var(--text-dim); }
.page-head__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  margin-top: 28px;
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.page-head__stats b { color: var(--silver); font-weight: 500; }

/* Breadcrumb path: ~/projects/name */
.crumbs {
  margin-bottom: 20px;
  font-size: var(--fs-sm);
  color: var(--text-faint);
  overflow-wrap: anywhere;
}
.crumbs a { color: var(--text-dim); text-decoration: none; }
.crumbs a:hover { color: var(--teal-hi); }
.crumbs [aria-current] { color: var(--teal-hi); }

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 19px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  color: var(--text);
  box-shadow: var(--shadow-sm);
  font: inherit;
  font-size: var(--fs-sm);
  letter-spacing: 0.02em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.2s var(--ease), box-shadow 0.2s, color 0.2s, background-color 0.2s;
}
.btn:hover { color: var(--teal-hi); transform: translateY(-1px); box-shadow: var(--shadow-md); }
.btn .arrow { transition: transform 0.25s var(--ease); }
.btn:hover .arrow { transform: translateX(3px); }

.btn--primary {
  background: var(--teal-fill);
  color: var(--text-head);
}
.btn--primary:hover { background: #1a4744; color: #fff; }

/* Tech chips */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chips li {
  padding: 2px 9px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.05);
  font-size: var(--fs-xs);
  line-height: 1.7;
  color: var(--silver);
  white-space: nowrap;
}
.chips li.key {
  background: rgba(61, 159, 150, 0.14);
  color: var(--teal-hi);
}

/* Pulsing teal square, used beside status text */
.dot {
  width: 7px;
  height: 7px;
  background: var(--teal);
  animation: pulse 2.4s ease-in-out infinite;
}

/* Ledger: rows of label / leader / value, used for contact links and lists */
.ledger { border-top: 1px solid var(--line); }
.ledger__row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 15px 6px;
  border-bottom: 1px solid var(--line);
  color: var(--text);
  text-decoration: none;
  transition: background-color 0.2s;
}
a.ledger__row:hover { background: var(--teal-wash); color: var(--text); }
.ledger__k { flex: 0 0 9ch; color: var(--text-faint); }
.ledger__leader {
  flex: 1 1 24px;
  min-width: 12px;
  border-bottom: 1px dotted var(--line-2);
  transform: translateY(-5px);
}
.ledger__v {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ledger__go { color: var(--text-faint); transition: transform 0.25s var(--ease), color 0.2s; }
a.ledger__row:hover .ledger__v { color: var(--teal-hi); }
a.ledger__row:hover .ledger__go { color: var(--teal-hi); transform: translate(2px, -2px); }

/* Closing call-to-action band */
.cta-band {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px 32px;
  padding: clamp(24px, 4vw, 36px);
  border-radius: var(--radius-lg);
  background: var(--bg-1);
  box-shadow: var(--shadow-lg);
}
.cta-band h2 { font-size: clamp(1.15rem, 2.4vw, 1.4rem); }
.cta-band p { margin-top: 6px; color: var(--text-dim); font-size: var(--fs-sm); }

/* Entrance animation. Set --i on an element to stagger it. */
.rise { animation: rise 0.7s var(--ease) backwards; animation-delay: calc(var(--i, 0) * 90ms); }

@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}
@keyframes bob {
  0%, 100% { transform: none; }
  50%      { transform: translateY(3px); }
}


/* 5. Landing page
   ========================================================================== */

/* The hero is also a .screen, and it must never be taller than one: a
   taller hero becomes something you scroll inside, and one scroll stops
   short of Get in touch. So its spacing scales with the screen's height
   (svh) as well as its width. The top padding keeps the card clear of the
   switch row, the bottom padding clear of the scroll cue. isolation keeps
   the animation canvas (z-index: -1) inside the hero. */
.hero {
  --hero-gap: clamp(10px, calc(4.2svh - 14px), 24px);
  position: relative;
  isolation: isolate;
  padding-block: clamp(54px, min(5vw, 8svh), 72px) clamp(62px, min(6vw, 9svh), 80px);
}

/* Solid card holding the hero text, in front of the animation */
.hero__card {
  container-type: inline-size;   /* lets the name size itself to the card (cqi) */
  padding: clamp(20px, min(2.5vw, calc(6svh - 18px)), 36px);
  border-radius: var(--radius-lg);
  background: var(--bg-1);
  box-shadow: var(--shadow-lg);
}

/* "scroll ↓" cue at the bottom of the hero, linking to Get in touch */
.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  text-decoration: none;
  transition: color 0.2s;
}
.scroll-cue:hover { color: var(--teal-hi); }
.scroll-cue span { display: inline-block; animation: bob 2.4s ease-in-out infinite; }

/* Landing animation canvas, created by assets/js/hero-animation.js. It fills
   the whole hero behind the card and is transparent except where it draws.
   Each animation reads its colours from here when it starts. */
.hero__anim {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: 100%;

  --life-1: #34445e;            /* conway (and the murmuration, now off): blue */
  --life-2: #2f5652;            /* teal */
  --life-3: #5e3533;            /* red */
  --life-4: #6b5035;            /* orange */
  --life-5: #4a3b55;            /* purple */
  --life-light: #a9a49b;        /* newborn cells are tinted toward this */
  --life-shade: #141617;        /* older cells darken toward this */

  --tank-ripple: #5e8f99;       /* fish tank: light on a ripple crest */
  --tank-trough: #0c0e0f;       /* shadow in a ripple trough */
  --tank-dot-dark: #223a2a;     /* a dot at rest */
  --tank-dot-light: #4b6e52;    /* a dot at the peak of its blink */
}

/* Switch button and "now playing" label: one row at the top left of the
   hero, one gutter in, so they line up with the logo above */
.anim-controls {
  position: absolute;
  top: 16px;
  left: var(--gutter);
  right: var(--gutter);         /* lets the row wrap rather than overflow */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}
.anim-switch,
.anim-label {
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* The switch is the only way to the other animations, so it wears the teal
   accent. Icon only; its name is in aria-label and title. */
.anim-switch {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--teal-fill);
  color: var(--text-head);
  box-shadow: var(--shadow-sm);
  font-family: inherit;
  font-size: 1rem;
  letter-spacing: 0;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s, transform 0.2s var(--ease);
}
.anim-switch:hover { background: #1a4744; color: #fff; transform: translateY(-1px); }

.anim-label {
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--text-faint);
  box-shadow: var(--shadow-sm);
}
.anim-label__name { color: var(--text-dim); }

/* Loader, while a switch loads the next animation (hero-animation.js adds
   .is-loading): the icon turns, three squares pulse in turn after the name,
   and the canvases fade out and in. */
.anim-label__dots { display: none; gap: 4px; margin-left: 8px; vertical-align: middle; }
.anim-label__dots span { width: 5px; height: 5px; background: var(--teal-hi); animation: pulse 1.2s ease-in-out infinite; }
.anim-label__dots span:nth-child(2) { animation-delay: 0.2s; }
.anim-label__dots span:nth-child(3) { animation-delay: 0.4s; }
.is-loading .anim-label__dots { display: inline-flex; }
.is-loading .anim-switch { cursor: progress; }
.is-loading .anim-switch span { display: inline-block; animation: spin 1s linear infinite; }

.hero__anim { transition: opacity 0.4s var(--ease); }
.hero__anim--out { opacity: 0; }
.hero__anim--in { animation: fade-in 0.6s var(--ease) both; }

@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fade-in { from { opacity: 0; } }

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(36px, 6vw, 88px);
}

.hero__status {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-bottom: var(--hero-gap);
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.hero__name {
  font-size: clamp(2.2rem, min(max(7vw, 2.6rem), 9svh), 4.8rem);
  line-height: 1;
  letter-spacing: -0.045em;
}
.hero__name > span { display: block; }
.hero__name .last { font-weight: 300; color: var(--silver); }

.hero__role {
  margin-top: var(--hero-gap);
  font-size: var(--fs-md);
  color: var(--text);
}
.hero__role::before { content: "→ "; color: var(--teal); }

.hero__intro { margin-top: calc(var(--hero-gap) * 0.6); max-width: 60ch; color: var(--text-dim); }

.hero__ctas { display: flex; flex-wrap: wrap; gap: 12px; margin-top: calc(var(--hero-gap) * 1.33); }

/* .hero__filler is the right-hand slot reserved for the landing filler
   (backlog V11-10). While empty it holds the column on wide screens and is
   hidden on narrow ones (see the 900px breakpoint). */

.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(32px, 6vw, 72px);
  align-items: start;
}
.contact-grid > div > p { color: var(--text-dim); max-width: 46ch; }
.contact-grid > div > p + p { margin-top: 14px; }


/* 6. Projects index
   ========================================================================== */

/* Area filter: shown by assets/js/project-filter.js, hidden without JavaScript */
.filters {
  padding: 16px 20px 20px;
  border-radius: var(--radius-md);
  background: var(--bg-1);
  box-shadow: var(--shadow-md);
}
.filters__label {
  margin-bottom: 10px;
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.filters__opts { display: flex; flex-wrap: wrap; gap: 8px; }

.filter {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 13px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  color: var(--text-dim);
  box-shadow: var(--shadow-sm);
  font: inherit;
  font-size: var(--fs-sm);
  line-height: 1.5;
  cursor: pointer;
  transition: transform 0.2s var(--ease), box-shadow 0.2s, color 0.2s, background-color 0.2s;
}
.filter:hover { color: var(--text); transform: translateY(-1px); box-shadow: var(--shadow-md); }
.filter[aria-pressed="true"] { background: var(--teal-fill); color: var(--text-head); }
.filter__n { font-size: var(--fs-xs); color: var(--text-faint); }
.filter[aria-pressed="true"] .filter__n { color: var(--teal-hi); }

/* "1 project · software systems", between the filter and the cards */
.filters-status {
  margin: 32px 0 16px;
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.filters-status b { color: var(--silver); font-weight: 500; }

/* Area colours, keyed by data-area on a card or a filter button */
[data-area="ai"]               { --area: var(--area-ai); }
[data-area="data-engineering"] { --area: var(--area-data); }
[data-area="software-systems"] { --area: var(--area-software); }
[data-area="research"]         { --area: var(--area-research); }
[data-area="other"]            { --area: var(--area-other); }

/* Area name with its coloured square, on cards and filter buttons */
.area { display: inline-flex; align-items: center; gap: 8px; }
.area::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  background: var(--area, var(--text-faint));
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  gap: 24px;
  counter-reset: project;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-md);
  background: var(--bg-1);
  box-shadow: var(--shadow-md);
  counter-increment: project;
  transition: transform 0.3s var(--ease), box-shadow 0.3s, background-color 0.3s;
}
.card:hover { background: var(--bg-2); transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.card:has(.card__link:focus-visible) { outline: 1px solid var(--teal-hi); outline-offset: 3px; }
/* Each project card is a dark shade of its area's colour. The faint text is
   lifted a little so it keeps its contrast on the tint. */
.card[data-area] {
  --text-faint: #8a939a;
  background: color-mix(in srgb, var(--area) 14%, var(--bg-1));
}
.card[data-area]:hover { background: color-mix(in srgb, var(--area) 20%, var(--bg-2)); }

.card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 20px 22px 22px;
}

.card__meta {
  display: flex;
  align-items: center;
  gap: 16px;
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
/* P-01, P-02 … numbered automatically in page order */
.card__meta::before { content: "P-" counter(project, decimal-leading-zero); color: var(--silver); }

.card__title { font-size: 1.3rem; }
.card__link { color: inherit; text-decoration: none; }
.card__link:hover { color: inherit; }
/* Makes the whole card clickable while the link text stays just the title */
.card__link::after { content: ""; position: absolute; inset: 0; z-index: 1; }

.card__desc { font-size: var(--fs-sm); color: var(--text-dim); }

.card__foot {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px dashed var(--line);
  font-size: var(--fs-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.card__cta { color: var(--teal-hi); white-space: nowrap; }
.card__cta .arrow { display: inline-block; transition: transform 0.25s var(--ease); }
.card:hover .card__cta .arrow { transform: translateX(4px); }

/* Placeholder slot for upcoming work. Delete it whenever you like. */
.card--ghost {
  justify-content: center;
  align-items: center;
  padding: 32px;
  text-align: center;
  background: rgba(255, 255, 255, 0.015);
  box-shadow: none;
  counter-increment: none;
  color: var(--text-faint);
  font-size: var(--fs-sm);
}
.card--ghost:hover { background: rgba(255, 255, 255, 0.015); transform: none; box-shadow: none; }
.card--ghost strong { display: block; margin-bottom: 6px; color: var(--text-dim); font-weight: 500; }


/* 7. Case study (project detail pages)
   ========================================================================== */

.cs-head { padding-block: clamp(40px, 7vw, 80px) clamp(40px, 6vw, 64px); }

/* Link back to the projects page, above the breadcrumb; matches the pager at the bottom */
.cs-back { display: inline-block; margin-bottom: 14px; font-size: var(--fs-sm); color: var(--text-dim); text-decoration: none; }
.cs-back:hover { color: var(--teal-hi); }

.cs-kicker {
  margin-bottom: 14px;
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal);
}
.cs-head h1 { font-size: clamp(2.2rem, 6vw, 3.6rem); letter-spacing: -0.035em; }
.cs-lede { margin-top: 18px; max-width: 66ch; font-size: var(--fs-md); color: var(--text-dim); }

.cs-head .chips { margin-top: 24px; }
.cs-links { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 24px; }

.cs-layout {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  align-items: start;
  gap: clamp(32px, 5vw, 72px);
  padding-bottom: clamp(56px, 8vw, 96px);
}

/* Table of contents */
.toc {
  position: sticky;
  top: calc(var(--header-h) + 28px);
  font-size: var(--fs-sm);
}
.toc summary {
  display: flex;
  justify-content: space-between;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  list-style: none;
  cursor: pointer;
}
.toc summary::-webkit-details-marker { display: none; }
.toc summary::after { content: "−"; }
.toc details:not([open]) summary::after { content: "+"; }
.toc ol {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 2px;
  margin-top: 12px;
  border-left: 1px solid var(--line);
  counter-reset: toc;
}
.toc li { counter-increment: toc; }
.toc a {
  display: flex;
  gap: 10px;
  margin-left: -1px;
  padding: 5px 0 5px 14px;
  border-left: 1px solid transparent;
  color: var(--text-dim);
  text-decoration: none;
  line-height: 1.5;
}
.toc a::before {
  content: counter(toc, decimal-leading-zero);
  padding-top: 1px;
  font-size: var(--fs-xs);
  color: var(--text-faint);
}
.toc a:hover { color: var(--teal-hi); border-left-color: var(--teal); }

/* Article body */
.prose { counter-reset: sec fig; color: var(--text-body); }

.cs-sec + .cs-sec { margin-top: clamp(56px, 8vw, 88px); }
.cs-sec > * + * { margin-top: 1.1rem; }

.cs-sec > h2 {
  position: relative;
  margin-bottom: 1.4rem;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: clamp(1.4rem, 3vw, 1.8rem);
  counter-increment: sec;
}
/* SEC 01, SEC 02 … numbered automatically */
.cs-sec > h2::before {
  content: "SEC " counter(sec, decimal-leading-zero);
  display: block;
  margin-bottom: 8px;
  font-size: var(--fs-xs);
  font-weight: 450;
  letter-spacing: 0.14em;
  color: var(--teal);
}
/* Short bright silver segment on the hairline */
.cs-sec > h2::after {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 48px;
  height: 1px;
  background: var(--silver);
}
.cs-sec:target > h2 { color: var(--teal-hi); }

.prose h3 {
  margin-top: 2.2rem;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
}
.prose h3::before { content: "## "; color: var(--text-faint); font-weight: 450; }
.prose h3 + * { margin-top: 0.8rem; }

.prose p,
.prose ul:not([class]),
.prose ol:not([class]) { max-width: 72ch; }

.prose ul:not([class]) { padding: 0; list-style: none; }
.prose ul:not([class]) > li { position: relative; padding-left: 22px; }
.prose ul:not([class]) > li + li { margin-top: 0.45em; }
.prose ul:not([class]) > li::before {
  content: "›";
  position: absolute;
  left: 4px;
  color: var(--teal);
}
.prose ol:not([class]) { padding-left: 2.6em; list-style: decimal-leading-zero; }
.prose ol:not([class]) > li + li { margin-top: 0.45em; }
.prose ol:not([class]) > li::marker { color: var(--teal); }

/* Quotations, such as an example post: "> text" in the Markdown */
.prose blockquote {
  max-width: 72ch;
  margin-inline: 0;
  margin-bottom: 0;
  padding: 2px 0 2px 18px;
  border-left: 2px solid var(--line-3);
  color: var(--text);
}

.prose strong { color: var(--text-head); font-weight: 600; }
.prose :not(pre) > code {
  padding: 1px 6px;
  border-radius: 4px;
  background: var(--bg-2);
  font-size: 0.88em;
  color: var(--teal-hi);
}

.cs-sec > .figure,
.cs-sec > .gallery,
.cs-sec > .code,
.cs-sec > .table-wrap,
.cs-sec > .metrics,
.cs-sec > .callout { margin-block: 2rem; }

/* Figures: images, screenshots, exported diagrams */
.figure {
  position: relative;
  overflow: hidden;              /* clips the image to the rounded corners */
  border-radius: var(--radius-md);
  background: var(--bg-1);
  box-shadow: var(--shadow-md);
  counter-increment: fig;
}
.figure__media {
  position: relative;
  display: block;
  background: var(--bg-media);
}
.figure__media img { width: 100%; }
.figure--pad .figure__media { padding: clamp(14px, 3vw, 28px); }

/* Wrap media in <a class="figure__media" href="…"> to let readers open it full size */
a.figure__media::after {
  content: "open full size ↗";
  position: absolute;
  top: 10px;
  right: 10px;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  background: rgba(11, 13, 14, 0.88);
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-xs);
  color: var(--text-dim);
  opacity: 0;
  transition: opacity 0.2s;
}
/* The figure clips its overflow, so draw the focus ring inside the image */
a.figure__media:focus-visible { outline-offset: -4px; }
a.figure__media:hover::after,
a.figure__media:focus-visible::after { opacity: 1; }

.figure figcaption {
  padding: 10px 16px;
  border-top: 1px solid var(--line);
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--text-dim);
}
/* FIG. 01, FIG. 02 … numbered automatically */
.figure figcaption::before {
  content: "FIG. " counter(fig, decimal-leading-zero) "  ";
  white-space: pre;
  letter-spacing: 0.08em;
  color: var(--teal);
}

/* Image grid: screenshots, wireframes */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  gap: 16px;
}
/* Wide images (over 2:1, such as before-and-after strips) go two to a row, so they stay legible */
.gallery--wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr)); }

/* HTML-only flow diagram: <ol class="flow"> with <li class="flow__node"> steps */
.flow {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 34px;
  counter-reset: step;
}
.flow__node {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-sm);
  line-height: 1.5;
  counter-increment: step;
}
.flow__node::before {
  content: counter(step, decimal-leading-zero);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  color: var(--text-faint);
}
.flow__node strong { color: var(--text-head); font-weight: 600; }
.flow__node span { font-size: var(--fs-xs); color: var(--text-dim); }
.flow__node--key { background: var(--teal-fill); }
.flow__node:not(:last-child)::after {
  content: "→";
  position: absolute;
  top: 50%;
  right: -28px;
  width: 22px;
  transform: translateY(-50%);
  text-align: center;
  color: var(--silver);
}

/* Callouts: notes and design decisions */
.callout {
  padding: 16px 20px;
  border-left: 3px solid var(--silver);
  border-radius: var(--radius-md);
  background: var(--bg-1);
  box-shadow: var(--shadow-sm);
}
.callout > * + * { margin-top: 0.6em; }
.callout--decision { border-left-color: var(--teal); background: var(--teal-wash); }
.callout__label {
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal);
}
.callout dl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; font-size: var(--fs-sm); }
.callout dt { color: var(--text-faint); }

/* Tables */
.table-wrap { overflow-x: auto; border-radius: var(--radius-md); background: var(--bg-1); box-shadow: var(--shadow-md); }
.table-wrap table { width: 100%; min-width: 34rem; border-collapse: collapse; font-size: var(--fs-sm); }
.table-wrap th,
.table-wrap td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}
.table-wrap thead th {
  background: var(--bg-1);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.table-wrap thead code { text-transform: none; }   /* code keeps its real case: sqrtf, not SQRTF */
.table-wrap td:first-child { white-space: nowrap; color: var(--text); }
/* A first column of sentences rather than short labels wraps like the rest */
.table-wrap--text td:first-child { white-space: normal; }
/* Columns of figures, right-aligned in the Markdown with ---: */
.table-wrap .num { text-align: right; }
.table-wrap tbody tr:last-child td { border-bottom: 0; }
.table-wrap tbody tr:hover { background: rgba(255, 255, 255, 0.015); }

/* Code listings */
.code { overflow: hidden; border-radius: var(--radius-md); background: var(--bg-media); box-shadow: var(--shadow-md); }
.code__file {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-xs);
  color: var(--text-faint);
  overflow-wrap: anywhere;
}
.code__file span:last-child { color: var(--teal); }
.code pre {
  padding: 16px 18px;
  overflow-x: auto;
  font-size: 0.8rem;
  line-height: 1.7;
  color: #b9c0c4;
  tab-size: 2;
}
/* Optional manual highlighting: wrap tokens in these spans */
.tok-c { color: #6b757a; }
.tok-k { color: var(--teal-hi); }
.tok-s { color: #e0e5e7; }
.tok-n { color: #9fd9d1; }
.tok-f { color: var(--text-head); font-weight: 600; }

/* Key numbers */
.metrics {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.metrics li {
  flex: 1 1 150px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 18px;
  border-radius: var(--radius-md);
  background: var(--bg-1);
  box-shadow: var(--shadow-md);
}
.metrics__val {
  font-size: 1.9rem;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.03em;
  color: var(--teal-hi);
}
.metrics__label { font-size: var(--fs-xs); line-height: 1.5; color: var(--text-dim); }

/* Bottom-of-article navigation */
.pager {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 16px;
  margin-top: clamp(56px, 8vw, 88px);
  padding-top: 24px;
  border-top: 1px solid var(--line-2);
  font-size: var(--fs-sm);
}
.pager a { color: var(--text-dim); text-decoration: none; }
.pager a:hover { color: var(--teal-hi); }


/* 8. About page
   ========================================================================== */

/* Middle section: taller than the screen, so it starts at the top */
.about-background {
  justify-content: flex-start;
  padding-top: clamp(48px, 7vw, 80px);
}

/* Final section: just the call-to-action card, centred both ways */
.about-end .cta-band {
  width: 100%;
  max-width: 760px;
  margin-inline: auto;
}

.about-intro {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: clamp(32px, 6vw, 80px);
}
/* Text and photo side by side, but only while the photo is in the page */
@media (min-width: 901px) {
  .about-intro:has(> .portrait) { grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.8fr); }
}
.about-intro__text > p { max-width: 62ch; color: var(--text-dim); }
.about-intro__text > p + p { margin-top: 14px; }
.about-intro__text .page-head__lede { color: var(--text); }

.portrait {
  padding: 10px;
  border-radius: var(--radius-md);
  background: var(--bg-1);
  box-shadow: var(--shadow-md);
}
.portrait img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-sm);
  filter: grayscale(0.2);
}
.portrait figcaption {
  display: flex;
  justify-content: space-between;
  padding-top: 10px;
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Timeline for education and experience */
.timeline { display: grid; }
.tl-item {
  display: grid;
  grid-template-columns: 170px minmax(0, 1fr);
  gap: 32px;
}
.tl-when {
  padding-top: 3px;
  text-align: right;
  font-size: var(--fs-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.tl-body {
  position: relative;
  padding: 0 0 52px 32px;
  border-left: 1px solid var(--line-2);
}
.tl-item:last-child .tl-body { padding-bottom: 8px; }
.tl-body::before {
  content: "";
  position: absolute;
  left: -5px;
  top: 8px;
  width: 9px;
  height: 9px;
  border: 1px solid var(--silver);
  background: var(--bg);
}
.tl-item.is-current .tl-body::before { background: var(--teal); border-color: var(--teal); }
.tl-body h3 { font-size: 1.1rem; }
.tl-where { margin-top: 4px; font-size: var(--fs-sm); color: var(--teal-hi); }
.tl-body > p { margin-top: 12px; max-width: 64ch; font-size: var(--fs-sm); color: var(--text-dim); }
.tl-points { margin-top: 12px; display: grid; gap: 6px; }
.tl-points li {
  position: relative;
  padding-left: 18px;
  max-width: 64ch;
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.tl-points li::before { content: "›"; position: absolute; left: 2px; color: var(--teal); }
.tl-body .chips { margin-top: 16px; }

/* Skills */
.skills {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));
  gap: 20px;
}
.skill-group {
  padding: 18px 20px 20px;
  border-radius: var(--radius-md);
  background: var(--bg-1);
  box-shadow: var(--shadow-md);
}
.skill-group h3 {
  margin-bottom: 14px;
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--silver);
}
.skill-group h3::after { content: "[]"; color: var(--text-faint); }
.skill-group p { margin-top: 12px; font-size: var(--fs-xs); color: var(--text-faint); }

/* Certifications and achievements */
.awards .ledger__k { flex-basis: 8ch; }
.awards .ledger__v { white-space: normal; color: var(--text); }
.awards .ledger__v small { display: block; font-size: var(--fs-xs); color: var(--text-faint); }


/* 9. Responsive + reduced motion
   ========================================================================== */

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

  .hero__filler:empty { display: none; }

  .cs-layout { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .toc {
    position: static;
    padding: 14px 16px;
    border-radius: var(--radius-md);
    background: var(--bg-1);
    box-shadow: var(--shadow-md);
  }

  .portrait { max-width: 260px; }
}

@media (max-width: 720px) {
  .flow { grid-auto-flow: row; gap: 30px; }
  .flow__node:not(:last-child)::after {
    content: "↓";
    top: auto;
    right: auto;
    bottom: -27px;
    left: 50%;
    transform: translateX(-50%);
  }

  .tl-item { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .tl-when {
    padding: 0 0 6px 32px;
    text-align: left;
    border-left: 1px solid var(--line-2);
  }
  .tl-body::before { top: -18px; }
}

@media (max-width: 560px) {
  .brand__name { display: none; }

  /* Just the animation's name beside the switch; "now playing" is still read out */
  .anim-label__key {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  /* The hero card becomes a full-width band, giving the text the whole width */
  .hero__card {
    margin-inline: calc(-1 * var(--gutter));
    padding: clamp(20px, 3.4svh, 28px) var(--gutter);
    border-radius: 0;
  }

  /* Phones with the browser's bars showing are short, so the hero's text
     scales with the screen's height too, and the buttons share one row */
  .hero { --hero-gap: clamp(10px, 2.2svh, 20px); }
  .hero__name { font-size: clamp(2.2rem, 5.4svh, 2.6rem); }
  .hero__role { font-size: clamp(var(--fs-sm), 2.1svh, var(--fs-md)); }
  .hero__intro { font-size: clamp(14px, 2svh, var(--fs-base)); line-height: 1.6; }
  .hero__ctas { gap: 6px; }
  .hero__ctas .btn { gap: 6px; padding: 10px; font-size: 0.72rem; }

  .ledger__k { flex-basis: 7ch; }
  .ledger__leader { display: none; }

  /* Decision terms go above their text, so a long term can't squeeze it */
  .callout dl { grid-template-columns: 1fr; gap: 2px; }
  .callout dd + dt { margin-top: 10px; }
}

/* Wide but short screens, such as laptops at 125–150% display scaling: the
   name goes on one line, saving a line of the largest text. cqi caps the size
   by the card's width (the one-line name is about 8.9em wide), so it can't
   overflow. */
@media (min-width: 901px) and (max-height: 760px) {
  .hero__name > span { display: inline; }
  .hero__name { font-size: clamp(2.2rem, min(10.5cqi, 9svh), 4.8rem); }
  .hero__intro { line-height: 1.65; }
}

/* Very short phones, such as an iPhone SE with Safari's bars showing: even
   scaled down, the card can't fit with its intro, so the intro is hidden here
   only (decision Q6). The same introduction is on the about page. */
@media (max-width: 560px) and (max-height: 620px) {
  .hero__intro { display: none; }
}

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

  /* The animations draw a still frame instead, so there is nothing to switch */
  .anim-label,
  .anim-switch { display: none !important; }
}
