/* ===========================================================================
   site.css — Joe Henderson, Video Editor & Social Media Manager
   ---------------------------------------------------------------------------
   Structure of this file:
     01. Tokens (colours, type, spacing)     06. Media frames & placeholders
     02. Reset & base                        07. Project cards & grids
     03. Type scale & shared text            08. Sections (incl. brand wall)
     04. Layout & the "timeline rail" motif  09. (free)
     05. Header & navigation                 10. Footer
                                             11. Motion & responsive

   To change the accent colour, edit --accent in section 01. That is the only
   place it is defined.
   =========================================================================== */


/* ===========================================================================
   01. TOKENS
   =========================================================================== */

:root {
  /* --- Colour ------------------------------------------------------------
     A near-black base rather than pure #000 so that video, which is rarely
     truly black, does not float on the page. Text is warm off-white, not
     white, which is easier to read at length on a dark ground. */
  --ink:            #08090a;
  --ink-2:          #0e1012;
  --surface-2:      #1a1d20;

  /* Four text tones, brightest to dimmest. Every one of them clears WCAG AA
     (4.5:1) against --ink, measured, not assumed:
       paper 16.6:1   paper-dim 9.5:1   muted 5.8:1   faint 4.6:1
     --faint is the floor. It is used for timestamps, field labels, captions
     and placeholder hints, so it has to stay legible however small it gets.
     Do not darken it without re-checking the ratio. */
  --paper:          #edeae4;
  --paper-dim:      #b6b3ae;
  --muted:          #8a8a90;
  --faint:          #7a7a80;

  --line:           rgba(255, 255, 255, 0.09);
  --line-mid:       rgba(255, 255, 255, 0.16);

  /* One accent, used sparingly: hairlines, play controls, hover states and
     small labels. Chosen to read as a timeline / render marker rather than
     as decoration. 5.9:1 on --ink. */
  --accent:         #ff4a1c;

  /* --- Type --------------------------------------------------------------- */
  --font-display: 'Archivo', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-body:    'Archivo', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-serif:   'Instrument Serif', 'Times New Roman', Times, serif;
  --font-mono:    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* --- Spacing ------------------------------------------------------------ */
  --gutter:      clamp(1.25rem, 4vw, 4.5rem);
  --section-y:   clamp(4.5rem, 11vw, 10rem);
  --maxw:        1560px;
  --maxw-text:   62ch;

  --radius:      3px;
  --header-h:    68px;

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


/* ===========================================================================
   02. RESET & BASE
   =========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchored sections must clear the fixed header. */
  scroll-padding-top: calc(var(--header-h) + 16px);
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* A very fine film grain over the whole page. It is what stops a flat dark
   background looking like a default dark theme, and it costs one element. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

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

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* Visible, high-contrast focus for keyboard users on every interactive thing.
   Deliberately not removed anywhere in this file. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}

::selection { background: var(--accent); color: #fff; }

/* Fixed, not absolute. Absolute positions it against the top of the DOCUMENT,
   so tabbing to it part-way down the page reveals it somewhere above the
   viewport — which is the same as it not working. */
.skip-link {
  position: fixed;
  left: 50%;
  top: 0;
  transform: translate(-50%, -120%);
  z-index: 10000;
  padding: 0.75rem 1.25rem;
  background: var(--accent);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: transform 0.2s var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }

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


/* ===========================================================================
   03. TYPE
   =========================================================================== */

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

/* Editorial serif italic, used only for single accent words inside a heading.
   Sparingly — it is a seasoning, not a typeface swap. */
.serif {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.01em;
}

.lede {
  max-width: var(--maxw-text);
  font-size: clamp(1rem, 1.5vw, 1.1875rem);
  line-height: 1.62;
  color: var(--paper-dim);
}

.muted { color: var(--muted); }
.mono  { font-family: var(--font-mono); }


/* ===========================================================================
   04. LAYOUT & THE TIMELINE RAIL
   =========================================================================== */

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--section-y); position: relative; }

/* The site's signature motif: a hairline with regular tick marks, borrowed
   from an NLE timeline ruler. Used as the divider between every section. */
.rail {
  position: relative;
  height: 1px;
  background: var(--line);
}
.rail::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 7px;
  background-image: repeating-linear-gradient(
    to right,
    var(--line-mid) 0 1px,
    transparent 1px 48px
  );
}
.rail::after {
  content: '';
  position: absolute;
  left: 0; top: 0;
  width: 88px; height: 1px;
  background: var(--accent);
}

/* Every section opens with a rail, then its heading block. The gap between
   the two is set here rather than inline on eleven separate sections. */
.rail + .section-head { padding-top: clamp(2rem, 4vw, 3rem); }

.section-head { margin-bottom: clamp(2rem, 4vw, 3.5rem); }

/* A step down from the main section headings, for the two supporting
   sections (brands, additional work) that should not shout. */
.section-title-sm { font-size: clamp(1.5rem, 3.4vw, 2.75rem); }

/* Small upper-case label used above a sub-list — the experience and
   experience column. Plain words, no numbering. */
.side-label {
  margin: 0 0 1.25rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The brands sit between the introduction and the work. They are supporting
   evidence, so they get less vertical room than a work section. */
.section-brands { padding-block: clamp(3.5rem, 7vw, 6rem); }
.section-brands .section-title-sm { margin-bottom: clamp(1.75rem, 3vw, 2.5rem); }

.section-title {
  font-size: clamp(2rem, 5.2vw, 4.25rem);
  margin-bottom: 0.5em;
}
.section-title:last-child { margin-bottom: 0; }

.section-head .lede { margin-top: -0.2em; }


/* ===========================================================================
   05. HEADER & NAVIGATION
   =========================================================================== */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 900;
  height: var(--header-h);
  display: flex;
  align-items: center;
  border-bottom: 1px solid transparent;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
/* Only becomes opaque once you scroll, so the hero reads full-bleed. */
.site-header.is-stuck {
  background: rgba(8, 9, 10, 0.82);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--line);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.9375rem;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
.brand .brand-mark {
  width: 9px; height: 9px;
  background: var(--accent);
  align-self: center;
  flex: none;
}
.brand .brand-role {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.nav { display: flex; align-items: center; gap: clamp(1rem, 2.2vw, 2.25rem); }

.nav a {
  position: relative;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--paper-dim);
  padding-block: 0.35rem;
  transition: color 0.2s var(--ease);
}
.nav a::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.28s var(--ease);
}
.nav a:hover { color: var(--paper); }
.nav a:hover::after,
.nav a[aria-current='page']::after { transform: scaleX(1); }
.nav a[aria-current='page'] { color: var(--paper); }


/* --- Mobile drawer ------------------------------------------------------- */
/* Flex column, not grid. As a grid the three bars each get their own auto
   row, and the rows stretch to fill the 42px button — which spreads them
   15px apart and puts the X animation's 6.5px offsets in the wrong place. */
.nav-toggle {
  display: none;
  width: 42px; height: 42px;
  margin-right: -8px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.nav-toggle span {
  display: block;
  width: 20px; height: 1.5px;
  background: var(--paper);
  transition: transform 0.3s var(--ease), opacity 0.2s var(--ease);
}
.nav-toggle span + span { margin-top: 5px; }
.nav-toggle[aria-expanded='true'] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-toggle[aria-expanded='true'] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded='true'] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }


/* ===========================================================================
   06. MEDIA FRAMES & PLACEHOLDERS
   ---------------------------------------------------------------------------
   Every piece of media on the site sits in a .media frame with a fixed
   aspect ratio. That is what prevents the page jumping about as video and
   posters load, on any connection.
   =========================================================================== */

.media {
  position: relative;
  overflow: hidden;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  aspect-ratio: 16 / 9;
}
.media[data-aspect='9:16'] { aspect-ratio: 9 / 16; }
.media[data-aspect='1:1']  { aspect-ratio: 1 / 1; }
.media[data-aspect='4:5']  { aspect-ratio: 4 / 5; }

.media > img,
.media > video,
.media > iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
}

/* --- The empty slot -----------------------------------------------------
   This is what you see before any video is added. It is designed to read as
   a deliberate, waiting slot — not as a broken image. Fine diagonal hatch,
   a bracketed label, and a hint line. */
.media-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.85rem;
  padding: 1.25rem;
  text-align: center;
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255, 255, 255, 0.022) 0 1px,
    transparent 1px 9px
  );
}
.media-empty .corner {
  position: absolute;
  width: 13px; height: 13px;
  border: 1px solid var(--line-mid);
}
.media-empty .corner.tl { top: 10px; left: 10px;  border-right: 0; border-bottom: 0; }
.media-empty .corner.tr { top: 10px; right: 10px; border-left: 0;  border-bottom: 0; }
.media-empty .corner.bl { bottom: 10px; left: 10px;  border-right: 0; border-top: 0; }
.media-empty .corner.br { bottom: 10px; right: 10px; border-left: 0;  border-top: 0; }

.media-empty .glyph {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border: 1px solid var(--line-mid);
  border-radius: 50%;
  color: var(--faint);
  font-size: 15px;
  line-height: 1;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.media-empty .label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper-dim);
}
.media-empty .hint {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.09em;
  color: var(--faint);
  max-width: 26ch;
  line-height: 1.65;
}

.card:hover .media-empty .glyph {
  color: var(--accent);
  border-color: var(--accent);
}

/* --- Playing state -------------------------------------------------------
   TikTok's embed ships its own chrome: the account, the caption and the music
   credit. That is part of what TikTok asks you to display, so the frame grows
   to make room for it rather than cropping their attribution away. YouTube
   and self-hosted files keep the card's own ratio. */
.media[data-playing='tiktok'] { aspect-ratio: 325 / 759; }   /* TikTok's own embed proportions */
.media[data-playing] { border-color: var(--line-mid); }

/* --- Play button (facade) ------------------------------------------------
   YouTube and Vimeo iframes are NOT loaded with the page. The poster image
   plus this button stands in for them; the iframe is only injected when the
   button is pressed. That keeps third-party scripts off the initial load. */
.media-play {
  position: absolute;
  inset: 0;
  width: 100%;
  display: grid;
  place-items: center;
  background: linear-gradient(to top, rgba(0,0,0,0.55), rgba(0,0,0,0.05) 55%);
  transition: background 0.3s var(--ease);
}
.media-play:hover { background: linear-gradient(to top, rgba(0,0,0,0.62), rgba(0,0,0,0.12) 55%); }

.media-play .play-ring {
  width: clamp(52px, 6vw, 74px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 50%;
  background: rgba(8, 9, 10, 0.32);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease), transform 0.4s var(--ease-out);
}
.media-play .play-ring::before {
  content: '';
  width: 0; height: 0;
  margin-left: 3px;
  border-left: 13px solid currentColor;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
}
.media-play:hover .play-ring {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  transform: scale(1.06);
}

.media-duration {
  position: absolute;
  right: 10px; bottom: 10px;
  padding: 0.2rem 0.45rem;
  background: rgba(8, 9, 10, 0.78);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  color: var(--paper);
}

/* ===========================================================================
   07. PROJECT CARDS & GRIDS
   ---------------------------------------------------------------------------
   The portfolio is exactly thirteen pieces: 5 short form, 3 long form,
   5 motion graphics. Each grid below is composed for its own count rather
   than being a generic repeating grid, so no row is left with an orphan.
   =========================================================================== */

.card { position: relative; display: flex; flex-direction: column; }

.card .media { transition: border-color 0.35s var(--ease); }
.card:hover .media { border-color: var(--line-mid); }
.card .media > img { transition: transform 0.7s var(--ease-out), opacity 0.4s var(--ease); }
.card:hover .media > img { transform: scale(1.035); }

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

.card-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.6rem;
  margin-bottom: 0.45rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}
/* Each part stays whole when the line wraps, and the separating dot belongs
   to the item that follows it rather than being left at a line start. */
.card-meta > span { display: inline-flex; align-items: center; gap: 0.6rem; white-space: nowrap; }
.card-meta > span + span::before {
  content: '';
  width: 2px; height: 2px;
  background: currentColor;
  border-radius: 50%;
  flex: none;
}

.card-title {
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.3;
}

/* View count, directly under the title. Quiet by design — it is a fact, not
   a headline. Renders only when `views` has something in it; nothing is ever
   generated to fill it. */
.card-views {
  margin-top: 0.3rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.card-desc {
  margin-top: 0.4rem;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--muted);
}

/* The credits line — what you did on the piece — directly under the title.
   Small and quiet: supporting detail, not a headline. Any number of roles
   fits, because it wraps as ordinary text instead of tiling as chips. */
.card-roles {
  margin-top: 0.35rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--paper-dim);
}

/* Whole card is clickable when it has a pop-out, without nesting links. */
.card-open {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: var(--radius);
}

.grid { display: grid; gap: clamp(1rem, 1.8vw, 1.75rem); align-items: start; }

/* Five vertical pieces, five columns. */
.grid-short { grid-template-columns: repeat(5, minmax(0, 1fr)); }

/* Five widescreen pieces: the featured one runs full width at the top, the
   other four tile two-by-two beneath it. Better than five-across, which would
   shrink every one of them to a thumbnail.

   The feature is chosen by `featured: true` in projects.js, not by position,
   so which video leads the section is a one-word edit in the data. */
.grid-long { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-long .card.is-featured { grid-column: span 2; }

/* Five motion pieces on a six-column field: two large, then three medium.
   The spans are what make five items fill two rows exactly. */
.grid-motion { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.grid-motion .card:nth-child(-n+2) { grid-column: span 3; }
.grid-motion .card:nth-child(n+3)  { grid-column: span 2; }

.empty-note {
  padding: 2rem;
  border: 1px dashed var(--line-mid);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--muted);
  text-align: center;
}


/* ===========================================================================
   08. SECTIONS
   =========================================================================== */

/* --- Hero ---------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: min(92svh, 900px);
  display: flex;
  align-items: flex-end;
  padding-top: calc(var(--header-h) + clamp(3rem, 9vw, 7rem));
  padding-bottom: clamp(2.5rem, 6vw, 5rem);
  overflow: hidden;
}

/* Faint timeline ruler bleeding across the hero. Pure CSS, no images. */
.hero::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: calc(var(--header-h) + 40px);
  height: 10px;
  background-image: repeating-linear-gradient(
    to right,
    rgba(255,255,255,0.09) 0 1px,
    transparent 1px 34px
  );
  mask-image: linear-gradient(to right, transparent, #000 12%, #000 62%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 62%, transparent);
  pointer-events: none;
}

.hero-inner { position: relative; z-index: 1; width: 100%; }

.hero-kicker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.65rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.hero-kicker .bar { width: 26px; height: 1px; background: var(--accent); }

.hero-title {
  font-size: clamp(2.75rem, 9.2vw, 9.5rem);
  font-weight: 600;
  line-height: 0.9;
  letter-spacing: -0.045em;
  text-transform: uppercase;
  text-wrap: initial;
}
.hero-title .line { display: block; }
.hero-title .serif {
  text-transform: none;
  letter-spacing: -0.02em;
  color: var(--paper-dim);
}

.hero-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(1.75rem, 4vw, 4rem);
  margin-top: clamp(2rem, 4.5vw, 3.5rem);
}
.hero-statement { max-width: 48ch; font-size: clamp(0.9375rem, 1.35vw, 1.125rem); color: var(--paper-dim); }

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

/* --- Buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.95rem 1.6rem;
  border: 1px solid var(--line-mid);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background 0.28s var(--ease), border-color 0.28s var(--ease), color 0.28s var(--ease);
}
.btn .arrow { transition: transform 0.32s var(--ease-out); }
.btn:hover .arrow { transform: translateX(4px); }

.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: #fff; border-color: #fff; color: var(--ink); }

.btn-ghost:hover { background: var(--paper); border-color: var(--paper); color: var(--ink); }

.btn-sm { padding: 0.7rem 1.1rem; font-size: 0.6875rem; }

/* --- Brands I've worked with --------------------------------------------
   An understated wall. Until a logo file is added, the tile shows the brand
   NAME set in type — which is why the wall never contains a broken image, a
   grey box, or an invented mark.

   Dividers are drawn as borders on the tiles rather than as a background
   showing through a 1px gap. With a gap, a row that does not fill completely
   leaves visible grey blocks where the missing tiles would be; with borders,
   a short last row simply ends. That keeps the wall tidy whatever number of
   brands you end up listing. */
.brand-wall {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
  border-left: 1px solid var(--line);
}

.brand-tile {
  /* Two fixed rows: the name band, then the logo box. Fixing both heights is
     what keeps all twelve tiles identical — a brand whose name wraps to two
     lines cannot push its logo out of line with its neighbours. */
  display: grid;
  grid-template-rows: 2.9em var(--logo-h);
  align-items: center;
  justify-items: center;
  row-gap: clamp(0.6rem, 1vw, 0.95rem);
  padding: clamp(1.1rem, 1.7vw, 1.6rem) 0.9rem;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  text-align: center;
  transition: background 0.35s var(--ease);

  /* One place to change the logo box for the whole wall. */
  --logo-w: clamp(104px, 8.4vw, 116px);
  --logo-h: clamp(46px, 4.2vw, 58px);
}
.brand-tile:hover { background: var(--ink-2); }

/* The brand name, above the logo. One typographic treatment for all twelve —
   same family, size, weight, tracking and alignment. Deliberately small and
   quiet: it labels the logo, it does not compete with the portfolio. */
.brand-name {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.35s var(--ease);
}
.brand-tile:hover .brand-name { color: var(--paper); }

/* THE IDENTICAL CONTAINER.
   Every brand gets exactly this box, at every screen size. The logo inside is
   scaled to fit by object-fit: contain, so a wide wordmark, a square badge and
   a circular mark all occupy the same footprint without being stretched,
   squashed or cropped. Nothing is sized per-brand: the box is fixed and only
   the artwork inside it scales.

   The box is roughly 2:1 on purpose. `contain` fits a square mark to the short
   edge, so in a wide box circular logos like Lenovo would look small beside a
   long wordmark like UNILAD. At 2:1 a square lands on 58x58 and UNILAD on
   116x27 — near-identical visual area. */
.brand-logo {
  display: block;
  width: 100%;
  max-width: var(--logo-w);
  height: var(--logo-h);
}
.brand-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;      /* proportional, never distorted, never cropped */
  object-position: center;
  /* Held slightly back so the wall supports the work rather than shouting over
     it. Full strength on hover. No grayscale: several of these marks are dark
     or low-luminance, and desaturating them costs real legibility. */
  opacity: 0.85;
  transition: opacity 0.35s var(--ease);
}
.brand-tile:hover .brand-logo img { opacity: 1; }

/* Safety net if a logo path is ever left blank. The name above still says
   which brand it is, so this stays quiet rather than shouting "missing". */
.brand-logo.is-empty {
  border: 1px dashed var(--line);
  border-radius: 2px;
}

/* --- About --------------------------------------------------------------- */
.about-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 5.5rem);
  align-items: start;
}
.about-copy h2 { font-size: clamp(1.625rem, 3.1vw, 2.5rem); margin-bottom: 1.4rem; }
.about-copy p { color: var(--paper-dim); font-size: clamp(0.9375rem, 1.25vw, 1.0625rem); }

.timeline { margin: 0; padding: 0; list-style: none; }
.timeline > li { padding: 1.35rem 0; border-top: 1px solid var(--line); }
.timeline > li:first-child { border-top-color: var(--line-mid); }

.tl-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
}
.tl-role { font-size: 1rem; font-weight: 600; letter-spacing: -0.015em; }
.tl-period {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.11em;
  color: var(--accent);
  white-space: nowrap;
}
.tl-org { margin-top: 0.15rem; font-size: 0.875rem; color: var(--muted); }
.tl-points { margin: 0.75rem 0 0; padding-left: 1.05rem; }
.tl-points li { font-size: 0.8125rem; line-height: 1.6; color: var(--paper-dim); margin-bottom: 0.35rem; }
.tl-points li::marker { color: var(--faint); }

/* --- Skills -------------------------------------------------------------- */
/* Column dividers are deliberately absent here. The number of groups does
   not divide evenly into the number of columns, so vertical rules would
   leave a stub hanging off the end of the second row. A top rule per group
   plus a column gap reads cleanly however the groups wrap. */
.skill-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  column-gap: clamp(1.5rem, 3vw, 3.25rem);
}

.skill-group { padding: 1.5rem 0 2rem; border-top: 1px solid var(--line-mid); }

.skill-group h3 {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 1.1rem;
}
.skill-group ul { margin: 0; padding: 0; list-style: none; }
.skill-group li { padding: 0.7rem 0; border-top: 1px solid var(--line); }
.skill-group li:first-child { border-top: 0; padding-top: 0; }

.sk-name { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; font-size: 0.9375rem; font-weight: 500; }
.sk-years { font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.08em; color: var(--faint); white-space: nowrap; }
.sk-note { margin-top: 0.2rem; font-size: 0.75rem; line-height: 1.55; color: var(--muted); }







/* ===========================================================================
   10. FOOTER
   ---------------------------------------------------------------------------
   Name, role, navigation, copyright. Nothing else — no email, no phone, no
   address, no social handles, no employer. That is a requirement of this
   site, not an oversight.
   =========================================================================== */

.site-footer { padding-block: clamp(2.5rem, 5vw, 4rem); border-top: 1px solid var(--line); }

.footer-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.75rem 2.5rem;
}

.footer-name {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 2vw, 1.5rem);
  font-weight: 600;
  letter-spacing: -0.03em;
}
.footer-role {
  margin: 0.2rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.footer-nav { display: flex; flex-wrap: wrap; gap: 0.35rem 1.5rem; }
.footer-nav a {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--paper-dim);
  transition: color 0.25s var(--ease);
}
.footer-nav a:hover { color: var(--accent); }

.footer-meta {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}


/* ===========================================================================
   11. MOTION & RESPONSIVE
   =========================================================================== */

/* Scroll reveal. Elements start slightly low and transparent, and are
   revealed by IntersectionObserver in ui.js.

   Note the `html.js` scope. That class is added by a one-line inline script
   in the head. If JavaScript is blocked or fails, the class is never added,
   these rules never apply, and the page renders fully visible instead of
   permanently invisible. Reveal animations must never be able to hide
   content from someone. */
html.js .reveal { opacity: 0; transform: translateY(18px); transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out); }
html.js .reveal.is-in { opacity: 1; transform: none; }
html.js .reveal[data-delay='1'] { transition-delay: 0.07s; }
html.js .reveal[data-delay='2'] { transition-delay: 0.14s; }
html.js .reveal[data-delay='3'] { transition-delay: 0.21s; }

@media (max-width: 1280px) {
  .grid-short  { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .brand-wall  { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  :root { --header-h: 60px; }

  .nav-toggle { display: flex; }

  .nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.5rem var(--gutter) 1.5rem;
    background: rgba(8, 9, 10, 0.97);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--line);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), visibility 0.25s;
  }
  .nav.is-open { opacity: 1; visibility: visible; transform: none; }
  .nav a { padding: 1rem 0; border-bottom: 1px solid var(--line); font-size: 0.8125rem; }
  .nav a::after { display: none; }

  .brand .brand-role { display: none; }

  .grid-short  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-long   { grid-template-columns: 1fr; }
  .grid-long .card.is-featured { grid-column: span 1; }
  .grid-motion { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-motion .card:nth-child(-n+2),
  .grid-motion .card:nth-child(n+3) { grid-column: span 1; }


  .about-grid  { grid-template-columns: 1fr; }

  .hero { min-height: auto; }
  .hero::before { display: none; }
}

@media (max-width: 560px) {
  /* On a narrow screen the close button lands on top of the player's own
     controls (captions, settings) in the video's top-right corner. Drop the
     content below it so the two are never in the same place. */
  .grid-motion { grid-template-columns: 1fr; }
  .brand-wall  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hero-actions .btn { flex: 1 1 100%; justify-content: center; }
  .footer-grid { flex-direction: column; align-items: flex-start; }
}

/* Respect the operating-system setting. Everything still works; it just
   stops moving. Required for anyone with vestibular sensitivity. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
  .card:hover .media > img { transform: none; }
}

/* Printing a portfolio is rare, but a hiring manager sometimes does it. */
@media print {
  body { background: #fff; color: #000; }
  body::after, .site-header, .media-play, .hero::before { display: none !important; }
  .media { border: 1px solid #ccc; }
  .brand-tile { background: #fff; }
  .brand-name { color: #000; }
}
