/* ============================================================================
   Tokens
   ========================================================================== */

:root {
  /* Accent: the site's one colour, used sparingly (marker strokes). */
  --accent: #7092eb;
  --accent: oklch(0.6733 0.1381 266.82);

  /* Type families (see fonts.css). Generic fallback only — no substitute face. */
  --font-display: "Forma Display", sans-serif;
  --font-text: "Forma Text", sans-serif;
  --font-micro: "Forma Micro", "Forma Text", sans-serif;

  /* Type scale — five styles, nothing else. */
  --type-display: clamp(2.45rem, 1.6rem + 2.6vw, 3.7rem);   /* name */
  --type-lead: clamp(1.1875rem, 1rem + 0.6vw, 1.5rem);      /* positioning, about */
  --type-title: clamp(1.1875rem, 1.1rem + 0.35vw, 1.4rem);  /* project titles */
  --type-body: clamp(1rem, 0.96rem + 0.14vw, 1.0625rem);    /* reading text */
  --type-meta: 0.8125rem;                                   /* metadata, nav */

  /* Spacing — one scale, used everywhere. */
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2.5rem;
  --space-5: 4rem;
  --space-6: clamp(5rem, 3rem + 6vw, 9rem);
  --space-7: clamp(7rem, 4rem + 10vw, 14rem);

  /* Grid */
  --columns: 4;
  --gutter: clamp(1rem, 0.6rem + 1.2vw, 1.75rem);
  --margin: clamp(1.25rem, -2rem + 10.5vw, 10rem);
  --max-width: 1600px;
  --measure: 36em;

  --rule-weight: 1px;
  --theme-duration: 280ms;

  /* Motion: one shared feel for every transition (README, "Motion").
     Soft, organic curves — quick to respond, long to settle. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);        /* arrivals, reveals, hovers */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);     /* things that travel */
  --ease-in: cubic-bezier(0.4, 0, 1, 1);             /* quiet exits */
  --dur-quick: 180ms;    /* colour, small hover feedback */
  --dur-base: 320ms;     /* nudges, toggles */
  --dur-slow: 700ms;     /* arrivals, reveals */
}

@media (min-width: 720px)  { :root { --columns: 8; } }
@media (min-width: 1040px) { :root { --columns: 12; } }

/* Light — bright, soft, architectural; slightly warm neutral surface. */
:root,
:root[data-theme="light"] {
  --bg: #f2f1ed;
  --text: #141416;
  --text-2: #5c5d63;          /* secondary: ~6:1 on --bg */
  --text-3: #85868c;          /* placeholders / tertiary */
  --rule: rgb(20 20 22 / 0.14);
  --rule-strong: rgb(20 20 22 / 0.32);
  --link-underline: rgb(20 20 22 / 0.35);
  --focus: #2d54c8;
  --media-surface: rgb(255 255 255 / 0.38);
  --media-filter: none;
  /* Selection inverts the text colour pair: dark grey ground, page-white text. */
  --selection-bg: #2b2b2f;
  --selection-text: #f2f1ed;
}

/* Dark — deep and spatial, not black; luminous elements held back. */
:root[data-theme="dark"] {
  --bg: #121212;            /* plain neutral dark grey — no gradient */
  --text: #ececec;
  --text-2: #a3a3a3;
  --text-3: #707070;
  --rule: rgb(236 236 236 / 0.12);
  --rule-strong: rgb(236 236 236 / 0.3);
  --link-underline: rgb(236 236 236 / 0.35);
  --focus: #d4d4d4;
  --media-surface: rgb(255 255 255 / 0.04);
  --media-filter: brightness(0.92);
  --selection-bg: #dcdcdc;
  --selection-text: #121212;
  color-scheme: dark;
}

/* ============================================================================
   Base
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* In-page links glide via motion.js (eased); native smooth scroll would
     fight it. */
  scroll-behavior: auto;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-text);
  font-size: var(--type-body);
  font-weight: 400;
  line-height: 1.55;
  font-kerning: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, ul, ol, dl, dd, figure { margin: 0; }
ul { list-style: none; padding: 0; }
img, video { display: block; max-width: 100%; }

/* Text selection: an inversion of the theme's text/background pair (the
   look of a difference blend; ::selection itself can't blend). */
::selection { background: var(--selection-bg); color: var(--selection-text); }

/* Theme change: short, understated. Only colour properties transition. */
.theme-transition,
.theme-transition *,
.theme-transition *::before,
.theme-transition *::after {
  transition:
    background-color var(--theme-duration) ease,
    color var(--theme-duration) ease,
    border-color var(--theme-duration) ease,
    text-decoration-color var(--theme-duration) ease !important;
}

/* Links — quiet underline that firms up on hover. */
a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: var(--rule-weight);
  text-underline-offset: 0.22em;
  text-decoration-color: var(--link-underline);
  transition: text-decoration-color 160ms ease, color 160ms ease;
}
a:hover { text-decoration-color: currentColor; }

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

.skip-link {
  position: absolute;
  left: var(--margin);
  top: -100px;
  font-size: var(--type-meta);
  background: var(--bg);
  padding: var(--space-1) 0;
}
.skip-link:focus { top: var(--space-1); }

/* Unwritten placeholders render muted so drafts are easy to scan. */
.is-placeholder { color: var(--text-3); }

/* ============================================================================
   Atmosphere (shader canvas) + static fallback
   ========================================================================== */

.atmosphere {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 600ms ease;
}
.atmosphere.is-ready { opacity: 1; }

/* Fallback when WebGL is unavailable: a still, soft light. */
.no-shader body {
  background:
    radial-gradient(90% 60% at 70% 0%, var(--fallback-light), transparent 70%),
    var(--bg) fixed;
  background-attachment: fixed;
}
:root { --fallback-light: rgb(214 225 240 / 0.7); }
:root[data-theme="dark"] { --fallback-light: transparent; }

/* ============================================================================
   Paper — a subtle texture over the whole site.
   One fixed layer with a small seamless tile (media/texture/paper.webp, 45KB)
   used as a mask over a solid ink colour: normal transparency, no blend mode,
   so it's cheap to composite over the animated layers, recolourable per
   theme, and on its own layer so scrolling never repaints it.
   ========================================================================== */

:root { --paper-ink: #3a3328; --paper: 0.03; }
:root[data-theme="dark"] { --paper-ink: #f2efe8; --paper: 0.02; }

html::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 2147483000;
  pointer-events: none;
  background-color: var(--paper-ink);
  -webkit-mask: url("../../media/texture/paper.webp") 0 0 / 100px 100px repeat;
  mask: url("../../media/texture/paper.webp") 0 0 / 100px 100px repeat;
  opacity: var(--paper);
  transition: opacity var(--theme-duration) ease, background-color var(--theme-duration) ease;
  will-change: transform;
}

/* ============================================================================
   Grid
   ========================================================================== */

.grid {
  display: grid;
  grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
  column-gap: var(--gutter);
  width: 100%;
  max-width: var(--max-width);
  margin-inline: auto;
  padding-inline: var(--margin);
}

/* ============================================================================
   Shared type roles
   ========================================================================== */

.t-meta {
  font-family: var(--font-micro);
  font-size: var(--type-meta);
  line-height: 1.4;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

.t-lead {
  font-family: var(--font-display);
  font-size: var(--type-lead);
  font-weight: 400;
  line-height: 1.3;
  letter-spacing: -0.005em;
}

.links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
}

/* Small uppercase label — borrowed from the previous site's technical voice. */
.ui-label {
  font-family: var(--font-micro);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* Quiet link: secondary tone that brightens on hover, no underline. */
.ui-link {
  color: var(--text-2);
  text-decoration: none;
  transition: color 200ms ease-out;
}
.ui-link:hover { color: var(--text); }

/* Link with a trailing arrow that nudges toward its destination on hover. */
.arrow-link {
  color: var(--text-2);
  text-decoration: none;
  transition: color var(--dur-quick) var(--ease-out);
}
.arrow-link:hover { color: var(--text); }
/* Arrows: a heavier glyph at 36% of the link's colour, so they read as a
   quiet direction marker rather than more text. */
.arrow {
  display: inline-block;
  margin-left: 0.35em;
  font-weight: 700;
  color: color-mix(in srgb, currentColor 36%, transparent);
  transition: transform var(--dur-base) var(--ease-out);
}
.arrow-link:hover .arrow { transform: translateX(2px); }
.arrow-link[target="_blank"]:hover .arrow { transform: translate(2px, -2px); }

/* Section heading: a hairline across the grid, then a small label. */
.section-title {
  /* Small, bold, sentence case, quiet colour (overrides .ui-label). */
  font-family: var(--font-text);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  text-transform: none;
  color: var(--text-3);
  grid-column: 1 / -1;
  border-top: var(--rule-weight) solid var(--rule);
  padding-top: var(--space-2);
  margin-bottom: var(--space-4);
}

/* ============================================================================
   01 Header — tagline left; sections, social links and theme right.
   ========================================================================== */

.site-header {
  align-items: baseline;
  padding-top: var(--space-3);
  font-size: var(--type-meta);
  line-height: 1.4;
}
/* Hairline under the header, inset to the page margins. */
.site-header::after {
  content: "";
  grid-column: 1 / -1;
  height: var(--rule-weight);
  margin-top: var(--space-3);
  background: var(--rule);
}

/* Phones: tagline on its own row, navigation below it. */
.site-header__tagline {
  grid-column: 1 / -1;
  letter-spacing: 0.02em;
}
.site-header__nav {
  grid-column: 1 / -1;
  margin-top: var(--space-1);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
}
.site-header__nav ul {
  display: flex;
  gap: var(--space-3);
}
.site-header__sections a { font-weight: 500; }
.site-header__social { display: none !important; }
.site-header__social a { color: var(--text-3); }
.site-header__social a:hover { color: var(--text); }

.site-header__social,
.theme-toggle {
  padding-left: var(--space-3);
  border-left: var(--rule-weight) solid var(--rule);
}

.theme-toggle {
  display: flex;
  gap: var(--space-1);
}
.theme-toggle button {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--text-3);
  cursor: pointer;
  transition: color 160ms ease;
}
.theme-toggle button:hover { color: var(--text-2); }
.theme-toggle button[aria-pressed="true"] { color: var(--text); cursor: default; }

@media (min-width: 720px) {
  .site-header__tagline { grid-column: 1 / span 3; }
  .site-header__nav { grid-column: 4 / -1; margin-top: 0; justify-content: flex-end; }
  .site-header__social { display: flex !important; }
}
@media (min-width: 1040px) {
  .site-header__tagline { grid-column: 1 / span 4; }
  .site-header__nav { grid-column: 5 / -1; }
}

/* ============================================================================
   02 Hero — layout and interaction from the previous site:
   a centred column; name, a rule beside stacked roles, then statement and
   "About me" on one line. No background imagery; the atmosphere shows through.
   ========================================================================== */

.hero {
  position: relative;
  display: grid;
  place-items: center;
  padding: var(--space-6) var(--margin);
}
@media (min-width: 720px) {
  .hero { min-height: clamp(480px, 74svh, 900px); padding-block: var(--space-5); }
}

/* Water drop (drop.js): sits to the right of the hero column, overlapping its
   edge slightly. Never takes pointer events — drop.js hit-tests its shape. */
.hero__drop {
  --drop: clamp(300px, 47.5vw, 720px);   /* ≈684px at 1440 wide */
  position: absolute;
  z-index: 0;
  width: var(--drop);
  height: var(--drop);
  /* Fallback; drop.js places it from the layout (top of name, column edge). */
  left: calc(50% + 360px - var(--drop) * 0.74);
  top: calc(50% - var(--drop) * 0.62);
  pointer-events: none;
  opacity: 0;
  transition: opacity 600ms ease;
}
.hero__drop.is-ready { opacity: 1; }
.hero__inner { position: relative; z-index: 1; }
@media (max-width: 759px) { .hero__drop { display: none; } }

.hero__inner {
  width: 100%;
  max-width: 720px;
  display: grid;
  row-gap: clamp(2rem, 1.5rem + 1.5vw, 2.75rem);
}

.hero__surname { opacity: 0.6; }
.hero__name {
  font-family: var(--font-display);
  font-size: var(--type-display);
  font-weight: 700;
  line-height: 0.98;
  letter-spacing: -0.02em;
}

.hero__roles {
  display: grid;
  row-gap: 0.4rem;
  justify-self: start;
  padding-left: 1.25rem;
  border-left: var(--rule-weight) solid color-mix(in srgb, var(--text) 80%, transparent);
  font-family: var(--font-text);
  font-size: clamp(1.25rem, 1.15rem + 0.35vw, 1.375rem);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.015em;
}
/* Some roles stay out of focus until they are hovered. */
/* Role definition (e.g. under Synesthete): sits just below the roles block,
   aligned with the role words, outside the rule; shown while its role is
   hovered or focused. Absolutely placed so nothing shifts. */
.hero__roles { position: relative; }
.hero__note {
  position: absolute;
  top: calc(100% + 0.4rem);
  left: 1.25rem;
  white-space: nowrap;
  font-family: var(--font-text);
  font-size: clamp(0.95rem, 0.9rem + 0.2vw, 1.05rem);
  font-weight: 300;
  line-height: 1.3;
  letter-spacing: 0;
  color: var(--text);
  opacity: 0;
  transform: translateY(-2px);
  transition: opacity 300ms ease, transform 300ms ease;
  pointer-events: none;
}
.hero__note-term { font-weight: 400; }
.hero__roles p:is(:hover, :focus) + .hero__note { opacity: 1; transform: none; }
.is-editing .hero__note { opacity: 1; transform: none; pointer-events: auto; }

/* Roles are hover targets (they reshape the drop), not text to edit. */
.hero__roles p:not([contenteditable]) { cursor: default; }
.hero__roles p { --role-blur: 1.1px; transition: filter 500ms ease-out; }
/* While the drop's slideshow runs (drop.js adds .has-slideshow), all five
   roles share one soft blur and the one being shown comes into focus.
   Without the slideshow (phones, reduced motion) only the extras
   (.is-blurred) keep that blur, revealed on hover. */
.hero.has-slideshow .hero__roles p,
.hero__roles .is-blurred { filter: blur(var(--role-blur)); }
.hero__roles p:is(:hover, :focus-within, .is-active),
.hero.has-slideshow .hero__roles p:is(:hover, :focus-within, .is-active) { filter: none; }

.hero__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  font-size: var(--type-body);
  line-height: 1.4;
}
.hero__statement { letter-spacing: 0.01em; }
.hero__about { margin-left: auto; white-space: nowrap; }

/* ============================================================================
   03 Selected Work — reusable project module
   ========================================================================== */

.work { padding-bottom: var(--space-6); }

.project {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--gutter);
  row-gap: var(--space-3);
  padding-top: var(--space-2);
  border-top: var(--rule-weight) solid var(--rule);
  grid-template-areas: "head" "media" "body";
}
.project + .project { margin-top: var(--space-6); }

/* The first project sits directly under the section rule. */
.section-title + .project { border-top: 0; padding-top: 0; }

.project__head {
  grid-area: head;
  display: grid;
  row-gap: 0.25rem;
}
.project__title {
  font-family: var(--font-text);
  font-size: var(--type-title);
  font-weight: 500;
  line-height: 1.3;
}
.project__description {
  color: var(--text-2);
  font-size: var(--type-title);
  font-weight: 400;
  line-height: 1.3;
}

/* A cover's frame may be rounded (media.radius); the media follows it. */
.project__media > .media,
.project__media > .project__cover,
.project__media > .project__cover > .media { border-radius: inherit; overflow: hidden; }
.project__media {
  grid-area: media;
  align-self: start;   /* the frame hugs the media, never the text's height */
}

.project__body {
  grid-area: body;
  display: grid;
  row-gap: var(--space-2);
  max-width: var(--measure);
  align-content: start;
}
.project__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-3);
  color: var(--text-2);
}
.project__text { display: grid; row-gap: 0.8em; }

@media (min-width: 720px) {
  .project {
    grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
    grid-template-areas:
      "head head head head head head head head"
      "media media media media media media media media"
      "body body body body body body body body";
  }
}


/* Narrower layouts: under the thumbnail, role, year and award on the left,
   team on the right. */
@media (max-width: 1039px) {
  .project__body {
    max-width: none;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--gutter);
  }
  .project__body > * { grid-column: 1; }
  .project__body > .project__team { grid-column: 2; grid-row: 1; text-align: right; }
}

@media (min-width: 1040px) {
  /* Text column (1–4) beside media (5–12). Title anchors top, body anchors bottom. */
  .project {
    grid-template-areas:
      "head head head head media media media media media media media media"
      "body body body body media media media media media media media media";
    grid-template-rows: auto 1fr;
    row-gap: var(--space-4);
  }
  .project__head { padding-right: var(--space-2); }
  .project__body { align-self: end; padding-right: var(--space-2); }
  /* Beside the thumbnail, award and team sit together as one credit. */
}

/* Media frame. Proportion comes from content.js (aspect). */
.media {
  position: relative;
  aspect-ratio: var(--aspect, 16 / 10);
  background: var(--media-surface);
  overflow: hidden;
}
.media img,
.media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--media-filter);
  transition: filter var(--theme-duration) ease;
}
.media--placeholder {
  display: grid;
  place-items: center;
  outline: var(--rule-weight) solid var(--rule);
  outline-offset: calc(var(--rule-weight) * -1);
  color: var(--text-3);
}

/* ============================================================================
   04 About — headline across the grid; portrait (1–3) beside identity,
   bio and links, which start on column 5 like the project text/media split.
   ========================================================================== */

.about {
  row-gap: var(--space-4);
  padding-bottom: var(--space-6);
}
.about .section-title { margin-bottom: 0; }

/* Gallery: the main photo, a smaller profile photo top-aligned beside it,
   and a second water drop (drop.js) over the main photo's bottom-right
   corner, overlapping both frames a little. Its own 12-column grid so the
   photos line up with the page columns. */
.about__gallery {
  --drop: clamp(230px, 36vw, 560px);
  grid-column: 1 / -1;
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter);
  align-items: start;
}
.about__photo { grid-column: 1 / span 7; }
.about__portrait { grid-column: 8 / span 5; align-self: start; }

.about__drop {
  position: absolute;
  z-index: 3;   /* above the photo frames and their edge light */
  width: var(--drop);
  height: var(--drop);
  /* Centred just past the main photo's right edge, low on its side. */
  left: calc(58.3% - var(--drop) / 2 + var(--gutter) * 0.3);
  top: calc(80% - var(--drop) / 2);
  pointer-events: none;
  opacity: 0;
  transition: opacity 600ms ease;
}
.about__drop.is-ready { opacity: 1; }
@media (max-width: 759px) { .about__drop { display: none; } }

/* "Rendering the World with Mavision" under the main photo, "Mavis" bold. */
.about__headline {
  grid-column: 1 / -1;
  max-width: 13em;
  font-family: var(--font-text);
  font-size: clamp(1.25rem, 1rem + 0.6vw, 1.625rem);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--text-3);   /* same as the section titles */
}
.about__headline b { font-weight: 700; }
.about__headline { white-space: pre-line; }   /* "\n" in content.js = line break */
/* Narrower layouts: one line (the content's line break becomes a space). */
@media (max-width: 1039px) {
  .about__headline { white-space: normal; max-width: none; }
}

.about__body {
  grid-column: 1 / -1;
  display: grid;
  row-gap: var(--space-4);
  align-content: start;
}

.about__identity { display: grid; row-gap: 0.35rem; }
.about__name {
  font-size: var(--type-title);
  font-weight: 500;
  line-height: 1.3;
}
.about__role { color: var(--text-2); }
.about__details { color: var(--text-3); margin-top: 0.35rem; }
.about__sep { margin-inline: 0.6em; opacity: 0.6; }

.about__text {
  display: grid;
  row-gap: 1em;
  max-width: var(--measure);
  color: var(--text-2);
  line-height: 1.7;
}

@media (min-width: 1040px) {
  .about { row-gap: var(--space-4); }
  /* Main photo cols 1–4, profile photo cols 5–7; headline under the main
     photo; bio on the right, below the drop. */
  .about__photo { grid-column: 1 / span 4; }
  .about__portrait { grid-column: 5 / span 3; }
  /* Centred just past the main photo's right edge (4 of 12 columns). */
  .about__drop { left: calc(33.33% - var(--drop) / 2 + var(--gutter) * 2); }
  .about__headline { grid-column: 1 / span 6; }
  .about__body { grid-column: 7 / span 6; margin-top: calc(var(--drop) * 0.15); }
}

/* ============================================================================
   04b Experiments — small works in a gallery, each group titled on the left
   (cols 1–4) with its pieces on the right (cols 5–12), like projects.
   ========================================================================== */

.experiments { padding-bottom: var(--space-6); }
.experiments .section-title { margin-bottom: var(--space-4); }

.exp-group {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--gutter);
  row-gap: var(--space-3);
}
.exp-group + .exp-group { margin-top: var(--space-5); }

.exp-group__head { display: grid; row-gap: 0.25rem; align-content: start; }
.exp-group__title {
  font-size: var(--type-title);
  font-weight: 500;
  line-height: 1.3;
}
.exp-group__caption { color: var(--text-2); }

.exp-grid {
  display: grid;
  grid-template-columns: repeat(min(var(--cols), 2), minmax(0, 1fr));
  gap: var(--space-1);
}
/* A wide piece (e.g. a banner) spans the whole row, at its own shape. */
.exp-item--wide { grid-column: 1 / -1; }
.exp-item {
  aspect-ratio: var(--tile, 1);
  background: var(--media-surface);
  overflow: hidden;
}
.exp-item canvas,
.exp-item img,
.exp-item video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (min-width: 720px) {
  .exp-grid { grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); }
}
@media (min-width: 1040px) {
  .exp-group { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .exp-group__head { grid-column: 1 / span 4; padding-right: var(--space-2); }
  .exp-grid { grid-column: 5 / -1; }
}

/* ============================================================================
   05 Footer — from the previous site: an image card with light text.
   Uses fixed light-on-image tones in both themes.
   ========================================================================== */

.site-footer {
  width: 100%;
  max-width: calc(var(--max-width) + 2 * clamp(1rem, 2.5vw, 2.5rem));
  margin-inline: auto;
  padding: clamp(1rem, 2.5vw, 2.5rem);
}

.footer-card {
  --text: #ffffff;
  --text-2: rgb(255 255 255 / 0.72);
  --text-3: rgb(255 255 255 / 0.55);
  --rule: rgb(255 255 255 / 0.2);
  --focus: #ffffff;
  position: relative;
  overflow: hidden;
  border-radius: 6px;
  background: #151619;
  color: var(--text);
  isolation: isolate;
}
.footer-card__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}
.footer-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgb(0 0 0 / 0.4);
}

.footer-card__content {
  display: grid;
  row-gap: clamp(3rem, 2.5rem + 1.5vw, 3.5rem);
  padding: clamp(1.75rem, 1rem + 3vw, 4rem) clamp(1.75rem, 1rem + 3vw, 4rem) clamp(1.5rem, 1rem + 2vw, 3rem);
}

.footer-card__brand {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
}
.footer-card__name {
  font-size: 1.625rem;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.01em;
}
.footer-card__tagline {
  margin-top: 0.5rem;
  color: rgb(255 255 255 / 0.8);
  font-size: clamp(1rem, 0.95rem + 0.2vw, 1.125rem);
}
.footer-card__resume { font-size: clamp(1rem, 0.95rem + 0.2vw, 1.125rem); }

.footer-card__sections {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-3) clamp(1.5rem, 1rem + 1vw, 2rem);
  font-size: clamp(1rem, 0.95rem + 0.2vw, 1.125rem);
}

.footer-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding-top: var(--space-2);
  border-top: var(--rule-weight) solid var(--rule);
  color: var(--text-3);
  font-size: clamp(0.875rem, 0.85rem + 0.1vw, 1rem);
}
.footer-card__social {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) clamp(1.5rem, 1rem + 1vw, 2rem);
}

/* ============================================================================
   Marker — a semi-dry stroke drawn under a few key words.
   The stroke is a pre-rendered texture (media/texture/marker-*.webp) used as
   a mask over a solid colour, so one texture takes any colour: each stroke's
   --marker-color comes from what surrounds it (marker.js — the cover's
   dominant colour), falling back to the site accent. It sits behind the text
   on its own pseudo-element and draws in once from the left.
   ========================================================================== */

.marker {
  --marker-mask: url("../../media/texture/marker-1.webp");
  position: relative;
  isolation: isolate;
  white-space: nowrap;
}
.marker--2 { --marker-mask: url("../../media/texture/marker-2.webp"); }
.marker::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: -0.12em;
  right: -0.14em;
  bottom: -0.04em;
  height: 0.58em;
  background: var(--marker-color, var(--accent));
  -webkit-mask: var(--marker-mask) no-repeat 0 0 / 100% 100%;
  mask: var(--marker-mask) no-repeat 0 0 / 100% 100%;
  opacity: 0.9;
  clip-path: inset(0 0 0 0);
  transition: clip-path 750ms var(--ease-out), background-color var(--theme-duration) ease;
  pointer-events: none;
}
/* Lighter ink in dark mode so the light text on top stays readable. */
:root[data-theme="dark"] .marker::before { opacity: 0.55; }

/* Page strokes draw in when first seen (marker.js adds these classes). */
.marker-pending main .marker:not(.is-drawn, .marker--role)::before { clip-path: inset(0 100% 0 0); }

/* Home-page titles are small, so their strokes are a little taller. */
.project__title .marker::before,
.exp-group__title .marker::before { height: 0.8em; bottom: -0.1em; }

/* Hero roles: the stroke shows only while the role is the one being shown
   (slideshow) or hovered/focused, drawing in and sweeping out. */
.marker--role::before { clip-path: inset(0 100% 0 0); }
.hero__roles p:is(.is-active, :hover, :focus-within) .marker--role::before {
  clip-path: inset(0 0 0 0);
  transition-delay: 400ms;   /* draw in a beat after the role sharpens; sweep out at once */
}
/* Case-study titles draw in as the layer opens. */
.marker.is-drawing::before { animation: marker-draw 750ms var(--ease-out) 180ms both; }
@keyframes marker-draw { from { clip-path: inset(0 100% 0 0); } }
@media (prefers-reduced-motion: reduce) {
  .marker::before { transition: none; }
  .marker.is-drawing::before { animation: none; }
}

/* ============================================================================
   Cursor label (cursor-label.js) — a status like "coming soon" travelling
   beside the arrow over a cover. Words unfurl outward from the cursor.
   ========================================================================== */

.cursor-label {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 2147483100;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  pointer-events: none;
  font-family: var(--font-text);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: -0.005em;
  color: #fff;
  text-align: right;
  text-shadow: 0 1px 8px rgb(0 0 0 / 0.28);
  will-change: transform;
}
.cursor-label__line {
  display: block;
  opacity: 0;
  clip-path: inset(0 0 0 100%);
  transform: translateX(5px);
  transition: opacity 200ms ease, clip-path 220ms ease-in, transform 220ms ease-in;
}
.cursor-label.is-on .cursor-label__line {
  opacity: 1;
  clip-path: inset(-0.2em -0.2em -0.2em -0.2em);
  transform: none;
  transition: opacity 360ms ease, clip-path 520ms var(--ease-out), transform 520ms var(--ease-out);
  transition-delay: calc(var(--i) * 70ms);
}
.cursor-label.is-dark { color: #141416; text-shadow: 0 1px 8px rgb(255 255 255 / 0.5); }
.cursor-label.is-status { opacity: 0.7; }
@media (prefers-reduced-motion: reduce) {
  .cursor-label__line, .cursor-label.is-on .cursor-label__line { transition: opacity 150ms ease; clip-path: none; transform: none; }
}
/* Covers with a status aren't links yet; a linked cover keeps the arrow
   too, since its label says where it goes. */
[data-status], [data-cursor] a { cursor: default; }

/* ============================================================================
   Case study layer — a page of its own over the page: 90% of the screen on
   desktop, full screen on phones, with the page dimmed behind it.
   ========================================================================== */

/* Project cards: title, cover and "View case study" open the layer. */
.project__open { color: inherit; text-decoration: none; }
.project__open:hover { text-decoration: underline; text-decoration-color: var(--link-underline); }
.project__cover { display: block; color: inherit; cursor: pointer; }
.project__award { margin: 0.1rem 0 0.15rem; }
.project__award a { color: var(--text); }
.project__team { color: var(--text-2); }
.case__links { display: grid; row-gap: 0.35rem; justify-items: start; }
.project__links { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
.project__case { color: var(--text); }

html.case-open { overflow: hidden; }

.case {
  --case-radius: 6px;
  width: min(90vw, 1600px);
  height: 90vh;
  height: 90dvh;
  max-width: none;
  max-height: none;
  margin: auto;
  padding: 0;
  border: 0;
  border-radius: var(--case-radius);
  background: var(--bg);
  color: var(--text);
  overflow: hidden;
}
.case::backdrop { background: rgb(8 8 10 / 0.5); }
:root[data-theme="dark"] .case { background: #161616; }
:root[data-theme="dark"] .case::backdrop { background: rgb(0 0 0 / 0.6); }

.case[open] { animation: case-in 260ms var(--ease-out); }
.case[open]::backdrop { animation: case-fade 260ms ease; }
.case.is-closing { animation: case-out 180ms ease forwards; }
.case.is-closing::backdrop { animation: case-fade 180ms ease reverse forwards; }

@keyframes case-in { from { opacity: 0; transform: translateY(14px); } }
@keyframes case-out { to { opacity: 0; transform: translateY(8px); } }
@keyframes case-fade { from { opacity: 0; } }

.case__scroll {
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.case__close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  z-index: 2;
  appearance: none;
  border: 0;
  padding: 0.35rem 0.6rem;
  background: var(--bg);
  font: inherit;
  font-size: var(--type-meta);
  cursor: pointer;
}
:root[data-theme="dark"] .case__close { background: #161616; }

.case__page {
  max-width: none;
  row-gap: var(--space-4);
  padding-top: var(--space-5);
  padding-bottom: var(--space-6);
}

.case__head { grid-column: 1 / -1; display: grid; row-gap: var(--space-2); }
.case__meta { display: flex; gap: var(--space-3); color: var(--text-2); }
.case__title {
  font-family: var(--font-display);
  font-size: var(--type-display);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.02em;
}
.case__lede { max-width: 28em; color: var(--text-2); }

.case__facts {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-3) var(--gutter);
  padding-top: var(--space-2);
  border-top: var(--rule-weight) solid var(--rule);
}
.case__fact { display: grid; row-gap: 0.35rem; align-content: start; }
.case__fact dd { margin: 0; font-family: "GenRyuMin Nijimu", var(--font-text); }

.case__cover { grid-column: 1 / -1; }

.case__section {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--gutter);
  row-gap: var(--space-2);
  padding-top: var(--space-2);
  border-top: var(--rule-weight) solid var(--rule);
}
.case__body { display: grid; row-gap: var(--space-3); align-content: start; }
.case__text { display: grid; row-gap: 0.9em; max-width: var(--measure); line-height: 1.65; }
.case__list {
  display: grid;
  row-gap: 0.5em;
  max-width: var(--measure);
  padding-left: 1.1em;
  list-style: disc;
  line-height: 1.55;
}
.case__list li::marker { color: var(--text-3); }

.case__media { display: grid; gap: var(--space-3); }
.case__figure { display: grid; row-gap: var(--space-1); }
.case__figure--phone { max-width: 300px; }
.case__caption { color: var(--text-2); }

.case__next {
  grid-column: 1 / -1;
  display: grid;
  row-gap: var(--space-1);
  padding-top: var(--space-2);
  border-top: var(--rule-weight) solid var(--rule);
}
.case__next-link {
  justify-self: start;
  color: var(--text);
  font-family: var(--font-display);
  font-size: var(--type-lead);
  font-weight: 500;
}

@media (min-width: 1040px) {
  .case__head { grid-column: 1 / span 10; }
  .case__section { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .case__heading { grid-column: 1 / span 4; }
  .case__body { grid-column: 5 / -1; }
  .case__next { grid-column: 5 / -1; }
}

/* Phones: the layer becomes a full-screen sheet. */
@media (max-width: 719px) {
  .case {
    --case-radius: 0;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
  }
  .case__close { top: var(--space-1); right: var(--space-1); }
}

@media (prefers-reduced-motion: reduce) {
  .case[open], .case[open]::backdrop,
  .case.is-closing, .case.is-closing::backdrop { animation: none; }
}

/* ============================================================================
   Material — designated surfaces catch light as the pointer approaches.
   Driven by material.js: --mp (proximity 0–1), --mx/--my (lit point on the
   edge, px), --dx/--dy (spectral offset along the edge, px).
   ========================================================================== */

[data-material] {
  --mp: 0;
  --ax: 1;
  --ay: 0;
  --mx: -999px;
  --my: -999px;
  --dx: 0px;
  --dy: 0px;
  --mr: calc(70px + 70px * var(--mp));   /* lit length grows as you approach */
  position: relative;
}

/* Eased falloff (after Andreas Larsen's "easing gradients"): a linear fade
   leaves a visible end line; these stops approximate an ease-out curve so
   the light dissolves with no perceptible boundary. */
[data-material] {
  --ease-core: var(--mat-core) 0%,
    color-mix(in srgb, var(--mat-core) 74%, transparent) 10%,
    color-mix(in srgb, var(--mat-core) 52%, transparent) 18%,
    color-mix(in srgb, var(--mat-core) 35%, transparent) 25%,
    color-mix(in srgb, var(--mat-core) 22%, transparent) 31%,
    color-mix(in srgb, var(--mat-core) 13%, transparent) 37%,
    color-mix(in srgb, var(--mat-core) 7%, transparent) 42%,
    color-mix(in srgb, var(--mat-core) 3%, transparent) 47%,
    transparent 54%;
}

/* Light caught by the edge: a 1px line, lit only near the nearest point,
   with cool and warm light separated slightly along the edge. */
[data-material]::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 1;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  background:
    radial-gradient(var(--mr) circle at calc(var(--mx) + 1px) calc(var(--my) + 1px), var(--ease-core)),
    radial-gradient(var(--mr) circle at calc(var(--mx) + 1px + var(--dx)) calc(var(--my) + 1px + var(--dy)),
      var(--mat-cool), color-mix(in srgb, var(--mat-cool) 45%, transparent) 20%,
      color-mix(in srgb, var(--mat-cool) 15%, transparent) 36%, transparent 54%),
    radial-gradient(var(--mr) circle at calc(var(--mx) + 1px - var(--dx)) calc(var(--my) + 1px - var(--dy)),
      var(--mat-warm), color-mix(in srgb, var(--mat-warm) 45%, transparent) 20%,
      color-mix(in srgb, var(--mat-warm) 15%, transparent) 36%, transparent 54%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  /* Eased (smoothstep) so the light gathers and dissolves rather than ramps. */
  opacity: calc(var(--mp) * var(--mp) * (3 - 2 * var(--mp)));
}

/* The haze beyond the edge is drawn by the atmosphere shader (organic,
   moving), not here — see atmosphere.js, "Material glow". */

/* Edge light in the same blue family as the shader glow (#3097FF → #5264EC). */
:root {
  --mat-core: rgb(82 100 236 / 0.9);
  --mat-cool: rgb(48 151 255 / 0.8);
  --mat-warm: rgb(122 108 240 / 0.6);
}
:root[data-theme="dark"] {
  --mat-core: rgb(150 186 255 / 0.85);
  --mat-cool: rgb(48 151 255 / 0.7);
  --mat-warm: rgb(118 124 245 / 0.6);
}
/* On the footer image the light is always white. */
.footer-material {
  --mat-core: rgb(255 255 255 / 0.9);
  --mat-cool: rgb(170 190 255 / 0.55);
  --mat-warm: rgb(255 200 170 / 0.5);
}

.footer-material { --material-radius: 6px; border-radius: 6px; }

/* ============================================================================
   Edit mode (?edit) — drafting aid, never shown to visitors
   ========================================================================== */

[contenteditable] {
  outline: 1px dashed transparent;
  outline-offset: 4px;
  cursor: text;
  transition: outline-color 120ms ease;
}
[contenteditable]:hover { outline-color: var(--rule-strong); }
[contenteditable]:focus { outline: 1px solid var(--focus); }

/* Empty optional fields become faint slots with a hint. */
.is-editing .is-empty { display: inline-block; min-width: 6em; }
.is-editing .is-empty::before {
  content: attr(data-hint);
  color: var(--text-3);
  opacity: 0.6;
  pointer-events: none;
}
.is-editing .is-empty:focus::before { content: ""; }

.edit-bar {
  position: fixed;
  left: var(--margin);
  bottom: var(--space-2);
  z-index: 10;
  display: flex;
  gap: var(--space-2);
  padding: 0.4rem 0.7rem;
  background: var(--bg);
  border: var(--rule-weight) solid var(--rule-strong);
  font-family: var(--font-micro);
  font-size: var(--type-meta);
  color: var(--text-2);
}

/* ============================================================================
   Reduced motion
   ========================================================================== */

/* ============================================================================
   Arrival (motion.js) — major blocks fade and rise slightly the first time
   they enter the view. Only applied once JS is running (.motion-ready), so
   without it everything simply shows.
   ========================================================================== */

.motion-ready .arrives {
  opacity: 0;
  transform: translateY(14px);
}
.motion-ready .arrives.is-in {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-slow) var(--ease-out), transform calc(var(--dur-slow) + 200ms) var(--ease-out);
}
/* Sections land with a little breathing room above them. */
main > section { scroll-margin-top: var(--space-4); }

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