/* ── FONT ───────────────────────────────────────────────────────────── */
@font-face {
  font-family: 'PPNeueMachina';
  src: url('../fonts/PPNeueMachina-InktrapRegular.woff2') format('woff2'),
       url('../fonts/PPNeueMachina-InktrapRegular.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Dreamer';
  src: url('../fonts/DreamerTM-Regular.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
}

/* ── TOKENS ─────────────────────────────────────────────────────────── */
:root {
  --blue:       #0066ff;
  --bg:         #f9f9f9;
  --edge:       48px;
  --font:       'PPNeueMachina', sans-serif;
  --font-alt:   'Dreamer', sans-serif;  /* swap --font for --font-alt to try Dreamer */
  --col-step:   calc((100vw - 96px) / 12);
  --hero-font:  15vw;
  --globe-size: 11vw;
}

/* ── RESET ──────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  overflow-x: hidden;
}

html, body {
  width: 100%;
  background: var(--bg);
  color: var(--blue);
  font-family: var(--font);
  overscroll-behavior-y: none; /* prevent elastic bounce at page bottom */
}

/* ── SECTION BASE ───────────────────────────────────────────────────── */
.section {
  position: relative;
  width: 100%;
  background: var(--bg); /* opaque so sections paint over fixed footer */
  z-index: 2;            /* above footer (z-index: 1) */
}

/* ── FIXED CHROME ───────────────────────────────────────────────────── */
.chrome {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 50;
}

.chrome__name,
.chrome__year {
  position: absolute;
  top: var(--edge);
  font-size: 22px;
  line-height: normal;
  white-space: nowrap;
}
/* When it's a home link (work/about pages) it stays styled like the plain name. */
.chrome__name { left: var(--edge); color: inherit; text-decoration: none; cursor: pointer; pointer-events: auto; }
.chrome__year { right: var(--edge); }

.chrome__atom {
  position: absolute;
  bottom: var(--edge);
  left: var(--edge);
  width: 40px;
  height: 40px;
}

.atom-svg { width: 100%; height: 100%; }

/* ── CURSOR COORDS ──────────────────────────────────────────────────── */
.cursor-coords {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: 999;
  font-family: var(--font);
  font-size: 11px;
  line-height: 1.4;
  color: var(--blue);
  white-space: nowrap;
  user-select: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.cursor-coords.is-visible { opacity: 1; }
.cursor-coords.on-footer  { opacity: 0 !important; }

/* ── WORK TOOLTIP ───────────────────────────────────────────────────── */
/* ── WORK TOOLTIP — square pill that follows the cursor, copy loops through ── */
.work-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: 100;
  display: flex;
  align-items: center;
  width: 116px;
  height: 32px;
  overflow: hidden;        /* clip the scrolling copy to the pill */
  background: var(--blue);
  color: var(--bg);
  border-radius: 2px;      /* square corners */
  opacity: 0;
  transition: opacity 0.2s ease;
}
.work-tooltip.is-visible { opacity: 1; }

.work-tooltip__track {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 16px;               /* gap between the end of one run and the start of the next */
  width: max-content;
  white-space: nowrap;
  will-change: transform;
}
.work-tooltip__track span {
  font-family: var(--font);
  font-size: 14px;
  line-height: 1;
  white-space: nowrap;
}

/* ── WORK GRID HOVER TRANSFORM ──────────────────────────────────────── */
.gi { transform-origin: center; }

/* Hide cursor coords while hovering grid */
body.grid-hovering .cursor-coords {
  opacity: 0 !important;
}

/* Hide cursor coords while hovering skill cards */
body:has(.skill-card:hover) .cursor-coords {
  opacity: 0 !important;
}


/* ══════════════════════════════════════════════════════════════════════
   HOME SECTION — tall section, sticky inner panel
   ══════════════════════════════════════════════════════════════════════ */
.section--home {
  min-height: 100vh;   /* simple hero — Timothy globe logo, scrolls away */
}

/* The always-visible viewport panel */
.home-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: visible; /* scalers start below viewport — clipped by viewport itself */
  background: var(--bg);
}

/* Column grid overlay behind the Timothy hero (matches the other sections). */
.home-sticky::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}

/* Column grid overlay on sticky panel — animatable div, replaces ::before */
#grid-reveal {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
  clip-path: inset(0% 0% 100% 0%);
}

/* ── Hero typography ── */
.hero {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  z-index: 1;
  pointer-events: none;
}

.hero__text {
  font-size: var(--hero-font);
  letter-spacing: calc(var(--hero-font) * -0.02);
  line-height: 1;
  color: var(--blue);
  font-family: var(--font);
  white-space: nowrap;
  display: flex;
  align-items: center;
}

.hero__globe-wrap {
  flex: 0 0 var(--globe-size);
  height: var(--globe-size);
  margin-left:  calc(var(--hero-font) * -0.02 + 4px);
  margin-right: calc(var(--hero-font) * -0.02 - 4px);
  position: relative;
  top: -2px;
}

#hero-globe { display: block; width: 100%; height: 100%; }

.home-bio {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--edge);
  font-size: 18px;
  line-height: 1;            /* match the page-nav so the bottoms align */
  text-align: center;        /* centred horizontally in the viewport */
  white-space: nowrap;
  z-index: 1;
  transition: opacity 0.4s ease;
}
/* Fade out as the footer reveals, like the menu items. */
body.footer-visible .home-bio {
  opacity: 0;
  pointer-events: none;
}

/* Experience scroll label sits above the stacked rows (z-index up to 6). */
#exp-scroll-label { z-index: 20; }

/* ══════════════════════════════════════════════════════════════════════
   SHOWCASE — toggleable Selected Work (drag-scroll) / Playground
   ══════════════════════════════════════════════════════════════════════ */
.section--showcase {
  position: relative;
  height: 100vh;                    /* fit the viewport exactly — stable panel */
  overflow: hidden;                 /* clip the off-screen scroll items */
  background: var(--bg);
  transition: background-color 0.45s ease;
}
.section--showcase.is-pg { background: var(--blue); }   /* Playground view */

/* Centred image's project name — bottom centre, mirrors the index bio.
   Driven (with a scramble) by js/showcase-gl.js; Selected Work view only. */
.sw-name {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--edge);
  margin: 0;
  text-align: center;
  font-family: var(--font);
  font-size: 18px;
  line-height: 1;
  color: var(--blue);
  white-space: nowrap;
  z-index: 10;
  pointer-events: none;
}
/* Static braces around the name — scramble only touches the inner text node */
.sw-name::before { content: '{ '; }
.sw-name::after  { content: ' }'; }
.section--showcase.is-pg .sw-name { opacity: 0; }   /* hide the Selected Work name on Playground */
/* Playground's own centred name — same placement, shown on the blue panel in #f9f9f9. */
#pg-name { color: #f9f9f9; opacity: 0; }
.section--showcase.is-pg #pg-name { opacity: 1; }

/* Column grid overlay — blue lines on light, white lines on the blue view. */
.section--showcase::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}
.section--showcase.is-pg::before {
  background-image: linear-gradient(
    to right,
    rgba(255, 255, 255, 0.20) 0px,
    rgba(255, 255, 255, 0.20) 1px,
    transparent 1px,
    transparent 100%
  );
}

/* Each view fills the section; only the active one shows. */
.sw-view, .pg-view {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  z-index: 1;                       /* above the grid overlay */
}
.pg-view { justify-content: center; }
.sw-view[aria-hidden="true"], .pg-view[aria-hidden="true"] {
  opacity: 0;
  pointer-events: none;
}
.sw-view, .pg-view { transition: opacity 0.4s ease; }

/* WebGL gallery canvas — fills the Selected Work view, above the (hidden) DOM track. */
.sw-gl {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 2;
  cursor: grab;
}

/* ── Selected Work: horizontal drag row ─────────────────────────────── */
/* The DOM row is the hidden no-WebGL / accessibility fallback — just a static
   layout; all motion lives in the WebGL canvas (js/showcase-gl.js). */
.sw-track {
  display: flex;
  align-items: center;
  gap: calc(var(--col-step) / 2);
}

.sw-item {
  position: relative;
  flex: 0 0 auto;
  width: calc(4 * var(--col-step));
  aspect-ratio: 476 / 300;
  display: block;
}

.sw-item__frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
  display: block;
}
.sw-item__frame img,
.pg-shot__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}

/* ── Playground: globe + two images ─────────────────────────────────── */
.pg-globe {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 630px;
  height: 630px;
  opacity: 0.4;
  pointer-events: none;
}
.pg-globe canvas { display: block; width: 630px; height: 630px; }

.pg-images {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 42px;
}
.pg-shot {
  position: relative;
  display: block;
  /* Match the SMALLER Selected Work plane: WebGL planeH (0.364·vh) × 0.98,
     aspect 476/300. Height-based so it tracks the same sizing as the gallery. */
  height: calc(100vh * 0.357);
  aspect-ratio: 476 / 300;
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Hover: tilt 1° anticlockwise + a slight scale (Playground images only). */
.pg-shot:hover {
  transform: rotate(-1deg) scale(1.04);
  z-index: 1;
}
.pg-shot__frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
  display: block;
  z-index: 0;   /* contain the glitch layers below the .pg-shot stroke/corners */
}

/* ── Playground glitch effect ────────────────────────────────────────────
   Base <img> with three clip-path-animated copies (as .glitch__layer divs,
   created in js/showcase.js) displaced sideways to fake channel-split tears.
   Animation/transform logic kept as the reference. */
.pg-shot__frame img {
  position: relative;
  z-index: 1;
}
.glitch__layers {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}
.glitch__layer {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
}
.glitch__layer:nth-child(1) {
  transform: translateX(-5%);
  animation: pg-glitch-anim-1 2s infinite linear alternate;
}
.glitch__layer:nth-child(2) {
  transform: translateX(3%) translateY(3%);
  animation: pg-glitch-anim-2 2.3s -0.8s infinite linear alternate;
}
.glitch__layer:nth-child(3) {
  transform: translateX(5%);
  animation: pg-glitch-anim-flash 1s infinite linear;
}
@keyframes pg-glitch-anim-1 {
  0%   { clip-path: polygon(0 0%,  100% 0%,  100% 5%,  0 5%);  }
  10%  { clip-path: polygon(0 15%, 100% 15%, 100% 15%, 0 15%); }
  20%  { clip-path: polygon(0 10%, 100% 10%, 100% 20%, 0 20%); }
  30%  { clip-path: polygon(0 1%,  100% 1%,  100% 2%,  0 2%);  }
  40%  { clip-path: polygon(0 35%, 100% 35%, 100% 35%, 0 35%); }
  50%  { clip-path: polygon(0 45%, 100% 45%, 100% 46%, 0 46%); }
  60%  { clip-path: polygon(0 50%, 100% 50%, 100% 70%, 0 70%); }
  70%  { clip-path: polygon(0 70%, 100% 70%, 100% 70%, 0 70%); }
  80%  { clip-path: polygon(0 80%, 100% 80%, 100% 80%, 0 80%); }
  90%  { clip-path: polygon(0 50%, 100% 50%, 100% 55%, 0 55%); }
  100% { clip-path: polygon(0 60%, 100% 60%, 100% 70%, 0 70%); }
}
@keyframes pg-glitch-anim-2 {
  0%   { clip-path: polygon(0 15%, 100% 15%, 100% 30%, 0 30%); }
  15%  { clip-path: polygon(0 3%,  100% 3%,  100% 3%,  0 3%);  }
  25%  { clip-path: polygon(0 8%,  100% 8%,  100% 20%, 0 20%); }
  30%  { clip-path: polygon(0 20%, 100% 20%, 100% 20%, 0 20%); }
  45%  { clip-path: polygon(0 45%, 100% 45%, 100% 45%, 0 45%); }
  50%  { clip-path: polygon(0 50%, 100% 50%, 100% 57%, 0 57%); }
  65%  { clip-path: polygon(0 60%, 100% 60%, 100% 60%, 0 60%); }
  75%  { clip-path: polygon(0 80%, 100% 80%, 100% 80%, 0 80%); }
  80%  { clip-path: polygon(0 40%, 100% 40%, 100% 60%, 0 60%); }
  95%  { clip-path: polygon(0 45%, 100% 45%, 100% 60%, 0 60%); }
  100% { clip-path: polygon(0 11%, 100% 11%, 100% 15%, 0 15%); }
}
@keyframes pg-glitch-anim-flash {
  0%        { opacity: 0.2; }
  30%, 100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .glitch__layers { display: none; }
}

/* Selected Work DOM fallback marker (only shows if WebGL is unavailable). */
.sw-item::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  border: 0.5px solid color-mix(in srgb, var(--blue) 60%, transparent);
}

/* ── Playground image stroke (#f9f9f9 on the blue): matches the Selected Work
   WebGL style — 0.5px/60% full stroke + 1.2px / 8px L-shaped corner frames. */
.pg-shot::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  border: 0.5px solid color-mix(in srgb, #f9f9f9 60%, transparent);
}
.pg-shot::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background-image:
    linear-gradient(#f9f9f9, #f9f9f9), linear-gradient(#f9f9f9, #f9f9f9),   /* top-left   */
    linear-gradient(#f9f9f9, #f9f9f9), linear-gradient(#f9f9f9, #f9f9f9),   /* top-right  */
    linear-gradient(#f9f9f9, #f9f9f9), linear-gradient(#f9f9f9, #f9f9f9),   /* bot-left   */
    linear-gradient(#f9f9f9, #f9f9f9), linear-gradient(#f9f9f9, #f9f9f9);   /* bot-right  */
  background-size:
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px;
  background-repeat: no-repeat;
  background-position:
    left top, left top,
    right top, right top,
    left bottom, left bottom,
    right bottom, right bottom;
}

/* ── View toggle — tab pill, centred top of screen ──────────────────────
   Square component with the picture-frame treatment (0.5px stroke + 1.2px/8px
   L-corner brackets). The active tab is filled (blue fill / off-white text on
   the light view); the inactive tab is outlined by the component stroke. Both
   tabs are equal width, sized to the widest label (grid 1fr 1fr). Flips to the
   inverse on the blue view (active off-white fill / blue text, white frame). */
.showcase-toggle,
.about-toggle {
  --frame: var(--blue);              /* stroke + corner-bracket colour */
  position: absolute;
  top: 43px;
  left: 50%;
  /* X-centring is pure CSS; the page-reveal only drives --reveal-y, so the
     horizontal centre can never be miscomputed mid-animation. */
  transform: translateX(-50%) translateY(var(--reveal-y, 0px));
  z-index: 10;
  /* Shrink-wrap to the tabs — an abspos box with inner 1fr tracks otherwise
     stretches to available width in Chrome, leaving the border + brackets too
     wide. max-content forces it to the content size consistently. */
  width: max-content;
  border: 0.5px solid var(--frame);
}
/* The grid lives on an inner wrapper so the bracket ::after below sits on a
   plain (non-grid) positioned box — a grid container resolves an absolutely
   positioned pseudo's containing block inconsistently across browsers. */
.showcase-toggle__inner,
.about-toggle__inner {
  display: grid;
  grid-template-columns: 1fr 1fr;    /* equal-width tabs, sized to the widest */
}
/* 4px / 1.2px L-corner brackets — explicit positioned elements (one per corner)
   straddling the border box, so corner placement never depends on a pseudo's
   background-position resolving against the box, which varies across browsers. */
.tab-corner {
  position: absolute;
  width: 4px;
  height: 4px;
  border: 0 solid var(--frame);
  pointer-events: none;
  z-index: 3;
}
.tab-corner--tl { top: -0.5px;    left: -0.5px;  border-top-width: 1.2px; border-left-width: 1.2px;  }
.tab-corner--tr { top: -0.5px;    right: -0.5px; border-top-width: 1.2px; border-right-width: 1.2px; }
.tab-corner--bl { bottom: -0.5px; left: -0.5px;  border-bottom-width: 1.2px; border-left-width: 1.2px;  }
.tab-corner--br { bottom: -0.5px; right: -0.5px; border-bottom-width: 1.2px; border-right-width: 1.2px; }
.showcase-toggle__btn,
.about-toggle__btn {
  font-family: var(--font);
  font-size: 14px;
  line-height: 1;
  padding: 10px 16px 7px;
  background: none;
  border: 0;
  color: var(--blue);
  cursor: pointer;
  white-space: nowrap;
  text-align: center;
  transition: background-color 0.25s ease, color 0.25s ease;
}
.showcase-toggle__btn.is-active,
.about-toggle__btn.is-active {
  background: var(--blue);
  color: #f9f9f9;
  pointer-events: none;      /* current view — inert */
}
.showcase-toggle__btn:not(.is-active):hover,
.about-toggle__btn:not(.is-active):hover {
  background: color-mix(in srgb, var(--blue) 10%, transparent);
}
/* Inverse on the blue view. */
.section--showcase.is-pg .showcase-toggle,
.section--about.is-whatwhy .about-toggle { --frame: #f9f9f9; }
.section--showcase.is-pg .showcase-toggle__btn,
.section--about.is-whatwhy .about-toggle__btn { color: #f9f9f9; }
.section--showcase.is-pg .showcase-toggle__btn.is-active,
.section--about.is-whatwhy .about-toggle__btn.is-active {
  background: #f9f9f9;
  color: var(--blue);
}
.section--showcase.is-pg .showcase-toggle__btn:not(.is-active):hover,
.section--about.is-whatwhy .about-toggle__btn:not(.is-active):hover {
  background: color-mix(in srgb, #f9f9f9 14%, transparent);
}

/* Fixed chrome flips white while the Playground view is in view. */
body.showcase-pg .chrome__name,
body.showcase-pg .chrome__year,
body.showcase-pg .cursor-coords,
body.showcase-pg .nav-menu__toggle,
body.about-blue .chrome__name,
body.about-blue .chrome__year,
body.about-blue .cursor-coords,
body.about-blue .nav-menu__toggle { color: #fff; }
body.showcase-pg .scroll-progress circle,
body.about-blue .scroll-progress circle { stroke: #fff; }

/* ── PAGE NAV — (Index)(Work)(About) text links, fixed bottom-right ───── */
.page-nav {
  position: fixed;
  bottom: var(--edge);
  right: var(--edge);
  display: flex;
  gap: 16px;
  z-index: 50;
  font-family: var(--font);
  font-size: 18px;
  line-height: 1;
  transition: opacity 0.4s ease;
}
/* Fade out while it sits over the revealed footer. */
body.footer-visible .page-nav {
  opacity: 0;
  pointer-events: none;
}

/* ── SCROLL CUE — bottom centre, baseline-aligned with the page nav ──────── */
.scroll-cue {
  position: fixed;
  bottom: var(--edge);
  left: 0;
  right: 0;
  margin: 0;
  text-align: center;
  font-family: var(--font);
  font-size: 12px;
  line-height: 1;
  color: var(--blue);
  z-index: 50;
  pointer-events: none;
  transition: color 0.3s ease, opacity 0.4s ease;
  animation: scroll-cue-pulse 2.2s ease-in-out infinite;   /* breathe to draw attention */
}
@keyframes scroll-cue-pulse {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1; }
}
/* White on the blue (My Story) view; persists across both views. */
body.about-blue .scroll-cue { color: #f9f9f9; }
/* Ease out completely once scrolled away from the top (or over the footer);
   reappears at the top. animation:none releases opacity so it can fade. */
body.scrolled .scroll-cue,
body.footer-visible .scroll-cue { animation: none; opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .scroll-cue { animation: none; }
}
.page-nav__link {
  color: var(--blue);              /* all links full colour */
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.3s ease;
}
/* Brackets as inline-block pseudo-elements — propagated text-decoration skips
   atomic inlines, so the underline (selected + hover) covers only the word. */
.page-nav__link::before { content: '('; display: inline-block; }
.page-nav__link::after  { content: ')'; display: inline-block; }
.page-nav__link.is-active { text-decoration: underline; }   /* selected page */
/* White on the blue views (Playground / My Story), like the rest of the chrome. */
body.showcase-pg .page-nav__link,
body.about-blue .page-nav__link { color: #fff; }

.home-scroll-label {
  position: absolute;
  bottom: 48px;
  left: 0;
  right: 0;
  text-align: center;
  font-family: var(--font);
  font-size: 24px;
  color: var(--blue);
  margin: 0;
  padding: 0;
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
  z-index: 3;
}

/* ── Work image grid — lives inside home-sticky ── */
.work-grid {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: visible;
  z-index: 2; /* above hero text so cards scroll over Timothy */
  pointer-events: none; /* disabled until grid is fully revealed */
}
.work-grid.interactive { pointer-events: auto; }

/* ── Playground blue backdrop ──────────────────────────────────────────
   Sits behind the work-grid images (z-index 1 < 2). Starts hidden below the
   viewport and is swiped up / off by GSAP (yPercent). Grid lines + globe
   match the about blue section. */
.home-blue {
  position: absolute;
  inset: 0;
  z-index: 1;
  transform: translateY(100%); /* hidden below until GSAP drives yPercent */
  background-color: var(--blue);
  background-image: linear-gradient(
    to right,
    rgba(255, 255, 255, 0.20) 0px,
    rgba(255, 255, 255, 0.20) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
  pointer-events: none;
  will-change: transform;
}
.home-blue__globe {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 700px;
  height: 700px;
  opacity: 0;          /* faded in by GSAP to 0.4 once the blue has filled */
  z-index: 1;          /* above the blue panel, below the images (z-index 2) */
  pointer-events: none;
}
.home-blue__globe canvas { display: block; width: 700px; height: 700px; }

/* Selection marker on the hero images recolours to #f9f9f9 while it's over the
   playground blue (toggled per-image by updateBlueInvert). Stroke stays at 60%. */
.gi-expander__frame.on-blue::before {
  border-color: color-mix(in srgb, #f9f9f9 60%, transparent);
}
.gi-expander__frame.on-blue::after {
  background-image:
    linear-gradient(#f9f9f9, #f9f9f9),
    linear-gradient(#f9f9f9, #f9f9f9),
    linear-gradient(#f9f9f9, #f9f9f9),
    linear-gradient(#f9f9f9, #f9f9f9);
}

.work-grid__inner {
  display: grid;
  grid-template-columns: repeat(4, calc(12.37vw + 16px));
  grid-template-rows:    repeat(3, calc((12.37vw + 16px) * 0.75));
  gap: 2.23vw;
  overflow: visible;
}

/* Base grid item */
.gi {
  overflow: visible;
  position: relative;
  border-radius: 0;
}
/* Hidden until GSAP fan-in reveals them — prevents flash of un-animated grid on load */
.gi-fade { opacity: 0; }
/* Applied via onLeaveBack when scroll returns to 0. Uses visibility rather
   than opacity so GSAP's scrub cannot override it — GSAP never animates
   visibility unless explicitly told to. */
.gi-fade.gi-reset { visibility: hidden; }
.gi img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Figma-style selection marker. Two layers because a background can't paint
   outside its own border-box — the squares need their own box expanded 2px on
   every side so the full 4px renders, straddling the stroke (2px in / 2px out).
   Drawn on the fade tiles (which scale with their container) and on the scaler
   frames (which scale with the hero image), so the marker hugs the image at
   every size.
     ::before — 0.5px stroke at 60% of the chosen colour, on the image edge.
     ::after  — four 4×4px solid squares centred on each corner of the stroke. */
.gi-fade::before,
.gi-scaler__frame::before,
.gi-expander__frame::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  border: 0.5px solid color-mix(in srgb, var(--blue) 60%, transparent);
}
.gi-fade::after,
.gi-scaler__frame::after,
.gi-expander__frame::after {
  content: '';
  position: absolute;
  inset: -2px; /* box 2px larger each side so the 4px squares aren't clipped */
  pointer-events: none;
  z-index: 3;
  background-image:
    linear-gradient(var(--blue), var(--blue)),
    linear-gradient(var(--blue), var(--blue)),
    linear-gradient(var(--blue), var(--blue)),
    linear-gradient(var(--blue), var(--blue));
  background-size: 4px 4px;
  background-repeat: no-repeat;
  background-position: left top, right top, left bottom, right bottom;
}
/* Fade-tile markers are gated by .markers-on so the two expander tiles don't
   show a stray cell-sized box while their image grows during the scroll-expand.
   They also fade naturally with each tile's opacity. */
.gi-fade::before,
.gi-fade::after {
  opacity: 0;
  transition: opacity 0.2s ease;
}
.work-grid.markers-on .gi-fade::before,
.work-grid.markers-on .gi-fade::after { opacity: 1; }
/* Scaler markers track the hero image and fade with the frame's container
   opacity, so they're shown throughout (no .markers-on gate needed). */
.gi-scaler__frame::before,
.gi-scaler__frame::after { opacity: 1; }
/* Expander frames fill their cell at rest (grid marker handled by .gi-fade);
   their own frame marker only shows during the hero-expand phase. */
.gi-expander__frame { width: 100%; height: 100%; }
.gi-expander__frame img { display: block; width: 100%; height: 100%; object-fit: cover; }
.gi-expander__frame::before,
.gi-expander__frame::after { opacity: 0; transition: opacity 0.2s ease; }
.work-grid.expanding .gi-expander__frame::before,
.work-grid.expanding .gi-expander__frame::after { opacity: 1; }
/* During expand the two expander tiles hand their marker to the frame, so hide
   their cell-anchored container marker (it stays cell-sized while the frame
   grows away and would otherwise sit as a stray box). Other tiles keep their
   markers and fade out with their own collapse. */
.work-grid.expanding .gi-fade:has(.gi-expander__frame)::before,
.work-grid.expanding .gi-fade:has(.gi-expander__frame)::after { opacity: 0; }

/* Scaler items — the 2 hero images */
/* Container must stay unclipped (image scales beyond its bounds during animation) */
.gi-scaler {
  overflow: visible;
  clip-path: none;
  border-radius: 0;
  z-index: 10;
}
/* The hero scalers become clickable/hoverable once the grid is interactive —
   the same gate the rest of the tiles use. Before that they sit centred behind
   the "Timothy" hero text, so leaving them interactive would let a hover over
   the name fire their tile label. pointer-events must be on the frame too —
   that's the element scaled up to hero size, while the container stays
   cell-sized. */
.gi-scaler,
.gi-scaler__frame { pointer-events: none; }
.work-grid.interactive .gi-scaler,
.work-grid.interactive .gi-scaler__frame { pointer-events: auto; }
/* yellow shows only once the image lands in the cell */
.gi-scaler--yellow { background: transparent; }

/* Frame is the element the animation scales (width/height/left/top). */
.gi-scaler__frame {
  position: absolute;
  top: 0;
  left: 0;
  min-width: calc(12.37vw + 16px);
  min-height: calc((12.37vw + 16px) * 0.75);
}
.gi-scaler__frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ══════════════════════════════════════════════════════════════════════
   ABOUT SECTION
   ══════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════
   ABOUT — toggleable: (Introduction) headline / (My Story) blue body
   ══════════════════════════════════════════════════════════════════════ */
.section--about {
  position: relative;
  height: 100vh;                    /* fit the viewport exactly — stable panel */
  overflow: hidden;
  background: var(--bg);
  transition: background-color 0.45s ease;
}
.section--about.is-whatwhy { background: var(--blue); }   /* My Story view */

/* Column grid overlay — blue lines on light, white lines on the blue view. */
.section--about::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}
.section--about.is-whatwhy::before {
  background-image: linear-gradient(
    to right,
    rgba(255, 255, 255, 0.20) 0px,
    rgba(255, 255, 255, 0.20) 1px,
    transparent 1px,
    transparent 100%
  );
}

/* Each view fills the section; only the active one shows. */
.about-view {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  z-index: 1;
  transition: opacity 0.4s ease;
}
.about-view--whatwhy { justify-content: center; }
.about-view[aria-hidden="true"] { opacity: 0; pointer-events: none; }

/* Headline block — column 3, eight columns wide */
.about__headline-wrap {
  position: relative;
  margin-left: calc(var(--edge) + 2 * var(--col-step));
  width: calc(8 * var(--col-step));
  z-index: 1;
}

/* Small "(About)" label sitting in the headline's first-line indent */
.about__label {
  position: absolute;
  left: 0;
  top: 0.55em;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.4;
  color: var(--blue);
  margin: 0;
}

.about__headline {
  font-family: var(--font);
  font-size: clamp(24px, 2.77vw, 52px);
  line-height: 1.2;
  color: var(--blue);
  margin: 0;
  text-indent: 96px; /* first line clears the (About) label */
  text-align: justify;
}

/* Line wrappers injected by entrance animation JS — indent first line only */
.about-headline-line { display: block; text-indent: 0; text-align: justify; text-align-last: justify; }
.about-headline-line:first-child { text-indent: 96px; }
.about-headline-line:last-child { text-align: left; text-align-last: left; }

/* ── My Story view: body copy + globe ─────────────────────────── */
.about__layout {
  position: relative;
  width: 100%;
  z-index: 1; /* above the globe */
}

/* ── Hover for the page nav links — underline (word only) + slight tilt + scale.
   The already-selected page is inert (pointer-events:none also blocks :hover). */
.page-nav__link {
  display: inline-block;
  transform-origin: center;
  transition: color 0.3s ease, transform 0.25s ease;
}
.page-nav__link:hover {
  text-decoration: underline;
  transform: rotate(-3deg) scale(1.08);
}
.page-nav__link.is-active {
  pointer-events: none;
}

/* Wireframe globe — centred in the section, behind the copy. */
.about__globe {
  position: absolute;
  top: 50%;
  left: 100%;                          /* centre on the right viewport edge — */
  transform: translate(-50%, -50%);    /* half off-screen, half showing       */
  width: 100vh;                        /* as tall as the viewport (square)     */
  height: 100vh;
  pointer-events: none;
  z-index: 0;
  opacity: 0.4;
}
.about__globe canvas { display: block; width: 100%; height: 100%; }

/* Body copy — 8 col-steps wide (cols 3–10), centred horizontally; the section
   centres it vertically. Type spec from Figma: 24px, justified, 1.2 line-height,
   56px first-line indent on every paragraph, 32px between paragraphs. */
.about__text {
  width: calc(6 * var(--col-step));   /* 3 displayed grid columns */
  margin-left: calc(var(--edge) + 2 * var(--col-step));   /* left stays at column 2 */
  display: flex;
  flex-direction: column;
  gap: 24px;
  font-family: var(--font);
  font-size: 23px;
  line-height: 1.3;
  text-align: justify;
  word-break: break-word;
  color: #f9f9f9;
}

.about__text-footer {
  width: calc(6 * var(--col-step));   /* 3 displayed grid columns */
  margin-left: calc(var(--edge) + 2 * var(--col-step));   /* left stays at column 2 */
  display: flex;
  flex-direction: column;
  gap: 24px;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.2;
  text-align: justify;
  word-break: break-word;
  color: #f9f9f9;
}
.about__text p { margin: 0; text-indent: 56px; }
.about__text p:not(:first-child) { text-indent: 0; }   /* indent first paragraph only */

/* ── Fixed chrome inverts to white per-element as a blue surface edge (the
   #about section or the home playground panel) crosses it (class toggled by
   updateBlueInvert in home-animations.js). ── */
.chrome__name,
.chrome__year,
.cursor-coords,
.home-scroll-label,
.nav-menu__toggle,
.scroll-progress circle { transition: color 0.18s ease, stroke 0.18s ease; }

.chrome__name.is-on-blue,
.chrome__year.is-on-blue,
.cursor-coords.is-on-blue,
.home-scroll-label.is-on-blue,
.nav-menu__toggle.is-on-blue { color: #fff; }
.scroll-progress.is-on-blue circle { stroke: #fff; }

/* Rotation on the group, scale on each line independently */
.aim-lines-group {
  transform-origin: 16px 16px;
  animation: aim-rotate 4.5s linear infinite;
}

/* Each line's transform-origin is at its INNER end so outer tip retracts first */
.aim-line-top    { transform-origin: 16px 9.90391px; animation: aim-scale-v 4.5s linear infinite; }
.aim-line-bottom { transform-origin: 16px 22.0957px; animation: aim-scale-v 4.5s linear infinite; }
.aim-line-right  { transform-origin: 22.0957px 16px; animation: aim-scale-h 4.5s linear infinite; }
.aim-line-left   { transform-origin: 9.90399px 16px; animation: aim-scale-h 4.5s linear infinite; }

@keyframes aim-rotate {
  /* Hold at 0° while lines retract */
  0%, 25% { transform: rotate(0deg); }
  /* Rotate 90° CW while lines are retracted — ease-in-out for smooth spin */
  25%     { transform: rotate(0deg);  animation-timing-function: ease-in-out; }
  70%     { transform: rotate(90deg); }
  /* Hold at 90° while lines extend — seamless loop (4-fold symmetry) */
  100%    { transform: rotate(90deg); }
}

@keyframes aim-scale-v {
  /* Hold extended */
  0%, 15% { transform: scaleY(1); animation-timing-function: ease-in; }
  /* Retract: outer tip pulls toward inner end */
  25%     { transform: scaleY(0.3); }
  /* Hold retracted through rotation */
  70%     { transform: scaleY(0.3); animation-timing-function: ease-out; }
  /* Extend: outer tip grows back out */
  82%     { transform: scaleY(1); }
  100%    { transform: scaleY(1); }
}

@keyframes aim-scale-h {
  0%, 15% { transform: scaleX(1); animation-timing-function: ease-in; }
  25%     { transform: scaleX(0.3); }
  70%     { transform: scaleX(0.3); animation-timing-function: ease-out; }
  82%     { transform: scaleX(1); }
  100%    { transform: scaleX(1); }
}

/* ══════════════════════════════════════════════════════════════════════
   EXPERTISE HEADING (fixed overlay, GSAP-driven)
   ══════════════════════════════════════════════════════════════════════ */
#expertise-heading-wrap {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  /* height ≈ cap-height so baseline of x/r/i sits at viewport bottom;
     descenders (p) clip below via overflow:hidden */
  height: 15vw;
  overflow: hidden;
  pointer-events: none;
  /* z-index: 6 → above skills-sticky (5) so heading shows over its background */
  z-index: 6;
}

#expertise-heading {
  display: block;
  font-family: var(--font);
  font-size: 18vw;
  line-height: -0.5;
  color: var(--bg);
  -webkit-text-stroke: 1.5px #E0EAFA;
  text-align: center;
  white-space: nowrap;
  letter-spacing: -0.03em;
  position: relative;
  left: -4px;
}


/* ══════════════════════════════════════════════════════════════════════
   DESIGNER OF THINGS SECTION
   ══════════════════════════════════════════════════════════════════════ */
.section--dot {
  height: 400vh;
}
/* Pull the (From Brand Design…) fallen-text section 40px closer to the section
   above it. */
.section--dot-2 {
  margin-top: -40px;
}
/* First DOT section — self-contained scroll zone (fuzz-in + letter fall),
   anchored to its own position (see buildDotAnim). */
.section--dot-1 {
  height: 400vh;
  background-color: var(--bg);
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}

.dot-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: visible;
  background: var(--bg);
  /* position:sticky already establishes a containing block for
     position:absolute children (stamp-placed nodes) */
}

/* Column grid lines — matches other sticky sections */
.dot-sticky::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}

.dot-filter-svg {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
  pointer-events: none;
}

.dot-text {
  font-family: 'Dreamer', sans-serif;
  font-size: 64px;
  color: var(--blue);
  text-align: center;
  line-height: 0.700;
  white-space: normal;
  margin: 0;
  padding: 0;
  user-select: none;
  position: fixed;
  top: 50%;
  left: 0;
  width: 100%;
  transform: translateY(-50%);
  z-index: 10;
  visibility: hidden;
  pointer-events: none;
}
/* "Designer of Things." is a single headline line — never wrap it. */
#dot-text { white-space: nowrap; }

/* ══════════════════════════════════════════════════════════════════════
   SKILLS SECTION
   ══════════════════════════════════════════════════════════════════════ */
.section--experience {
  position: relative;
  /* Tall scroll zone — the sticky child pins while the names enter and the image
     gallery scrolls through. Animation range = min-height − 100vh. */
  min-height: 700vh;
}

/* Sticky wrapper pins the names + gallery while the gallery scrolls */
.exp-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: clip;   /* clip the gallery as it scrolls through */
}

/* Column grid overlay — on the sticky element so lines stay put while pinned */
.exp-sticky::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}

/* ── Company names — vertically centred list (left) ───────────────── */
.exp-names {
  position: absolute;
  left: calc(var(--edge) + 2 * var(--col-step));
  top: 50%;
  transform: translateY(-50%);
  width: calc(4 * var(--col-step));
  display: flex;
  flex-direction: column;
  gap: 32px;
  z-index: 2;
}

.exp-name {
  display: flex;
  align-items: center;
  gap: 16px;
  font-family: var(--font);
  font-size: 32px;
  line-height: 1;
  color: color-mix(in srgb, var(--blue) 60%, transparent);   /* inactive */
  white-space: nowrap;
  transition: color 0.3s ease;
}
.exp-name.is-active { color: var(--blue); }   /* active = solid blue */
.exp-name__company {
  flex-shrink: 0;
  display: inline-block;
  transform-origin: center;
  color: inherit;
  text-decoration: none;
  transition: color 0.3s ease, transform 0.25s ease;
}
/* Same scale + underline as the page-nav menu items, with a gentler 2° tilt. */
.exp-name__company:hover { transform: rotate(-2deg) scale(1.08); text-decoration: underline; }

/* Date pill — only visible on the active company. Same framing as the images:
   0.5px/60% stroke + 1.2px / 8px L-shaped corner frames. */
.exp-name__date {
  position: relative;
  font-size: 14px;
  line-height: 1;
  border: 0.5px solid color-mix(in srgb, var(--blue) 60%, transparent);
  padding: 7px 8px 5px 8px;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(-4px); /* nudge up to sit level with the company name */
  transition: opacity 0.3s ease;
}
.exp-name__date::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--blue), var(--blue)), linear-gradient(var(--blue), var(--blue)),   /* top-left   */
    linear-gradient(var(--blue), var(--blue)), linear-gradient(var(--blue), var(--blue)),   /* top-right  */
    linear-gradient(var(--blue), var(--blue)), linear-gradient(var(--blue), var(--blue)),   /* bot-left   */
    linear-gradient(var(--blue), var(--blue)), linear-gradient(var(--blue), var(--blue));   /* bot-right  */
  background-size:                       /* 4px arms — shorter, for this small pill */
    4px 1.2px, 1.2px 4px,
    4px 1.2px, 1.2px 4px,
    4px 1.2px, 1.2px 4px,
    4px 1.2px, 1.2px 4px;
  background-repeat: no-repeat;
  background-position:
    left top, left top,
    right top, right top,
    left bottom, left bottom,
    right bottom, right bottom;
}
.exp-name.is-active .exp-name__date { opacity: 1; }

/* ── Image gallery — scrolls up through the right column ──────────── */
.exp-gallery {
  position: absolute;
  left: calc(var(--edge) + 6 * var(--col-step));
  top: 0;
  width: calc(2 * var(--col-step));   /* fill one displayed grid column (2 col-steps) */
  z-index: 1;
  will-change: transform;
}

.exp-shot {
  position: relative;
  width: 100%;
  aspect-ratio: 468 / 409;
  margin-bottom: 36px;
}
.exp-shot__frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
.exp-shot__frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Two layers for the active crossfade: the next image sits underneath (bottom)
   while the current one (top) eases out to reveal it. */
.exp-shot__img--bottom { z-index: 0; }
.exp-shot__img--top    { z-index: 1; }
/* Selection marker — 0.5px 60% stroke + 4×4px corner squares (matches tiles).
   On .exp-shot (not the frame) so the frame's overflow:hidden clips only the
   images, letting the corner squares straddle the edge (2px in / 2px out). */
.exp-shot::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  border: 0.5px solid color-mix(in srgb, var(--blue) 60%, transparent);
}
.exp-shot::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background-image:
    linear-gradient(var(--blue), var(--blue)), linear-gradient(var(--blue), var(--blue)),   /* top-left   */
    linear-gradient(var(--blue), var(--blue)), linear-gradient(var(--blue), var(--blue)),   /* top-right  */
    linear-gradient(var(--blue), var(--blue)), linear-gradient(var(--blue), var(--blue)),   /* bot-left   */
    linear-gradient(var(--blue), var(--blue)), linear-gradient(var(--blue), var(--blue));   /* bot-right  */
  background-size:
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px;
  background-repeat: no-repeat;
  background-position:
    left top, left top,
    right top, right top,
    left bottom, left bottom,
    right bottom, right bottom;
}

/* ── Skill cards — absolutely positioned within sticky ──────────── */
.skill-card {
  position: absolute;
  width: 266px;
  height: 276px;
  border: 0.5px solid rgba(0, 102, 255, 0.6);
  border-radius: 0;
  padding: 24px;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  color: var(--blue);
  transform-origin: center center;
  will-change: transform;
}

/* Vertical: odd cards higher, even cards lower (80px offset) — all lifted 24px */
.skill-card:nth-child(odd)  { top: calc(50% - 214px); }
.skill-card:nth-child(even) { top: calc(50% - 134px); }

/* Horizontal: centred group of 5, 216px spacing, 266px card width */
.skill-card:nth-child(1) { left: calc(50% - 565px); z-index: 1; }
.skill-card:nth-child(2) { left: calc(50% - 349px); z-index: 2; }
.skill-card:nth-child(3) { left: calc(50% - 133px); z-index: 3; }
.skill-card:nth-child(4) { left: calc(50% + 83px);  z-index: 4; }
.skill-card:nth-child(5) { left: calc(50% + 299px); z-index: 5; }

/* Experience card content */
.skill-card__top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}

.skill-card__globe {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
}

.skill-card__hover-icon {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

.skill-card__bottom {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.skill-card__date {
  font-family: var(--font);
  font-size: 14px;
  font-weight: 400;
  color: rgba(0, 102, 255, 0.7);
  white-space: nowrap;
  margin: 0;
}

.skill-card__company {
  font-family: var(--font);
  font-size: 22px;
  font-weight: 400;
  line-height: normal;
  margin: 0;
}

.skill-card__role {
  font-family: var(--font);
  font-size: 15px;
  font-weight: 400;
  line-height: normal;
  margin: 0;
}

/* Corner dots — match expertise carousel style, sit above hover image */
#experience .skill-card__dot {
  position: absolute;
  width: 4px;
  height: 4px;
  background: var(--blue);
  z-index: 2;
}
#experience .skill-card__dot--tl { top: -2.75px;    left: -2.75px;  }
#experience .skill-card__dot--tr { top: -2.75px;    right: -1.75px; }
#experience .skill-card__dot--bl { bottom: -1.75px; left: -2.75px;  }
#experience .skill-card__dot--br { bottom: -1.75px; right: -1.75px; }

/* Hover image overlay — full-bleed, cycles through project images */
.skill-card__hover-media {
  position: absolute;
  inset: 8px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  z-index: 1;
}
.skill-card:hover .skill-card__hover-media {
  opacity: 1;
}
/* Hide card content the moment hover begins — prevents bleed-through between frames */
.skill-card:hover .skill-card__top,
.skill-card:hover .skill-card__bottom {
  opacity: 0;
}

/* Keep existing title/desc styles intact for carousel usage below */
.skill-card__title {
  font-family: var(--font);
  font-size: 20px;
  font-weight: 400;
  line-height: normal;
  margin: 0;
}

.skill-card__desc-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.skill-card:hover .skill-card__desc-wrap { grid-template-rows: 1fr; }

.skill-card__desc::before { content: ''; display: block; height: 16px; }

.skill-card__desc {
  min-height: 0;
  overflow: hidden;
  font-family: var(--font);
  font-size: 12px;
  line-height: 1.4;
  opacity: 0;
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 35%, transparent 65%, transparent 100%);
          mask-image: linear-gradient(to bottom, black 0%, black 35%, transparent 65%, transparent 100%);
  -webkit-mask-size: 100% 300%;
          mask-size: 100% 300%;
  -webkit-mask-position: 0% 100%;
          mask-position: 0% 100%;
  transition:
    opacity            0.15s ease     0.25s,
    -webkit-mask-position 0.55s cubic-bezier(0.4, 0, 0.2, 1) 0.28s,
            mask-position 0.55s cubic-bezier(0.4, 0, 0.2, 1) 0.28s;
}
.skill-card:hover .skill-card__desc {
  opacity: 1;
  -webkit-mask-position: 0% 0%;
          mask-position: 0% 0%;
}

/* ══════════════════════════════════════════════════════════════════════
   EXPERTISE DETAIL — marquee + scrolling discipline items
   ══════════════════════════════════════════════════════════════════════ */
.section--expertise {
  padding: 0;
  height: 150vh;
  margin-top: 0;
  position: relative;
  background: var(--bg);
}
.expertise-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
 
  box-sizing: border-box;
}

.section--expertise::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}

/* Column grid overlay lines */

/* ── Scroll progress indicator (fixed, bottom-right) ────────────── */
.scroll-progress {
  position: fixed;
  bottom: calc(var(--edge) + 32px);   /* stacked above the bottom-right page nav */
  right: var(--edge);
  width: 32px;
  height: 32px;
  z-index: 250;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}
.scroll-progress.is-visible {
  opacity: 1;
}
body.footer-visible .scroll-progress {
  opacity: 0 !important;
}
.scroll-progress__icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* ── Skill card carousel ─────────────────────────────────────────── */
.skill-carousel {
  display: flex;
  flex-direction: column;
  gap: 28px;
  width: 100%;
  position: relative;
  z-index: 1;
  overflow: hidden;
  padding: 6px 0 0px;
  user-select: none;
}

.skill-carousel__row {
  overflow: visible;
  padding: 2px 0;
}

.skill-carousel__track {
  display: flex;
  gap: 28px;
  width: max-content;
  will-change: transform;
}

/* One set = 4 cards × (468px + 28px gap) = 1984px */
@keyframes marquee-rtl {
  from { transform: translateX(0); }
  to   { transform: translateX(-1984px); }
}
@keyframes marquee-ltr {
  from { transform: translateX(-1984px); }
  to   { transform: translateX(0); }
}

.skill-carousel__row--rtl .skill-carousel__track {
  animation: marquee-rtl 45s linear infinite;
}
.skill-carousel__row--ltr .skill-carousel__track {
  animation: marquee-ltr 45s linear infinite;
}
.skill-carousel:has(.skill-card:hover) .skill-carousel__track {
  animation-play-state: paused;
}


/* ── Carousel skill card — override ALL base .skill-card styles ────── */
.skill-carousel .skill-card {
  position: relative !important;
  top: auto !important;
  left: auto !important;
  width: 468px !important;
  height: auto !important;
  flex-shrink: 0;
  padding: 32px !important;
  border: 0.5px solid color-mix(in srgb, var(--blue) 60%, transparent) !important;
  border-radius: 0 !important;
  background: var(--bg) !important;
  display: flex !important;
  flex-direction: column !important;
  justify-content: flex-start !important;
  gap: 40px !important;
  font-family: var(--font);
  transition: border-color 0.3s ease !important;
}

.skill-carousel .skill-card__header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  white-space: nowrap;
}

.skill-carousel .skill-card__title {
  font-family: var(--font);
  font-size: 22px !important;
  font-weight: 400 !important;
  line-height: normal;
  color: var(--blue) !important;
  margin: 0;
  transition: color 0.3s ease;
}

.skill-carousel .skill-card__num {
  font-family: var(--font);
  font-size: 22px;
  line-height: normal;
  color: color-mix(in srgb, var(--blue) 60%, transparent);
  transition: color 0.3s ease;
}

/* Override all the hover-reveal styles from the base .skill-card__desc */
.skill-carousel .skill-card__desc {
  font-family: var(--font);
  font-size: 14px !important;
  line-height: 1.6 !important;
  color: var(--blue) !important;
  opacity: 1 !important;
  min-height: auto !important;
  overflow: visible !important;
  -webkit-mask-image: none !important;
          mask-image: none !important;
  transition: color 0.3s ease !important;
}
.skill-carousel .skill-card__desc::before {
  display: none !important;
}

/* Corner frames — 8px L-brackets at 1.2px, matching the image picture frames.
   Drawn as 8 gradients on ::before; --frame-c lets the hover-dim recolour them. */
@property --frame-c {
  syntax: '<color>';
  inherits: true;
  initial-value: transparent;
}
.skill-carousel .skill-card { --frame-c: var(--blue); transition: border-color 0.3s ease, --frame-c 0.3s ease !important; }
.skill-carousel .skill-card__dot { display: none; }
.skill-carousel .skill-card::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background-image:
    linear-gradient(var(--frame-c), var(--frame-c)), linear-gradient(var(--frame-c), var(--frame-c)),
    linear-gradient(var(--frame-c), var(--frame-c)), linear-gradient(var(--frame-c), var(--frame-c)),
    linear-gradient(var(--frame-c), var(--frame-c)), linear-gradient(var(--frame-c), var(--frame-c)),
    linear-gradient(var(--frame-c), var(--frame-c)), linear-gradient(var(--frame-c), var(--frame-c));
  background-size:
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px,
    8px 1.2px, 1.2px 8px;
  background-repeat: no-repeat;
  background-position:
    left top, left top,
    right top, right top,
    left bottom, left bottom,
    right bottom, right bottom;
}

/* ── Carousel card hover ─────────────────────────────────────────── */

/* Diagonal lines loop — 10px period at -45deg, scrolling rightward */
@keyframes skill-card-lines {
  from { background-position: 0 0; }
  to   { background-position: 14.14px 0; }
}

/* Overlay: hidden by default, full-coverage + looping on hover */
.skill-carousel .skill-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    -45deg,
    color-mix(in srgb, var(--blue) 13%, transparent) 0px,
    color-mix(in srgb, var(--blue) 13%, transparent) 0.75px,
    transparent 0.75px,
    transparent 10px
  );
  opacity: 0;
  pointer-events: none;
  z-index: 0;
}
.skill-carousel .skill-card:hover::after {
  opacity: 1;
  animation: skill-card-lines 0.4s linear infinite;
}

/* Keep text and dots rendered above the diagonal overlay */
.skill-carousel .skill-card__header,
.skill-carousel .skill-card__desc {
  position: relative;
  z-index: 1;
}
.skill-carousel .skill-card__dot { z-index: 2; }

/* Dim all non-hovered cards when any card in the carousel is hovered */
.skill-carousel:has(.skill-card:hover) .skill-card:not(:hover) {
  border-color: color-mix(in srgb, var(--blue) 30%, transparent) !important;
  --frame-c: color-mix(in srgb, var(--blue) 30%, transparent);
}
.skill-carousel:has(.skill-card:hover) .skill-card:not(:hover) .skill-card__title,
.skill-carousel:has(.skill-card:hover) .skill-card:not(:hover) .skill-card__num,
.skill-carousel:has(.skill-card:hover) .skill-card:not(:hover) .skill-card__desc {
  color: color-mix(in srgb, var(--blue) 30%, transparent) !important;
}


/* ══════════════════════════════════════════════════════════════════════
   NAV MENU — fixed bottom-right, expands left then up
   ══════════════════════════════════════════════════════════════════════ */
/* Hide the smiley — replaced by the nav menu trigger */
.chrome__atom { display: none; }

/* Hide cursor coords while hovering the menu */
body.menu-hovering .cursor-coords { opacity: 0 !important; }

.nav-menu {
  --nav-bg: var(--bg);                                                /* panel fill   */
  --nav-stroke: color-mix(in srgb, var(--blue) 60%, transparent);     /* 0.5px stroke */
  --nav-fg: var(--blue);                                              /* corners/text/dots */
  position: fixed;
  bottom: 24px;
  left: 24px;
  z-index: 300;
  background: var(--nav-bg);
  border: 0.5px solid var(--nav-stroke);                                  /* 60% stroke, like the work images */
  border-radius: 0;                                                       /* square corners for the L-frames */
  padding: 24px;
  width: 240px;
  /* Closed: clipped to bottom-left 68×68 (icon + padding), bg transparent */
  clip-path: inset(calc(100% - 72px) calc(100% - 72px) 0 0 round 0px);
  background-color: transparent;
  border-color: transparent;
  transition: opacity 0.3s ease, box-shadow 0.3s ease;
}
/* Inverse — over the blue Playground / My Story views: panel filled with the
   chosen colour, white stroke, corners, text, divider, dots and swatch ring. */
body.showcase-pg .nav-menu,
body.about-blue .nav-menu {
  --nav-bg: var(--blue);
  --nav-stroke: color-mix(in srgb, #f9f9f9 60%, transparent);
  --nav-fg: #f9f9f9;
}

/* ── Corner frames (1.2px / 8px L-brackets, theme-coloured) ──────────────
   ::before = the two BOTTOM corners, ::after = the two TOP corners, so they
   can ease in separately: bottom first, then top, once the panel is open. */
.nav-menu::before,
.nav-menu::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  background-image:
    linear-gradient(var(--nav-fg), var(--nav-fg)), linear-gradient(var(--nav-fg), var(--nav-fg)),
    linear-gradient(var(--nav-fg), var(--nav-fg)), linear-gradient(var(--nav-fg), var(--nav-fg));
  background-size: 8px 1.2px, 1.2px 8px, 8px 1.2px, 1.2px 8px;
  background-repeat: no-repeat;
}
.nav-menu::before {
  background-position: left bottom, left bottom, right bottom, right bottom;   /* bottom-left, bottom-right */
  transition: opacity 0.3s ease;
}
.nav-menu::after {
  background-position: left top, left top, right top, right top;               /* top-left, top-right */
  transition: opacity 0.3s ease 0.3s;   /* top corners ease in after the bottom ones */
}
/* Eased in once the panel is fully open (set in nav-menu.js). */
.nav-menu.nm-frames-on::before,
.nav-menu.nm-frames-on::after { opacity: 1; }

body.footer-visible .nav-menu {
  opacity: 0;
  pointer-events: none;
}


/* Phase 1: expand left at footer height */
.nav-menu.nm-phase-1 {
  clip-path: inset(calc(100% - 72px) 0 0 0 round 0);
  background-color: var(--nav-bg);
  border-color: var(--nav-stroke);
  transition: clip-path 200ms cubic-bezier(0.4, 0, 0.2, 1),
              background-color 80ms linear,
              border-color 80ms linear;
}

/* Phase 2: expand up to full height */
.nav-menu.nm-phase-2 {
  clip-path: inset(0 0 0 0 round 0);
  background-color: var(--nav-bg);
  border-color: var(--nav-stroke);
  transition: clip-path 320ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Fully open: drop the clip-path (it would clip the shadow) and add the drop
   shadow. clip-path:none is visually identical to inset(0) for the content. */
.nav-menu.nm-content-in {
  clip-path: none;
  box-shadow: 4px 4px 20px 0 rgba(0, 0, 0, 0.05);
  transition: box-shadow 0.3s ease;   /* fade the shadow in once fully open */
}

/* Closing: reverse phase 2 → phase 1 → closed */
.nav-menu.nm-closing-2 {
  clip-path: inset(calc(100% - 72px) 0 0 0 round 0);
  transition: clip-path 260ms cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-menu.nm-closing-1 {
  clip-path: inset(calc(100% - 72px) calc(100% - 72px) 0 0 round 0px);
  background-color: transparent;
  border-color: transparent;
  transition: clip-path 180ms cubic-bezier(0.4, 0, 0.2, 1),
              background-color 100ms linear 80ms,
              border-color 100ms linear 80ms;
}

/* ── Menu body (links) ── */
.nav-menu__body {
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-bottom: 32px;
}

.nav-menu__main-links,
.nav-menu__sub-links {
  display: flex;
  flex-direction: column;
}

.nav-menu__main-links { gap: 11px; }
.nav-menu__sub-links  { gap: 11px; }

.nav-menu__main-links a {
  font-size: 18px;
  font-family: var(--font);
  color: var(--nav-fg);
  text-decoration: none;
  line-height: normal;
  display: flex;
  align-items: center;
}
.nav-menu__main-links a::before {
  content: '→';
  display: inline-block;
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
  margin-right: 0;
  flex-shrink: 0;
  transition: max-width 0.22s cubic-bezier(0.4, 0, 0.2, 1),
              opacity    0.18s ease,
              margin-right 0.22s ease;
}
.nav-menu__main-links a:hover::before {
  max-width: 1.5em;
  opacity: 1;
  margin-right: 6px;
}

.nav-menu__sub-links a {
  font-size: 14px;
  font-family: var(--font);
  color: var(--nav-fg);
  text-decoration: none;
  line-height: normal;
  display: flex;
  align-items: center;
}
.nav-menu__sub-links a::before {
  content: '→';
  display: inline-block;
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
  margin-right: 0;
  flex-shrink: 0;
  transition: max-width 0.22s cubic-bezier(0.4, 0, 0.2, 1),
              opacity    0.18s ease,
              margin-right 0.22s ease;
}
.nav-menu__sub-links a:hover::before {
  max-width: 1.5em;
  opacity: 1;
  margin-right: 6px;
}

.nav-menu__divider {
  height: 0;
  border: none;
  border-top: 0.5px solid var(--nav-fg);
  opacity: 0.3;
  margin: 0;
}

/* ── Footer row ── */
.nav-menu__footer {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
}

.nav-menu__toggle {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  width: 24px;
  height: 24px;
  position: relative;
  flex-shrink: 0;
  color: var(--nav-fg);
}

/* Both icons stacked; fade between them */
.nav-menu__icon-default,
.nav-menu__icon-open {
  display: block;
  transition: opacity 0.22s ease;
}
.nav-menu__icon-open {
  position: absolute;
  top: 0; left: 0;
  opacity: 0;
  pointer-events: none;
}
.nav-menu.nm-is-open .nav-menu__icon-default { opacity: 0; pointer-events: none; }
.nav-menu.nm-is-open .nav-menu__icon-open    { opacity: 1; pointer-events: auto; }

/* Default icon: circles can animate fill */
.nav-menu__icon-default circle {
  transition: fill 0.2s ease;
}

/* Hover (closed only): corners lose fill → hollow rings, forming a + */
.nav-menu:not(.nm-is-open) .nav-menu__toggle:hover .nav-menu__icon-default circle:nth-child(1),
.nav-menu:not(.nm-is-open) .nav-menu__toggle:hover .nav-menu__icon-default circle:nth-child(3),
.nav-menu:not(.nm-is-open) .nav-menu__toggle:hover .nav-menu__icon-default circle:nth-child(7),
.nav-menu:not(.nm-is-open) .nav-menu__toggle:hover .nav-menu__icon-default circle:nth-child(9) {
  fill: none;
}

/* ── Colour toggles — hidden until container is fully open ── */
.nav-menu__colours {
  display: flex;
  align-items: center;
  gap: 6px;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.nav-menu.nm-content-in .nav-menu__colours {
  opacity: 1;
  transition-delay: 360ms; /* after last content item fades in */
}

.nav-menu__colour {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  transition: transform 0.15s ease, outline 0.15s ease;
}
.nav-menu__colour:hover { transform: scale(1.25); }
.nav-menu__colour.is-active {
  outline: 0.5px solid var(--nav-fg);
  outline-offset: 2px;
}

/* ── Content stagger: items start hidden, revealed on open ── */
.nav-menu__item {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.nav-menu.nm-content-in .nav-menu__item { opacity: 1; transform: translateY(0); }

.nav-menu__divider-wrap {
  opacity: 0;
  transition: opacity 0.25s ease;
}
.nav-menu.nm-content-in .nav-menu__divider-wrap { opacity: 1; }

/* Stagger delays assigned via inline style */

/* ══════════════════════════════════════════════════════════════════════
   SITE FOOTER — fixed behind content, revealed by scrolling to bottom
   ══════════════════════════════════════════════════════════════════════ */
body { padding-bottom: 600px; } /* updated by JS after font measurement */

.site-footer {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 1; /* below sections (z-index: 2) */
}

/* ── White card: top portion, same bg as page ── */
.footer__card {
  position: relative;
  height: 384px;
  background: var(--bg);
  box-shadow: 0 4px 62px 0 rgba(0, 0, 0, 0.10);
  overflow: hidden;
}

.footer__card::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}


/* ── Blue section: main footer content ── */
.footer__blue {
  position: relative;
  height: 600px; /* updated by JS after font measurement */
  background: var(--blue);
  overflow: hidden;
  padding: 72px var(--edge) 0;
}

.footer__blue::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: linear-gradient(
    to right,
    rgba(255, 255, 255, 0.05) 0px,
    rgba(255, 255, 255, 0.05) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}

.footer__main-content {
  position: relative;
  z-index: 1;
  max-width: 1189px;
}

.footer__globe-wrap {
  width: 100px;
  height: 100px;
  margin-bottom: 32px;
}

#footer-globe { display: block; width: 100%; height: 100%; }

.footer__sub {
  font-family: var(--font);
  font-size: 26px;
  color: #fff;
  line-height: normal;
  margin-bottom: 32px;
}

.footer__headline {
  font-family: var(--font);
  font-size: 40px;
  color: #fff;
  line-height: normal;
  margin-bottom: 64px;
}

.footer__download {
  font-family: var(--font);
  font-size: 32px;
  color: #fff;
  text-decoration: none;
  letter-spacing: -0.02em;
  line-height: normal;
  display: inline-block;
  transform-origin: center;
  transition: transform 0.25s ease;
}
.footer__download:hover {
  text-decoration: underline;
  text-decoration-skip-ink: none;
  text-underline-position: from-font;
  transform: rotate(-2deg) scale(1.08);
}

.footer__divider {
  position: absolute;
  left: var(--edge);
  right: var(--edge);
  top: 470px; /* JS will override with precise measurement */
  height: 0.5px;
  background: rgba(255, 255, 255, 0.4);
  z-index: 1;
}

.footer__bottom-bar {
  position: absolute;
  left: var(--edge);
  right: var(--edge);
  top: 518px; /* JS will override with precise measurement */
  display: flex;
  justify-content: space-between;
  align-items: center;
  z-index: 1;
}

.footer__social,
.footer__credit {
  font-family: var(--font);
  font-size: 20px;
  color: #fff;
  line-height: normal;
  white-space: nowrap;
}

.footer__social a {
  color: inherit;
  text-decoration: none;
  display: inline-block;
  transform-origin: center;
  transition: transform 0.25s ease;
}
.footer__social a:hover { text-decoration: underline; transform: rotate(-2deg) scale(1.08); }


/* Second dot section — two-line text */
.dot-text-2 { white-space: normal; line-height: 0.700; }

/* Per-letter parallax exit spans */
.dot-letter, .dot-letter-2 { display: inline-block; }
.dot-space  { min-width: 0.28em; }  /* Dreamer has no space glyph — force gap */

/* ── Landing section: dedicated scroll zone after expertise cards ──
   Same visual stack as all other sections (relative, z:2, --bg background)
   so it scrolls naturally over the fixed footer.
   overflow:hidden clips the SVG 1px at the section's bottom edge.         */
.section--dot-landing {
  min-height: 360px;
  overflow: hidden;
  margin-top: -1px; /* overlap pin-spacer by 1px to close compositing seam */
}

/* Column grid lines — same 1px blue repeating pattern as other sections */
.section--dot-landing::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: linear-gradient(
    to right,
    color-mix(in srgb, var(--blue) 10%, transparent) 0px,
    color-mix(in srgb, var(--blue) 10%, transparent) 1px,
    transparent 1px,
    transparent 100%
  );
  background-size: calc(var(--col-step) * 2) 100%;
  background-position: var(--edge) 0;
  background-repeat: repeat-x;
}

/* SVG sits 1px below the section's bottom edge — clipped by overflow:hidden
   so it appears to be just peaking out, flush with the section boundary.
   As the section scrolls upward the SVG rides with it.                    */
.dot-landed {
  position: absolute;
  bottom: -24px;
  left: 50%;
  transform: translateX(-50%);
  width: 80%;
  max-width: 1100px;
  pointer-events: none;
  user-select: none;
}

/* ══════════════════════════════════════════════════════════════════════
   STAMP CURSOR
   ══════════════════════════════════════════════════════════════════════ */

/* Hide OS cursor + coords label while stamp mode is active */
body.stamps-mode .cursor-coords { opacity: 0 !important; }
body.stamps-mode .site-footer   { cursor: none; }
/* Restore pointer cursor on links inside the footer stamp zone */
body.stamps-mode .site-footer a { cursor: pointer; }

/* The stamp that floats on the cursor — positioned via JS RAF */
#stamp-cursor {
  position: fixed;
  left: 0;
  top: 0;
  width: 170px;
  height: 170px;
  pointer-events: none;
  z-index: 9999;
  opacity: 0;
  will-change: transform, opacity;
}
#stamp-cursor .stamp-svg {
  display: block;
  width: 100%;
  height: 100%;
  color: var(--blue);   /* inline SVG fills use currentColor → tracks the theme */
}
#stamp-cursor .stamp-svg svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Stamps placed onto the page — fixed so they don't scroll with content */
.stamp-placed {
  position: fixed;
  width: 150px;
  height: 150px;
  pointer-events: none;
  z-index: 2; /* above about-inner (z-index:1) */
  transform-origin: center;
}
.stamp-placed .stamp-svg {
  display: block;
  width: 100%;
  height: 100%;
  color: var(--blue);   /* inline SVG fills use currentColor → tracks the theme */
}
.stamp-placed .stamp-svg svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* ── PAGE LOADER ─────────────────────────────────────────────────── */
#page-loader {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: var(--bg);
  color: var(--blue);
  font-family: var(--font);
  overflow: hidden;
}
#page-loader.loader--exit {
  opacity: 0;
  transition: opacity 0.5s ease;
  pointer-events: none;
}
.loader__name,
.loader__copy {
  position: absolute;
  top: var(--edge);
  font-size: 22px;
  line-height: normal;
  margin: 0;
}
.loader__name { left: var(--edge); }
.loader__copy { right: var(--edge); }
.loader__status {
  position: absolute;
  bottom: var(--edge);
  left: var(--edge);
  font-size: 18px;
  line-height: 1.3;
  margin: 0;
}
.loader__counter {
  position: absolute;
  right: var(--edge);
  bottom: calc(var(--edge) - 55px);
  font-size: clamp(100px, 24vw, 430px);
  line-height: 0.85;
  text-align: right;
  margin: 0;
  white-space: nowrap;
  letter-spacing: -0.02em;
}

/* Rising blue fill (the swipe-up during loading), height driven by loader.js.
   Holds a viewport-sized off-white clone of the loader content, clipped to the
   fill so the text knocks out below the rising line. */
.loader__fill {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  background: var(--blue);
  overflow: hidden;
}
.loader__fill-inner {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100vw;
  height: 100vh;
  color: var(--bg);
}

/* ════════════════════════════════════════════════════════════════════════
   PAGE WIPE TRANSITION
   A full-viewport coloured panel (::before on <html>) that covers the page
   during navigation. Classes are toggled by js/page-wipe.js.
   ════════════════════════════════════════════════════════════════════════ */
html.wipe-out::before,
html.wipe-cover::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 100000;
  background: var(--blue);
  pointer-events: none;
  will-change: transform;
}
/* Outgoing — starts below the viewport, rises up to cover. */
html.wipe-out::before           { transform: translateY(100%); }
html.wipe-out.wipe-out-go::before {
  transform: translateY(0);
  transition: transform 0.7s cubic-bezier(0.76, 0, 0.24, 1);
}
/* Incoming — covers on load, then rises up and off the top to reveal. */
html.wipe-cover::before         { transform: translateY(0); }
html.wipe-cover.wipe-reveal::before {
  transform: translateY(-100%);
  transition: transform 0.85s cubic-bezier(0.76, 0, 0.24, 1);
}
/* No "%" loader flash when arriving via a wipe. */
html.wipe-cover #page-loader { display: none !important; }

/* ── Wipe loader — three frames crossfading, centred above the wipe box ─── */
.wipe-loader {
  position: fixed;
  inset: 0;
  z-index: 100001;            /* above the wipe box (100000) */
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.wipe-loader.is-on { opacity: 1; }
.wipe-loader__img {
  position: absolute;
  width: min(210px, 32vw);          /* 30% smaller */
  height: auto;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.wipe-loader__img.is-frame { opacity: 1; }


/* ══════════════════════════════════════════════════════════════════════════
   RESPONSIVE — under 1000px
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1000px) {
  /* Index: drop the (Index)(Work)(About) text nav and move the bio to the
     bottom-right where the nav sat. */
  .page-nav { display: none; }
  .home-bio {
    /* Fixed (like the nav-menu icon) so both pin to the visual viewport and stay
       aligned on mobile — an absolute bio would sit in the layout viewport,
       behind iOS Safari's dynamic toolbar, dropping it below the fixed icon. */
    position: fixed;
    left: var(--edge);
    right: var(--edge);
    text-align: right;
  }

  /* Cold loader: align the big % counter bottom-right (40px above the bottom,
     48px from the right). The digit baseline sits ~3.86vw above the text box
     bottom at this font (24vw, line-height 0.85), so subtract that to land the
     ink on the 40px margin. */
  .loader__counter {
    right: 48px;
    bottom: calc(40px - 3.86vw);
  }
  /* Match the counter's 40px bottom margin. */
  .loader__status { bottom: 40px; }

  /* Experience gallery: two grid columns wide (was one) — expand to the right,
     keeping the left edge. One displayed grid column = 2 col-steps. */
  body:has(#about) .exp-gallery { width: calc(4 * var(--col-step)); }

  /* My Story copy: expand one more grid column to the right (3 → 4 columns),
     keeping the left edge. */
  body:has(#about) .about__text,
  body:has(#about) .about__text-footer { width: calc(8 * var(--col-step)); }
  body:has(#about) .about__text { font-size: 20px; }
}


/* ══════════════════════════════════════════════════════════════════════════
   RESPONSIVE — under 600px
   ══════════════════════════════════════════════════════════════════════════ */
/* Mobile hero — hidden until the breakpoint. */
.hero-m { display: none; }

@media (max-width: 600px) {
  /* Tighter edge gutter to match the mobile design. */
  body:has(#home) { --edge: 32px; }

  /* Bio shortens to just the role (drops the location). */
  .home-bio__loc { display: none; }

  /* Nav menu icon: 32px from the left + bottom. The icon sits ~25px inside the
     menu container (24px padding + 0.5px border), so offset the container to
     land the icon on the 32px margin. */
  .nav-menu { bottom: 7px; left: 7px; }

  /* Cold loader: align the % counter to the 32px page margin (bottom-right),
     and drop the "(Loading)" status so the big number has room to fit. */
  .loader__counter {
    right: 32px;
    bottom: calc(32px - 3.86vw);
  }
  .loader__status { display: none; }

  /* ── Footer (all pages using styles.css: index, work, about) ── */
  /* Drop the "Built by Tim Kennedy" credit. */
  .footer__credit { display: none; }
  /* Match the 4-column / 32px-margin grid of the section above. */
  .footer__card::before,
  .footer__blue::before {
    background-size: calc((100vw - 64px) / 4) 100%;
    background-position: 32px 0;
  }
  /* Scale the footer text down to fit the narrow viewport. */
  .footer__sub      { font-size: 16px; margin-bottom: 20px; }
  .footer__headline { font-size: 26px; line-height: 1.5; margin-bottom: 40px; }
  .footer__download { font-size: 24px; }
  .footer__social   { font-size: 16px; }

  /* Index grid: 32px side margins, 4 columns. */
  #home .home-sticky::before,
  #home #grid-reveal {
    background-size: calc((100vw - 64px) / 4) 100%;
    background-position: 32px 0;
  }

  /* Swap the single-row animated hero for the mobile treatment:
     "Tim" + a spinning globe 50% off the right; a globe 50% off the left + "thy". */
  #home .hero { display: none; }
  .hero-m {
    --mglobe: 28.8vw;          /* globe diameter (matches the JS canvas size) */
    display: block;
    position: absolute;
    inset: 0;
    overflow: hidden;
    z-index: 1;
    pointer-events: none;
  }
  .hero-m__line {
    position: absolute;
    display: flex;
    align-items: center;
    white-space: nowrap;
    font-family: var(--font);
    color: var(--blue);
    font-size: 39vw;
    line-height: 1;
    letter-spacing: -0.78vw;   /* ≈ -0.02em at 39vw */
  }
  /* Globe centred on each viewport edge → 50% on / 50% off. */
  .hero-m__line--1 { top: 26%; right: calc(var(--mglobe) / -2); left: auto; }
  .hero-m__line--2 { top: 47%; left:  calc(var(--mglobe) / -2); }
  .hero-m__globe {
    flex: 0 0 var(--mglobe);
    width: var(--mglobe);
    height: var(--mglobe);
    display: block;
  }

  /* ════════════════════════════════════════════════════════════════════
     WORK PAGE (under 600px)
     ════════════════════════════════════════════════════════════════════ */
  /* Match the index page's 32px edge gutter for the fixed chrome
     (Tim Kennedy / ©—26 / nav menu) and the footer padding. */
  body:has(#showcase) { --edge: 32px; }

  /* Grid overlay: 4 columns within 32px side margins — same as the index. */
  body:has(#showcase) .section--showcase::before {
    background-size: calc((100vw - 64px) / 4) 100%;
    background-position: 32px 0;
  }

  /* Drop the centred project name at the bottom. */
  body:has(#showcase) .sw-name { display: none; }

  /* Vertical gallery is event-stepped (js/showcase-gl.js): each scroll gesture
     advances exactly one image; the section stays a normal 100vh panel and the
     footer reveals after the last image (handled in JS). */

  /* Move the Selected Work / Playground tabs to the bottom-right corner. Fixed
     so they stay viewport-anchored. Keep the reveal-y slide-in. */
  body:has(#showcase) .showcase-toggle {
    position: fixed;
    top: auto;
    bottom: 32px;
    left: auto;
    right: 32px;
    transform: translateY(var(--reveal-y, 0px));
    transition: opacity 0.3s ease;
  }
  /* Fade out with the menu icon once the footer is revealed. */
  body.footer-visible:has(#showcase) .showcase-toggle {
    opacity: 0;
    pointer-events: none;
  }

  /* The gallery is itself the scroll affordance — drop the corner progress ring. */
  body:has(#showcase) .scroll-progress { display: none; }
  body:has(#showcase) .showcase-toggle__btn { font-size: 12px; }

  /* Playground: stack the two shots vertically, 32px apart, each sized like the
     enlarged Selected Work image (full width less the 32px side margins). */
  body:has(#showcase) .pg-images {
    flex-direction: column;
    gap: 32px;
  }
  body:has(#showcase) .pg-shot {
    height: auto;
    width: calc(100vw - 64px);   /* aspect-ratio 476/300 derives the height */
  }
  /* Background globe 20% larger (stays centred via its translate(-50%,-50%)). */
  body:has(#showcase) .pg-globe,
  body:has(#showcase) .pg-globe canvas { width: 756px; height: 756px; }

  /* ════════════════════════════════════════════════════════════════════
     ABOUT PAGE (under 600px)
     ════════════════════════════════════════════════════════════════════ */
  /* Grid overlay: 4 columns within 32px side margins — match index/work. */
  body:has(#about) .section--about::before {
    background-size: calc((100vw - 64px) / 4) 100%;
    background-position: 32px 0;
  }

  /* Intro headline spans the full width within 32px side margins. */
  body:has(#about) .about__headline-wrap {
    margin-left: 32px;
    margin-right: 32px;
    width: auto;
  }
  /* "(Intro)" sits as a block above the paragraph (not in the text indent). */
  body:has(#about) .about__label {
    position: static;
    display: block;
    top: auto;
    margin-bottom: 16px;
  }
  /* Paragraph: left-aligned, 26px, no first-line indent. */
  body:has(#about) .about__headline {
    text-indent: 0;
    text-align: left;
    font-size: 30px;
  }
  body:has(#about) .about-headline-line,
  body:has(#about) .about-headline-line:first-child,
  body:has(#about) .about-headline-line:last-child {
    text-indent: 0;
    text-align: left;
    text-align-last: left;
  }

  /* My Story body copy spans the full width within the 32px side margins. */
  body:has(#about) .about__text,
  body:has(#about) .about__text-footer {
    width: auto;
    margin-left: 32px;
    margin-right: 32px;
  }
  body:has(#about) .about__text { font-size: 16px; }   /* fits within the iPhone 12 viewport above the tabs */

  /* Drop the "Scroll" cue. */
  body:has(#about) .scroll-cue { display: none; }

  /* Tabs pinned bottom-right; fade out (instead of scrolling away) once the user
     scrolls down from the top section. Tab text 12px. */
  body:has(#about) .about-toggle {
    position: fixed;
    top: auto;
    bottom: 32px;
    left: auto;
    right: 32px;
    transform: translateY(var(--reveal-y, 0px));
    transition: opacity 0.3s ease;
  }
  body.scrolled:has(#about) .about-toggle,
  body.footer-visible:has(#about) .about-toggle {
    opacity: 0;
    pointer-events: none;
  }
  body:has(#about) .about-toggle__btn { font-size: 12px; }

  /* Align the fixed chrome to the 32px margin, like index/work (scoped to about
     so the not-yet-mobile sections below keep their --edge). */
  body:has(#about) .chrome__name { top: 32px; left: 32px; }
  body:has(#about) .chrome__year { top: 32px; right: 32px; }

  /* ── Experience section (under 600px) ── */
  /* Grid overlay matches the section above: 4 columns within 32px margins. */
  body:has(#about) .exp-sticky::before {
    background-size: calc((100vw - 64px) / 4) 100%;
    background-position: 32px 0;
  }
  /* Company names align to the left grid line (32px from the edge). */
  body:has(#about) .exp-names {
    left: 32px;
    width: auto;
  }
  /* Image gallery: right side (32px from the edge), spanning 2 of the 4 grid
     columns — so it sits clear of the company names on the left. */
  body:has(#about) .exp-gallery {
    left: auto;
    right: 32px;
    width: calc((100vw - 64px) / 2);
  }

  /* Cursor X/Y coordinate readout is a desktop-pointer feature — hide on mobile. */
  .cursor-coords { display: none; }

  /* Hide the "From Brand Design to Digital Experiences" section. */
  body:has(#about) #dot-2 { display: none; }

  /* ── Expertise section (under 600px) ── */
  /* Grid overlay matches the sections above (4 cols / 32px). */
  body:has(#about) .section--expertise::before {
    background-size: calc((100vw - 64px) / 4) 100%;
    background-position: 32px 0;
  }
  /* Un-pin and drop the horizontal marquee — stack the cards down the page. */
  body:has(#about) .section--expertise { height: auto; }
  body:has(#about) .expertise-sticky {
    position: static;
    height: auto;
    display: block;
    padding: 64px 0;
  }
  body:has(#about) .skill-carousel {
    gap: 32px;
    overflow: visible;
    padding: 0 32px;
  }
  body:has(#about) .skill-carousel__row { padding: 0; }
  body:has(#about) .skill-carousel__track {
    flex-direction: column;
    width: 100%;
    gap: 32px;
    animation: none !important;
  }
  /* Cards span the full width within the 32px margins. */
  body:has(#about) .skill-carousel .skill-card {
    width: 100% !important;
    box-sizing: border-box;
  }
  /* Drop the duplicated Set 2 (cards 5–8 in each track). */
  body:has(#about) .skill-carousel__track .skill-card:nth-child(n + 5) { display: none !important; }
  /* No hover on touch — keep the descriptions expanded. */
  body:has(#about) .skill-card__desc-wrap { grid-template-rows: 1fr; }

  /* Fade + rise each card in as it crosses the viewport midpoint. JS adds
     .exp-armed (so cards still show if JS is unavailable) and toggles
     .is-revealed per card via an IntersectionObserver. */
  body:has(#about) .skill-carousel.exp-armed .skill-card {
    opacity: 0;
    transform: translateY(40px);
    transition: opacity 0.6s ease,
                transform 0.6s cubic-bezier(0.22, 1, 0.36, 1),
                border-color 0.3s ease !important;
  }
  body:has(#about) .skill-carousel.exp-armed .skill-card.is-revealed {
    opacity: 1;
    transform: translateY(0);
  }
}

