.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Senul's fonts. Geist + Geist Mono self-hosted from @fontsource-variable;
   PP Editorial New is a licensed display face (Ultralight 200 + Ultrabold
   800 only — NO Regular 400, see the usage notes below each selector that
   uses it). Replaces the reference site's three licensed fonts, deleted
   from assets/fonts/. */
@font-face {
  font-family: 'Geist Variable';
  src: url('../assets/fonts/geist-latin-wght-normal.woff2') format('woff2-variations'), url('../assets/fonts/geist-latin-wght-normal.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'Geist Variable';
  src: url('../assets/fonts/geist-latin-wght-italic.woff2') format('woff2-variations'), url('../assets/fonts/geist-latin-wght-italic.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: block;
}

@font-face {
  font-family: 'Geist Mono Variable';
  src: url('../assets/fonts/geist-mono-latin-wght-normal.woff2') format('woff2-variations'), url('../assets/fonts/geist-mono-latin-wght-normal.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: block;
}

/* PP Editorial New — display face, >=32px only. Ultralight 200 for
   wordmark and italic emphasis, Ultrabold 800 for bold uppercase headings.
   There is no 400 weight; never request one. */
@font-face {
  font-family: 'PP Editorial New';
  src: url('../assets/fonts/PPEditorialNew-Ultralight.otf') format('opentype');
  font-weight: 200;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'PP Editorial New';
  src: url('../assets/fonts/PPEditorialNew-UltralightItalic.otf') format('opentype');
  font-weight: 200;
  font-style: italic;
  font-display: block;
}

@font-face {
  font-family: 'PP Editorial New';
  src: url('../assets/fonts/PPEditorialNew-Ultrabold.otf') format('opentype');
  font-weight: 800;
  font-style: normal;
  font-display: block;
}

/* Design tokens. These were referenced throughout the stylesheet but never
   declared anywhere — index.html loads no other stylesheet, so every
   var(--rule)/--fg/--font-display lookup resolved to nothing and the
   declaration using it was dropped at computed-value time. That is why
   .proj-item had no border and .proj-title fell back off the display face.
   Values are the contract in senulmapa-site/docs/DESIGN.md; about.css
   declares the same set for the standalone about page. */
:root {
  --bg: #0a0a0a;
  --fg: #f0f0f0;
  --accent: #7FA9FF;
  --rule: rgba(255, 255, 255, 0.15);
  --hairline: rgba(255, 255, 255, 0.08);
  --muted: rgba(255, 255, 255, 0.45);

  /* PP Editorial New ships ONLY weights 200 and 800 — never request 400, and
     never use this face below 32px. */
  --font-display: 'PP Editorial New', Georgia, serif;
  --font-text: 'Geist Variable', system-ui, sans-serif;
  --font-mono: 'Geist Mono Variable', ui-monospace, monospace;
}

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

html {
  width: 100%;
  max-width: 100vw;
  min-height: 100%;
  scrollbar-width: none;
  overflow-x: clip;
}

html::-webkit-scrollbar {
  display: none;
}

body {
  width: 100%;
  max-width: 100vw;
  min-height: 100%;
  overflow-x: clip;
  background: #0a0a0a;
  color: #f0f0f0;
  font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}


.intro-bg {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100dvh;
  z-index: 10000;
  background: #0a0a0a;
}


.name-layer {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100dvh;
  z-index: 10005;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  overflow: hidden;
}

.preloader-content {
  display: flex;
  align-items: baseline;
  position: relative;
  transform-origin: center center;
}


#preloader-logo {
  opacity: 0;
  position: relative;
  font-weight: 300;
  z-index: 2;
  flex-shrink: 0;
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(2.2rem, 9vw, 9rem);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: #f0f0f0;
}


#preloader-first {
  opacity: 0;
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(2.2rem, 9vw, 9rem);
  font-weight: 300;
  line-height: 1.2;
  letter-spacing: -0.08em;
  color: #f0f0f0;
  white-space: nowrap;
  position: absolute;
  left: 100%;
  top: 0;
}


#preloader-second {
  opacity: 0;
  font-family: 'PP Editorial New', Georgia, serif;
  font-weight: 200;
  font-style: italic;
  font-size: clamp(2.2rem, 9vw, 9rem);
  line-height: 1.2;
  letter-spacing: -0.08em;
  color: #f0f0f0;
  white-space: nowrap;
  position: absolute;
  top: 0;
}

#preloader-logo .char,
#preloader-second .char,
#preloader-first .char {
  overflow: visible;
}


#preloader-dot {
  font-family: 'PP Editorial New', Georgia, serif;
  font-weight: 200;
  font-style: italic;
  font-size: clamp(2.2rem, 9vw, 9rem);
  line-height: 1.2;
  color: #f0f0f0;
  position: absolute;
  top: 0;
  opacity: 0;
}


@media (max-width: 768px) {
  #preloader-first {
    position: static;
    white-space: normal;
    font-size: clamp(2.2rem, 22vw, 9rem);
  }

  #preloader-second {
    position: static;
    white-space: normal;
    font-size: clamp(2.2rem, 22vw, 9rem);
  }

  #preloader-dot {
    position: static;
    font-size: clamp(2.2rem, 22vw, 9rem);
  }
}


.transition-panel {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100dvh;
  z-index: 10002;
  pointer-events: none;
}

.t-panel-red {
  position: absolute;
  inset: 0;
  background: #7FA9FF;
  transform: translateY(100%);
  will-change: transform;
}

.t-panel-dark {
  position: absolute;
  inset: 0;
  background: #0a0a0a;
  transform: translateY(100%);
  will-change: transform;
}


.scroll-wrap {
  position: relative;
  height: 400vh;
}


.hero {
  position: sticky;
  top: 0;
  width: 100%;
  height: 100vh;
  overflow: hidden;
  opacity: 0;
}

.hero-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  z-index: 0;
  background: #222;
}

.hero-content {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 2rem 3rem;
  pointer-events: none;
}

.hero-content * {
  pointer-events: auto;
}


.hero-tagline {
  position: absolute;
  top: 3rem;
  left: 3rem;
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: 0.85rem;
  line-height: 1.7;
  letter-spacing: 0.01em;
  color: #f0f0f0;
  mix-blend-mode: difference;
  clip-path: inset(0 0 100% 0);
  opacity: 0;
  max-width: 28rem;
}

.hero-tagline .other-accent {
  font-family: 'Geist Mono Variable', ui-monospace, monospace;
}


.hero-bar {
  position: absolute;
  bottom: 3rem;
  left: 3rem;
  right: 3rem;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  mix-blend-mode: difference;
  clip-path: inset(0 0 100% 0);
  opacity: 0;
}

.hero-line {
  position: absolute;
  bottom: calc(3rem + 2.8rem);
  left: 3rem;
  right: 3rem;
  height: 1px;
  background: rgba(255, 255, 255, 0.5);
  mix-blend-mode: difference;
  transform-origin: left center;
  opacity: 0;
}


.chr-hover {
  display: inline-flex;
  gap: 0;
  cursor: default;
  text-decoration: none;
  color: inherit;
}

a.chr-hover {
  cursor: pointer;
}

.ch-wrap {
  position: relative;
  overflow: hidden;
  display: inline-block;
}

.ch-top,
.ch-bot {
  display: block;
  font-size: 1.1rem;
  font-weight: 600;
  letter-spacing: 0em;
  text-transform: uppercase;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.8);
  transition: transform 0.6s cubic-bezier(0.87, 0, 0.13, 1);
  transition-delay: calc(var(--i) * 28ms);
}

.ch-bot {
  position: absolute;
  top: 100%;
  left: 0;
}

.chr-hover:hover .ch-top {
  transform: translateY(-100%);
}

.chr-hover:hover .ch-bot {
  transform: translateY(-100%);
}

.hero-bar-left .chr-hover .ch-top,
.hero-bar-left .chr-hover .ch-bot {
  transition: none;
}

.hero-bar-left .chr-hover:hover .ch-top,
.hero-bar-left .chr-hover:hover .ch-bot {
  transform: none;
}

.hero-bar-center {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 1.5rem;
  align-items: center;
}

.hero-bar-center .sep {
  font-size: 1.2rem;
  color: rgba(255, 255, 255, 0.2);
  user-select: none;
}

.hero-bar-right {
  display: flex;
  gap: 2rem;
}

@media (max-width: 768px) {

  .hero-bar-left,
  .hero-bar-right {
    display: none;
  }

  .hero-bar-center {
    position: static;
    transform: none;
    margin: 0 auto;
  }
}


.reveal-image-wrap {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  z-index: 10002;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  overflow: hidden;
}

.reveal-image {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  will-change: transform;
  transform: scale(0);
}

.reveal-frame {
  position: absolute;
  inset: 0;
  transform: scale(0);
  will-change: transform;
  pointer-events: none;
}

.reveal-overlay {
  position: absolute;
  inset: 0;
  background: #0a0a0a;
  opacity: 0;
  pointer-events: none;
}


.reveal-corner {
  position: absolute;
  width: 18px;
  height: 18px;
  z-index: 2;
  pointer-events: none;
}

.reveal-corner::before,
.reveal-corner::after {
  content: '';
  position: absolute;
  background: #fff;
}

.reveal-corner::before {
  width: 1px;
  height: 100%;
  left: 50%;
  top: 0;
}

.reveal-corner::after {
  height: 1px;
  width: 100%;
  top: 50%;
  left: 0;
}

.reveal-corner.tl {
  top: 2rem;
  left: 2rem;
}

.reveal-corner.tr {
  top: 2rem;
  right: 2rem;
}

.reveal-corner.bl {
  bottom: 2rem;
  left: 2rem;
}

.reveal-corner.br {
  bottom: 2rem;
  right: 2rem;
}


.reveal-phrase {
  position: absolute;
  z-index: 2;
  font-family: 'Geist Mono Variable', ui-monospace, monospace;
  font-size: clamp(1.1rem, 2.2vw, 2rem);
  color: #f0f0f0;
  text-align: center;
  letter-spacing: -0.02em;
  line-height: 1.1;
  pointer-events: none;
}

.reveal-phrase .rp-char {
  text-shadow: 0 0 18px rgba(0, 0, 0, 0.9), 0 0 40px rgba(0, 0, 0, 0.6);
}


.section-after {
  position: relative;
  min-height: 100vh;
  background: #0a0a0a;
  z-index: 20010;
}

.section-after::before {
  content: '';
  position: absolute;
  top: -30vh;
  left: 0;
  right: 0;
  height: 30vh;
  background: linear-gradient(to top, #0a0a0a, transparent);
  pointer-events: none;
  z-index: 3;
}


.about {
  position: relative;
  min-height: 100vh;
  padding: 45vh 4rem 80vh;
}

.about-text {
  position: relative;
  /* Above the full-width lanyard canvas, which would otherwise swallow this
     link's clicks now that it covers the whole section. */
  z-index: 30020;
  width: 66.6%;
  font-family: 'Geist Variable', system-ui, sans-serif;
  /* Floor raised 1.8rem -> 2rem: the .other-accent spans inside this line are
     PP Editorial (display face, >=32px only) and INHERIT this size, so a
     28.8px floor rendered them hairline between ~769px and ~1066px. */
  font-size: clamp(2rem, 3.6vw, 3.4rem);
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: #f0f0f0;
}

.about-text .word,
.about-sub .word,
.about-version .word {
  display: inline-block;
  opacity: 0;
  filter: blur(8px);
  transition: none;
}

@media (hover: none) and (pointer: coarse) {
  .about-text .word,
  .about-sub .word,
  .about-version .word {
    filter: none;
  }
}

.about-text .other-accent {
  font-family: 'PP Editorial New', Georgia, serif;
  font-weight: 200;
  font-style: italic;
}

.about-sub .other-accent {
  font-family: 'Geist Mono Variable', ui-monospace, monospace;
}

.about-sub {
  position: relative;
  z-index: 20;
  width: 23%;
  margin-top: 12vh;
  margin-left: 20%;
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(0.9rem, 1.8vw, 1.7rem);
  line-height: 1.6;
  letter-spacing: -0.005em;
  color: #f0f0f0;
}

.about-btn {
  position: relative;
  z-index: 20;
  margin-top: 3.5rem;
  margin-left: 20%;
}

.about-version {
  position: relative;
  z-index: 20;
  width: 66.6%;
  margin-top: 16vh;
  text-align: right;
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(1.8rem, 3.6vw, 3.4rem);
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: #f0f0f0;
}


.projects {
  position: relative;
  /* This is the pinned element, so its own height IS the stage. Any padding
     here pushes the wheel below the fold during the pin — 20vh top+bottom made
     it 1260px tall in a 900px viewport. Vertical rhythm lives on the sections
     either side instead. */
  padding: 0;
  height: 100vh;
  min-height: 100vh;
  z-index: 20010;

  /* Stage tokens live here so BOTH .projects-track and its parent
     .projects-track-wrap can read them (a child cannot supply a var to its
     own parent). --band-amp must match the AMP constant used when building
     the band path in js/index.js: the band arcs that far above and below the
     card row, so the stage has to be taller than the cards by 2x that or the
     SVG clips the whole arc away. */
  --card-w: clamp(300px, 44vw, 600px);
  --card-h: min(38vh, 340px);
  --band-amp: 90px;
  --stage-h: calc(var(--card-h) + var(--band-amp) * 2 + 60px);
}

.projects-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  gap: 1.5rem;
}

.projects-track {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4rem;
  height: var(--stage-h);
  flex: 0 0 auto;
  will-change: transform;
  padding-inline: calc((100vw - var(--card-w)) / 2);
}

.projects-track-wrap {
  position: relative;
  width: 100%;
  overflow: hidden;
  height: var(--stage-h);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
}

/* Left blur panel: progressive gaussian blur with linear-gradient mask */
.projects-track-wrap::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 18vw;
  z-index: 10;
  pointer-events: none;
  background: linear-gradient(to right, #0a0a0a, transparent 80%);
}

.projects-track-wrap::before {
  box-shadow: inset 0 0 60px 40px rgba(0, 0, 0, 0.8), inset 0 0 40px 60px rgba(0, 0, 0, 0.4);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  -webkit-mask-image: linear-gradient(to right, #000 0%, rgba(0, 0, 0, 0.6) 40%, transparent 100%);
  mask-image: linear-gradient(to right, #000 0%, rgba(0, 0, 0, 0.6) 40%, transparent 100%);
}

/* Right blur panel: progressive gaussian blur with linear-gradient mask */
.projects-track-wrap::after {
  content: '';
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: 18vw;
  z-index: 10;
  pointer-events: none;
  background: linear-gradient(to left, #0a0a0a, transparent 80%);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  -webkit-mask-image: linear-gradient(to left, #000 0%, rgba(0, 0, 0, 0.6) 40%, transparent 100%);
  mask-image: linear-gradient(to left, #000 0%, rgba(0, 0, 0, 0.6) 40%, transparent 100%);
}

.proj-item {
  position: relative;
  z-index: 1;
  flex: 0 0 var(--card-w);
  width: var(--card-w);
  height: var(--card-h);
  background: #101010;
  border: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  cursor: pointer;
  transition: filter 0.4s ease;
  opacity: 1;
  /* Two independent filter channels driven by custom properties. The entrance
     animation animates --enter-blur; the active/inactive state sets --dim.
     Writing a whole `filter` string from GSAP instead would put it INLINE,
     and inline beats the .active class — card 1 could then never brighten. */
  --enter-blur: 0px;
  --dim: 0.55;
  filter: blur(var(--enter-blur)) brightness(var(--dim));
}

.proj-item.active {
  --dim: 1;
}

.proj-cover {
  flex: 1 1 auto;
  overflow: hidden;
}

.proj-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.proj-meta {
  flex: 0 0 auto;
  padding: 1.25rem 1.5rem;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.proj-index,
.proj-tag {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.proj-title {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: clamp(2rem, 3.2vw, 3rem);
  color: var(--fg);
  margin: 0;
  line-height: 1;
}

.projects-wheel-mount {
  position: relative;
  width: 100%;
  height: auto;
  flex: 0 0 auto;
  /* The wheel is now the ONLY place project names appear, so it needs room to
     render them in full. Vertical text is as long as the name: "bag counting"
     at 2.2rem runs ~230px, which a 148px slot was clipping mid-word. */
  min-height: 285px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem 2rem;
  /* The wheel curves items outward past its own box; clip them here so nothing
     bleeds below the pinned 100vh stage. */
  overflow: hidden;
  z-index: 11;
}

.proj-preview {
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  width: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 800px;
  pointer-events: none;
  opacity: 0;
  z-index: 30005;
  transition: opacity 0.4s ease;
}

.proj-preview.visible {
  /* Deliberately NOT pointer-events:auto. This is a fixed 50%-width panel over
     the right half of the viewport, left from the reference site's hover-cover
     design which the horizontal card stage replaced. With events enabled it
     sat on top of everything (z-index 30005) and swallowed every mouse event
     on the right half of the page — including drags on the lanyard. */
  pointer-events: none;
  opacity: 1;
}

.proj-card {
  position: relative;
  width: min(30vw, 440px);
  transition: opacity 0.4s ease;
  will-change: transform;
  transform-style: preserve-3d;
}

.proj-card img {
  width: 100%;
  height: auto;
  display: block;
  object-fit: cover;
  cursor: none;
}

.proj-meta {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.6rem;
}

.proj-date,
.proj-label {
  font-family: 'Inter', sans-serif;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: rgba(255, 255, 255, 0.45);
  text-transform: uppercase;
}

.proj-cursor {
  position: fixed;
  pointer-events: none;
  z-index: 30006;
  opacity: 0;
  transform: translate(-50%, -50%);
  background: #f0f0f0;
  color: #0a0a0a;
  font-family: 'Inter', sans-serif;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 1em 1.6em;
  border-radius: 50px;
  transition: opacity 0.3s ease, transform 0.3s ease;
  white-space: nowrap;
}

.proj-cursor.active {
  opacity: 1;
}


.circle-gallery {
  position: relative;
  height: 600vh;
  background: #0a0a0a;
  z-index: 20001;
  overflow: hidden;
}

.circle-gallery-pin {
  position: relative;
  width: 100%;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 1200px;
}

.cg-img {
  position: absolute;
  width: clamp(120px, 14vw, 210px);
  aspect-ratio: 3/2;
  transform-style: preserve-3d;
}

.cg-slice {
  position: absolute;
  top: 0;
  height: 100%;
  will-change: transform;
}

.cg-phrase {
  position: absolute;
  font-family: 'Geist Variable', system-ui, sans-serif;
  /* Floor raised 1.6rem -> 2rem for the same reason as .about-text: the
     display-face accent span inherits this size. */
  font-size: clamp(2rem, 3.5vw, 3.2rem);
  color: #fff;
  text-align: center;
  max-width: 600px;
  line-height: 1.3;
  will-change: opacity, transform;
  z-index: 900;
}

.cg-phrase .word {
  display: inline-block;
  opacity: 0;
  filter: blur(8px);
}

.cg-phrase .other-accent {
  font-family: 'PP Editorial New', Georgia, serif;
  font-weight: 200;
  font-style: italic;
}


.fluid-line-svg {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
}

#fluid-line-svg-bg {
  z-index: 0;
}

#fluid-line-svg-mask {
  z-index: 2;
}

.fluid-line {
  fill: none;
  stroke: #7FA9FF;
  stroke-width: 72;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Spans the WHOLE about section rather than occupying one fixed 720px window
   partway down it. Previously the lanyard sat at top:20vh and the manifesto
   copy simply scrolled past it, so for most of the section there was nothing
   on the right at all and it read as missing. The wrap is the sticky container;
   the lanyard itself sticks inside it. pointer-events:none here keeps the copy
   underneath selectable — the canvas re-enables them for itself. */
.about-photo-wrap {
  position: absolute;
  /* Spans the FULL section width, not a 660px box on the right. The canvas is
     the only surface that can receive a drag, so the card could never be pulled
     beyond its own container. It now has the whole section to swing across;
     the camera is offset in Lanyard.jsx to keep the card sitting
     right-of-centre, where it appeared before. */
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 30010;
  width: auto;
  overflow: visible;
  pointer-events: none;
}

.about-photo {
  width: min(55vw, 820px);
  height: auto;
  object-fit: cover;
  display: block;
  will-change: transform;
  opacity: 0;
  filter: blur(20px);
  border-radius: 280px 0 0 280px;
}

.lanyard-wrapper {
  width: 100% !important;
  /* Sticky inside the full-height .about-photo-wrap: the lanyard stays parked
     in the viewport for the entire time the about section scrolls past, so it
     is always on screen and always grabbable. 18vh + 64vh leaves it optically
     centred. overflow must stay visible or the card is clipped mid-swing. */
  /* !important because the island injects its own stylesheet from JS at
     runtime, which lands later in the cascade and re-asserts position:relative
     at equal specificity — which silently un-sticks this. */
  position: sticky !important;
  top: 18vh !important;
  height: min(76vh, 700px) !important;
  overflow: visible;
  pointer-events: auto;
  z-index: 30011;
}

.lanyard-wrapper canvas {
  pointer-events: auto !important;
}


.page-fade {
  position: fixed;
  inset: 0;
  background: #0a0a0a;
  z-index: 30012;
  opacity: 0;
  pointer-events: none;
}


.work-transition-overlay {
  position: fixed;
  inset: 0;
  background: #0a0a0a;
  z-index: 30030;
  opacity: 0;
  pointer-events: none;
}

.work-flying-text {
  position: fixed;
  z-index: 30035;
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-weight: 300;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: #f0f0f0;
  pointer-events: none;
  white-space: nowrap;
  opacity: 0;
  will-change: transform;
}


.flying-title {
  position: fixed;
  z-index: 30030;
  font-family: 'Geist Variable', system-ui, sans-serif;
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: #f0f0f0;
  pointer-events: none;
  white-space: nowrap;
  opacity: 0;
  will-change: transform;
  transform-origin: left top;
}


.project-detail {
  position: fixed;
  inset: 0;
  z-index: 30015;
  background: #0a0a0a;
  pointer-events: none;
  opacity: 0;
  overflow: hidden;
}

.project-detail.active {
  pointer-events: auto;
}

.detail-back {
  position: absolute;
  top: 3rem;
  left: 3rem;
  z-index: 2;
  cursor: pointer;
  opacity: 0;
}

.detail-info {
  width: 70%;
  height: 100vh;
  padding: 22vh 4rem 4rem;
  display: flex;
  flex-direction: column;
}

.detail-title-wrap {
  display: flex;
  align-items: baseline;
  gap: 0.8rem;
  opacity: 0;
}

.detail-title {
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(3rem, 5vw, 5rem);
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: #f0f0f0;
  white-space: nowrap;
}

.detail-year {
  font-family: 'Inter', sans-serif;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: #f0f0f0;
  white-space: nowrap;
}

.detail-desc {
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(0.85rem, 1.1vw, 1.05rem);
  line-height: 1.8;
  color: rgba(255, 255, 255, 0.45);
  max-width: 28rem;
  margin-top: 2.5rem;
  opacity: 0;
}

.detail-tags {
  display: flex;
  gap: 0.5rem;
  margin-top: 1.5rem;
  flex-wrap: wrap;
  opacity: 0;
}

.detail-tag {
  font-family: 'Inter', sans-serif;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.1);
  padding: 0.4em 0.8em;
  border-radius: 50px;
}

.detail-gallery-wrap {
  position: absolute;
  top: 0;
  right: 0;
  width: 55%;
  height: 100vh;
  display: flex;
  align-items: stretch;
}

.detail-thumbs {
  width: 14%;
  height: 100vh;
  overflow-y: clip;
  overflow-x: visible;
  flex-shrink: 0;
}

.detail-thumbs-inner {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 50vh 0;
  will-change: transform;
}

.detail-thumbs-inner img {
  width: 100%;
  height: auto;
  display: block;
  opacity: 0.4;
  cursor: pointer;
  transition: opacity 0.3s ease;
}

.detail-thumbs-inner img.active {
  opacity: 1;
}

.detail-selected {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  perspective: 800px;
}

.detail-selected img {
  max-width: 100%;
  max-height: 85vh;
  height: auto;
  display: block;
  object-fit: contain;
  will-change: transform;
  transform-style: preserve-3d;
}


.scroll-timeline {
  position: fixed;
  right: 2rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 30015;
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 80vh;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s;
}

.scroll-timeline.visible {
  opacity: 1;
}

.st-label {
  position: absolute;
  right: calc(100% + 14px);
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(0.8rem, 1vw, 0.95rem);
  color: #fff;
  white-space: nowrap;
  letter-spacing: 0.02em;
}

.scroll-pct {
  position: fixed;
  left: 2rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 30015;
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(0.8rem, 1vw, 0.95rem);
  color: #fff;
  letter-spacing: 0.02em;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s;
}

.scroll-pct.visible {
  opacity: 1;
}

.st-bar {
  position: relative;
  width: 2px;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.st-seg {
  flex: 1;
  position: relative;
  background: rgba(255, 255, 255, 0.08);
  cursor: pointer;
  pointer-events: auto;
  transform-origin: center;
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}

.st-seg:hover {
  transform: scaleX(3);
}

.scroll-timeline:not(.visible) .st-seg {
  pointer-events: none;
}

.st-seg::before {
  content: '';
  position: absolute;
  inset: 0 -10px;
}

.st-seg-fill {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 0%;
  background: #fff;
}


.skills {
  position: relative;
  min-height: 100vh;
  padding-bottom: 30vh;
  background: #0a0a0a;
  z-index: 30010;
}

.skills-inner {
  position: relative;
  z-index: 30013;
  display: flex;
  align-items: flex-start;
  min-height: 100vh;
}

.skills-left {
  position: sticky;
  top: 0;
  /* Was 60/40. The skills list is now large display type on one line per row,
     so the width had to move to the right column or "AGENT INFRASTRUCTURE"
     (727px set at the 4rem clamp ceiling) wraps mid-word. */
  width: 36%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding-top: 15vh;
  padding-left: clamp(1.5rem, 5vw, 8rem);
  padding-right: 3rem;
  flex-shrink: 0;
}

.skills-subtitle {
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  font-weight: 300;
  letter-spacing: -0.02em;
  color: #f0f0f0;
  margin-bottom: 2.5rem;
  line-height: 1.2;
}

.skills-text {
  font-family: 'PP Editorial New', Georgia, serif;
  /* Floor is 2rem = 32px, NOT 1.8rem. This is the display face, which must
     never render below 32px — at a 1.8rem floor it dropped to ~29px for every
     viewport between ~769px (where the mobile override stops) and ~1066px
     (where 3vw finally clears 32px), a window neither 1440 nor 390 testing
     catches. */
  font-size: clamp(2rem, 3vw, 2.5rem);
  font-weight: 800;
  font-style: normal;
  text-transform: uppercase;
  line-height: 1.1;
  color: #f0f0f0;
  max-width: 100%;
  margin-bottom: 0;
}

.skills-text .other-accent {
  font-family: 'PP Editorial New', Georgia, serif;
  font-weight: 800;
  font-style: normal;
}

.skills-separator {
  width: 100%;
  height: 1px;
  background: rgba(255, 255, 255, 0.15);
  margin: 3rem 0;
}

.skills-contact {
  display: inline-flex;
  cursor: pointer;
  text-decoration: none;
  color: inherit;
  margin-bottom: 3rem;
}

.skills-arrow {
  font-size: clamp(8rem, 14vw, 12rem);
  color: #7FA9FF;
  line-height: 1;
  user-select: none;
  pointer-events: none;
  will-change: transform;
}

.skills-right {
  width: 64%;
  /* No left padding: the row hairlines start flush at the column edge, which
     is what makes the list read as a stack of rules rather than a boxed panel.
     The right gutter has to clear the fixed scroll-progress rail pinned to the
     viewport edge — at 2rem the thumbnails ran straight into it. */
  padding: 15vh clamp(1.5rem, 4vw, 4.5rem) 50vh 0;
  flex-shrink: 0;
  min-width: 0;
}

/* ------------------------------------------------------------- skills list */
/* One capability per row: the display-face name gets the FULL row width, and
   the mono tool line runs underneath with the project covers that prove it
   inline on its right. Hairline separates rows, last row closes the stack.

   The name deliberately does NOT share a line with the thumbnails. Measured:
   "AGENT INFRASTRUCTURE" sets 727px at the 4rem clamp ceiling, and a
   name-beside-thumbnails row only leaves it 656px — it broke to two lines
   while every other row stayed on one, which read as a mistake. Given the
   whole width it clears with ~100px to spare. */
.skill-row {
  display: flex;
  flex-direction: column;
  gap: clamp(0.6rem, 1.1vw, 1rem);
  padding: clamp(1.4rem, 2.6vw, 2.4rem) 0;
  border-top: 1px solid var(--hairline);

  /* Dimming is a FILTER, never opacity: an opacity < 1 would make the row a
     translucent layer over #0a0a0a and every hairline/edge would go soft.
     Same --dim custom-property channel .proj-item uses, so the two sections
     brighten identically.

     The DEFAULT is full contrast, and the dim is applied only where a real
     pointer exists. Written the other way round — dim by default, brighten on
     :hover — a touch device gets a permanently 62%-brightness list it can
     never light up, because it can never hover. */
  --dim: 1;
  filter: brightness(var(--dim));
}

.skill-row:last-child {
  border-bottom: 1px solid var(--hairline);
}

@media (hover: hover) and (pointer: fine) {
  .skill-row {
    --dim: 0.62;
  }

  .skill-row:hover {
    --dim: 1;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .skill-row {
    transition: filter 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  }

  .skill-shot {
    transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
      filter 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  }
}

/* Tool line + its proof imagery share one band under the name. */
.skill-row-foot {
  display: flex;
  align-items: center;
  gap: clamp(1.5rem, 3vw, 3rem);
}

.skill-name {
  font-family: var(--font-display);
  /* PP Editorial New ships Ultralight 200 and Ultrabold 800 ONLY — there is no
     400 to request. The clamp floor is 2rem = 32px, the display face's minimum
     size; below that it is replaced outright in the <=768px block. */
  font-weight: 200;
  font-size: clamp(2rem, 5vw, 4rem);
  line-height: 1.02;
  letter-spacing: -0.01em;
  color: #f0f0f0;
  /* Spacing to the tool line is the row's flex `gap`, not a margin here —
     two sources of the same space is how it drifts. */
  margin: 0;
  text-transform: uppercase;
}

.skill-tools {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  line-height: 1.75;
  color: var(--muted);
  margin: 0;
  /* min-width:0 is what stops a long uppercase run from forcing the row wider
     than .skills-right and pushing document scrollWidth past the viewport. */
  flex: 1 1 auto;
  min-width: 0;
}

.skill-shots {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 0 0 auto;
  margin-left: auto;
}

.skill-shot {
  width: clamp(80px, 9vw, 130px);
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  transform-origin: center;
}

/* Same rule as the row: fully visible by default, held back only where a
   pointer exists to bring them forward again. Brightness + scale, never
   opacity — these sit directly on the section background and translucency
   would wash them out instead of dimming them. */
@media (hover: hover) and (pointer: fine) {
  .skill-shot {
    filter: brightness(0.72);
    transform: scale(0.94);
  }

  .skill-row:hover .skill-shot {
    filter: brightness(1);
    transform: scale(1);
  }
}


.contact {
  position: relative;
  min-height: 180vh;
  z-index: 30015;
  overflow: hidden;
}

.contact-pin {
  position: sticky;
  top: 0;
  height: 100vh;
  z-index: 20006;
}

.contact-bg {
  position: fixed;
  inset: 0;
  background: #0a0a0a;
  z-index: 5;
  pointer-events: none;
  display: none;
}

.contact-blob-wrap {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 0;
  z-index: 30012;
  pointer-events: none;
  display: flex;
  justify-content: center;
  align-items: flex-end;
}

.contact-blob {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 300vmax;
  height: 300vmax;
  background: #f0f0f0;
  /* An organic ink-bloom, not a disc and not a star. A star reads as spikes
     once it fills the viewport; a plain circle is inert. Asymmetric
     border-radius (four horizontal / four vertical radii) gives a liquid edge,
     and morphing those radii makes the bloom feel alive as it expands.
     border-radius is animated rather than transform, because GSAP owns this
     element's transform for the scale(0) -> scale(1) reveal. */
  border-radius: 42% 58% 63% 37% / 41% 44% 56% 59%;
  transform: translate(-50%, 50%) scale(0);
  will-change: transform;
}

.contact-title {
  position: absolute;
  top: 6%;
  left: 6%;
  max-width: 62%;
  font-family: 'PP Editorial New', Georgia, serif;
  /* Senul's headline is a full question, not a single word like the
     reference's — smaller clamp + wrap so it doesn't run under the
     contact-frame image or off the right edge (.contact has
     overflow:hidden, so nowrap here would silently clip). */
  font-size: clamp(2.2rem, 5vw, 4.6rem);
  font-weight: 800;
  font-style: normal;
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: #0a0a0a;
  z-index: 3;
  pointer-events: none;
  white-space: normal;
  will-change: transform;
}

.contact-frame {
  position: absolute;
  top: 10%;
  left: 75%;
  width: 25%;
  aspect-ratio: 2/3;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
  will-change: transform, filter;
}

.frame-corner {
  position: absolute;
  width: 14px;
  height: 14px;
  z-index: 5;
  pointer-events: none;
}

.frame-corner::before,
.frame-corner::after {
  content: '';
  position: absolute;
  background: #fff;
}

.frame-corner::before {
  width: 1px;
  height: 100%;
  left: 50%;
  transform: translateX(-50%);
}

.frame-corner::after {
  height: 1px;
  width: 100%;
  top: 50%;
  transform: translateY(-50%);
}

.frame-corner.tl {
  top: 8px;
  left: 8px;
}

.frame-corner.tr {
  top: 8px;
  right: 8px;
}

.frame-corner.bl {
  bottom: 8px;
  left: 8px;
}

.frame-corner.br {
  bottom: 8px;
  right: 8px;
}

.contact-frame-img {
  width: 100%;
  height: 145%;
  object-fit: cover;
  display: block;
  will-change: transform;
}

.contact-dispo {
  position: absolute;
  width: 12.5%;
  text-align: justify;
  z-index: 4;
  pointer-events: none;
  will-change: transform, opacity;
}

#contact-dispo {
  top: 32%;
  left: 56.25%;
}

.contact-dispo p {
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(0.95rem, 1.5vw, 1.35rem);
  line-height: 1;
  letter-spacing: -0.005em;
  color: #0a0a0a;
  margin: 0.65rem 0;
}

.contact-dispo .other-accent {
  font-family: 'Geist Mono Variable', ui-monospace, monospace;
}

#contact-frame-2 {
  top: 70%;
  left: 50%;
}

#contact-dispo-2 {
  top: 72%;
  left: 81.25%;
  right: auto;
}

.contact-bottom {
  position: absolute;
  bottom: 8vh;
  left: 6%;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 1.4rem;
  align-items: flex-start;
}

.contact-socials {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  align-items: flex-start;
  clip-path: inset(0 0 100% 0);
}

.contact-socials .chr-hover {
  font-size: clamp(1.2rem, 2vw, 1.8rem);
}

.contact-socials .chr-hover .ch-top,
.contact-socials .chr-hover .ch-bot {
  color: #0a0a0a;
  font-size: inherit;
  text-transform: none;
  letter-spacing: -0.01em;
  font-family: 'Geist Variable', system-ui, sans-serif;
}

.contact-mail {
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: clamp(1rem, 1.5vw, 1.25rem);
  color: #0a0a0a;
  text-decoration: none;
  clip-path: inset(0 0 100% 0);
  transition: opacity 0.3s ease;
  letter-spacing: -0.01em;
}

.contact-mail:hover {
  opacity: 0.4;
}


.footer-transition {
  position: relative;
  height: 80vh;
  z-index: 20007;
  pointer-events: none;
}

.footer {
  position: fixed;
  inset: 0;
  height: 100vh;
  overflow: hidden;
  z-index: 20008;
  visibility: hidden;
  background: #0a0a0a;
}

.footer-content {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.footer-ascii-wrap {
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: space-between;
  pointer-events: none;
  overflow: hidden;
}

.footer-ascii {
  width: 50%;
  height: 100%;
  overflow: hidden;
  display: flex;
  align-items: center;
  will-change: transform;
}

.footer-ascii pre {
  font-family: 'Geist Mono Variable', 'Courier New', monospace;
  font-size: clamp(0.35rem, 0.5vw, 0.6rem);
  line-height: 1.1;
  letter-spacing: 0.5em;
  color: #7FA9FF;
  white-space: pre;
  user-select: none;
  opacity: 0.7;
  pointer-events: auto;
  cursor: default;
}

.footer-ascii.left {
  justify-content: flex-start;
}

.footer-ascii.right {
  justify-content: flex-end;
}

.footer-top {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  padding: 2.5rem 3rem;
}

.footer-top-col {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.footer-top-col a {
  text-decoration: none;
}

.footer-top-col .chr-hover .ch-top,
.footer-top-col .chr-hover .ch-bot {
  color: #fff;
}

.footer-top-col .footer-mail .ch-top,
.footer-top-col .footer-mail .ch-bot {
  text-transform: none;
}

.footer-top-col .footer-date .ch-top,
.footer-top-col .footer-date .ch-bot {
  color: #fff;
  pointer-events: none;
}

.footer-top-col .footer-date .ch-wrap {
  cursor: default;
}

.footer-name {
  position: absolute;
  bottom: 0;
  left: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  width: 100%;
  padding: 0 1.5vw;
  white-space: nowrap;
}

.footer-name-first {
  font-family: 'Geist Variable', system-ui, sans-serif;
  font-size: 17vw;
  font-weight: 300;
  line-height: 0.8;
  letter-spacing: -0.06em;
  color: #f0f0f0;
}

.footer-name-first .first-letter {
  letter-spacing: -0.02em;
}

.footer-name-second {
  font-family: 'PP Editorial New', Georgia, serif;
  font-weight: 200;
  font-style: italic;
  font-size: 17vw;
  line-height: 0.8;
  letter-spacing: -0.06em;
  color: #f0f0f0;
}

.footer-name-dot {
  font-family: 'PP Editorial New', Georgia, serif;
  font-weight: 200;
  font-style: italic;
  font-size: 17vw;
  line-height: 0.8;
  color: #7FA9FF;
}


@media (max-width: 768px) {

  
  
  .hero {
    height: 100dvh;
  }

  .hero-content {
    padding: 1.5rem;
  }

  .hero-tagline {
    top: 1.5rem;
    left: 1.5rem;
    font-size: 0.75rem;
    max-width: calc(100vw - 3rem);
  }

  .hero-line {
    bottom: calc(1.5rem + 2.8rem);
    left: 1.5rem;
    right: 1.5rem;
  }

  .hero-bar {
    bottom: 1.5rem;
    left: 1.5rem;
    right: 1.5rem;
  }

  .hero-bar .chr-hover .ch-top,
  .hero-bar .chr-hover .ch-bot {
    font-size: 0.9rem;
  }

  
  .about {
    padding: 12vh 1.5rem 20vh;
  }

  .about-text {
    width: 100%;
  }

  /* about-text's clamp() floors to 28.8px under ~888px viewport width —
     under the display face's 32px floor. Drop the accent to mono here so
     it never renders PP Editorial New below its safe size. */
  .about-text .other-accent {
    font-family: 'Geist Mono Variable', ui-monospace, monospace;
    font-style: normal;
    font-weight: 400;
  }

  .about-sub {
    width: 100%;
    margin-left: 0;
    margin-top: 5vh;
  }

  .about-btn {
    margin-left: 0;
  }

  .about-version {
    width: 100%;
    margin-top: 8vh;
  }

  .about-photo-wrap {
    position: relative;
    right: auto;
    top: auto;
    margin-top: 6vh;
    width: 100%;
    height: 400px;
  }

  .about-photo {
    width: 100%;
    height: 400px;
    border-radius: 140px 140px 0 0;
  }

  .lanyard-wrapper {
    width: 100% !important;
    height: 400px !important;
    border-radius: 140px 140px 0 0;
  }


  .projects {
    padding: 35vh 0 10vh;
    /* No pin on mobile — the stage is a plain vertical stack, so it must grow. */
    height: auto;
    z-index: 30030;
    position: relative;
  }

  .projects-inner {
    flex-direction: column;
  }

  .projects-track-wrap {
    height: auto;
    overflow: hidden;
  }

  .projects-track {
    flex-direction: column;
    height: auto;
    padding-inline: 1.5rem;
    gap: 2rem;
  }

  .proj-item {
    width: 100%;
    flex-basis: auto;
    height: auto;
    opacity: 1;
  }

  .proj-cover {
    aspect-ratio: 3/2;
  }

  .projects-wheel-mount {
    display: none;
  }

  .fluid-line-svg {
    display: none;
  }

  .proj-preview {
    display: none !important;
  }

  .proj-cursor {
    display: none;
  }

  
  .circle-gallery {
    display: none !important;
  }
  .cg-phrase {
    display: none !important;
  }

  
  .skills-inner {
    flex-direction: column;
  }

  .skills-left {
    position: relative;
    width: 100%;
    height: auto;
    padding: 8vh 1.5rem 3vh;
  }

  .skills-right {
    width: 100%;
    padding: 0 1.5rem 15vh;
  }

  .skills-arrow {
    font-size: clamp(4rem, 20vw, 7rem);
  }

  /* Skills list on small screens. The dim/scale states are gated behind
     hover:hover + pointer:fine, so a touch device is already at full contrast
     with the thumbnails fully visible — nothing here has to undo them. This
     block only handles LAYOUT: rows stack, type comes down, thumbnails move
     under the text. (A narrow desktop window does still get the hover lift,
     which is correct — it has a mouse.) */
  .skill-row {
    gap: 1rem;
    padding: 1.75rem 0;
  }

  /* The tool line and the thumbnails stop sharing a band and stack, so the
     mono run gets the full column instead of ~55% of a 342px viewport. */
  .skill-row-foot {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.1rem;
  }

  .skill-name {
    /* 5vw floors to 32px only above a 640px viewport; at 390px it would ask
       PP Editorial New for ~20px, well under the display face's 32px minimum.
       Swap the family out rather than render the serif small. */
    font-family: 'Geist Variable', system-ui, sans-serif;
    font-weight: 300;
    font-size: clamp(1.75rem, 8vw, 2.5rem);
    letter-spacing: -0.02em;
  }

  .skill-tools {
    font-size: 0.6rem;
    letter-spacing: 0.16em;
    line-height: 1.9;
  }

  .skill-shots {
    margin-left: 0;
    gap: 0.5rem;
  }

  .skill-shot {
    width: clamp(84px, 26vw, 120px);
  }

  /* skills-text's clamp() floors to 28.8px under ~1000px viewport width —
     under the display face's 32px floor. Same fix as about-text above. */
  .skills-text,
  .skills-text .other-accent {
    font-family: 'Geist Variable', system-ui, sans-serif;
    font-style: normal;
  }


  .contact-title {
    font-size: clamp(3.5rem, 15vw, 6rem);
    top: 15%;
    left: 50%;
    width: 100%;
    text-align: center;
    transform: translateX(-50%) !important;
  }

  .contact-frame {
    display: none !important;
  }

  .contact-dispo {
    width: 85%;
    left: 50% !important;
    transform: translateX(-50%) !important;
    text-align: center;
  }

  #contact-dispo {
    top: 36%;
  }

  #contact-dispo-2 {
    top: 54%;
    right: auto;
  }

  .contact-dispo p {
    font-size: clamp(0.95rem, 4vw, 1.2rem);
    line-height: 1.4;
    text-align: center;
  }

  .contact-bottom {
    left: 50%;
    transform: translateX(-50%) !important;
    bottom: 8vh;
    align-items: center;
    width: 100%;
  }

  .contact-socials {
    align-items: center;
  }

  
  .project-detail {
    overflow-y: auto;
  }

  .detail-info {
    width: 100%;
    height: auto;
    padding: 5rem 1.5rem 2rem;
  }

  .detail-gallery-wrap {
    position: relative;
    width: 100%;
    height: auto;
    top: auto;
    right: auto;
  }

  .detail-back {
    top: 1.5rem;
    left: 1.5rem;
  }

  .detail-thumbs {
    width: 18%;
  }

  
  .scroll-timeline {
    display: none;
  }

  .scroll-pct {
    display: none;
  }

  
  .footer-top {
    position: absolute;
    inset: 0;
    padding: 0;
    display: block;
  }

  .footer-top-col {
    position: absolute;
    width: auto;
    gap: 0.8rem;
  }

  .footer-top-col:nth-child(1) {
    
    top: 60vh;
    left: 50%;
    transform: translateX(-50%);
    align-items: center;
    text-align: center;
    width: 100%;
  }

  .footer-top-col:nth-child(2) {
    
    top: 2rem;
    right: 2rem;
    align-items: flex-end;
    text-align: right;
  }

  .footer-top-col:nth-child(3) {
    
    top: 2rem;
    left: 2rem;
    align-items: flex-start;
    text-align: left;
  }

  .footer-ascii-wrap {
    display: flex;
    overflow: visible;
  }

  .footer-ascii {
    width: max-content;
    overflow: visible;
  }
}

@media (max-width: 1200px) {
  .footer-ascii.right {
    display: none !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --- AWARDS SECTION --- */
.awards {
  position: relative;
  background: #0a0a0a;
  z-index: 30010;
  padding: 0 4rem 15vh;
}
.awards-inner {
  max-width: 100%;
}
.awards-list {
  display: flex;
  flex-direction: column;
}
.award-item {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 2rem 1rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.15);
  font-family: 'Inter', sans-serif;
  font-size: clamp(1rem, 2vw, 1.5rem);
  color: rgba(255, 255, 255, 0.3);
  transition: color 0.4s ease;
  cursor: none;
  z-index: 1;
}
.award-item::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background-color: #ffffff;
  z-index: -1;
  clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
  transition: clip-path 0.5s cubic-bezier(0.77, 0, 0.175, 1);
}
.award-item:first-child {
  border-top: 1px solid rgba(255, 255, 255, 0.15);
}
.award-item > div {
  flex: 1;
}
.award-org {
  text-align: left;
}
.award-site {
  text-align: center;
}
.award-prize {
  text-align: center;
}
.award-date {
  text-align: right;
}

@media (max-width: 768px) {
  .awards {
    padding: 0 1.5rem 10vh;
  }
  .award-item {
    font-size: clamp(0.7rem, 3vw, 1rem);
  }
  .award-date {
    display: none;
  }
  .award-org {
    flex: 1.5;
  }
}

.awards-title {
  padding: 0;
  margin-bottom: 2rem;
}

.active-award {
  color: #000000 !important;
}
.active-award::before {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}

.awards {
  margin-top: -15vh;
  margin-bottom: 0;
  padding-bottom: 45vh;
}

.awards-title {
  font-size: clamp(2rem, 3vw, 2.4rem) !important;
}


@keyframes blob-morph {
  0%   { border-radius: 42% 58% 63% 37% / 41% 44% 56% 59%; }
  25%  { border-radius: 58% 42% 38% 62% / 52% 38% 62% 48%; }
  50%  { border-radius: 62% 38% 46% 54% / 63% 57% 43% 37%; }
  75%  { border-radius: 38% 62% 57% 43% / 44% 62% 38% 56%; }
  100% { border-radius: 42% 58% 63% 37% / 41% 44% 56% 59%; }
}

@media (prefers-reduced-motion: no-preference) {
  .contact-blob {
    animation: blob-morph 18s ease-in-out infinite;
  }
}


/* ==========================================================================
   Hero pickup — images lifted out from "under" the page (js/hero-pickup.js).
   The layer shares z-index 1 with .hero-content but sits earlier in DOM
   order, so pickups paint above the hero canvas and below the wordmark/nav.
   ========================================================================== */
.hero-pickup-layer {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

.hero-pickup-img {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  width: clamp(150px, 16vw, 260px);
  height: auto;
  /* Every source SVG is 1200x900; locking the ratio keeps spawn-clamping
     math correct even before the image has decoded. */
  aspect-ratio: 4 / 3;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  border-radius: 2px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.45);
  clip-path: inset(100% 0% 0% 0%);
  will-change: transform, clip-path, opacity;
}

/* --- ASCII TURNTABLE (footer) ---------------------------------------------
   Fills the empty band between .footer-top and the .footer-name wordmark.
   The box spans the whole footer and reserves that band with padding, so the
   deck stays centred in the gap at any viewport height instead of being
   pinned to a magic percentage. */
.deck-turntable {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  /* bottom pad clears the wordmark (17vw at line-height 0.8 = 13.6vw tall);
     top pad clears the footer-top row */
  padding: 7rem 2rem 15vw;
  overflow: hidden;
  pointer-events: none;
}

/* line-height and letter-spacing are load-bearing, not taste: Geist Mono's
   advance is 0.6em, so 0.6em + 0.5em tracking == 1.1em cell width, which is
   exactly the 1.1 line-height. Square cells are what keep the platter a
   circle rather than an ellipse. Changing either one alone squashes it. */
.deck-ascii {
  margin: 0;
  font-family: var(--font-mono);
  /* Fluid, but both tracking and leading are em-relative, so the cells stay
     square at every size. The vh term is the binding one — the band's height
     is what runs out first on a laptop. */
  font-size: clamp(5px, min(0.75vw, 0.96vh), 12px);
  line-height: 1.1;
  letter-spacing: 0.5em;
  color: var(--fg);
  opacity: 0.72;
  white-space: pre;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Column counts below must stay in step with gridFor() in
   js/ascii-turntable.js. */
@media (max-width: 1024px) {
  .deck-ascii {
    font-size: clamp(4px, min(1vw, 1.2vh), 11px);
  }
}

@media (max-width: 768px) {
  .deck-turntable {
    /* the mail/date column sits at top:60vh on mobile — stay above it */
    padding: 6rem 1rem 42vh;
  }

  .deck-ascii {
    font-size: clamp(4px, min(1.5vw, 0.9vh), 8px);
  }
}


/* ==========================================================================
   Load counter — vanilla <Counter /> (js/load-counter.js)
   Lives inside #name-layer, bottom-right. Every rule below is new and
   namespaced `lc-`; nothing above this banner is touched.
   ========================================================================== */

.lc-counter {
  --lc-size: clamp(2rem, 3.2vw, 2.9rem);
  --lc-digit-h: 1.16em;
  --lc-mask: 0.2em;

  position: absolute;
  right: 3rem;
  bottom: 3rem;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.5rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 620ms cubic-bezier(0.22, 1, 0.36, 1);
}

.lc-counter.lc-ready {
  opacity: 1;
}

.lc-counter.lc-done {
  opacity: 0;
}

/* The three digit windows, plus the slot masks that fade glyphs into and out
   of the page background at both edges. */
.lc-digits {
  position: relative;
  display: flex;
  align-items: flex-start;
  font-family: var(--font-mono);
  font-size: var(--lc-size);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--fg);
}

.lc-digits::before,
.lc-digits::after {
  content: '';
  position: absolute;
  left: -0.12em;
  right: -0.12em;
  height: var(--lc-mask);
  pointer-events: none;
  z-index: 2;
}

.lc-digits::before {
  top: 0;
  background: linear-gradient(to bottom, var(--bg) 0%, rgba(10, 10, 10, 0) 100%);
}

.lc-digits::after {
  bottom: 0;
  background: linear-gradient(to top, var(--bg) 0%, rgba(10, 10, 10, 0) 100%);
}

/* One-digit-tall window. */
.lc-col {
  height: var(--lc-digit-h);
  overflow: hidden;
}

/* Ten glyphs stacked; translateY(-N * 10%) of its own height lands digit N. */
.lc-strip {
  display: block;
  will-change: transform;
  transform: translateY(0);
  transition: transform 450ms cubic-bezier(0.22, 1, 0.36, 1);
}

.lc-glyph {
  display: block;
  height: var(--lc-digit-h);
  line-height: var(--lc-digit-h);
  text-align: center;
  min-width: 0.62em;
}

/* Hundreds column keeps its width reserved from the start so nothing shifts
   when the load hits 100 — it just fades in with the 1. */
.lc-col--h {
  opacity: 0;
  transition: opacity 450ms cubic-bezier(0.22, 1, 0.36, 1);
}

.lc-counter.lc-hundreds .lc-col--h {
  opacity: 1;
}

.lc-label {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

@media (max-width: 700px) {
  .lc-counter {
    --lc-size: clamp(1.6rem, 8vw, 2.2rem);
    right: 1.5rem;
    bottom: 1.75rem;
    gap: 0.35rem;
  }

  .lc-label {
    font-size: 0.6rem;
    letter-spacing: 0.14em;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lc-counter,
  .lc-strip,
  .lc-col--h {
    transition: none;
  }
}

/* --- SPOTIFY WIDGET (footer) -----------------------------------------------
   "Now playing / last played", parked in the same band as the ASCII
   turntable but pushed to the right edge, so the two read as one row without
   either one moving. Absolutely positioned against .footer-content for the
   same reason .deck-turntable is: the band is defined by clearing the
   .footer-top row above and the .footer-name wordmark below, and those are
   both viewport-relative.

   Injected by js/spotify-widget.js. Hidden by default and only un-hidden once
   /api/spotify has returned a usable payload — an unconfigured deployment or
   a failed request leaves it as it is here, invisible. */
.sp-widget {
  position: absolute;
  /* Matches .deck-turntable's own padding box, so the widget centres on the
     same band as the platter rather than on the whole footer. */
  top: 7rem;
  bottom: 15vw;
  right: clamp(1.5rem, 4vw, 3.5rem);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0.55rem;
  width: clamp(140px, 15vw, 200px);
  pointer-events: none;
}

/* Beats .sp-widget's display:flex on specificity, so the `hidden` attribute
   set by the widget script actually hides it. */
.sp-widget[hidden] {
  display: none;
}

.sp-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.sp-art-link {
  display: block;
  width: clamp(84px, 9vw, 120px);
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border: 1px solid var(--hairline);
  background: rgba(255, 255, 255, 0.03);
  pointer-events: auto;
}

.sp-art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Long titles are the failure mode here — "Song Title (Deluxe Remastered
   2011 Edition)" would otherwise push the footer band open. min-width:0 is
   what lets the ellipsis engage inside the flex column. */
.sp-title,
.sp-artist {
  display: block;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sp-title {
  font-family: var(--font-text);
  font-size: 0.85rem;
  font-weight: 400;
  line-height: 1.2;
  color: var(--fg);
  text-decoration: none;
  pointer-events: auto;
  transition: opacity 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sp-title:hover,
.sp-title:focus-visible {
  opacity: 0.6;
}

.sp-artist {
  margin-top: -0.35rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* Three-bar level meter. Only present while something is actually playing. */
.sp-bars {
  display: none;
  align-items: flex-end;
  gap: 2px;
  height: 9px;
}

.sp-widget.sp-playing .sp-bars {
  display: flex;
}

.sp-bar {
  display: block;
  width: 2px;
  height: 3px;
  background: var(--accent);
}

@media (prefers-reduced-motion: no-preference) {
  .sp-widget.sp-playing .sp-bar {
    animation: sp-bounce 900ms ease-in-out infinite;
  }

  .sp-widget.sp-playing .sp-bar:nth-child(2) {
    animation-delay: 180ms;
  }

  .sp-widget.sp-playing .sp-bar:nth-child(3) {
    animation-delay: 360ms;
  }
}

@keyframes sp-bounce {
  0%, 100% {
    height: 3px;
  }
  50% {
    height: 9px;
  }
}

@media (max-width: 1024px) {
  .sp-widget {
    right: 1.5rem;
    width: clamp(130px, 20vw, 170px);
  }
}

/* Below 768 the footer stacks: the platter band ends at 58vh and the
   mail/date column sits at 60vh, so the widget drops underneath both,
   centred, still clear of the wordmark. */
@media (max-width: 768px) {
  .sp-widget {
    top: 70vh;
    right: auto;
    bottom: auto;
    left: 50%;
    transform: translateX(-50%);
    width: min(78vw, 240px);
    align-items: center;
    justify-content: flex-start;
    text-align: center;
    gap: 0.45rem;
  }

  .sp-art-link {
    width: clamp(72px, 22vw, 96px);
  }
}
