/* ============================================================
   ISAAC ANHELUK — PORTFOLIO
   One full-screen slide per project (cyclemon-style),
   cursor-reactive layered illustrations, Nohemi throughout.
   Palette drawn from Portfolio_IsaacAnheluk.pdf:
   ink & paper · pool aqua · steel grey · amber · coral/sage/marigold
============================================================ */

/* ---------- font: Nohemi (variable, self-hosted) ---------- */
@font-face {
  font-family: "Nohemi";
  src: url("../fonts/Nohemi-VF.ttf") format("truetype-variations"),
       url("../fonts/Nohemi-VF.ttf") format("truetype");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ---------- tokens (gate.css reads these too) ---------- */
:root {
  --bg: #0E0E0E;
  --fg: #F2F1EC;
  --fg-dim: #8F8E86;
  --fg-faint: #55544E;
  --line-strong: rgba(242, 241, 236, 0.45);
  --accent: #D0342C;
  --red: #D0342C;
  --sage: #AEC7B0;
  --font-display: "Nohemi", "Space Grotesk", system-ui, sans-serif;
  --font-mono: "Nohemi", "JetBrains Mono", system-ui, sans-serif;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

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

html {
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
  background-color: var(--bg);
  transition: background-color 0.7s var(--ease);
  scrollbar-width: none;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar { display: none; }
html.is-locked { overflow: hidden; }

body {
  font-family: var(--font-display);
  color: var(--fg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; text-decoration: none; }
button { background: none; border: none; font: inherit; color: inherit; cursor: pointer; }
::selection { background: var(--accent); color: #F2F1EC; }

/* the dot IS the cursor — nothing may swap in a pointer/hand/text caret */
@media (hover: hover) and (pointer: fine) {
  html.has-cursor,
  html.has-cursor body,
  html.has-cursor * { cursor: none; }
}

/* ---------- grain ---------- */
.grain {
  position: fixed;
  inset: -60%;
  z-index: 9000;
  pointer-events: none;
  opacity: 0.06;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grainShift 1.4s steps(6) infinite;
}
@keyframes grainShift {
  0%   { transform: translate3d(0, 0, 0); }
  25%  { transform: translate3d(-2%, 3%, 0); }
  50%  { transform: translate3d(3%, -2%, 0); }
  75%  { transform: translate3d(-3%, -3%, 0); }
  100% { transform: translate3d(2%, 2%, 0); }
}

/* ---------- custom cursor: a single dot in the slide's accent ---------- */
.cursor-dot {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 30000;
  pointer-events: none;
  border-radius: 50%;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  background: var(--cursor-accent, #D0342C);
  transition: opacity 0.3s var(--ease), background-color 0.4s var(--ease);
  opacity: 0;
}
html.has-cursor .cursor-dot { opacity: 1; }

/* ---------- fixed chrome ---------- */
.chrome { position: fixed; z-index: 1000; mix-blend-mode: difference; color: #fff; }

.site-header {
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.5rem 2rem;
}
.brand {
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  display: inline-block;
}
.header-link {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  display: inline-block;
}

/* section dots */
.dots {
  right: 1.6rem;
  top: 50%;
  translate: 0 -50%;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  align-items: flex-end;
}
.dot {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.25rem;
}
.dot-mark {
  width: 8px;
  height: 8px;
  border: 1.5px solid #fff;
  border-radius: 50%;
  transition: background-color 0.35s var(--ease), transform 0.35s var(--ease);
}
.dot.is-current .dot-mark { background: #fff; transform: scale(1.35); }
.dot-label {
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0;
  transform: translateX(8px);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
  white-space: nowrap;
}
.dot:hover .dot-label,
.dot.is-current .dot-label { opacity: 0.9; transform: translateX(0); }

/* slide counter */
.counter {
  left: 2rem;
  bottom: 1.8rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.18em;
}
.counter-sep { width: 2.4rem; height: 1px; background: #fff; opacity: 0.6; }

/* scroll cue (intro only) */
.scroll-cue {
  left: 50%;
  bottom: 1.6rem;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  transition: opacity 0.5s var(--ease);
}
.cue-label {
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}
.cue-arrow { font-size: 1rem; animation: cueBob 1.8s var(--ease) infinite; }
@keyframes cueBob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(7px); }
}
body:not([data-active="0"]) .scroll-cue { opacity: 0; pointer-events: none; }

/* magnetic elements ease back into place */
[data-magnetic] { transition: transform 0.4s var(--ease); will-change: transform; }

/* ============================================================
   SLIDES
============================================================ */
.slide {
  position: relative;
  height: 100vh;
  height: 100dvh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  overflow: hidden;
  display: grid;
  place-items: center;
  padding: clamp(4.5rem, 10vh, 6rem) clamp(1.2rem, 4vw, 3rem);
  /* background is the fixed TopoBackground canvas (recoloured per slide);
     --s-bg stays defined here because JS reads it for the canvas scheme */
  color: var(--s-ink);
}

/* figure + caption */
.slide-stage {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(1rem, 3vh, 2.2rem);
}
.figure {
  transition: opacity 0.9s var(--ease), transform 1s var(--ease);
}
.figure svg {
  display: block;
  width: min(72vmin, 620px);
  max-height: 52vh;
  height: auto;
  overflow: visible;
}
[data-depth] { will-change: transform; }
[data-depth].is-resetting { transition: transform 0.7s var(--ease); }

.slide-meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  text-align: center;
}
.slide-index {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  opacity: 0.75;
}
.slide-title {
  font-size: clamp(2.2rem, 5.6vw, 4.4rem);
  font-weight: 750;
  line-height: 1;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}
.slide-tag {
  font-size: 0.95rem;
  font-weight: 500;
  line-height: 1.5;
  max-width: 42ch;
  opacity: 0.8;
}

/* grainy glow on menu text, only when it reads lighter than the slide
   background (html.chrome-glow toggled per slide by main.js; the filter's
   noise is re-seeded each frame by topo-init.js) */
html.chrome-glow .brand,
html.chrome-glow .header-link,
html.chrome-glow .dot-label,
html.chrome-glow .counter,
html.chrome-glow .cue-label,
html.chrome-glow .cue-arrow {
  filter: url(#grain-glow-sm);
}

/* title line reveal */
.tline-wrap { display: block; overflow: hidden; }
.tline {
  display: inline-block;
  transform: translateY(0);
  transition: transform 0.9s var(--ease);
}
.tfade { transition: opacity 0.8s var(--ease), transform 0.8s var(--ease); }

/* hidden states (only once JS is up, so no-JS still shows content) */
html.is-ready .slide:not(.is-active) .tline { transform: translateY(130%); transition-duration: 0.6s; }
html.is-ready .slide:not(.is-active) .tfade { opacity: 0; transform: translateY(16px); }
html.is-ready .slide:not(.is-active) .figure { opacity: 0; transform: translateY(46px) scale(0.96); }

/* staggered entrance */
.slide.is-active .figure { transition-delay: 0.05s; }
.slide.is-active .tline { transition-delay: 0.16s; }
.slide.is-active .tfade { transition-delay: 0.3s; }

/* ============================================================
   INTRO — black cover plate, like the specimen
============================================================ */
.intro-content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.4rem;
  text-align: center;
}
/* 3D logo: stacked svg plies form an extrusion; rotation follows the
   cursor (main.js); grainy glow applies to the flattened projection */
.icon-glow {
  filter: url(#grain-glow);
}
.icon-scene {
  perspective: 1100px;
}
.icon-3d {
  position: relative;
  width: clamp(170px, 30vmin, 300px);
  aspect-ratio: 514.22 / 530.86;
  transform-style: preserve-3d;
  will-change: transform;
  color: #fff;
}
.icon-ply {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}


/* ============================================================
   SPATIAL COMMS — extruded card deck (carousel.js)
   Each card is an image face backed by a stack of accent-coloured
   plies — same extrusion trick as the intro logo — so the curved
   thickness reads in the slide's accent colour.
============================================================ */
.card-carousel {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 100%;
  min-height: min(56vh, 500px);
  transition: opacity 0.9s var(--ease), transform 1s var(--ease);
}
html.is-ready .slide:not(.is-active) .card-carousel {
  opacity: 0;
  transform: translateY(46px) scale(0.96);
}
.slide.is-active .card-carousel { transition-delay: 0.05s; }

.deck {
  position: relative;
  height: min(48vh, 430px);
  aspect-ratio: 1200 / 1697;
}

/* Each card is its OWN perspective + a SINGLE preserve-3d layer (.scard-3d)
   — exactly like the intro logo, which is the one 3D thing WebKit renders
   correctly. Nesting preserve-3d inside preserve-3d (a deck-wide 3D space)
   is what flattened the depth before; giving every card its own perspective
   avoids that entirely.
   .scard  = 2D placement in the deck (spread, lift, scale, z) + perspective
   .scard-3d = the actual 3D: fan angle + pointer tilt, holding the extrusion */
.scard {
  position: absolute;
  inset: 0;
  perspective: 900px;
  will-change: transform;
  transition: transform 0.5s var(--ease);
  /* --mx/--my: pointer drift, written by carousel.js from anywhere on the page */
  transform:
    translate3d(var(--mx, 0px), var(--my, 0px), 0)
    translateX(var(--tx, 0)) translateY(var(--ty, 0))
    rotateZ(var(--rz, 0deg)) scale(var(--sc, 1));
}
.scard-3d {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transition: transform 0.42s var(--ease);
  transform:
    rotateX(calc(var(--rx, 0deg) + var(--px, 0deg)))
    rotateY(calc(var(--ry, 0deg) + var(--py, 0deg)));
}

/* white grainy glow — a plain layer BEHIND the 3D card (not inside it), using
   the same grainy #grain-glow-lg filter as the logo. Being a flat sibling
   before .scard-3d, it always paints behind and blooms out as a halo. */
.scard-glow {
  position: absolute;
  inset: 4%;
  border-radius: 18px;
  background: #fff;
  filter: url(#grain-glow-lg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.45s var(--ease);
}
.scard.is-hover .scard-glow { opacity: 1; }

.scard-face {
  position: absolute;
  inset: 0;
  border-radius: 18px;
  overflow: hidden;
  /* accent (not black) so the rounded-corner clip never shows a dark seam */
  background: var(--s-accent);
  box-shadow: 0 34px 70px -24px rgba(0, 0, 0, 0.55);
  backface-visibility: hidden;
  transition: box-shadow 0.4s var(--ease);
}
.scard-face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 18px; /* round the image itself too, so no clip fringe */
}
/* front-face sheen — a light hotspot + directional streak driven by JS */
.scard-sheen {
  position: absolute;
  inset: 0;
  border-radius: 18px;
  pointer-events: none;
  background:
    radial-gradient(62% 55% at var(--sx, 50%) var(--sy, 16%),
      rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0) 60%),
    linear-gradient(var(--sheen-ang, 120deg),
      rgba(255, 255, 255, 0) 38%,
      rgba(255, 255, 255, 0.28) 50%,
      rgba(255, 255, 255, 0) 62%);
  mix-blend-mode: screen;
  opacity: var(--sheen-o, 0.32);
  transition: opacity 0.45s var(--ease);
}

/* ---- deck slots: the fanned resting arrangement. --tx/--ty/--rz/--sc drive
        the 2D placement (.scard); --rx/--ry drive the 3D fan angle (.scard-3d). ---- */
.scard.slot-center { --tx: 0;    --ty: 0; --rz: 0deg;  --sc: 1;    --rx: 0deg; --ry: 0deg;   z-index: 3; }
.scard.slot-left   { --tx: -58%; --ty: 0; --rz: -4deg; --sc: 0.94; --rx: 0deg; --ry: 30deg;  z-index: 1; }
.scard.slot-right  { --tx: 58%;  --ty: 0; --rz: 4deg;  --sc: 0.94; --rx: 0deg; --ry: -30deg; z-index: 1; }

/* ---- the hovered card lifts (scale + rise) and squares up toward you ---- */
.scard.slot-left.is-hover   { --ty: -24px; --rz: -2deg; --sc: 1.08; --rx: 4deg; --ry: 14deg;  z-index: 6; }
.scard.slot-center.is-hover { --ty: -24px; --rz: 0deg;  --sc: 1.08; --rx: 3deg; --ry: 0deg;   z-index: 6; }
.scard.slot-right.is-hover  { --ty: -24px; --rz: 2deg;  --sc: 1.08; --rx: 4deg; --ry: -14deg; z-index: 6; }
.scard.is-hover .scard-face { box-shadow: 0 46px 90px -26px rgba(0, 0, 0, 0.55); }

/* ---- the OTHER cards slide away from whichever card is hovered. When a SIDE
        card is hovered the centre only nudges (never darts from the cursor). ---- */
.deck.hover-center .scard.slot-left  { --tx: -88%; --rz: -6deg; --sc: 0.9;  --ry: 34deg; }
.deck.hover-center .scard.slot-right { --tx: 88%;  --rz: 6deg;  --sc: 0.9;  --ry: -34deg; }

.deck.hover-left .scard.slot-center { --tx: 14%;  --sc: 0.95; --ry: -6deg; }
.deck.hover-left .scard.slot-right  { --tx: 92%;  --rz: 6deg;  --sc: 0.87; --ry: -34deg; }

.deck.hover-right .scard.slot-center { --tx: -14%; --sc: 0.95; --ry: 6deg; }
.deck.hover-right .scard.slot-left   { --tx: -92%; --rz: -6deg; --sc: 0.87; --ry: 34deg; }

@media (max-width: 720px) {
  .deck { height: min(42vh, 340px); }
  .card-carousel { min-height: min(50vh, 400px); }
}
@media (prefers-reduced-motion: reduce) {
  .scard, .scard-3d { transition: none; }
}

/* ============================================================
   CONTACT — closing black plate
============================================================ */
.contact-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.3rem;
  text-align: center;
}
.contact-cta {
  display: inline-block;
  font-size: clamp(2.8rem, 8.5vw, 7rem);
  font-weight: 750;
  line-height: 1;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}
.contact-cta .tline { transition-property: transform, color; }
.contact-cta:hover .tline { color: var(--accent); }
.contact-mail {
  font-size: 0.95rem;
  color: var(--fg-dim);
  letter-spacing: 0.04em;
}
.contact-links {
  display: flex;
  gap: 2.2rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.contact-links a { display: inline-block; opacity: 0.8; transition: opacity 0.3s var(--ease), color 0.3s var(--ease); }
.contact-links a:hover { opacity: 1; color: var(--accent); }

.contact-foot {
  margin-top: clamp(1.5rem, 6vh, 3.5rem);
  display: flex;
  align-items: center;
  gap: 1.1rem;
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--fg-dim);
}
.to-top {
  font-size: inherit;
  letter-spacing: inherit;
  color: inherit;
  transition: color 0.3s var(--ease);
}
.to-top:hover { color: var(--fg); }

/* firebase status dot */
.status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg-faint);
  transition: background-color 0.4s var(--ease);
}
.status-dot.is-ok { background: var(--sage); }
.status-dot.is-error { background: var(--red); }

/* ============================================================
   RESPONSIVE / PREFERENCES
============================================================ */
@media (max-width: 720px) {
  .site-header { padding: 1.1rem 1.2rem; }
  .counter { left: 1.2rem; bottom: 1.3rem; }
  .dots { right: 0.8rem; gap: 0.55rem; }
  .dot-label { display: none; }
  .figure svg { width: min(80vw, 72vmin); max-height: 44vh; }
  .slide-tag { font-size: 0.85rem; max-width: 34ch; }
  .contact-links { gap: 1.4rem; }
  .contact-foot { flex-wrap: wrap; justify-content: center; }
}

@media (hover: none), (pointer: coarse) {
  .cursor-dot { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .cursor-dot { display: none; }
  .grain { animation: none; }
  .cue-arrow { animation: none; }
  .tline, .tfade, .figure, [data-magnetic] { transition: none !important; }
  html.is-ready .slide:not(.is-active) .tline,
  html.is-ready .slide:not(.is-active) .tfade,
  html.is-ready .slide:not(.is-active) .figure {
    opacity: 1;
    transform: none;
  }
}
