/* JCLDEV portfolio. Palette taken from the avatar: black and white split suit,
   purple hair, gold glasses. */
:root {
  color-scheme: dark;
  --ink: #0d0b11;          /* black half of the suit */
  --surface: #17141d;
  --surface-2: #201c28;
  --line: #2e2939;
  --paper: #f6f4f8;        /* white half of the suit */
  --text: #f1eef5;
  --muted: #a8a1b4;
  --muted-dark: #5b5466;
  --purple: #8b5cf6;       /* hair */
  --purple-soft: #b69cff;
  --purple-deep: #5b2fb0;
  --gold: #e0b44c;         /* glasses */
  --gold-deep: #b98a24;

  --f-display: "Syne", "Arial Black", system-ui, sans-serif;
  --f-body: "Manrope", "Segoe UI", system-ui, sans-serif;
  --f-mono: "JetBrains Mono", ui-monospace, "Cascadia Code", Consolas, monospace;

  --wrap: 1120px;
  --gutter: clamp(16px, 4vw, 40px);
  --radius: 14px;
  --nav-h: 64px;
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: calc(var(--nav-h) + 12px); }
body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font: 400 1rem/1.65 var(--f-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img, video, iframe { max-width: 100%; display: block; }
[hidden] { display: none !important; }
a { color: var(--purple-soft); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--gold); }
:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; border-radius: 4px; }
h1, h2, h3 { font-family: var(--f-display); line-height: 1.05; margin: 0; text-wrap: balance; }
h2 { font-size: clamp(2rem, 4.4vw, 3rem); font-weight: 700; letter-spacing: -0.01em; margin-bottom: 2rem; }
h3 { font-size: 1.3rem; font-weight: 700; }
p { margin: 0; }

.skip { position: absolute; left: -999px; top: 8px; background: var(--gold); color: var(--ink); padding: 8px 14px; z-index: 100; border-radius: 8px; }
.skip:focus { left: 12px; }

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

.eyebrow {
  font-family: var(--f-mono);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-bottom: 0.75rem;
}
.eyebrow--gold { color: var(--gold); }
.eyebrow--purple { color: var(--purple-deep); }
.label { font-family: var(--f-mono); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }

/* ---------- Nav ---------- */
.nav {
  position: sticky;
  top: env(safe-area-inset-top, 0px);
  z-index: 50;
  background: color-mix(in srgb, var(--ink) 82%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
  animation: drop .8s cubic-bezier(.2,.7,.1,1) both;
}
@keyframes drop { from { transform: translateY(-100%); } }
/* Scroll progress line along the bottom of the nav. It sits on its own dark track so it stays
   readable where the translucent nav is over white (the hero slab, the tools section). The fill
   is clipped rather than scaled, so each color stays fixed to its spot across the page. */
.nav__progress {
  position: absolute; left: 0; right: 0; bottom: -1px; height: 3px;
  background: var(--ink);
}
.nav__progress::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, var(--purple), var(--purple-soft) 55%, var(--gold));
  box-shadow: 0 0 10px color-mix(in srgb, var(--purple) 70%, transparent);
  clip-path: inset(0 calc((1 - var(--progress, 0)) * 100%) 0 0);
}
.nav__inner {
  max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter);
  height: var(--nav-h); display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.nav__brand { font-family: var(--f-display); font-weight: 800; font-size: 1.2rem; color: var(--text); text-decoration: none; letter-spacing: 0.02em; }
.nav__links { display: flex; align-items: center; gap: clamp(10px, 2.4vw, 28px); }
.nav__links a { color: var(--muted); text-decoration: none; font-weight: 600; font-size: 0.95rem; transition: color .2s; }
.nav__links a:hover, .nav__links a.is-active { color: var(--text); }
.nav__links a.nav__cta { color: var(--ink); background: var(--gold); padding: 7px 14px; border-radius: 999px; }
.nav__links a.nav__cta:hover { background: var(--paper); }
@media (max-width: 640px) {
  .nav__links a:not(.nav__cta) { display: none; }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 13px 22px; border-radius: 999px; font-weight: 700; text-decoration: none;
  transition: transform .2s ease, background .2s, color .2s, box-shadow .2s;
}
.btn:hover { transform: translateY(-2px); }
.btn--gold { background: var(--gold); color: var(--ink); box-shadow: 0 8px 30px -10px var(--gold); }
.btn--gold:hover { background: var(--paper); color: var(--ink); }
.btn--purple { background: var(--purple); color: #fff; box-shadow: 0 8px 30px -10px var(--purple); }
.btn--purple:hover { background: var(--purple-soft); color: var(--ink); }

/* ---------- Hero ---------- */
/* --p goes 0 → 1 as the hero scrolls away (set in main.js) and drives the parallax. */
.hero { position: relative; overflow: hidden; isolation: isolate; --p: 0; }
.hero__split { position: absolute; inset: 0; z-index: -1; display: block; }
.hero__white, .hero__black { position: absolute; inset: 0; }
.hero__black {
  background: var(--ink);
}
.hero__white {
  background: var(--paper);
  z-index: 1;
  /* The slab tilts further as you scroll. `backwards` fill hands control back to this rule after the intro. */
  clip-path: polygon(0 0, calc(40% + var(--p) * 10%) 0, calc(30% - var(--p) * 6%) 100%, 0 100%);
  animation: slab 1.1s cubic-bezier(.2,.7,.1,1) backwards;
}
@keyframes slab { from { clip-path: polygon(0 0, 0 0, 0 100%, 0 100%); } }

.hero__inner {
  max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter);
  padding-block: clamp(56px, 10vw, 120px);
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: clamp(24px, 5vw, 64px); align-items: center;
}
.hero__text { display: grid; gap: 28px; }
/* Parallax goes on these two, not .hero__text: a translate on a parent would create a stacking
   context and cut .hero__blend off from the slab, breaking the difference blend. */
.hero__blend, .hero__actions { translate: 0 calc(var(--p) * 70px); }
/* White text with difference blending reads black over the white half and white over the black half. */
.hero__blend { color: #fff; mix-blend-mode: difference; display: grid; gap: 14px; }
.hero__blend .eyebrow { color: #fff; margin: 0; }
.hero__name {
  /* Sized to its column: the name is ~6.75em wide, the column ~57% of the wrap minus gutters and gap. */
  font-size: clamp(2.5rem, calc((min(100vw, var(--wrap)) - 2 * var(--gutter) - clamp(24px, 5vw, 64px)) * 0.082), 9rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 0.9;
  white-space: nowrap; /* keep the per-letter spans on one line like a single word */
}
/* main.js splits the name into letters that drop in one by one. */
.hero__name .ch { display: inline-block; animation: letter .9s cubic-bezier(.2,.7,.1,1) both; animation-delay: calc(.35s + var(--i) * 55ms); }
@keyframes letter { from { opacity: 0; transform: translateY(.55em) rotate(10deg); } }
.hero__title { font-family: var(--f-mono); font-size: clamp(0.9rem, 1.6vw, 1.05rem); letter-spacing: 0.04em; }
.hero__bio { font-size: 1.15rem; max-width: 34ch; }
.hero__stats { display: flex; flex-wrap: wrap; gap: 28px; margin: 8px 0 0; }
.hero__stats div { display: grid; gap: 2px; }
.hero__stats dt { font-family: var(--f-display); font-weight: 800; font-size: 1.8rem; line-height: 1; order: -1; }
.hero__stats dd { margin: 0; font-family: var(--f-mono); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; }
.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; }

/* Intro sequence: each hero line rises in after the last. These run on load and always finish. */
.hero__blend > :not(.hero__name) {
  animation: rise-in .9s cubic-bezier(.2,.7,.1,1) both;
  animation-delay: calc(.3s + var(--d, 0) * 110ms);
}
.hero__blend > :nth-child(3) { --d: 5; }
.hero__blend > :nth-child(4) { --d: 6; }
.hero__blend > :nth-child(5) { --d: 7; }
@keyframes rise-in { from { opacity: 0; transform: translateY(28px); } }

/* Pop-ins on load: scale up from small with a springy overshoot. `backwards` fill (not `both`)
   so hover transforms still work once the pop is done. Delays are ordered top to bottom. */
@keyframes pop { from { opacity: 0; transform: scale(.4); } }
.pop, .nav__brand, .nav__links a, .hero__stats > div, .hero__actions .btn, .hero__photo .ph > *, .worked > .eyebrow, .worked__list li {
  animation: pop .7s cubic-bezier(.34, 1.56, .64, 1) backwards;
  animation-delay: calc(var(--at, 0s) + var(--i, 0) * 80ms);
}
.nav__brand { --at: .3s; }
.nav__links a { --at: .4s; }
.nav__links a:nth-child(2) { --i: 1; }
.nav__links a:nth-child(3) { --i: 2; }
.nav__links a:nth-child(4) { --i: 3; }
.nav__links a:nth-child(5) { --i: 4; }
.hero__stats > div { --at: 1.1s; }
.hero__actions .btn { --at: 1.35s; }
.hero__actions .btn + .btn { --i: 1; }
.hero__photo .ph > * { --at: 1.4s; }
.hero__photo .ph > span { --i: 1; }
.worked > .eyebrow { --at: 1.5s; }
.worked__list li { --at: 1.6s; }
/* Cards already on screen at load (main.js adds .pop and --i) follow the hero. */
.pop { --at: 1.7s; }

.hero__photo {
  margin: 0; justify-self: center; width: min(100%, 360px); aspect-ratio: 4 / 5;
  border-radius: 24px; position: relative;
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--line), 0 0 90px -10px color-mix(in srgb, var(--purple) 70%, transparent);
  overflow: hidden;
  translate: 0 calc(var(--p) * 140px);
  animation: photo-in 1.2s .55s cubic-bezier(.2,.7,.1,1) both, float 6s 1.8s ease-in-out infinite alternate;
}
@keyframes photo-in {
  0% { opacity: 0; transform: translateY(40px) rotate(6deg) scale(.7); }
  60% { opacity: 1; transform: translateY(-6px) rotate(-1.5deg) scale(1.04); }
  100% { transform: none; }
}
@keyframes float { to { transform: translateY(-12px) rotate(-1deg); } }
.hero__photo::after {
  content: ""; position: absolute; inset: 10px; border-radius: 16px;
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent); pointer-events: none;
}
.hero__photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

@media (max-width: 820px) {
  .hero__inner { grid-template-columns: minmax(0, 1fr); }
  .hero__name { font-size: clamp(2.5rem, calc((100vw - 2 * var(--gutter)) * 0.14), 9rem); }
  .hero__white { clip-path: polygon(0 0, calc(46% + var(--p) * 10%) 0, calc(36% - var(--p) * 6%) 100%, 0 100%); }
  .hero__photo { width: min(100%, 300px); justify-self: start; }
}

/* ---------- Placeholders ---------- */
.ph {
  display: grid; place-items: center; text-align: center; gap: 6px;
  position: absolute; inset: 0; padding: 16px;
  border: 1.5px dashed color-mix(in srgb, var(--muted) 45%, transparent);
  border-radius: inherit;
  color: var(--muted); font-family: var(--f-mono); font-size: 0.8rem; letter-spacing: 0.05em;
  background:
    repeating-linear-gradient(135deg, transparent 0 12px, color-mix(in srgb, var(--purple) 7%, transparent) 12px 24px);
}
.ph svg { width: 28px; height: 28px; opacity: .7; }
.ph-text { color: var(--muted); font-style: italic; opacity: .8; }

/* ---------- Worked with ---------- */
.band { border-block: 1px solid var(--line); background: var(--surface); }
.worked { display: flex; align-items: center; gap: 24px 40px; flex-wrap: wrap; padding-block: 22px; }
.worked .eyebrow { margin: 0; }
.worked__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 16px 40px; align-items: center; }
.worked__list a, .worked__list span { font-family: var(--f-display); font-weight: 700; font-size: 1.25rem; color: var(--text); text-decoration: none; }
.worked__list img { height: 36px; width: auto; }

/* ---------- Sections ---------- */
.section { padding-block: clamp(64px, 10vw, 112px); }
.section--white { background: var(--paper); color: var(--ink); }
.section--white h2 { color: var(--ink); }

/* Skills */
.skills { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.skill {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 28px; display: grid; gap: 14px; align-content: start;
  transition: border-color .25s, transform .25s;
}
.skill:hover { border-color: var(--purple); transform: translateY(-3px); }
.skill__icon { width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center; background: color-mix(in srgb, var(--purple) 18%, transparent); color: var(--purple-soft); }
.skill__icon svg { width: 22px; height: 22px; }
.skill p { color: var(--muted); }
@media (max-width: 820px) { .skills { grid-template-columns: 1fr; } }

/* Games */
.games { display: grid; gap: 28px; }
.game {
  display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 0;
  background: var(--surface); border: 1px solid var(--line); border-radius: 20px; overflow: hidden;
}
.game:nth-child(even) .game__media { order: 2; }
/* Side by side: fill the card's full height (the image crops to fit). Stacked: a 16:9 box. */
.game__media { position: relative; min-height: 240px; background: var(--ink); border-radius: 0; }
.game__body { padding: clamp(22px, 3vw, 34px); display: grid; gap: 16px; align-content: start; }
.game__head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.game__period { font-family: var(--f-mono); font-size: 0.8rem; color: var(--muted); }
.game__role { font-family: var(--f-mono); font-size: 0.85rem; color: var(--gold); }
.game__desc { color: var(--muted); }
.game__stats { display: flex; flex-wrap: wrap; gap: 12px; margin: 0; }
.game__stats div { background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px; padding: 10px 14px; min-width: 120px; }
.game__stats dt { font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.game__stats dd { margin: 0; font-family: var(--f-display); font-weight: 700; font-size: 1.35rem; font-variant-numeric: tabular-nums; }
.game__play { justify-self: start; font-weight: 700; text-decoration: none; display: inline-flex; gap: 6px; align-items: center; }
@media (max-width: 820px) {
  .game { grid-template-columns: 1fr; }
  .game:nth-child(even) .game__media { order: 0; }
  .game__media { aspect-ratio: 16 / 9; min-height: 0; }
}

/* Media (video/image) */
/* Media boxes (.hero__photo, .game__media, .work__media) set their own size with aspect-ratio,
   and everything inside is absolutely positioned to cover them. Don't give the contents
   percentage heights or their own aspect-ratio: that makes the box's height depend on its
   contents and vice versa, and some iPhone Safari versions resolve that loop by growing the
   image forever (the page became an endless Slap Battles thumbnail). */
.media { position: absolute; inset: 0; overflow: hidden; background: var(--ink); }
.media iframe, .media video, .media img { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; object-fit: cover; }
.media__poster { all: unset; cursor: pointer; position: absolute; inset: 0; display: grid; place-items: center; }
.media__poster img { position: absolute; inset: 0; }
.media__play {
  position: relative; width: 68px; height: 68px; border-radius: 50%;
  background: var(--gold); color: var(--ink); display: grid; place-items: center;
  box-shadow: 0 10px 40px -8px rgba(0,0,0,.6); transition: transform .2s;
}
.media__poster:hover .media__play, .media__poster:focus-visible .media__play { transform: scale(1.08); }
.media__play svg { width: 26px; height: 26px; margin-left: 4px; }

/* Showcase */
.tabs { display: inline-flex; gap: 4px; padding: 4px; background: var(--surface); border: 1px solid var(--line); border-radius: 999px; margin-bottom: 28px; flex-wrap: wrap; }
.tab {
  font: 600 0.95rem var(--f-body); color: var(--muted); background: none; border: 0;
  padding: 9px 18px; border-radius: 999px; cursor: pointer; transition: background .2s, color .2s;
}
.tab[aria-selected="true"] { background: var(--purple); color: #fff; }
.tab:hover:not([aria-selected="true"]) { color: var(--text); }
.showcase { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.work { display: grid; gap: 12px; align-content: start; }
.work__media { position: relative; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); aspect-ratio: 16 / 9; }
.work h3 { font-size: 1.05rem; }
.work p { color: var(--muted); font-size: 0.95rem; }
@media (max-width: 900px) { .showcase { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .showcase { grid-template-columns: 1fr; } }

/* Tools (white section) */
.tools { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px 32px; }
.tools__group h3 { font-family: var(--f-mono); font-weight: 500; font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted-dark); margin-bottom: 12px; }
.tools__chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.tools__chips li {
  padding: 8px 14px; border-radius: 999px; background: #fff; border: 1px solid #ddd6e6;
  font-weight: 600; font-size: 0.95rem; color: var(--ink); transition: border-color .2s, background .2s;
}
.tools__chips li:hover { border-color: var(--purple); background: color-mix(in srgb, var(--purple) 8%, #fff); }
@media (max-width: 820px) { .tools { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .tools { grid-template-columns: 1fr; } }

/* Contact */
.contact { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: start; }
.contact h2 { margin-bottom: 1.25rem; }
.contact__commissions { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; font-size: 1.1rem; }
.contact__commissions .label { color: var(--gold); }
.contact__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.contact__item {
  display: grid; grid-template-columns: 44px 1fr auto; gap: 14px; align-items: center;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px;
  transition: border-color .2s;
}
.contact__item:hover { border-color: var(--purple); }
.contact__icon { width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center; background: color-mix(in srgb, var(--gold) 16%, transparent); color: var(--gold); }
.contact__icon svg { width: 20px; height: 20px; }
.contact__value { font-weight: 700; overflow-wrap: anywhere; color: var(--text); text-decoration: none; }
.contact__value:hover { color: var(--gold); }
.copy {
  font: 600 0.8rem var(--f-mono); color: var(--text); background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 8px; padding: 7px 10px; cursor: pointer; transition: border-color .2s, color .2s;
}
.copy:hover { border-color: var(--gold); color: var(--gold); }
.copy.is-done { color: var(--ink); background: var(--gold); border-color: var(--gold); }
@media (max-width: 820px) { .contact { grid-template-columns: 1fr; } }

/* Footer */
.footer { border-top: 1px solid var(--line); padding-block: 24px; color: var(--muted); font-size: 0.9rem; }
.footer__inner { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.footer a { color: var(--muted); }

/* Scroll reveal: content is always visible; JS only offsets things below the fold and
   slides them into place (staggered by --i) as they enter. No opacity involved. */
/* 2D translate on purpose: translate3d would give each of the ~47 waiting elements its own GPU
   layer, which phones with tight graphics memory handle badly. */
.reveal.is-below { transform: translate(var(--rx, 0px), var(--ry, 56px)) scale(var(--rs, .96)); }
.reveal.is-entering { transition: transform 1s cubic-bezier(.2,.7,.1,1) calc(var(--i, 0) * 90ms); }
.game:nth-child(odd) { --rx: -90px; --ry: 24px; }
.game:nth-child(even) { --rx: 90px; --ry: 24px; }
.tools__chips li { --ry: 20px; --rs: .8; }
.contact__item { --rx: 60px; --ry: 0px; --rs: 1; }
/* Only slide in from the side when there's margin to slide in from. Otherwise the waiting cards
   poke past the screen edge, and phones zoom the whole page out to fit them (they ignore
   overflow-x on body). Don't clip `main` to hide it instead: on phones that forces the page into
   one texture too tall for the GPU, and whole sections stop drawing. */
@media (max-width: 1240px) {
  .game:nth-child(odd), .game:nth-child(even) { --rx: 0px; --ry: 48px; }
  .contact__item { --rx: 0px; --ry: 32px; }
}

/* Showcase tab switch: the new panel's cards pop in. */
.showcase.is-switching .work { animation: rise-in .6s cubic-bezier(.2,.7,.1,1) both; animation-delay: calc(var(--i, 0) * 70ms); }

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