/* ════════════════════════════════════════════════════════════════
   "Builder Dark" — Giri's own mark, layered on top of the cindyly.design
   architecture. Loads after base.css + style.css, overriding tokens and
   the handful of surfaces that weren't already token-driven.
   Palette pulled from Giri's real portfolio (portfolio-redesign/):
   near-black bg, #1a4bff electric blue accent, per-project accent colors.
   ════════════════════════════════════════════════════════════════ */

:root {
  /* base surfaces */
  --color-bg: #0a0a0c;
  --color-sand: #141416;
  --color-surface: #141416;
  --color-dark: #131315;

  /* text */
  --color-label: #f3f3f3;
  --Label: #f3f3f3;
  --color-text: #d3d3d8;
  --Text: #d3d3d8;
  --color-ink: #f3f3f3;
  --color-muted: #a6a6ac;
  --color-subtle: #7d7d84;
  --Grey-Text: #9a9aa0;
  --color-grey-light: #8f8f96;

  /* borders / shadows */
  --Gray-outline: rgba(255, 255, 255, .14);
  --color-shadow: rgba(0, 0, 0, .55);
  --color-shadow-strong: rgba(0, 0, 0, .75);
  --color-sand-border: rgba(255, 255, 255, .08);
  --color-grid-line: rgba(255, 255, 255, .045);

  /* brand accent — Giri's real electric blue */
  --color-accent: #1a4bff;
  --color-blue-select: #1a4bff;
  --color-badge-light: rgba(255, 255, 255, .08);

  --font-tech: 'Space Grotesk', var(--font-sans);
  --font-pixel: 'Press Start 2P', var(--font-mono);

  /* sharper, more geometric than cindyly's soft "paper" radii */
  --radius-card: 6px;
  --radius-sm: 4px;
  --radius-folder: 6px;
}

/* ── corner radius: pill shapes -> toolbar shapes ── */
.folder-nav-inner { border-radius: 12px; }
.dock-bar { border-radius: 12px; }
.badge--on-light, .badge--shipped, .badge--building, .cs-tags li { border-radius: 4px; }
.mac-window, .sq-window { border-radius: 8px; }

/* ── hero tagline card: smaller text so the line fits without an extra
   wrap, and a different (non "night sky") gradient since the star field
   is disabled below — sized per the same breakpoints style.css uses. */
.card-night {
  /* previous gradients, kept here to revert easily:
     v1 (muted navy):   linear-gradient(150deg, #182348, #0d1230, #0a0a0c);
     v2 (brand blue):   linear-gradient(145deg, #2540a8 0%, #10184a 55%, #0a0a0c 100%);
     v3 (teal):         linear-gradient(145deg, #0fa88f 0%, #0e2a26 55%, #0a0a0c 100%);
     v4 (orange):       linear-gradient(145deg, #e8650a 0%, #3a1f0a 55%, #0a0a0c 100%);
     v5, magenta/rose to black: */
  background: linear-gradient(145deg, #c81e6b 0%, #2a0f28 55%, #0a0a0c 100%);
}
.card-night-text { font-size: 30px; font-weight: 600; }

/* TEMP: color comparison swatcher — see the HTML comment next to
   #card-night-swatcher for removal instructions. */
.card-night-swatcher {
  position: fixed;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 10px;
  max-width: min(90vw, 420px);
  background: rgba(10, 10, 12, .85);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 999px;
  backdrop-filter: blur(6px);
}
.cns-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-subtle);
  padding-left: 2px;
  padding-right: 2px;
}
.cns-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, .35);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  transition: transform .15s ease, border-color .15s ease;
}
.cns-dot:hover { transform: scale(1.15); }
.cns-dot.is-active { border-color: #fff; box-shadow: 0 0 0 2px rgba(255, 255, 255, .25); }
/* dark-theme's unconditional 30px above accidentally overrode
   style.css's own mobile-specific 22px (same specificity, dark-theme
   loads later — wins even inside style.css's own @media block), making
   the pink card's text render far larger and more dominant on mobile
   than the base template ever intended. Restoring a mobile-scoped
   override here (not quite back to the original 22px — a bit more,
   since dark-theme's serif got wider/heavier since that value was
   tuned) brings it back down to a supporting-tagline weight instead of
   competing with the hero name above it. */
@media (max-width: 480px) {
  .card-night-text { font-size: 23px; }
}
/* Bio card lines ("Currently: ...", "Previously: ...") were wrapping
   mid-phrase on mobile — the unwrapped text is only ~15px wider than
   its container at the base 13px, so a small nudge down is enough to
   keep each line on one row without needing to shrink it drastically. */
@media (max-width: 480px) {
  .card-bio-text { font-size: 12px; }
}

/* "I'm Giri." flush with the right edge of the craft.fig card above it.
   Tried making .hero itself calc(var(--hero-w)+56px) to match cards-row —
   broke worse: --hero-w is JS-measured FROM .hero's own rendered width,
   so making .hero's width a function of --hero-w corrupts the very
   measurement it depends on (JS ends up measuring the CSS fallback
   value instead of the real text width, locking in a broken size).
   Reverted .hero to its original content-sized width. hero-name already
   sits flush with .hero's own right edge by design (that's what defines
   --hero-w in the first place) — cards-row/card-bio are simply +56px
   wider than that and centered, so the gap to their edge is exactly
   half of that: 28px, a fixed constant, not something to re-tune per
   breakpoint. */
.hero-name { transform: translateX(28px); }
/* ...except on mobile: at <=480px .hero switches to width:100% (see
   style.css) instead of being content-sized to match cards-row/card-bio,
   so there's no "+56px wider sibling" to flush-align against anymore —
   the 28px nudge above just pushes the text 28px past its own container
   edge, clipped by body's overflow-x:hidden (the last italic character
   of "I'm Giri." was getting cut off). */
@media (max-width: 480px) {
  .hero-name { transform: none; }
}

/* card-bio and cards-row share --hero-w (JS-computed to match the hero
   text's own width) — widen both a bit past that shared value. card-fish
   stays a fixed square, so card-night (flex:1) absorbs the extra width. */
.cards-row, .card-bio {
  width: calc(var(--hero-w, 430px) + 56px);
}
@media (min-width: 1440px) and (max-width: 1919px) {
  .card-night-text { font-size: 28px; }
}
@media (min-width: 1920px) {
  .card-night-text { font-size: 32px; }
}
@media (min-width: 2400px) {
  .card-night-text { font-size: 40px; }
}

/* ── page chrome ── */
.nav-name { font-family: var(--font-tech); font-weight: 700; letter-spacing: -.01em; }
.nav-hint { color: #8a8a92; }

/* ── hero cards: translucent glass + paper surfaces need a dark reglaze ── */
.card-bio,
.card-bio:hover {
  background: var(--color-surface);
  border-color: var(--color-sand-border);
}
.card-bio:hover { border-color: #2c4bb0; }
.handle { color: #7d9bff; }
.handle:hover { color: #a8bdff; }

/* ── works grid: frosted-glass card shell was tuned for a cream page ── */
.work-card {
  background: rgba(24, 24, 28, .78);
  border: 1px solid var(--color-sand-border);
  box-shadow: 0 1px 12px rgba(0, 0, 0, .4);
}
.work-card:hover {
  box-shadow: 0 4px 22px rgba(26, 75, 255, .18) !important;
}

/* InstaPoll card (repurposed .work-card--reddit) — purple, matches its real brand accent */
.work-card--reddit .work-card-preview,
.work-card--reddit .work-card-preview:before {
  background: radial-gradient(ellipse 115% 85% at 52% 40%, #2a1a45, #3d2565 45%, #5b3a8f 78%, #8b5cf6);
}
.work-card--reddit .reddit-ticker-track { color: #d9c6ff; }

/* ── real screenshots (Merchant Onboarding, Automating Conversational
   Checkout) shown "zoomed out" on a colored backdrop, like a framed
   browser window sitting on a canvas, instead of bleeding full-bleed —
   matches the treatment on cindyly.design (the template this site is
   built on) that got lost when these two cards were swapped from a
   template background-image to a real <img>. That swap also left the
   original background-image rules in style.css pointing at template
   assets that were never carried over (neuralyfe/polimeter/zendesk
   thumbnails) — the source of a handful of persistent 404s all
   session. Overriding the full `background` shorthand here (not just
   background-color) clears those dead image refs for good. */
.work-card--dark-navy .work-card-preview { background: #0a0e1a; }
.work-card--green .work-card-preview {
  background: linear-gradient(155deg, #16392a, #0a1810);
  display: flex;
  align-items: center;
  justify-content: center;
}
.work-card--dark .work-card-preview {
  background: linear-gradient(155deg, #0f2c30, #081a1c);
  display: flex;
  align-items: center;
  justify-content: center;
}
.work-card--green .work-card-preview img,
.work-card--dark .work-card-preview img {
  width: 86%;
  height: 82%;
  object-fit: contain;
  border-radius: 8px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, .45), 0 2px 10px rgba(0, 0, 0, .3);
}

/* ── work-card hover metrics: floating dark pills that surface a real
   stat from each case study on hover. Sits as a sibling of
   .work-card-preview (not inside it, since that has overflow:hidden —
   these pills are meant to spill slightly past the preview's edge, the
   way the reference examples do), positioned absolute over the whole
   card, pointer-events:none throughout so the card link stays fully
   clickable underneath. */
.work-card-metrics {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}
.metric-pill {
  position: absolute;
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  background: rgba(10, 10, 12, .94);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 999px;
  padding: 10px 18px;
  font-family: var(--font-tech);
  font-size: 12px;
  font-weight: 500;
  color: var(--color-grey-light);
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
  box-shadow: 0 14px 30px rgba(0, 0, 0, .5);
  opacity: 0;
  transform: translateY(12px) rotate(var(--rot, -4deg)) scale(.94);
  transition: opacity .3s ease, transform .3s cubic-bezier(.22, 1, .36, 1);
}
.metric-pill strong {
  font-family: var(--font-sans);
  font-size: 18px;
  font-weight: 700;
  color: #fff;
  text-transform: none;
  letter-spacing: normal;
}
.work-card:hover .metric-pill {
  opacity: 1;
  transform: translateY(0) rotate(var(--rot, -4deg)) scale(1);
}
.metric-pill--1 { --rot: -4deg; top: 58%; left: 30%; }
.metric-pill--2 { --rot: -4deg; top: 74%; left: 46%; transition-delay: .05s; }
.work-card-metrics--single .metric-pill--1 { top: 66%; left: 28%; }
@media (hover: none) {
  /* no hover on touch devices — nothing to reveal these with, so don't
     pay their transition/paint cost */
  .metric-pill { display: none; }
}

/* ── work-card category tag: replaces the old white glass badge pill
   with plain, quiet uppercase text — no background/border. RefAgent's
   "Conversational UX" and InstaPoll's "Shipped" are carried over as
   placeholders for now (Giri's call — will get real copy later); the
   other two have their real tags. */
.work-card-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-tech);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--color-grey-light);
  white-space: nowrap;
}
.work-card-tag--status { color: var(--color-text); }

/* ── work-card subtitle: only Krishna Recon uses this (a supporting
   line under the title) since it's the one card whose title alone
   doesn't say what the project actually is. */
.work-card-subtitle {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 400;
  line-height: 1.45;
  color: var(--color-subtle);
  margin-top: -6px;
}

/* ── badges: were an opaque white glass pill ── */
.badge--on-light, .badge--shipped, .badge--building, .cs-tags li {
  background: rgba(255, 255, 255, .07);
  border-color: var(--Gray-outline);
  color: var(--color-text);
  font-family: var(--font-tech);
}

/* ── profile: mac-window chrome + fact cards were pure white ── */
.mac-window {
  background: var(--color-surface);
  border-color: var(--color-sand-border);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .5), 0 2px 6px rgba(0, 0, 0, .3);
}
.mac-chrome {
  background: linear-gradient(to bottom, #1c1c20, #131316);
  border-bottom-color: var(--color-sand-border);
}
.fact-card { background: #18181b; }

/* ── sidequests: preview overlay mac-window shares the same chrome ── */
.sq-window {
  background: var(--color-surface);
  border-color: var(--color-sand-border);
  box-shadow: 0 20px 60px rgba(0, 0, 0, .6), 0 2px 6px rgba(0, 0, 0, .3);
}
.sq-chrome {
  background: linear-gradient(to bottom, #1c1c20, #131316);
  border-bottom-color: var(--color-sand-border);
}
.sq-info-panel { border-left-color: var(--color-sand-border); }

/* Sticky notes stay warm paper-yellow on purpose — the one deliberately
   "physical" pop of color against the dark desk, like a Post-it on a
   black monitor bezel. Just deepen the shadow so it lifts off the page. */
.sq-note {
  box-shadow: 0 10px 28px rgba(0, 0, 0, .45), 0 2px 6px rgba(0, 0, 0, .2), inset 0 1px #fff6;
}

/* ── shimmer loading skeleton ── */
.shimmer-frame:before {
  background-color: #1c1c1f;
  background-image: linear-gradient(90deg, transparent, rgba(255, 255, 255, .09), transparent);
}

/* ── dock (mobile) glass pill ── */
.dock-bar {
  background: rgba(20, 20, 24, .72);
  border-color: rgba(255, 255, 255, .12);
  box-shadow: 0 4px 20px rgba(0, 0, 0, .5), inset 0 1px rgba(255, 255, 255, .08), inset 0 -1px rgba(0, 0, 0, .3);
}
.folder-nav-inner {
  background: rgba(20, 20, 24, .72);
}
.dock-label, .folder-label, .sq-icon-label { font-family: var(--font-tech); }

/* ── fish tank feed button, pixel-y on purpose ── */
.fish-btn {
  font-family: var(--font-pixel);
  font-size: 7px;
  letter-spacing: 0;
  padding: 6px 7px;
}

/* ════════════════════════════════════════════════════════════════
   Craft carousel — replaces the fish tank. Three small scenes that
   each stand in for a facet of the positioning: Design, Product,
   Builder. Click NEXT to cycle. Container-query units so it scales
   cleanly across the card's own responsive breakpoints (84/128/148/196px).
   ════════════════════════════════════════════════════════════════ */
.craft-card {
  background: #0d0d12;
  container-type: inline-size;
}
.craft-scenes {
  position: absolute;
  inset: 0;
}
.craft-scene {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: scale(1.035);
  /* Was .75s with both scenes toggled in the same tick, so outgoing and
     incoming genuinely overlapped mid-fade for a good chunk of that
     time (confirmed: at 350ms in, one scene sits at .72 opacity while
     the other's still at .28) — fine on a big canvas, but at the small
     mobile card size with the builder scene's dense multi-line text, it
     read as broken/overlapping rather than a crossfade. script.js now
     drives this sequentially (fade out fully, then fade in), so this
     duration is just one leg of that — kept short so the full
     out-then-in round trip still feels quick. */
  transition: opacity .28s cubic-bezier(.22, .61, .36, 1), transform .28s cubic-bezier(.22, .61, .36, 1);
  display: flex;
  align-items: center;
  justify-content: center;
  will-change: opacity, transform;
}
.craft-scene.is-active { opacity: 1; transform: scale(1); z-index: 1; }

/* This label's own size/position is fixed (not container-query scaled),
   since it should stay tiny and legible at every card size. But each
   scene's content DOES scale/reposition with the container — centered
   content in particular just centers within whatever box it has, with
   no awareness of the label sitting on top of it. At the card's
   smallest size (84px, mobile) there isn't enough room for that to
   clear on its own, so each scene below reserves a fixed top clearance
   (not cqw-scaled, since the label it's clearing never scales either)
   to guarantee no overlap regardless of container size. */
.craft-label {
  position: absolute;
  top: 6px;
  left: 8px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: .02em;
  color: rgba(255, 255, 255, .55);
  z-index: 2;
  pointer-events: none;
}

.craft-card .fish-hud {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  justify-content: flex-end;
  padding-right: 8px;
}

/* ── scene 1: design / visual craft — typography + a real color system,
   the two most legible-at-a-glance signals of "visual design" there are.
   Swatches are Giri's own actual per-project accent colors (RefAgent
   blue, InstaPoll purple, WhatToCook orange, FreshScan green) — his
   palette IS his palette, not a stock one. */
.craft-scene--design { background: linear-gradient(155deg, #181a2c, #0c0d18); flex-direction: column; gap: 8cqw; padding-top: 22px; }
.craft-type-sample {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 40cqw;
  line-height: .85;
  color: #fff;
  animation: craftTypeBreathe 3.2s ease-in-out infinite;
}
@keyframes craftTypeBreathe {
  0%, 100% { opacity: .85; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.06); }
}
.craft-swatch-row { display: flex; gap: 5cqw; }
.craft-swatch-row span {
  width: 9cqw;
  height: 9cqw;
  min-width: 7px;
  min-height: 7px;
  border-radius: 2px;
  display: block;
  animation: craftSwatchBounce 2.4s ease-in-out infinite;
}
.craft-swatch-row span:nth-child(1) { animation-delay: 0s; }
.craft-swatch-row span:nth-child(2) { animation-delay: .12s; }
.craft-swatch-row span:nth-child(3) { animation-delay: .24s; }
.craft-swatch-row span:nth-child(4) { animation-delay: .36s; }
@keyframes craftSwatchBounce {
  0%, 60%, 100% { transform: translateY(0); }
  30%           { transform: translateY(-4px); }
}

/* ── scene 2: product / product sense — a small growth chart ── */
.craft-scene--product { background: linear-gradient(155deg, #10241c, #0b1712); flex-direction: column; gap: 6cqw; padding-top: 22px; }
.craft-chart {
  display: flex;
  align-items: flex-end;
  gap: 4cqw;
  height: 45cqw;
  width: 62cqw;
  border-bottom: 1px solid rgba(255,255,255,.18);
}
.craft-bar {
  flex: 1;
  height: var(--h);
  background: linear-gradient(to top, #2f9e5c, #7fe3a8);
  border-radius: 1.5px 1.5px 0 0;
  transform-origin: bottom;
  animation: craftBarGrow 3.6s cubic-bezier(.22,1,.36,1) infinite;
}
.craft-bar:nth-child(1) { animation-delay: 0s; }
.craft-bar:nth-child(2) { animation-delay: .12s; }
.craft-bar:nth-child(3) { animation-delay: .24s; }
.craft-bar:nth-child(4) { animation-delay: .36s; }
.craft-bar:nth-child(5) { animation-delay: .48s; }
@keyframes craftBarGrow {
  0%   { transform: scaleY(0); }
  20%  { transform: scaleY(1); }
  85%  { transform: scaleY(1); }
  100% { transform: scaleY(0); }
}
.craft-stat {
  font-family: var(--font-tech);
  font-weight: 700;
  font-size: 13px;
  color: #7fe3a8;
}

/* ── scene 3: builder — a small terminal ── */
.craft-scene--builder { background: #0a0a0c; align-items: flex-start; justify-content: flex-start; padding: 24px 8cqw 10cqw; flex-direction: column; }
.craft-term-line {
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1.9;
  color: #a6a6ac;
  white-space: nowrap;
}
.craft-term-line--ok { color: #6f8cff; }
.craft-term-line--prompt { opacity: 0; transition: opacity .2s ease; }
.craft-term-line--prompt.is-visible { opacity: 1; }
.craft-blink { animation: craftBlink 1s step-end infinite; color: #6f8cff; }
.craft-blink--hidden { opacity: 0 !important; animation: none; }
@keyframes craftBlink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* ── brand touches ── */
::selection { background: #1a4bff; color: #fff; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background: #1a4bff; }

/* badge shimmer sweep: was a gold/champagne glint (barely visible, 9s cycle,
   only ~2.5s of it actually animating). Recolored blue + sped up so it reads. */
.badge--on-light:after, .cs-tag--award:after {
  background: linear-gradient(120deg, transparent 20%, rgba(26,75,255,.22) 38%, rgba(150,180,255,.65) 50%, rgba(26,75,255,.22) 62%, transparent 80%);
  animation-duration: 3.2s;
}

/* ── "normal popup" chrome: plain dark bar, no traffic lights ── */
.mac-chrome { min-height: 40px; }
.sq-chrome {
  display: flex;
  align-items: center;
  justify-content: center;
}
.sq-close-btn {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  border-radius: 6px;
  border: none;
  background: rgba(255, 255, 255, .06);
  color: var(--color-muted);
  font-size: 17px;
  line-height: 1;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.sq-close-btn:hover { background: rgba(255, 255, 255, .12); color: #fff; }

.sq-window--info { position: relative; }
.sq-nav-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .1);
  background: rgba(10, 10, 12, .55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  color: #fff;
  font-size: 20px;
  line-height: 1;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background .15s ease, transform .15s ease, opacity .15s ease;
}
.sq-nav-btn:hover { background: rgba(10, 10, 12, .8); }
.sq-nav-btn:active { transform: translateY(-50%) scale(.92); }
.sq-nav-btn:disabled { opacity: .3; cursor: default; }
.sq-nav-btn--prev { left: 14px; }
.sq-nav-btn--next { right: 14px; }
@media (max-width: 700px) {
  .sq-nav-btn { width: 32px; height: 32px; font-size: 18px; }
  .sq-nav-btn--prev { left: 8px; }
  .sq-nav-btn--next { right: 8px; }
}

/* ── atmosphere: CRT scanlines over the whole page — commented off for now,
   Giri wants to see how it reads without them. Uncomment to bring back.
html:before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9998;
  pointer-events: none;
  background-image: repeating-linear-gradient(to bottom, rgba(0, 0, 0, .5) 0px, rgba(0, 0, 0, .5) 1px, transparent 1px, transparent 4px);
  opacity: .35;
}
*/

/* ── works-panel entrance choreography: replaced the "fly to center, stack
   like a deck of cards, deal back out" motion with a plainer staggered
   reveal — cards materialize in place with a top-down wipe, more like a
   system booting than a card trick. Redefines the same keyframe NAMES the
   existing JS/CSS state machine already drives (script.js still computes
   --sx/--sy/--srot/--sscale and toggles .stacking/.emerge/.settled with the
   same timing — those custom properties are just unused by these keyframes
   now), so none of the JS or the mobile/overflow rules tied to those
   classes needed to change, only the visual keyframes themselves. */
@keyframes cardStackIn {
  0%, 100% { opacity: 0; }
}
@keyframes cardDealOut {
  /* was also wiping in via clip-path (a growing band from the top) —
     that read as "a line appears, then the card" rather than a clean
     reveal, so it's commented off; plain fade + slide-up only now. */
  0%   { opacity: 0; transform: translateY(20px) scale(.97); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* ── works-panel entrance, ALTERNATE "folder zoom" — commented off for
   now, reverted back to the boot-sequence choreography below per Giri's
   call. Uncomment alongside the matching JS block in script.js (search
   "folder zoom") to bring it back.
#panel-works.works-zooming {
  animation: none !important;
  will-change: transform, opacity;
}
*/

/* ── works-panel entrance: full-screen boot sequence. A dark takeover
   plays a small terminal log (typed command, "mounting design/" and
   "mounting product/" steps carrying the craft carousel's mini scenes,
   then "ready"), then the whole overlay opens like a camera iris — a
   circular hole grows from center via an animated mask — to reveal the
   work cards (already settled underneath, no competing entrance
   animation — script.js sets skipEntranceAnim for this path). Toggled
   via .is-visible/.is-opening by script.js (runWorksTransitionLine).
   Experimental — easy to rip out by deleting this block + the JS hook
   + the markup (search "works-boot" in each file). */
@property --iris-r {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}
.works-boot {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
  --iris-r: 0%;
  -webkit-mask-image: radial-gradient(circle at 50% 44%, transparent var(--iris-r), #000 calc(var(--iris-r) + 1.5px));
          mask-image: radial-gradient(circle at 50% 44%, transparent var(--iris-r), #000 calc(var(--iris-r) + 1.5px));
  opacity: 0;
  pointer-events: none;
}
.works-boot.is-visible { opacity: 1; }
/* JS is the single source of truth for the --iris-r *value* (set as an
   inline style, since an inline style would otherwise always beat this
   rule's specificity and block the animation) — this class only turns
   the transition timing on, so the reset-to-0% step stays instant and
   only the deliberate "open" step animates. */
.works-boot.is-opening {
  transition: --iris-r .65s cubic-bezier(.65, 0, .35, 1);
}
.works-boot-log {
  display: flex;
  flex-direction: column;
  gap: 10px;
  transform: translateY(-8%);
}
.works-boot-line {
  font-family: var(--font-mono);
  font-size: clamp(14px, 2.2vw, 18px);
  letter-spacing: .01em;
  color: #8fa3ff;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .2s ease, transform .2s ease;
}
.works-boot-line--cmd { opacity: 1; transform: none; color: #cfd8ff; }
.works-boot-line--ok { color: #6f8cff; font-weight: 600; }
.works-boot-line--ok.is-visible { opacity: 1; transform: none; }
.works-boot-row {
  display: flex;
  align-items: center;
  gap: 16px;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .2s ease, transform .2s ease;
}
.works-boot-row.is-visible { opacity: 1; transform: none; }
.works-boot-label {
  font-family: var(--font-mono);
  font-size: clamp(12px, 1.8vw, 14px);
  color: var(--color-grey-light);
  min-width: 150px;
}
.wt-glyph {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 30px;
  color: #f3f3f3;
}
.wt-swatches { display: flex; gap: 6px; }
.wt-swatches span { width: 13px; height: 13px; border-radius: 4px; display: inline-block; }
.wt-bars { display: flex; align-items: flex-end; gap: 4px; height: 30px; }
.wt-bars span {
  width: 6px;
  height: var(--h);
  background: #6f8cff;
  border-radius: 2px 2px 0 0;
  display: inline-block;
}
.wt-stat { font-family: var(--font-mono); font-size: 14px; font-weight: 600; color: #6f8cff; }
@media (prefers-reduced-motion: reduce) {
  .works-boot { display: none; }
}

/* ── click-spark accent ── */
.click-spark {
  position: fixed;
  z-index: 9999;
  pointer-events: none;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  border: 1.5px solid #6f8cff;
  animation: clickSparkExpand .5s ease-out forwards;
}
@keyframes clickSparkExpand {
  0%   { transform: scale(.2); opacity: .8; }
  100% { transform: scale(3.2); opacity: 0; }
}

/* ════════════════════════════════════════════════════════════════
   Sidequests preview overlay — live prototype embed. When a project
   has a URL, the modal shows a real running iframe instead of a
   static image, and grows to a size that's actually usable. Some
   sites refuse to be iframed (X-Frame-Options/CSP) — no way to fix
   that from here, so there's always an "open in new tab" fallback.
   ════════════════════════════════════════════════════════════════ */
.sq-preview-iframe {
  width: 100%;
  height: 100%;
  border: none;
  border-radius: 6px;
  background: #fff;
  position: relative;
  z-index: 1;
}
.sq-proto-link {
  display: block;
  text-align: center;
  padding: 8px 0 0;
  font-family: var(--font-mono);
  font-size: 12px;
  color: #7d9bff;
  text-decoration: none;
}
.sq-proto-link:hover { color: #a8bdff; }

.sq-window--proto {
  width: 92vw !important;
  max-width: 1400px !important;
  height: 88vh !important;
  max-height: 900px !important;
}
.sq-window--proto .sq-window-body--info {
  grid-template-columns: 1fr 300px !important;
}
.sq-window--proto .sq-info-img {
  max-height: none !important;
  padding: 16px !important;
}
/* constrain to a phone-width column — the embedded site's own responsive
   CSS reacts to the iframe's actual rendered width, so a narrow iframe
   is what makes it render its mobile layout, same as a real phone would */
.sq-window--proto .sq-preview-iframe {
  width: 420px;
  max-width: 100%;
  height: 100%;
  max-height: 860px;
  border-radius: 24px;
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow: 0 20px 50px rgba(0, 0, 0, .5);
}

/* Gallery strip under the live iframe — real product screenshots for
   projects that define a `screenshots` array in PROJECTS. Base .sq-gallery-*
   sizing/layout comes from style.css; just re-tint the light-mode border
   and add the "Live" text button style (the base only styled <img> thumbs). */
.sq-gallery-strip { border-top-color: var(--color-sand-border); }
.sq-gallery-thumb--live {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface);
  border-color: var(--color-sand-border);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .02em;
  color: var(--color-subtle);
}
.sq-gallery-thumb--live:hover { color: var(--color-text); }
.sq-gallery-thumb--live.active {
  color: #a8bdff;
  border-color: var(--color-blue-select);
}

/* ════════════════════════════════════════════════════════════════
   Side Quests row — folded into the Works panel below the featured
   work cards, reusing the .sq-icon / .sq-icon-thumb markup from the
   (now commented-out) standalone sidequests panel/tab.
   ════════════════════════════════════════════════════════════════ */
.works-sidequests {
  width: 100%;
  margin-top: 40px;
  padding-top: 28px;
  border-top: 1px solid var(--color-sand-border);
}
.works-sidequests-label {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--color-grey-light);
  margin-bottom: 16px;
}
.works-sidequests-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 22px 18px;
}
.works-sidequests-grid .sq-icon {
  position: static !important;
}

/* ── Side Quests icons, die-cut sticker treatment — cream card stock,
   thick ink outline, hard offset shadow, resting at a slight per-icon
   tilt; hovering straightens and lifts it. Only overrides surface/motion
   properties (not width/height/radius) so the base .sq-icon-thumb
   responsive sizing in style.css keeps controlling size across
   breakpoints. ─────────────────────────────────────────────────── */
.works-sidequests-grid .sq-icon-thumb--app {
  background: #f4f1e9;
  border: 2px solid #1a1a1c;
  box-shadow: 3px 3px 0 rgba(0, 0, 0, .5);
  transform: rotate(var(--sticker-tilt, 0deg));
  transition: transform .18s cubic-bezier(.2, .8, .2, 1), box-shadow .18s ease;
}
.works-sidequests-grid .sq-icon:hover .sq-icon-thumb--app {
  transform: rotate(0deg) translateY(-3px);
  box-shadow: 5px 6px 0 rgba(0, 0, 0, .55);
}
.works-sidequests-grid .sq-icon:nth-child(1) .sq-icon-thumb--app { --sticker-tilt: -4deg; }
.works-sidequests-grid .sq-icon:nth-child(2) .sq-icon-thumb--app { --sticker-tilt: 5deg; }
.works-sidequests-grid .sq-icon:nth-child(3) .sq-icon-thumb--app { --sticker-tilt: -6deg; }
.works-sidequests-grid .sq-icon:nth-child(4) .sq-icon-thumb--app { --sticker-tilt: 7deg; }
.works-sidequests-grid .sq-icon:nth-child(5) .sq-icon-thumb--app { --sticker-tilt: -8deg; }
.works-sidequests-grid .sq-icon:nth-child(6) .sq-icon-thumb--app { --sticker-tilt: 9deg; }
.works-sidequests-grid .sq-icon:nth-child(7) .sq-icon-thumb--app { --sticker-tilt: -4deg; }
.works-sidequests-grid .sq-icon:nth-child(8) .sq-icon-thumb--app { --sticker-tilt: 5deg; }
.works-sidequests-grid .sq-icon:nth-child(9) .sq-icon-thumb--app { --sticker-tilt: -6deg; }

/* ── Side Quests, terminal-listing version — a real `ls -la` style row
   per quest, echoing the boot-sequence transition and ship.sh carousel
   scene's terminal voice. Reuses .sq-icon on each row purely so the
   existing "click an icon -> preview overlay" wiring in script.js
   (document.querySelectorAll('.sq-icon')) picks these up with zero JS
   changes — the row layout below overrides everything from the base
   .sq-icon rule that doesn't apply to a row (position, flex layout,
   padding, filter), and deliberately leaves color/hover/transition
   alone so it still matches the rest of the site's icon language. */
.works-sidequests--term { border-top: none; padding-top: 0; }
.sqt-window {
  width: 100%;
  background: #08080a;
  border: 1px solid var(--color-sand-border);
  border-radius: 10px;
  overflow: hidden;
}
.sqt-chrome {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 14px;
  background: linear-gradient(to bottom, #1c1c20, #131316);
  border-bottom: 1px solid var(--color-sand-border);
}
.sqt-dot { width: 8px; height: 8px; border-radius: 50%; background: #303034; display: inline-block; }
.sqt-chrome-title {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-subtle);
  margin-left: 6px;
}
.sqt-body { padding: 16px 18px 8px; }
.sqt-prompt {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--color-ink);
  margin: 0 0 14px;
}
.sqt-cols {
  display: grid;
  grid-template-columns: 22px 1fr 76px minmax(0, 1fr) 38px;
  align-items: center;
  gap: 14px;
}
.sqt-head {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-subtle);
  text-transform: lowercase;
  letter-spacing: .02em;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--color-sand-border);
  margin-bottom: 2px;
}
.sqt-rows { display: flex; flex-direction: column; }
.sqt-row {
  position: static;
  width: 100%;
  text-align: left;
  padding: 10px 0;
  border-radius: 6px;
  filter: none;
}
.sqt-rows .sq-icon + .sq-icon { border-top: 1px solid var(--color-grid-line); }
.sqt-glyph { font-size: 15px; line-height: 1; }
.sqt-name {
  font-family: var(--font-tech);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--color-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sqt-tag {
  justify-self: start;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .03em;
  padding: 2px 7px;
  border-radius: 4px;
  border: 1px solid var(--color-sand-border);
  white-space: nowrap;
}
.sqt-tag--shipped { color: #7fe3a3; border-color: rgba(127, 227, 163, .3); }
.sqt-tag--live    { color: #6f8cff; border-color: rgba(111, 140, 255, .35); }
.sqt-tag--tool    { color: #e0b25a; border-color: rgba(224, 178, 90, .3); }
.sqt-tag--oss     { color: #c9a4ff; border-color: rgba(201, 164, 255, .3); }
.sqt-tag--wip     { color: var(--color-grey-light); }
.sqt-desc {
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--color-subtle);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sqt-year {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-subtle);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
@media (max-width: 640px) {
  .sqt-cols { grid-template-columns: 22px 1fr 76px 38px; }
  .sqt-col-desc { display: none; }
}
@media (max-width: 420px) {
  .sqt-cols { grid-template-columns: 22px 1fr 38px; }
  .sqt-tag, .sqt-col-status { display: none; }
}

/* ── pre-existing gap: on narrow mobile, #panel-works.active's own
   padding-bottom (40px, from style.css) isn't enough to clear the
   ~100px floating mobile-dock pill, so the last bit of content at the
   very bottom of the panel sits partly behind it. Wasn't very visible
   with the shorter icon-grid Side Quests, but the taller terminal
   listing pushes real content into that gap — bumping the reserved
   space so nothing ends up stuck under the dock. */
@media (max-width: 480px) {
  #panel-works.active { padding-bottom: 140px; }
}
