/* Game library theme: blue-black ground, one green accent, square corners.
   The earlier Ocean Depths light palette is replaced; the tokens keep their names. */

:root {
  --bg: #0d1117;
  --card: #161c25;
  --band: #161c25;
  --band-text: #eef1f5;
  --band-link: #35e07a;
  --header-bg: rgb(13 17 23 / 0.9);
  --text: #eef1f5;
  --text-muted: #9aa4b2;
  --link: #35e07a;
  --btn-bg: #35e07a;
  --btn-bg-hover: #6ff0a3;
  --btn-text: #0d1117;
  --btn-alt: #eef1f5;
  --border: #2a3442;
  --accent: #2a3442;
  --tint: #161c25;
  --tag-bg: #1f2733;
  --focus: #35e07a;
  --code-bg: #07120d;
  --code-text: #2fe08a;

  --page-width: 48rem;
  --wide-width: 76rem;
  --radius: 0;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --font: "Barlow", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Barlow Condensed", "Barlow", system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  interpolate-size: allow-keywords;
  color-scheme: dark;
}

/* Dark palette, kept for a future toggle. It only applies when <html data-theme="dark"> is set,
   so the site stays on the light palette whatever the visitor's device setting. */
:root[data-theme="dark"] {
  --bg: #1a2332;
  --card: #222d40;
  --band: #111823;
  --header-bg: rgb(26 35 50 / 0.85);
  --text: #f1faee;
  --text-muted: #b8c4d0;
  --link: #a8dadc;
  --btn-bg: #a8dadc;
  --btn-bg-hover: #c2e8ea;
  --btn-text: #1a2332;
  --btn-alt: #a8dadc;
  --border: #34425a;
  --accent: #34425a;
  --tint: #2a3850;
  --tag-bg: #2a3850;
  --focus: #a8dadc;
  color-scheme: dark;
}

/* Base */

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

html {
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
}

@media (min-width: 40em) {
  html {
    font-size: 106.25%; /* 17px on desktop, 16px on small phones */
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  line-height: 1.6;
}

main.container {
  max-width: var(--wide-width);
  margin: 0 auto;
  padding: 2rem 1.25rem 1rem;
}

main.narrow {
  max-width: var(--page-width);
}

h1,
h2,
h3 {
  line-height: 1.2;
  letter-spacing: -0.015em;
}

h1,
h2 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.01em;
}

h1 {
  font-size: clamp(2.5rem, 5vw, 4rem);
  line-height: 0.98;
  margin: 0 0 1rem;
}

h2 {
  font-size: 2.25rem;
  line-height: 1;
  margin: 3rem 0 1rem;
}

h3 {
  font-size: 1.2rem;
  margin: 1.5rem 0 0.5rem;
}

p,
li {
  max-width: 70ch;
}

a {
  color: var(--link);
}

a:hover {
  text-decoration-thickness: 2px;
}

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

main img {
  max-width: 100%;
  height: auto;
  border: 1px solid var(--border);
}

/* Skip link for keyboard users */

.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -4rem;
  padding: 0.5rem 0.75rem;
  background: var(--btn-bg);
  color: var(--btn-text);
  z-index: 10;
}

.skip-link:focus {
  top: 0.5rem;
}

/* Sticky header: light, blurred once content scrolls under it */

.site-header {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--header-bg);
  border-bottom: 1px solid var(--border);
  view-transition-name: site-header;
}

/* header.js adds .scrolled once content slides under the header, so the blur isn't always on */
.site-header.scrolled {
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.header-inner {
  max-width: var(--wide-width);
  margin: 0 auto;
  padding: 0.9rem 1.25rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 2rem;
}

.site-name {
  color: var(--text);
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.75rem;
}

.nav-list li {
  max-width: none;
}

.nav-list a {
  position: relative;
  display: inline-block;
  padding: 0.35rem 0.1rem;
  color: var(--text);
  text-decoration: none;
}

.nav-list a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--link);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 180ms var(--ease-out);
}

.nav-list a[aria-current="page"]::after {
  transform: scaleX(1);
}

.nav-list a[aria-current="page"] {
  font-weight: 600;
}

/* Footer band */

.band {
  background: var(--band);
  color: var(--band-text);
}

.band a {
  color: var(--band-link);
}

.band a:focus-visible {
  outline-color: var(--band-link);
}

.band-inner {
  max-width: var(--wide-width);
  margin: 0 auto;
  padding: 1.5rem 1.25rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
}

.band-footer {
  margin-top: 4rem;
  font-size: 0.9rem;
}

.band-footer p {
  margin: 0;
}

/* Buttons */

.btn {
  display: inline-flex;
  align-items: center;
  min-height: 3rem;
  padding: 0 1.4rem;
  border: 2px solid var(--btn-bg);
  border-radius: 0;
  background: var(--btn-bg);
  color: var(--btn-text);
  font-weight: 600;
  text-decoration: none;
  transition: transform 160ms var(--ease-out), background-color 160ms ease, border-color 160ms ease;
}

.btn:active {
  transform: scale(0.97);
}

.btn-secondary {
  background: transparent;
  border-color: var(--btn-alt);
  color: var(--btn-alt);
}

/* Code and log blocks */

pre,
code {
  font-family: var(--font-mono);
}

pre {
  margin: 1rem 0;
  padding: 1rem;
  overflow-x: auto;
  background: var(--code-bg);
  color: var(--code-text);
  border-radius: 0.375rem;
  font-size: 0.9rem;
  line-height: 1.5;
}

:not(pre) > code {
  padding: 0.1em 0.35em;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 0.25rem;
  font-size: 0.9em;
}

/* Home page hero: text on the left, photo on the right, both vertically centred */

.hero {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 2.5rem 3rem;
  padding: 3rem 0 2rem;
}

.hero-text {
  flex: 1 1 28rem;
  min-width: 0;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 1.25rem;
  color: var(--link);
  font-size: 0.95rem;
  font-weight: 600;
}

.eyebrow::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
}

.hero h1 {
  max-width: 16ch;
}

.hero .lead {
  margin: 1.5rem 0 0;
  font-size: 1.2rem;
  line-height: 1.55;
  color: var(--text-muted);
  max-width: 58ch;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 2rem 0 0;
}

.fineprint {
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* Placeholder for a photo. Swap the div for an <img class="photo"> when the picture is ready. */

.photo-ph,
.photo {
  flex: 0 1 27rem;
  width: 100%;
  max-width: 27rem;
  aspect-ratio: 4 / 5;
  border-radius: 1.25rem;
}

.photo-ph {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 1.5rem;
  background: var(--tint);
  border: 2px dashed var(--accent);
  color: var(--text-muted);
  font-size: 0.95rem;
  text-align: center;
}

.photo-ph svg {
  stroke: var(--link);
}

.photo-ph small {
  font-size: 0.8rem;
}

/* Snapshot card for the Home hero photo: a tilted white print with a handwritten caption.
   Uses the independent rotate property so the page-load rise (transform) doesn't cancel it. */

.photo-card {
  flex: 0 1 24rem;
  width: 100%;
  max-width: 24rem;
  margin: 0;
  padding: 0.9rem 0.9rem 0.75rem;
  background: #ffffff;
  border-radius: 0.4rem;
  box-shadow: 0 0.9rem 2rem rgb(26 35 50 / 0.22);
  rotate: 2.5deg;
}

.photo-card img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: 0.15rem;
}

.photo-card figcaption {
  padding-top: 0.8rem;
  color: #4a5568;
  font-family: "Segoe Print", "Bradley Hand", "Comic Sans MS", cursive;
  font-size: 1.3rem;
  text-align: center;
}

/* The photo card lands like a print dropped on a table: a short swing that settles into its
   resting tilt (the rotate property). It plays once on load, and only where the card sits beside
   the text. On a phone the card is below the fold, so it keeps the plain fade-in of the rest of
   the hero. */
@media (min-width: 48em) {
  .is-home .photo-card.rise {
    transform: translateY(14px) rotate(-4deg) scale(0.96);
    animation-name: snap-in;
    animation-duration: 380ms;
  }
}

@keyframes snap-in {
  to {
    opacity: 1;
    transform: none;
  }
}
.photo {
  object-fit: cover;
}

.photo.wide,
.photo-ph.wide {
  aspect-ratio: 4 / 3;
  max-width: 30rem;
  flex-basis: 30rem;
}

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.section-head h2 {
  margin-bottom: 1rem;
}

.section-head a {
  font-weight: 600;
  font-size: 0.95rem;
}

/* Cards */

.card-grid {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.25rem;
}

.card-grid > li {
  max-width: none;
}

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem;
}

.card h3 {
  margin: 0 0 0.25rem;
  font-size: 1.1rem;
}

.card p {
  margin: 0.4rem 0 0;
}

.card .muted {
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* Cards that are one big link, with a cover image on top */
.card-link {
  display: block;
  padding: 0.75rem;
  color: var(--text);
  text-decoration: none;
  transition: border-color 180ms ease, transform 200ms var(--ease-out);
}

.card-link:active {
  transform: scale(0.99);
}

.card-link .cover-img,
.card-link .cover-ph {
  display: block;
  width: 100%;
  height: 10rem;
  border-radius: 0.5rem;
  object-fit: cover;
  object-position: top;
}

.card-link img.cover-img {
  border: 1px solid var(--border);
}

.card-link .cover-ph {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tint);
  color: var(--text-muted);
  font-size: 0.85rem;
}

.card-body {
  padding: 1rem 0.5rem 0.5rem;
}

.card-link .tagline {
  display: block;
  margin-top: 0.75rem;
  color: var(--link);
  font-size: 0.85rem;
  font-weight: 600;
}

@media (hover: hover) and (pointer: fine) {
  .card-link:hover {
    border-color: var(--link);
  }
}

/* Group labels inside a project card's role section */
.card h4 {
  margin: 0.75rem 0 0;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.card h4 + ul {
  margin-top: 0.25rem;
}

/* Screenshot thumbnails inside a project card; each links to the full-size image */
.shots {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: 0.5rem;
}

.shots li {
  margin: 0;
}

.shots a {
  display: block;
  border: 1px solid var(--border);
  border-radius: 0.375rem;
  overflow: hidden;
  transition: transform 200ms var(--ease-out);
}

.shots a:active {
  transform: scale(0.98);
}

@media (hover: hover) and (pointer: fine) {
  .shots a:hover {
    transform: scale(1.02);
  }
}

.shots img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: top;
}

/* Screenshot viewer opened by assets/js/lightbox.js */
.lightbox {
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: rgb(0 0 0 / 0.95);
  color: #ffffff;
  /* Fade in over 200ms, out over 150ms. display and overlay are discrete so the exit can play. */
  opacity: 0;
  transition: opacity 150ms var(--ease-out), display 150ms allow-discrete, overlay 150ms allow-discrete;
}

.lightbox[open] {
  display: flex;
  flex-direction: column;
  opacity: 1;
  transition-duration: 200ms;
}

@starting-style {
  .lightbox[open] {
    opacity: 0;
  }
}

.lightbox-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
}

.lightbox-count {
  margin-right: auto;
  font-size: 0.9rem;
}

.lightbox button {
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0.25rem 0.75rem;
  border: 1px solid rgb(255 255 255 / 0.35);
  border-radius: 0.375rem;
  background: rgb(255 255 255 / 0.12);
  color: inherit;
  font: inherit;
  font-size: 1rem;
  cursor: pointer;
  transition: transform 160ms var(--ease-out), background-color 160ms ease;
}

.lightbox button:active {
  transform: scale(0.97);
}

@media (hover: hover) and (pointer: fine) {
  .lightbox button:hover {
    background: rgb(255 255 255 / 0.22);
  }
}

.lightbox-stage {
  flex: 1;
  min-height: 0;
  overflow: auto;
  display: grid;
  place-items: center;
  padding: 0 1rem 1rem;
}

.lightbox-stage img {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - 5rem);
  border-radius: 0.375rem;
  cursor: zoom-in;
  transition: opacity 150ms var(--ease-out), filter 150ms var(--ease-out);
}

/* Brief blur while one screenshot swaps for the next (set by lightbox.js) */
.lightbox-stage img.is-swapping {
  opacity: 0.6;
  filter: blur(2px);
}

.lightbox.is-zoomed .lightbox-stage {
  place-items: start;
}

.lightbox.is-zoomed .lightbox-stage img {
  max-width: none;
  max-height: none;
  cursor: zoom-out;
}

.badge {
  display: inline-block;
  padding: 0.1rem 0.7rem;
  border: 1px solid var(--link);
  border-radius: 999px;
  font-size: 0.85rem;
  color: var(--link);
}

.badge-progress {
  border-style: dashed;
}

.tags {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
}

.tags li {
  max-width: none;
  padding: 0.15rem 0.8rem;
  border-radius: 999px;
  background: var(--tag-bg);
  font-size: 0.9rem;
}

/* Certificate cards: official badge on the left, text on the right */
.cert-grid {
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}

.cert-card {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  padding: 1.1rem 1.25rem;
}

.cert-badge {
  flex: none;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  object-fit: contain;
}

main img.cert-badge {
  border: 0;
}

.cert-badge-ph,
.cert-badge-soon {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.4rem;
  font-size: 0.7rem;
  line-height: 1.2;
  text-align: center;
  color: var(--text-muted);
}

.cert-badge-ph {
  background: var(--tint);
  border: 2px dashed var(--accent);
}

.cert-badge-soon {
  background: var(--bg);
  border: 2px solid var(--border);
  color: #7d8a99;
}

.cert-body {
  min-width: 0;
}

.cert-card h3 {
  margin: 0 0 0.25rem;
  font-size: 1.05rem;
  line-height: 1.3;
}

.cert-card p {
  margin: 0.35rem 0 0;
}

/* Experience timeline on the home page */
.timeline {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  border-left: 2px solid var(--accent);
}

.timeline > li {
  position: relative;
  max-width: none;
  padding: 0 0 1.75rem 1.75rem;
}

.timeline > li::before {
  content: "";
  position: absolute;
  left: -0.45rem;
  top: 0.4rem;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: var(--link);
}

.timeline h3 {
  margin: 0;
  font-size: 1.1rem;
}

.timeline .muted {
  margin: 0.15rem 0 0.5rem;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.timeline p {
  margin: 0;
}

/* Collapsible project details; the panel eases open where the browser supports it */
.more {
  margin-top: 0.75rem;
}

.more > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--link);
  padding: 0.25rem 0;
}

.more > summary:focus-visible {
  outline: 2px solid var(--link);
  outline-offset: 2px;
}

.more::details-content {
  block-size: 0;
  opacity: 0;
  overflow: clip;
  transition: block-size 220ms var(--ease-out), opacity 220ms var(--ease-out), content-visibility 220ms allow-discrete;
}

.more[open]::details-content {
  block-size: auto;
  opacity: 1;
}

/* Project cards run one per row so the details and screenshots have room */
.project-list {
  grid-template-columns: 1fr;
  align-items: start;
}

.project-list > .card {
  padding: 1.5rem;
}

/* Thumbnails that must show whole, such as a phone screen or a log table */
.shots-fit {
  align-items: center;
}

.shots-fit a {
  display: flex;
  justify-content: center;
  background: var(--card);
}

.shots-fit img {
  width: auto;
  max-width: 100%;
  max-height: 15rem;
  aspect-ratio: auto;
  object-fit: contain;
}

/* Hobbies page */
.cover-row {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0 0 2.5rem; /* room under the genre labels before the next section's rule */
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1.25rem;
}

.cover-row > li {
  max-width: none;
}

.game .cover-ph {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 3 / 4;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  background: var(--tint);
  color: var(--text-muted);
  font-size: 0.85rem;
  transition: transform 200ms var(--ease-out);
}

.game .game-img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  transition: transform 200ms var(--ease-out);
}

.game h3 {
  margin: 0.75rem 0 0.1rem;
  font-size: 1.05rem;
}

.game p {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9rem;
}

@media (hover: hover) and (pointer: fine) {
  .game:hover .cover-ph,
  .game:hover .game-img {
    transform: translateY(-3px);
  }
}

/* Scroll reveal: fade and rise 12px, once. assets/js/reveal.js adds .rv and then .in.
   It only touches elements below the fold, and only when it runs, so content never stays hidden. */
.rv {
  transition-property: opacity, translate, border-color, transform;
  transition-duration: 240ms, 240ms, 180ms, 200ms;
  transition-delay: var(--d, 0ms), var(--d, 0ms), 0ms, 0ms;
  transition-timing-function: var(--ease-out), var(--ease-out), ease, var(--ease-out);
}

.rv-on .rv {
  opacity: 0;
  translate: 0 12px;
}

.rv-on .rv.in {
  opacity: 1;
  translate: 0 0;
}

/* Set by reveal.js while an in-page link jumps, so the target shows at once instead of fading in */
.rv-jump .rv {
  transition-duration: 0ms;
  transition-delay: 0ms;
}

/* Page-load entrance for the Home hero only. Buttons and fine print are left out so the
   primary action is usable straight away. */
.is-home .rise {
  opacity: 0;
  transform: translateY(8px);
  animation: rise 280ms var(--ease-out) forwards;
}

.is-home .rise.d2 { animation-delay: 60ms; }
.is-home .rise.d3 { animation-delay: 120ms; }

@keyframes rise {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (hover: hover) and (pointer: fine) {
  .nav-list a:hover::after {
    transform: scaleX(1);
  }

  .btn:hover {
    background: var(--btn-bg-hover);
    border-color: var(--btn-bg-hover);
    color: var(--btn-text);
  }

  .btn-secondary:hover {
    background: transparent;
    border-color: var(--link);
    color: var(--link);
  }

  .card:not(.card-link):hover {
    border-color: var(--link);
  }
}

/* Reduced motion: keep the fades, drop the movement */
@media (prefers-reduced-motion: reduce) {
  .rv-on .rv {
    translate: none;
  }

  .is-home .rise {
    transform: none;
  }

  /* Only fade in; no swing */
  .is-home .photo-card.rise {
    transform: none;
    animation-name: rise;
  }

  .card-link,
  .shots a,
  .btn,
  .game .cover-ph,
  .game .game-img {
    transition-property: opacity, border-color, background-color;
  }

  .card-link:hover,
  .shots a:hover,
  .game:hover .cover-ph,
  .game:hover .game-img {
    transform: none;
  }

  /* Details still fade, but the panel no longer slides open */
  .more::details-content {
    transition: opacity 150ms ease, content-visibility 150ms allow-discrete;
  }

  .lightbox-stage img.is-swapping {
    filter: none;
  }
}

/* Short crossfade between pages where the browser supports it; header and footer hold still */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

.band-footer {
  view-transition-name: site-footer;
}

/* ---- Game library theme ---- */

/* Square corners everywhere, to match the buttons and tiles. The clip button stays round. */
.card-link .cover-img,
.card-link .cover-ph,
.shots a,
.project-row .thumb,
.game .cover-ph,
.game .game-img,
.feature-media,
.sn-card,
.sn-indicator,
.tags li,
.badge,
.photo,
.photo-ph,
.clip-spot,
pre {
  border-radius: 0;
}

/* Email button at the end of the header nav, visible on every page */
.nav-list {
  align-items: center;
}

.nav-list a.nav-btn {
  display: inline-flex;
  align-items: center;
  min-height: 2.5rem;
  padding: 0 1.1rem;
  color: var(--btn-text);
  font-weight: 600;
}

.nav-list a.nav-btn::after {
  display: none;
}

/* Footer: the name returns at display size */
.footer-main {
  padding-top: 2.5rem;
  padding-bottom: 1.25rem;
}

.footer-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.5rem;
  line-height: 1.1;
  letter-spacing: -0.01em;
}

.footer-role {
  margin-top: 0.2rem !important;
  color: var(--text-muted);
}

.footer-links {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.5rem;
  margin: 0;
  padding: 0;
}

.footer-links li {
  max-width: none;
}

.footer-sub {
  padding-top: 0;
  padding-bottom: 1.75rem;
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* Home page */
.is-home main h2 {
  margin-top: 0;
}

.is-home main > section + section {
  margin-top: 6rem;
}

.hero-home {
  gap: 2.5rem 4rem;
  padding: 2.5rem 0 1rem;
}

.hero-home .hero-text {
  flex: 1 1 34rem;
}

.hero-home h1 {
  max-width: none;
  font-size: clamp(2.75rem, 7vw, 4.75rem);
  line-height: 0.92;
}

.hero-media {
  flex: 0 1 28rem;
  width: 100%;
  max-width: 28rem;
  margin: 0;
}

.hero-frame {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--card);
  overflow: hidden;
}

.hero-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
  object-position: 50% 52%;
}

.hero-media figcaption {
  margin-top: 0.75rem;
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* Project tiles under the hero */
.tiles {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.5rem;
}

.tiles > li {
  max-width: none;
}

.tile {
  display: block;
  color: var(--text);
  text-decoration: none;
  transition: transform 200ms var(--ease-out);
}

/* The frame clips the picture, so it can zoom on hover without growing past its edges */
.tile-frame {
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  background: var(--card);
}

.tile img {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
  object-position: top;
  transition: transform 250ms var(--ease-out);
}

/* The bar fills from the left when a tile is active, and retracts a little faster */
.tile-bar {
  display: block;
  height: 6px;
  background: var(--btn-bg);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 120ms var(--ease-out);
}

.tile h3 {
  margin: 0.75rem 0 0.25rem;
  font-size: 1.6rem;
  line-height: 1.15;
}

.tile p {
  margin: 0;
  color: var(--text-muted);
}

.tile:focus-visible .tile-bar {
  transform: scaleX(1);
  transition-duration: 220ms;
}

.tile:active {
  transform: scale(0.99);
}

/* About statement and the example ticket */
.about {
  display: flex;
  flex-wrap: wrap;
  gap: 3rem;
  align-items: center;
}

.statement {
  flex: 1 1 24rem;
  margin: 0;
  max-width: 34ch;
  font-size: 1.75rem;
  line-height: 1.35;
}

.ticket {
  flex: 1 1 24rem;
  max-width: 34rem;
  padding: 2rem;
  background: var(--card);
}

.ticket-tag {
  margin: 0 0 0.75rem;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.ticket-title {
  margin: 0 0 1.5rem;
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.15;
}

.ticket-steps {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0;
}

.ticket-steps li {
  max-width: none;
  padding: 0.5rem 1rem;
  background: var(--bg);
  font-weight: 600;
  transition: background-color 150ms var(--ease-out), color 150ms var(--ease-out);
}

.ticket-steps li.is-done {
  background: #2a3442;
}

.ticket-steps li.is-on {
  background: var(--stage, var(--btn-bg));
  color: var(--btn-text);
}

.ticket-track {
  height: 6px;
  margin-bottom: 1.5rem;
  background: var(--bg);
  overflow: hidden;
}

/* home.js sets the scale and --bar from the scroll position. No transition, so it tracks 1:1.
   Without JavaScript it shows the finished state: full and green. */
.ticket-bar {
  height: 100%;
  background: var(--bar, var(--btn-bg));
  transform-origin: left;
  transform: scaleX(1);
}

.ticket-text {
  min-height: 5.2rem;
  margin: 0;
  line-height: 1.55;
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
}

/* home.js sets this for a moment while the text changes: out fast, back in a little slower */
.ticket-text.is-out {
  opacity: 0;
  transform: translateY(6px);
  transition-duration: 120ms;
}

/* Achievements */
.achievements {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  max-width: 60rem;
  margin: 1.5rem 0 0;
  padding: 0;
}

.achievement {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.5rem;
  max-width: none;
  padding: 1.25rem 1.5rem;
  background: var(--card);
}

.achievement-badge,
.achievement-mark {
  flex: none;
  width: 4.5rem;
  height: 4.5rem;
}

.achievement-badge {
  border: 0;
  object-fit: contain;
}

.achievement-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  color: var(--link);
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 700;
}

.achievement-body {
  flex: 1 1 16rem;
  min-width: 0;
}

.achievement h3 {
  margin: 0;
  font-size: 1.5rem;
  line-height: 1.15;
}

.achievement .muted {
  margin: 0.25rem 0 0;
  color: var(--text-muted);
}

.chip {
  padding: 0.4rem 1rem;
  background: var(--bg);
  color: var(--text);
  font-weight: 600;
}

.chip {
  position: relative;
  isolation: isolate;
}

.chip-done {
  background: var(--btn-bg);
  color: var(--btn-text);
}

/* The green sits on its own layer so it can sweep in with a transform */
.chip-done::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--btn-bg);
  transform-origin: left;
}

/* Unlock beat. While a row is waiting to be revealed (.rv), its chip is dark and "locked" and its
   badge is slightly small. Once reveal.js adds .in, the row lands, the badge settles, then the green
   sweeps across the chip. --d is the row's own stagger delay. "In progress" never fills. */
.achievement.rv .chip-done {
  background: var(--bg);
  color: var(--text);
}

.achievement.rv .chip-done::before {
  transform: scaleX(0);
}

.achievement.rv .achievement-badge,
.achievement.rv .achievement-mark {
  opacity: 0;
  transform: scale(0.92);
}

.achievement.rv.in .achievement-badge,
.achievement.rv.in .achievement-mark {
  opacity: 1;
  transform: none;
  transition: opacity 240ms var(--ease-out) calc(var(--d, 0ms) + 60ms), transform 240ms var(--ease-out) calc(var(--d, 0ms) + 60ms);
}

.achievement.rv.in .chip-done {
  color: var(--btn-text);
  transition: color 120ms ease calc(var(--d, 0ms) + 200ms);
}

.achievement.rv.in .chip-done::before {
  transform: scaleX(1);
  transition: transform 260ms var(--ease-out) calc(var(--d, 0ms) + 160ms);
}

/* Toolkit */
.section-note {
  margin: 0 0 1.5rem;
  color: var(--text-muted);
}

.toolkit {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  align-items: stretch;
  margin-top: 1rem;
}

.slot-grid {
  flex: 1 1 28rem;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.75rem;
}

.slot {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: flex-start;
  min-height: 9.5rem;
  padding: 1rem;
  border: 0;
  background: var(--card);
  color: var(--text);
  font: 600 1.5rem/1.1 var(--font);
  text-align: left;
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease, transform 120ms var(--ease-out);
}

.slot:active {
  transform: scale(0.97);
}

.slot[aria-pressed="true"] {
  background: var(--btn-bg);
  color: var(--btn-text);
}

.slot-panel {
  flex: 1 1 22rem;
  padding: 2.5rem;
  background: var(--card);
}

.slot-name {
  margin: 0 0 1rem;
  color: var(--link);
  font-family: var(--font-display);
  font-size: 3.5rem;
  font-weight: 700;
  line-height: 0.95;
}

/* The tools sit on the tile, under its label */
.slot-sub {
  margin-top: 0.35rem;
  font-size: 0.9rem;
  font-weight: 400;
  line-height: 1.3;
}

/* Panel: what the tools are for (quiet), what I did (full colour), and where to see it */
.slot-use {
  margin: 0 0 1.25rem;
  color: var(--text-muted);
  font-size: 1.15rem;
  line-height: 1.3;
}

.slot-used {
  margin: 0 0 1.5rem;
  max-width: 44ch;
  line-height: 1.6;
}

.slot-link {
  margin: 0;
  font-weight: 600;
}

.slot-fallback {
  margin: 1rem 0 0;
  padding-left: 1.25rem;
  line-height: 1.7;
}

/* The detail text fades out quickly, home.js swaps it, then it fades back in a little slower.
   Transitions, so a quick second pick retargets from where it is instead of restarting. */
.slot-name,
.slot-use,
.slot-used,
.slot-link {
  transition: opacity 120ms var(--ease-out), transform 120ms var(--ease-out);
}

.slot-name.is-swapping,
.slot-use.is-swapping,
.slot-used.is-swapping,
.slot-link.is-swapping {
  opacity: 0;
  transform: translateY(4px);
  transition-duration: 80ms;
}

/* Contact: one block of accent colour */
.contact-field {
  padding: 4rem 2rem 4.5rem;
  background: var(--btn-bg);
  color: var(--btn-text);
}

/* The block wipes in from the left once, as it scrolls into view (home.js adds the classes) */
.contact-field.wipe {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 420ms var(--ease-out);
}

.contact-field.wipe.wipe-in {
  clip-path: inset(0);
}

/* Hobbies: the numbered markers on the Flipper photo pop in one after another (1, 2, 3) once the
   row has been revealed. An animation with a backwards fill, so the hover scale below isn't delayed. */
.marker {
  transition: transform 150ms var(--ease-out), background-color 150ms var(--ease-out), color 150ms var(--ease-out);
}

.photo-key li {
  transition: color 150ms var(--ease-out);
}

.photo-key .num {
  transition: background-color 150ms var(--ease-out), color 150ms var(--ease-out);
}

.marker:nth-of-type(1) { --i: 0; }
.marker:nth-of-type(2) { --i: 1; }
.marker:nth-of-type(3) { --i: 2; }

.feature-row.in .marker {
  animation: marker-in 220ms var(--ease-out) calc(var(--d, 0ms) + 200ms + var(--i, 0) * 80ms) backwards;
}

@keyframes marker-in {
  from {
    opacity: 0;
    transform: scale(0.8);
  }
}

@keyframes marker-fade {
  from {
    opacity: 0;
  }
}

/* The screenshot link in the Building row: press feedback, and a slight zoom on hover */
a.feature-media.has-image {
  transition: transform 160ms var(--ease-out);
}

a.feature-media.has-image img {
  transition: transform 250ms var(--ease-out);
}

a.feature-media.has-image:active {
  transform: scale(0.98);
}

/* Favourite games (Hobbies): the whole card lifts, a green bar fills under the cover, and the title
   turns green. The bar is the same one the Home project tiles use. */
.game {
  transition: transform 200ms var(--ease-out);
}

.game h3 {
  transition: color 160ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .game:hover {
    transform: translateY(-6px);
  }

  /* The card moves as one piece, so the cover itself no longer lifts on its own */
  .game:hover .game-img,
  .game:hover .cover-ph {
    transform: none;
  }

  .game:hover .tile-bar {
    transform: scaleX(1);
    transition-duration: 220ms;
  }

  .game:hover h3 {
    color: var(--link);
  }

  /* A marker on the photo and its line in the key light up together, whichever one you hover:
     the marker fills green and lifts, the key line goes to full text colour and its number fills green. */
  .marker:hover,
  .feature-row:has(.photo-key li:nth-child(1):hover) .marker:nth-of-type(1),
  .feature-row:has(.photo-key li:nth-child(2):hover) .marker:nth-of-type(2),
  .feature-row:has(.photo-key li:nth-child(3):hover) .marker:nth-of-type(3) {
    background: var(--btn-bg);
    color: var(--btn-text);
    transform: scale(1.2);
  }

  .photo-key li:hover,
  .feature-row:has(.marker:nth-of-type(1):hover) .photo-key li:nth-child(1),
  .feature-row:has(.marker:nth-of-type(2):hover) .photo-key li:nth-child(2),
  .feature-row:has(.marker:nth-of-type(3):hover) .photo-key li:nth-child(3) {
    color: var(--text);
  }

  .photo-key li:hover .num,
  .feature-row:has(.marker:nth-of-type(1):hover) .photo-key li:nth-child(1) .num,
  .feature-row:has(.marker:nth-of-type(2):hover) .photo-key li:nth-child(2) .num,
  .feature-row:has(.marker:nth-of-type(3):hover) .photo-key li:nth-child(3) .num {
    background: var(--btn-bg);
    color: var(--btn-text);
  }

  a.feature-media.has-image:hover img {
    transform: scale(1.02);
  }
}

.contact-field h2 {
  max-width: 18ch;
  margin: 0 0 1rem;
  color: inherit;
  font-size: clamp(2.5rem, 5vw, 4rem);
}

.contact-field p {
  color: inherit;
}

.contact-field .btn {
  background: var(--bg);
  border-color: var(--bg);
  color: var(--text);
}

.contact-field .btn:first-child {
  color: var(--link);
}

@media (hover: hover) and (pointer: fine) {
  .tile:hover {
    transform: translateY(-4px);
  }

  .tile:hover img {
    transform: scale(1.06);
  }

  .tile:hover .tile-bar {
    transform: scaleX(1);
    transition-duration: 220ms;
  }

  .contact-field .btn:hover {
    background: var(--card);
    border-color: var(--card);
    color: var(--text);
  }
}

@media (max-width: 40em) {
  /* The header isn't pinned on a phone and the page has its own Email button, so the nav one
     would only wrap onto a row of its own */
  .nav-cta {
    display: none;
  }

  /* Badge and text share a row; the status chip drops underneath */
  .achievement {
    gap: 0.75rem 1rem;
    padding: 1rem;
  }

  .achievement-badge,
  .achievement-mark {
    width: 3.5rem;
    height: 3.5rem;
    font-size: 1.4rem;
  }

  .achievement-body {
    flex-basis: 10rem;
  }

  .achievement h3 {
    font-size: 1.25rem;
  }

  .is-home main > section + section {
    margin-top: 4rem;
  }

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

  .slot {
    min-height: 7rem;
  }

  .slot-panel {
    padding: 1.5rem;
  }

  .slot-name {
    font-size: 2.75rem;
  }

  .ticket {
    padding: 1.25rem;
  }

  .ticket-text {
    min-height: 7.5rem;
  }

  .contact-field {
    padding: 2.5rem 1.25rem 3rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ticket-bar,
  .tile,
  .slot,
  .tile-bar {
    transition-property: opacity, background-color, color;
  }

  /* Hobbies: the game card doesn't lift, and the bar just appears */
  .game:hover {
    transform: none;
  }

  .game:hover .tile-bar {
    opacity: 1;
  }

  /* Hobbies: markers fade in without scaling, and the screenshot link doesn't zoom */
  .feature-row.in .marker {
    animation-name: marker-fade;
  }

  .feature-row:has(.photo-key li:hover) .marker,
  .marker:hover,
  a.feature-media.has-image:hover img,
  a.feature-media.has-image:active {
    transform: none;
  }

  /* Text still fades, but nothing travels or scales */
  .ticket-text.is-out {
    transform: none;
  }

  /* Achievements: rows still fade, but chips show their final state and nothing scales */
  .achievement.rv .chip-done {
    background: var(--btn-bg);
    color: var(--btn-text);
  }

  .achievement.rv .chip-done::before {
    display: none;
  }

  .achievement.rv .achievement-badge,
  .achievement.rv .achievement-mark {
    opacity: 1;
    transform: none;
  }

  .slot-name.is-swapping,
  .slot-use.is-swapping,
  .slot-used.is-swapping,
  .slot-link.is-swapping {
    transform: none;
  }

  .tile-bar {
    transform: none;
    opacity: 0;
  }

  .tile.is-active .tile-bar,
  .tile:focus-visible .tile-bar {
    opacity: 1;
  }

  .contact-field.wipe {
    clip-path: none;
    opacity: 0;
    transition: opacity 240ms ease;
  }

  .contact-field.wipe.wipe-in {
    opacity: 1;
  }

  .tile:hover,
  .tile:hover img {
    transform: none;
  }
}

::view-transition-group(root) {
  animation-duration: 180ms;
}

/* Projects index: open rows, no boxes */
.project-rows {
  margin: 1.5rem 0 0;
  border-bottom: 1px solid var(--border);
}

.project-row {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
  gap: 2.5rem;
  padding: 2.25rem 0;
  border-top: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
}

/* The frame holds the border and clips the picture, so the picture can zoom on hover without
   growing past its edges. */
.project-row .thumb-frame {
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--border);
  background: var(--card);
}

.project-row .thumb {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
  object-position: top;
  transition: transform 250ms var(--ease-out);
}

.project-row .thumb-fit {
  object-fit: contain;
  object-position: center;
  background: var(--tint);
}

.project-row:active .thumb {
  transform: scale(0.98);
}

.project-row h2 {
  margin: 0 0 0.75rem;
  font-size: 1.9rem;
  line-height: 1.15;
  transition: color 160ms ease;
}

.project-row p {
  margin: 0 0 0.75rem;
}

.project-row .kind {
  color: var(--link);
  font-size: 0.8rem;
  font-weight: 600;
}

.project-row .outcome {
  font-size: 1.05rem;
}

.project-row .role {
  color: var(--text-muted);
}

.project-row .role strong {
  color: var(--text);
}

.read-more {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
  color: var(--link);
  font-weight: 600;
}

.read-more span {
  display: inline-block;
  transition: transform 200ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .project-row:hover .thumb {
    transform: scale(1.06);
  }

  .project-row:hover h2 {
    color: var(--link);
  }

  .project-row:hover .read-more span {
    transform: translateX(4px);
  }
}

/* Case study page */
.case {
  max-width: 65rem;
  margin: 0 auto;
  padding-top: 1rem;
}

.back-link {
  display: inline-block;
  margin: 0 0 1.25rem;
  font-weight: 600;
  font-size: 0.95rem;
  text-decoration: none;
}

.case .kind {
  margin: 0 0 0.5rem;
  color: var(--link);
  font-size: 0.9rem;
  font-weight: 600;
}

.case .intro {
  margin: 0.5rem 0 0;
  max-width: 60ch;
  font-size: 1.2rem;
  line-height: 1.55;
  color: var(--text-muted);
}

.strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1.5rem;
  margin: 2rem 0 0;
  padding: 1.5rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.strip h2 {
  margin: 0 0 0.25rem;
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.strip p {
  margin: 0;
}

.case-section {
  margin-top: 2.5rem;
  padding-top: 2.25rem;
  border-top: 1px solid var(--border);
}

.case-section h2 {
  margin: 0 0 1rem;
  font-size: 1.6rem;
}

.case-section ul {
  margin: 0;
  padding-left: 1.25rem;
  line-height: 1.7;
  font-size: 1.05rem;
}

.case-section li + li {
  margin-top: 0.4rem;
}

/* Grid items must line up, so the list spacing above must not push later screenshots down */
.case-section .shots li + li {
  margin-top: 0;
}

.case-section > p {
  font-size: 1.05rem;
  line-height: 1.7;
}

.case-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 3rem;
  padding-top: 2rem;
  border-top: 1px solid var(--border);
}

.case-foot a:not(.btn) {
  font-weight: 600;
}

/* Short personal line under the home hero */
/* Even rhythm: 2.5rem outside each rule, 2rem inside. The hero already supplies 2rem below it,
   and the next section's heading margin is zeroed so the space below the rule matches. */
.why {
  margin: 0.5rem 0 2.5rem;
  padding: 2rem 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.why + section .section-head h2 {
  margin-top: 0;
}

.why p {
  margin: 0;
  max-width: 60ch;
  font-size: 1.35rem;
  line-height: 1.5;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

/* Contact strip at the bottom of Home and Resume */
.contact-strip {
  margin-top: 3rem;
  padding: 1.75rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.contact-strip h2 {
  margin: 0 0 0.5rem;
  font-size: 1.5rem;
}

.contact-strip p {
  margin: 0;
}

.contact-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.25rem !important;
}

/* Resume page: pinned sidebar plus a main column with a timeline */
.resume-layout {
  display: grid;
  grid-template-columns: minmax(0, 21rem) minmax(0, 1fr);
  gap: 4rem;
  align-items: start;
  padding-top: 1.5rem;
}

.side-sticky {
  position: sticky;
  top: 5.5rem;
}

.resume-side h1 {
  margin: 0;
  font-size: 2.1rem;
  line-height: 1.1;
}

.resume-side .roles {
  margin: 0.75rem 0 0;
  color: var(--link);
  font-weight: 600;
  line-height: 1.5;
}

.resume-side .contact-lines {
  margin: 0.9rem 0 1.25rem;
  color: var(--text-muted);
  line-height: 1.6;
}

.btn-block {
  width: 100%;
  justify-content: center;
  gap: 0.6rem;
}

.side-block {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

.side-block h2 {
  margin: 0 0 0.6rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.side-block ul {
  margin: 0;
  padding-left: 1.1rem;
  line-height: 1.6;
}

.side-block p {
  margin: 0;
  line-height: 1.6;
}

.side-block .fineprint {
  margin-top: 0.6rem;
}

.resume-main h2 {
  margin: 0 0 1.25rem;
  font-size: 1.65rem;
}

.resume-main section {
  padding-bottom: 2.5rem;
}

.resume-main .summary {
  max-width: 62ch;
  margin: 0;
  font-size: 1.2rem;
  line-height: 1.6;
}

.skill-list {
  margin: 0;
  padding-left: 1.25rem;
  line-height: 1.65;
}

.skill-list li + li {
  margin-top: 0.4rem;
}

.entry {
  position: relative;
  max-width: none;
  padding: 0 0 1.75rem 1.75rem;
  border-left: 2px solid var(--accent);
}

.entry:last-child {
  padding-bottom: 0;
}

.entry::before {
  content: "";
  position: absolute;
  left: -0.45rem;
  top: 0.4rem;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: var(--link);
}

.entry h3 {
  margin: 0;
  font-size: 1.1rem;
  line-height: 1.3;
}

.entry .meta {
  margin: 0.2rem 0 0.6rem;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.entry ul {
  margin: 0;
  padding-left: 1.25rem;
  line-height: 1.65;
}

.entry li + li {
  margin-top: 0.3rem;
}

/* Resume page section navigation: a sticky strip that shows where you are and collapses to one line.
   The strip is 3rem tall open or collapsed, so toggling it never moves the page below. */
.section-nav {
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  position: sticky;
  top: 4.5rem;
  z-index: 4;
  margin: 0 0 1.75rem;
}

.sn-card {
  position: relative;
  height: 3rem;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  box-shadow: 0 0.4rem 1rem rgb(26 35 50 / 0.08);
}

.sn-tabs,
.sn-summary {
  position: absolute;
  inset: 0 3rem 0 0;
  transition: opacity 150ms ease, filter 150ms ease, visibility 0s linear 0s;
}

.sn-tabs {
  overflow-x: auto;
  scrollbar-width: none;
}

.sn-tabs::-webkit-scrollbar {
  display: none;
}

.sn-list {
  display: flex;
  height: 100%;
  min-width: max-content;
  margin: 0;
  padding: 0;
  list-style: none;
}

.sn-list li {
  margin: 0;
  padding: 0;
  max-width: none;
}

.sn-list a {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 1rem;
  color: var(--text-muted);
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  /* The colour swap waits 80ms so it lands as the pill arrives, not before it */
  transition: color 120ms ease 80ms, transform 120ms var(--ease-out);
}

.sn-list a:active {
  transform: scale(0.97);
}

.sn-list a[aria-current="location"] {
  color: var(--btn-text);
}

/* One marker only: a solid pill behind the current section, sliding between tabs */
.sn-indicator {
  position: absolute;
  top: 0.5rem;
  left: 0;
  width: var(--w, 0);
  height: 2rem;
  border-radius: 0.5rem;
  background: var(--btn-bg);
  transform: translateX(var(--x, 0));
  opacity: 0;
}

.sn-ready .sn-indicator {
  opacity: 1;
  transition: transform 200ms var(--ease-in-out), width 200ms var(--ease-in-out);
}

.sn-summary {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0 1rem;
  opacity: 0;
  filter: blur(2px);
  visibility: hidden;
  pointer-events: none;
  transition: opacity 150ms ease, filter 150ms ease, visibility 0s linear 150ms;
}

.sn-position {
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.sn-label {
  color: var(--link);
  font-weight: 700;
}

.section-nav.is-collapsed .sn-tabs {
  opacity: 0;
  filter: blur(2px);
  visibility: hidden;
  pointer-events: none;
  transition: opacity 150ms ease, filter 150ms ease, visibility 0s linear 150ms;
}

.section-nav.is-collapsed .sn-summary {
  opacity: 1;
  filter: none;
  visibility: visible;
  transition: opacity 150ms ease, filter 150ms ease, visibility 0s linear 0s;
}

.sn-toggle {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  padding: 0;
  background: var(--bg);
  border: 0;
  border-left: 1px solid var(--border);
  color: var(--text);
  cursor: pointer;
}

.sn-toggle[hidden] {
  display: none;
}

.sn-toggle svg {
  transition: transform 200ms var(--ease-out);
}

.is-collapsed .sn-toggle svg {
  transform: rotate(180deg);
}

/* Headings land below the sticky header and strip when a link jumps to them */
.resume-main h2[id] {
  scroll-margin-top: 9rem;
}

@media (hover: hover) and (pointer: fine) {
  .sn-list a:not([aria-current]):hover {
    color: var(--text);
  }

  .sn-toggle:hover {
    background: var(--tint);
  }
}

@media (max-width: 40em) {
  /* The site header isn't pinned here, so the strip sits near the top of the screen */
  .section-nav {
    top: 0.5rem;
  }

  .resume-main h2[id] {
    scroll-margin-top: 5rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sn-ready .sn-indicator,
  .sn-toggle svg {
    transition: none;
  }
}

@media (max-width: 54em) {
  .resume-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 2rem;
  }

  .side-sticky {
    position: static;
  }
}

/* Feature rows: media on one side, text on the other. Used for hobbies; reusable elsewhere. */
.feature-row {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: 3rem;
  align-items: center;
  padding: 2.5rem 0;
  border-top: 1px solid var(--border);
}

.feature-row.flip .feature-media {
  order: 2;
}

.feature-media {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  padding: 1rem;
  border: 2px dashed var(--accent);
  border-radius: 0.75rem;
  background: var(--tint);
  color: var(--text-muted);
  font-size: 0.9rem;
  text-align: center;
}

.feature-row .kicker {
  margin: 0 0 0.5rem;
  color: var(--link);
  font-size: 0.85rem;
  font-weight: 600;
}

.feature-row h2 {
  margin: 0 0 0.75rem;
  font-size: 1.7rem;
}

.feature-row p {
  font-size: 1.05rem;
  line-height: 1.65;
}

.feature-row .tags {
  margin-top: 1rem;
}

/* Shared by case-study pages */
.back-link {
  display: inline-block;
  font-weight: 600;
  font-size: 0.95rem;
  text-decoration: none;
}

.feature-row .org {
  margin: 0;
  color: var(--text-muted);
}

.feature-row ul:not(.tags) {
  margin: 0.9rem 0 0;
  padding-left: 1.25rem;
  line-height: 1.65;
  max-width: 56ch;
}

.feature-row li + li {
  margin-top: 0.3rem;
}

/* The tag pills sit in a wrapping row, so they must not inherit the list item spacing above */
.feature-row .tags li + li {
  margin-top: 0;
}

/* Short looping clip inside a feature row */
/* Tags with a small logo in front of the label. */
.tags.has-icons li {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.tags.has-icons img {
  display: block;
  width: 1rem;
  height: 1rem;
}

/* Gives the image the wider column, about 700px at full width. Below 40em the row stacks as usual. */
@media (min-width: 40.01em) {
  .feature-row.media-lg {
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  }
}

.feature-media.has-image {
  display: block;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--tint);
}

.feature-media.has-image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Photo with numbered markers and a key beside it (Hobbies, security tools). */
.feature-media.annotated {
  position: relative;
  aspect-ratio: 3 / 4;
  max-width: 24rem;
  width: 100%;
  justify-self: center;
}

.marker,
.photo-key .num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #1f6f6f;
  color: #ffffff;
  font-weight: 700;
  line-height: 1;
}

.marker {
  position: absolute;
  width: 2rem;
  height: 2rem;
  margin: -1rem 0 0 -1rem;
  border: 3px solid #ffffff;
  font-size: 0.9rem;
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.35);
}

.photo-key {
  margin: 1.1rem 0 0;
  padding: 0.9rem 1rem;
  list-style: none;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
}

.photo-key li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.photo-key .num {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.8rem;
}

.feature-media.has-video {
  position: relative;
  margin: 0;
  padding: 0;
  overflow: hidden;
  aspect-ratio: 1600 / 880;
  border: 0;
  background: #000;
}

.has-video .clip {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: pointer;
}

.has-video figcaption {
  position: absolute;
  left: 0.75rem;
  bottom: 0.7rem;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  background: rgb(0 0 0 / 0.6);
  color: #ffffff;
  font-size: 0.8rem;
  pointer-events: none;
}

.clip-toggle {
  position: absolute;
  right: 0.75rem;
  bottom: 0.6rem;
  width: 2.75rem;
  height: 2.75rem;
  border: 0;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.6);
  cursor: pointer;
  transition: transform 160ms var(--ease-out), background-color 160ms ease;
}

.clip-toggle::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 0.9rem;
  height: 0.9rem;
  background: #ffffff;
}

.clip-toggle[data-state="playing"]::before {
  clip-path: polygon(0 0, 35% 0, 35% 100%, 0 100%, 0 0, 65% 0, 100% 0, 100% 100%, 65% 100%, 65% 0);
}

.clip-toggle[data-state="paused"]::before {
  clip-path: polygon(15% 0, 100% 50%, 15% 100%);
}

.clip-toggle:active {
  transform: scale(0.97);
}

@media (hover: hover) and (pointer: fine) {
  .clip-toggle:hover {
    background: rgb(0 0 0 / 0.8);
  }
}

/* Darkens the page around a playing clip. clip.js moves this frame over the video. */
.clip-spot {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 40;
  border-radius: 0.75rem;
  box-shadow: 0 0 0 100vmax rgb(8 12 20 / 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms var(--ease-out);
}

.clip-spot.on {
  opacity: 1;
  transition-duration: 250ms;
}

/* Phones and narrow windows */
@media (max-width: 40em) {
  main.container {
    padding: 1.25rem 1rem 0.5rem;
  }

  /* The nav wraps to two rows here, so don't pin it over the content */
  .site-header {
    position: static;
  }

  .header-inner {
    padding: 0.6rem 1rem;
    gap: 0.25rem 1rem;
  }

  /* Nav links get a 44px tap height and wrap onto a second row */
  .nav-list {
    gap: 0 1.1rem;
  }

  .nav-list a {
    padding: 0.7rem 0.1rem;
    font-size: 0.95rem;
  }

  .hero {
    padding: 1.5rem 0 1rem;
    gap: 2rem;
  }

  .hero .lead {
    font-size: 1.1rem;
  }

  .hero-actions .btn {
    flex: 1 1 100%;
    justify-content: center;
  }

  .photo-ph,
  .photo,
  .photo.wide,
  .photo-card,
  .photo-ph.wide {
    max-width: 20rem;
    margin-inline: auto;
    flex-basis: auto;
  }

  h2 {
    margin-top: 2.25rem;
    font-size: 1.5rem;
  }

  .card {
    padding: 1rem;
  }

  .card-link {
    padding: 0.6rem;
  }

  .project-list > .card {
    padding: 1rem;
  }

  .card-grid,
  .cover-row {
    gap: 1rem;
  }

  .cover-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .timeline > li {
    padding-left: 1.25rem;
  }

  .band-inner {
    padding: 1.25rem 1rem;
  }

  .band-footer {
    margin-top: 3rem;
  }

  .lightbox-bar {
    flex-wrap: wrap;
    padding: 0.5rem 0.75rem;
  }

  .project-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.25rem;
    padding: 1.75rem 0;
  }

  .project-row h2 {
    font-size: 1.6rem;
  }

  .strip {
    padding: 1.1rem;
  }

  .case-foot .btn {
    width: 100%;
    justify-content: center;
  }

  .why {
    padding: 1.5rem 0;
    margin-bottom: 2rem;
  }

  .why p {
    font-size: 1.15rem;
  }

  .contact-strip {
    padding: 1.25rem;
  }

  .contact-actions .btn {
    flex: 1 1 100%;
    justify-content: center;
  }

  .feature-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.25rem;
    padding: 1.75rem 0;
  }

  .feature-row.flip .feature-media {
    order: 0;
  }
}

/* Reduced motion, part 2: these override rules defined after the first reduced-motion block,
   so they sit last. Press scales and hover movement go; colour and fade changes stay. */
@media (prefers-reduced-motion: reduce) {
  .btn:active,
  .card-link:active,
  .shots a:active,
  .lightbox button:active,
  .clip-toggle:active,
  .sn-list a:active,
  .project-row:active .thumb,
  .project-row:hover .thumb,
  .project-row:hover .read-more span {
    transform: none;
  }

  .nav-list a::after {
    transition: none;
  }
}
