:root {
  --bg: #07090c;
  --panel: #101318;
  --panel-soft: #151922;
  --text: #f5f5f5;
  --muted: #b8b8b8;

  --accent: #d6a037;
  --accent-rgb: 214, 160, 55;
  --accent-light: #e1ae45;
  --accent-dark: #9f6d1e;
  --accent-soft: rgba(214, 160, 55, 0.08);
  --accent-border: rgba(214,one i am revers  160, 55, 0.7);
  --accent-shadow: rgba(214, 160, 55, 0.35);

  --border: rgba(255, 255, 255, 0.12);

  --footer-h: 90px;
}
	
* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
  font-family: "Sora", Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Hide scrollbars but keep scrolling working */

.portfolio-side,
.project-detail-panel,
.mosaic-intro {
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.portfolio-side::-webkit-scrollbar,
.project-detail-panel::-webkit-scrollbar,
.mosaic-intro::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

/* Standalone pages (about, contact, direct project view) scroll the whole
   document instead of an inner panel — let the body scroll, scrollbar hidden. */

body:has(.standalone-detail-page) {
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

body:has(.standalone-detail-page)::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

/* Header */

header {
  width: 100%;
  min-height: 86px;
  /* Solid (was rgba 0.92 + backdrop-filter: blur). The blur re-rendered every
     frame that content moved behind the sticky header — that was the header/logo
     flicker, worst when the full-page mosaic animates right under the header. */
  background: #07090c;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  position: sticky;
  top: 0;
  z-index: 30;
  padding-bottom: 14px;
}

.header-inner {
  width: 100%;
  max-width: 97%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 40px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 16px;
  line-height: 1.1;

  text-decoration: none;
  color: inherit;
  cursor: pointer;
}

.brand-logo {
  width: 45px;
  height: 45px;
  object-fit: contain;
  display: block;
}

.brand-text {
  display: flex;
  flex-direction: column;
}

.brand-title {
  font-size: 24px;
  letter-spacing: 5px;
  font-weight: 700;
}

.brand-subtitle {
  margin-top: 6px;
  font-size: 14px;
  color: var(--accent);
}

.menu {
  display: flex;
  align-items: center;
  gap: 56px;
  position: relative;   /* anchor for the sliding .menu-underline */
}

.menu a {
  color: var(--text);
  text-decoration: none;
  font-size: 15px;
  opacity: 0.9;
  position: relative;
}

.menu a.active::after,
.menu a:hover::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -16px;
  height: 2px;
  background: var(--accent);
}

/* --- Sliding active indicator ---------------------------------------------
   A per-link ::after can only blink on/off between items. This is ONE bar that
   travels, driven by positionMenuUnderline() in portfolio.js, using the same
   0.75s cubic-bezier(0.77, 0, 0.175, 1) as the .hero-side / .portfolio-side
   transform — so the indicator glides to its new item while the view slides,
   arriving together. `.has-underline` is added by JS only once the bar is
   actually positioned, and suppresses the active link's own ::after so there is
   never a double underline; with JS off, the original ::after still marks the
   active page. Hover keeps its own ::after regardless. */
.menu-underline {
  position: absolute;
  bottom: -16px;
  left: 0;
  width: 0;
  height: 2px;
  background: var(--accent);
  transform: translateX(0);
  opacity: 0;
  pointer-events: none;

  /* Position and width only — opacity is NOT transitioned, so the bar never
     fades in/out anywhere; it is either absent (no JS) or sliding. */
  transition:
    transform 0.75s cubic-bezier(0.77, 0, 0.175, 1),
    width 0.75s cubic-bezier(0.77, 0, 0.175, 1);
}

.menu.has-underline .menu-underline { opacity: 1; }

/* With the shared bar live it owns BOTH states: it rests under the active item
   and follows the pointer on hover. So neither the active nor the hover
   ::after may draw, or there'd be two golds bars at once. Without JS these
   rules don't apply and the original per-link underlines still work. */
.menu.has-underline a.active::after,
.menu.has-underline a:hover::after { display: none; }

/* First placement / resize: jump straight there, don't sweep in from the left. */
.menu-underline.no-anim { transition: none; }

/* Main layout */

.portfolio-layout {
  width: 100%;
  flex: 1;
  min-height: 0;
  background: var(--bg);
  overflow: hidden;
  position: relative;
}

/* Left project grid panel */

.portfolio-side {
  position: absolute;
  left: 0;
  top: 0;

  width: 50vw;
  height: 100%;

  overflow-y: auto;
  overflow-x: hidden;

  background:
    radial-gradient(circle at top, var(--accent-soft), transparent 32%),
    var(--bg);

  transform: translateX(0);
  opacity: 1;
  visibility: visible;
  pointer-events: auto;

  transition:
    transform 0.75s cubic-bezier(0.77, 0, 0.175, 1);
}

/* Banner / hero panel */

.hero {
  position: relative;
  height: 100%;
  min-height: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  border-left: 1px solid var(--border);
  background: #050608;
}

.hero-side {
  position: absolute;
  top: 0;
  left: 50vw;

  width: 50vw;
  height: 100%;

  border-left: 1px solid var(--border);
  border-right: none;

  transform: translateX(0);

  transition:
    left 0.75s cubic-bezier(0.77, 0, 0.175, 1),
    width 0.75s cubic-bezier(0.77, 0, 0.175, 1),
    transform 0.75s cubic-bezier(0.77, 0, 0.175, 1),
    border-color 0.35s ease;
}

/* Project detail panel */

.project-detail-panel {
  position: absolute;
  top: 0;
  left: 100vw;

  width: 100vw;
  height: 100%;

  background:
    radial-gradient(circle at top right, var(--accent-soft), transparent 38%),
    var(--panel);

  border-left: 1px solid var(--border);

  overflow-y: auto;
  overflow-x: hidden;

  /* Room for the slide-up footer so it doesn't cover the prev/next bar */
  padding-bottom: var(--footer-h);

  opacity: 1;
  visibility: visible;

  transition:
    left 0.75s cubic-bezier(0.77, 0, 0.175, 1);
}

/* Open project state */

/* Deep link / refresh restore: suppress the slide so the page appears already
   open instead of animating in from the home grid (see openContent `instant`). */
.portfolio-layout.no-open-anim .portfolio-side,
.portfolio-layout.no-open-anim .hero-side,
.portfolio-layout.no-open-anim .project-detail-panel,
.portfolio-layout.no-open-anim .project-cloud,
.portfolio-layout.no-open-anim #portfolio-grid {
  transition: none !important;
}

/* During the initial-load window (no-open-anim is present only until the first
   paint, then the JS lifts it) hide the PARKED, off-screen side view so it can
   never flash into view before its transform settles on a refresh — the Home
   hero briefly showing on a Projects/Mosaic refresh, and vice-versa. The active
   view stays visible; both are revealed together once no-open-anim is removed,
   so in-app slides still animate normally. Desktop only: this is where the views
   are parked via transform (side-by-side panels). On mobile (<=950px) the hero is
   moved INTO .portfolio-side and visibility is handled by display rules, so this
   would wrongly blank the stacked view. */
@media (min-width: 951px) {
  .portfolio-layout.no-open-anim.projects-mode .hero-side,
  .portfolio-layout.no-open-anim.home-mode .portfolio-side {
    visibility: hidden;
  }
}

/* Mosaic (Cloud view) refresh: the server renders `cloud-view` so the overlay is
   in place on the first paint, but the thumbnail grid parked behind it must not
   flash during that load window either. Hide it until no-open-anim is lifted, on
   every viewport (the Mosaic overlay is full-bleed in all sizes). */
.portfolio-layout.no-open-anim.cloud-view #portfolio-grid {
  visibility: hidden;
}

.portfolio-layout.project-open .portfolio-side {
  transform: translateX(-105%);
  pointer-events: none;
}

.portfolio-layout.project-open .hero-side {
  transform: translateX(-105%);
  pointer-events: none;
}

.portfolio-layout.project-open .project-detail-panel {
  left: 0;
  width: 100vw;
}

/* Hero */

.hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;

  transition:
    opacity 0.6s ease,
    visibility 0.6s ease;
}

.hero-slide.active {
  opacity: 1;
  visibility: visible;
}

.hero-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  transform: scale(1.02);
}

.hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  padding: 70px 9%;
}

.eyebrow {
  color: var(--accent);
  font-size: 14px;
  letter-spacing: 2px;
  font-weight: 700;
  text-transform: uppercase;
  margin-bottom: 20px;
}

.hero h1 {
  margin: 0;
  max-width: 620px;
  font-size: clamp(42px, 4.6vw, 78px);
  line-height: 0.98;
  letter-spacing: -2px;
}

.hero p {
  max-width: 520px;
  margin: 28px 0 0;
  font-size: 19px;
  line-height: 1.5;
  color: var(--muted);
}

.hero-actions {
  display: flex;
  gap: 18px;
  margin-top: 34px;
}

.hero-button {
  border: 1px solid var(--accent);
  color: var(--text);
  background: transparent;
  padding: 16px 28px;
  border-radius: 8px;

  font-family: "Sora", Arial, sans-serif;
  font-size: 16px;

  cursor: pointer;
  text-decoration: none;
  display: inline-block;

  transition:
    transform 0.25s ease,
    box-shadow 0.25s ease,
    background 0.25s ease,
    color 0.25s ease,
    border-color 0.25s ease;
}

.hero-button.primary {
  background: linear-gradient(135deg, var(--accent-light), var(--accent-dark));
  color: #ffffff;
  border-color: transparent;
  box-shadow: 0 14px 32px var(--accent-shadow);
}

.hero-button.primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 42px var(--accent-shadow);
}

/* Slider controls */

.hero-slider-controls {
  position: absolute;
  right: 28px;
  bottom: 34px;
  z-index: 5;
  display: flex;
  gap: 10px;

  transition: bottom 0.45s cubic-bezier(0.77, 0, 0.175, 1);
}

.hero-slider-controls button {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.45);
  color: var(--text);
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(8px);

  transition:
    color 0.22s ease,
    border-color 0.22s ease,
    background 0.22s ease,
    transform 0.22s ease;
}

.hero-slider-controls button:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: rgba(0, 0, 0, 0.62);
  transform: translateY(-1px);
}

.hero-slider-dots {
  position: absolute;
  left: 9%;
  bottom: 36px;
  z-index: 5;
  display: flex;
  gap: 8px;

  transition: bottom 0.45s cubic-bezier(0.77, 0, 0.175, 1);
}

.hero-slider-dots button {
  width: 28px;
  height: 3px;
  padding: 0;
  border: none;
  background: rgba(255, 255, 255, 0.35);
  cursor: pointer;
}

.hero-slider-dots button.active {
  background: var(--accent);
}

/* Filters */

.filter-wrap {
  position: sticky;
  top: 0;
  z-index: 20;
  background: linear-gradient(180deg, var(--panel), var(--bg));
  padding: 18px 28px;
  border-bottom: none;
}

/* ============================ Control bar =============================
   Homepage filter/sort/view bar above the thumbnails. Two cyclers (category +
   sort) with ‹ › arrows, and the Grid / Project Cloud view toggles. */
.control-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  width: 100%;
  align-items: stretch;
  justify-content: flex-end;   /* align the filter/sort box to the right of the header */
}
.ctrl {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 44px;
  padding: 6px 12px 6px 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background:
    radial-gradient(120% 140% at 0% 0%, rgba(var(--accent-rgb), 0.08), transparent 60%),
    linear-gradient(160deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.015));
}
/* --- Filter / Sort control group -------------------------------------------
   A horizontal group inside the unified box: [ icon ] [ value + ▼ ] [ ‹ › ].
   Two of these (filter + sort) sit in one .ctrl-unified box, split by a divider. */
.ctrl-group {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 0 1 auto;
  min-width: 0;
}
.ctrl-dropdown { position: relative; }

/* Filter + Sort share ONE rounded box: .ctrl-unified carries the border/bg
   (via .ctrl); the two groups sit inside it, split by a divider (.ctrl-sort). */
.ctrl-unified { gap: 0; }

/* Icon that replaces the text label (funnel = filter, arrows = sort). */
.ctrl-icon {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  color: var(--accent);
}
.ctrl-icon svg { width: 18px; height: 18px; }

/* The value + chevron area. FIXED width so it doesn't resize (and shift the rest
   of the bar) as the value changes; borderless now that .ctrl-unified is the box.
   The value truncates with an ellipsis if it ever exceeds this width. */
.ctrl-box {
  flex: 0 0 auto;
  width: 200px;
  display: flex;
  align-items: center;
  min-width: 0;
}

/* Compact, borderless arrows inside the unified box. */
.ctrl-unified .ctrl-arrow {
  width: 30px;
  height: 30px;
  border: 0;
  background: none;
}
.ctrl-unified .ctrl-arrow:hover { background: rgba(var(--accent-rgb), 0.14); }

/* Label — outside the box, to its left. */
.ctrl-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
  flex-shrink: 0;
}

/* Clickable value + chevron inside the box (reset button styling). space-between
   pushes the ▼ to the right edge of the box. */
.ctrl-open {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ctrl-value {
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.ctrl-arrows { display: flex; gap: 7px; flex-shrink: 0; }

/* iPhone-only original cycler cards: label + value on one line + ‹ › arrows,
   each in its own .ctrl box. Hidden by default; shown ≤600px (where the unified
   box is hidden — see the phone media query). */
.ctrl-phone-only { display: none; }
.ctrl-cycle { flex: 1 1 240px; justify-content: space-between; }
.ctrl-text { display: flex; flex-direction: row; align-items: baseline; gap: 9px; min-width: 0; }

/* Category dropdown menu (opens under the card, themed to match Sort). */
.ctrl-menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 40;
  display: none;
  flex-direction: column;
  gap: 2px;
  min-width: max(100%, 220px);
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: linear-gradient(160deg, rgba(20, 22, 29, 0.55), rgba(12, 13, 18, 0.55));
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);            /* keep text legible over the see-through bg */
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
}
.ctrl-dropdown.open .ctrl-menu { display: flex; }
.ctrl-option {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 40px;
  padding: 0 12px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
  text-align: left;
  cursor: pointer;
  transition: background 0.16s ease, color 0.16s ease;
}
.ctrl-option:hover { background: rgba(255, 255, 255, 0.06); }
.ctrl-option.active {
  background: rgba(var(--accent-rgb), 0.14);
  color: var(--accent-light);
}
.ctrl-option-check {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--accent);
  opacity: 0;
}
.ctrl-option.active .ctrl-option-check { opacity: 1; }
.ctrl-arrow {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: rgba(0, 0, 0, 0.25);
  color: var(--accent);
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease, transform 0.08s ease;
}
.ctrl-arrow svg { width: 18px; height: 18px; }
.ctrl-arrow:hover {
  border-color: var(--accent-border);
  background: rgba(var(--accent-rgb), 0.14);
  color: var(--accent-light);
}
.ctrl-arrow:active { transform: scale(0.92); }

/* View toggle buttons (Grid Thumbnails / Project Cloud) */
.ctrl-view {
  flex: 0 0 auto;
  gap: 12px;
  padding-right: 20px;
  color: var(--text);
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.ctrl-view-icon { color: var(--accent); display: grid; place-items: center; }
.ctrl-view-icon svg { width: 20px; height: 20px; }
.ctrl-view-label { font-size: 14px; font-weight: 600; white-space: nowrap; }
.ctrl-view:hover { border-color: var(--accent-border); }
.ctrl-view.active {
  border-color: var(--accent-border);
  background:
    radial-gradient(120% 140% at 0% 0%, rgba(var(--accent-rgb), 0.20), transparent 60%),
    linear-gradient(160deg, rgba(var(--accent-rgb), 0.12), rgba(var(--accent-rgb), 0.04));
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), 0.35), 0 0 18px rgba(var(--accent-rgb), 0.12);
}

@media (max-width: 600px) {
  .ctrl { min-height: 42px; padding: 6px 10px 6px 14px; }
  .ctrl-value { font-size: 15px; }
  .ctrl-view { flex: 1 1 auto; justify-content: center; padding-right: 14px; }

  /* iPhone: use the ORIGINAL cycler cards instead of the unified/dropdown box. */
  .ctrl-unified { display: none; }
  .ctrl-phone-only { display: flex; }
}

/* The Grid / Project Cloud view switcher is a desktop-only feature — hide it on
   touch devices (phones and all iPads), which just use the thumbnail grid. */
@media (hover: none) and (pointer: coarse) {
  .ctrl-view { display: none; }
}

/* ===== "Project Cloud" view (homepage) ================================
   Shown in place of the thumbnail grid when the Cloud toggle is active. The hub
   fills the visible panel area below the control bar (no scroll) — the side
   becomes a flex column and the stage flexes to fill the remaining viewport, so
   timeline.js lays the nodes out to populate exactly what's on screen. */
/* Mosaic (cloud) is an OVERLAY that slides in from the right over the grid, so
   switching Projects <-> Mosaic (main menu OR the view toggle) animates instead
   of snapping. It sits BELOW the sticky controls (z-20, which stay visible) and
   below the detail panel (z-15, so a project opened from Mosaic still slides over
   it), and ABOVE the grid. `--sa-controls-h` (set from JS) offsets it below the
   controls bar so the mosaic content isn't hidden behind it. */
.project-cloud {
  position: absolute;
  top: var(--sa-controls-h, 0px);
  left: 0;
  right: 0;
  bottom: 0;                          /* tablet (≤1024px) uses this with height:auto */
  /* CSS-driven height so the overlay (and its hub stage) auto-tracks the viewport
     on window resize / maximize — timeline.js re-lays out natively off that size
     change. On desktop top+height win (bottom is ignored); the ≤1024px stacked
     layout overrides this with height:auto and falls back to bottom:0. Do NOT set
     an explicit px height from JS here: the hub's own resize handler is registered
     first and would read the stale height before the JS updated it, freezing the
     network. JS only nudges `top` (see pinMosaicOverlay) to offset the grid's
     frozen scroll. */
  height: calc(100% - var(--sa-controls-h, 0px));
  z-index: 5;
  display: flex;
  flex-direction: row;              /* hub (left) + mosaic intro (right) */
  align-items: stretch;
  overflow: hidden;
  transform: translateX(100%);      /* parked off-screen right in grid view */
  transition: transform 0.6s cubic-bezier(0.77, 0, 0.175, 1);
  pointer-events: none;
  background:
    radial-gradient(circle at top right, var(--accent-soft), transparent 38%),
    var(--panel);
}
/* Don't let the grid behind scroll while the Mosaic overlay is in. */
.portfolio-layout.cloud-view .portfolio-side {
  overflow: hidden;
}
/* Cloud stays below the detail panel so a project opened from Mosaic slides OVER
   it (not snapping in behind) — still under the gallery viewer (20) / footer (25). */
.portfolio-layout.cloud-view .project-detail-panel {
  z-index: 15;
}
.portfolio-layout.cloud-view .project-cloud {
  transform: translateX(0);         /* slid in */
  pointer-events: auto;
}
/* Coordinated page-to-page slide: as the Mosaic overlay slides IN from the
   right, push the thumbnails grid OUT to the left in lockstep (same 0.6s /
   easing), so Projects and Mosaic read as connected panels being swapped
   rather than the Mosaic appearing on top of a static grid. The two edges
   stay flush through the whole sweep, and it reverses on the way back. The
   grid is clipped by .portfolio-side's overflow:hidden in cloud view. */
#portfolio-grid {
  transition: transform 0.6s cubic-bezier(0.77, 0, 0.175, 1);
}
.portfolio-layout.cloud-view #portfolio-grid {
  transform: translateX(-100%);
}
.project-cloud .about-hub {
  margin: 0;
  padding: 4px 0 0;
  border-top: none;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
/* Stays within the panel; icons may bleed a few px past the edge (overflow
   visible + the small clamp allowance in timeline.js). Height flexes to fill. */
.project-cloud .hub-stage {
  width: 100%;
  margin-left: 0;
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
}
/* The intro column scrolls independently and its bottom sits under the fixed
   footer. Reserve the footer height + a gap so the last paragraph / stat cards
   clear it instead of being hidden behind it. Keyed to .mosaic-page (a STATIC
   class on the overlay) rather than the toggled .cloud-view: gating it on
   cloud-view meant leaving Mosaic removed this bottom padding mid-transition,
   which re-centered the auto-margined .mosaic-intro-inner and made the text
   jump down while the overlay was still sliding out. A constant reserve keeps
   the intro content locked in place through the transition. */
.mosaic-page .mosaic-intro {
  padding-bottom: calc(var(--footer-h) + 48px);
}

/* Dropdown toggle is only used on mobile (see ≤950px); hidden on desktop */
.filter-toggle {
  display: none;
}

/* Outer wrapper for the category filters + Sort control. On desktop it just
   holds .filter-scroll; on mobile it becomes the collapsible dropdown panel
   (see media queries). */
.filter-bar {
  width: 100%;
  display: flex;
  align-items: stretch;
  gap: 10px;

  /* Must NOT clip: the Sort dropdown (.sort-wrap > .sort-menu) opens below the
     row and would be cut off by any scroll container's promoted overflow-y. */
  overflow: visible;
}

/* The category buttons + the Sort control flow as one row. They stack onto new
   rows as the window narrows (no horizontal scroll), then the whole thing is
   collapsed into the dropdown at the mobile breakpoint (see ≤950px). Because
   nothing here scrolls, there's no overflow to clip the Sort dropdown. */
.filter-scroll {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 10px;
}

/* The in-dropdown sort group + section labels are mobile-only (see ≤950px);
   on desktop, sorting lives in the standalone .sort-wrap control instead.
   Use the child combinator so this hides ONLY the mobile sort buttons in the
   filter list — not the ones nested in .sort-wrap > .sort-menu (the desktop
   dropdown), which would otherwise open empty. */
.filter-section-label,
.filter-scroll > .sort-option {
  display: none;
}

.filter-bar button {
  flex: 1 1 auto;
  white-space: nowrap;
  min-height: 48px;
  padding: 13px 16px;

  background: rgba(255, 255, 255, 0.035);
  color: rgba(255, 255, 255, 0.72);

  border: 1px solid rgba(255, 255, 255, 0.055);
  border-radius: 8px;

  cursor: pointer;

  font-family: "Sora", Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.035),
    0 8px 18px rgba(0, 0, 0, 0.28);

  transition:
    background 0.22s ease,
    color 0.22s ease,
    border-color 0.22s ease,
    box-shadow 0.22s ease,
    transform 0.22s ease;
}

.filter-bar button:hover {
  background: rgba(255, 255, 255, 0.065);
  color: #ffffff;
  border-color: rgba(var(--accent-rgb), 0.22);

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 10px 22px rgba(0, 0, 0, 0.34),
    0 0 16px rgba(var(--accent-rgb), 0.08);

  transform: translateY(-1px);
}

.filter-bar button.active {
  background: linear-gradient(
    135deg,
    rgba(var(--accent-rgb), 0.34),
    rgba(var(--accent-rgb), 0.16)
  );

  color: var(--accent-light);
  border-color: rgba(var(--accent-rgb), 0.18);

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 10px 24px rgba(0, 0, 0, 0.36),
    0 0 18px rgba(var(--accent-rgb), 0.10);
}

.filter-bar button.active:hover {
  background: linear-gradient(
    135deg,
    rgba(var(--accent-rgb), 0.48),
    rgba(var(--accent-rgb), 0.24)
  );

  color: #ffffff;
  border-color: rgba(var(--accent-rgb), 0.28);

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 12px 28px rgba(0, 0, 0, 0.4),
    0 0 22px rgba(var(--accent-rgb), 0.14);

  transform: translateY(-1px);
}

/* Sort-by control — inline at the end of the filter bar, divided from the
   categories by a vertical rule (see the design). */
.sort-wrap {
  position: relative;
  flex: 0 0 auto;
}

.sort-toggle {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;

  min-height: 48px;
  min-width: 200px;
  padding: 8px 40px 8px 16px;

  background: rgba(255, 255, 255, 0.035);
  color: #ffffff;

  border: 1px solid rgba(255, 255, 255, 0.055);
  border-radius: 8px;

  cursor: pointer;
  font-family: "Sora", Arial, sans-serif;
  text-align: left;

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.035),
    0 8px 18px rgba(0, 0, 0, 0.28);

  transition:
    background 0.22s ease,
    border-color 0.22s ease,
    box-shadow 0.22s ease;
}

.sort-toggle:hover {
  background: rgba(255, 255, 255, 0.065);
  border-color: rgba(var(--accent-rgb), 0.22);
}

.sort-toggle[aria-expanded="true"] {
  border-color: rgba(var(--accent-rgb), 0.22);
}

.sort-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-light);
}

.sort-value {
  font-size: 14px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.92);
  white-space: nowrap;
}

.sort-caret {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--accent-light);
  font-size: 14px;
  line-height: 1;
  pointer-events: none;
  transition: transform 0.22s ease;
}

.sort-toggle[aria-expanded="true"] .sort-caret {
  transform: translateY(-50%) rotate(180deg);
}

.sort-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 30;
  min-width: 100%;

  display: none;
  flex-direction: column;
  gap: 4px;
  padding: 8px;

  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;

  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
}

.sort-menu.open {
  display: flex;
}

.sort-menu button {
  min-height: 42px;
  padding: 11px 14px;

  background: transparent;
  color: rgba(255, 255, 255, 0.75);

  border: 1px solid transparent;
  border-radius: 7px;

  cursor: pointer;
  text-align: left;
  white-space: nowrap;

  font-family: "Sora", Arial, sans-serif;
  font-size: 13px;
  font-weight: 600;

  transition:
    background 0.18s ease,
    color 0.18s ease,
    border-color 0.18s ease;
}

.sort-menu button:hover {
  background: rgba(255, 255, 255, 0.06);
  color: #ffffff;
}

.sort-menu button.active {
  background: linear-gradient(
    135deg,
    rgba(var(--accent-rgb), 0.30),
    rgba(var(--accent-rgb), 0.14)
  );
  color: var(--accent-light);
  border-color: rgba(var(--accent-rgb), 0.18);
}

/* Portfolio grid */

main {
  width: 100%;
  flex: 1;
  padding: 28px 28px calc(28px + var(--footer-h));
}

.main-grid {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 10px;   /* fine unit; each card's height is set via grid-row span */
  gap: 18px;
  position: relative; /* anchor for cards animating out of flow on filter/sort */
}

/* Cards */

.content-box {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--border);
  cursor: pointer;
  background: var(--panel);

  transition:
    transform 0.25s ease,
    border-color 0.25s ease,
    box-shadow 0.25s ease;
}

/* Masonry heights: cards span a number of grid rows to vary their height and
   interlock (featured = tall). With grid-auto-rows:10px + 18px row-gap, a span
   of N renders ~ (28N - 18)px tall. */
.content-box            { grid-row: span 11; } /* default = medium (~290px) */
.content-box.tile-tall  { grid-row: span 14; } /* ~374px */
.content-box.tile-medium{ grid-row: span 11; } /* ~290px */
.content-box.tile-short { grid-row: span 9;  } /* ~234px */

.content-box:hover {
  transform: translateY(-5px);
  border-color: var(--accent-border);
  box-shadow: 0 22px 50px rgba(0, 0, 0, 0.35);
}

.content-box img {
  width: 100%;
  height: 100%;
  min-height: 230px;
  object-fit: cover;
  display: block;
  opacity: 0.72;

  transition:
    transform 0.35s ease,
    opacity 0.35s ease;
}

.content-box:hover img {
  transform: scale(1.06);
  opacity: 0.9;
}

.content-box::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.57),
    rgba(0, 0, 0, 0.10),
    rgba(0, 0, 0, 0.05)
  );
  pointer-events: none;
}

.card-content {
  position: absolute;
  left: 22px;
  right: 22px;
  bottom: 22px;
  z-index: 3;
}

/* Date shown below the title inside the card. */
.card-date {
  margin-top: 6px;

  color: rgba(255, 255, 255, 0.6);
  font-family: "Sora", Arial, sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
}

/* Inline "Loading content" overlay shown inside a clicked thumbnail
   while its project detail is being fetched. */
.content-box-loading {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: rgba(7, 9, 12, 0.72);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

.content-box-loading-text {
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
}

.content-box-spinner {
  flex: 0 0 auto;   /* never let a short flex container squash it into an oval */
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid rgba(var(--accent-rgb), 0.25);
  border-top-color: var(--accent);
  animation: content-box-spin 0.7s linear infinite;
}

@keyframes content-box-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Spinner-only loading overlay for small controls (prev/next buttons): no text,
   a smaller round loader, transparent so it doesn't fully black out the button. */
.content-box-loading--compact {
  gap: 0;
  background: rgba(7, 9, 12, 0.55);
}
.content-box-loading--compact .content-box-spinner {
  width: 20px;
  height: 20px;
  border-width: 2px;
}

.card-category {
  color: var(--accent);
  font-size: 12px;
  letter-spacing: 2px;
  font-weight: 700;
  margin-bottom: 8px;
}

.card-title {
  font-size: 22px;
  font-weight: 700;
  margin-bottom: 6px;
}

.card-subtitle {
  color: var(--muted);
  font-size: 14px;
  line-height: 1.35;
}

/* Project detail */

.project-detail-loading {
  padding: 56px;
  color: var(--muted);
  font-size: 18px;
}

/* Prev/next navigation: the old content slides out while the new content slides
   in, both as absolutely-positioned layers over the stationary panel (whose dark
   background keeps the homepage hidden). Each layer keeps its own scroll so the
   swap is seamless — no snap. Transforms/timing are applied inline by the JS. */
.project-detail-panel .detail-layer {
  position: absolute;
  inset: 0;
  overflow-x: hidden;
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  will-change: transform;

  /* Match the panel's scroll geometry so a restored scroll position doesn't
     clamp and snap the content. */
  padding-bottom: var(--footer-h);
}

.project-detail-panel .detail-layer::-webkit-scrollbar {
  width: 0;
  height: 0;
}

.project-detail-inner {
  max-width: 1500px;
  min-width: auto;
  margin: 0 auto;
  padding: 48px;
}

/* Full-width project banner with the title inside it */

.project-detail-banner {
  position: relative;
  width: 100%;
  min-height: clamp(300px, 46vh, 520px);

  display: flex;
  align-items: flex-end;

  background-size: cover;
  background-position: center;

  border-bottom: 1px solid var(--border);
}

.project-detail-banner-inner {
  width: 100%;
  padding: 48px;
}

.project-detail-banner h1 {
  margin: 0;
  color: #ffffff;
  font-size: clamp(40px, 5.4vw, 80px);
  line-height: 0.98;
  letter-spacing: -2px;
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.45);
}

.project-back-button--overlay {
  position: absolute;
  top: 28px;
  left: 28px;
  margin: 0;
  z-index: 2;
}

.project-back-button {
  position: relative;   /* anchor for the in-button loading overlay (showBoxLoading), like .project-nav-link */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 34px;
  text-decoration: none;

  border: 1px solid rgba(var(--accent-rgb), 0.25);
  background: rgba(var(--accent-rgb), 0.10);
  color: var(--text);

  border-radius: 8px;
  padding: 12px 18px;

  font-family: "Sora", Arial, sans-serif;
  font-size: 14px;
  font-weight: 700;

  cursor: pointer;

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 8px 18px rgba(0, 0, 0, 0.25);

  transition:
    background 0.22s ease,
    border-color 0.22s ease,
    transform 0.22s ease;
}

.project-back-button:hover {
  background: rgba(var(--accent-rgb), 0.18);
  border-color: rgba(var(--accent-rgb), 0.42);
  transform: translateY(-1px);
}

.project-detail-id {
  color: var(--muted);
  font-size: 13px;
  margin-bottom: 18px;
}

.project-detail-category {
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  margin-bottom: 18px;
}

.project-detail-inner h1 {
  margin: 0;
  font-size: clamp(40px, 5vw, 72px);
  line-height: 0.98;
  letter-spacing: -2px;
}

.project-detail-subtitle {
  margin-top: 22px;
  color: var(--muted);
  font-size: 21px;
  line-height: 1.5;
  max-width: 760px;
}

.project-detail-body {
  margin: 34px 0 0;
  color: var(--text);
  font-size: 19px;
  line-height: 1.7;
  max-width: 820px;
}

/* ===========================================================================
   About page: three-column layout (bio / profile details / focus)
   Rendered both standalone (about.php) and inside the index panel (id=about).
   =========================================================================== */
/* Container width in both contexts. The standalone rule is qualified with
   .standalone-detail-page to beat that block's 1180px cap; the panel rule uses
   the #project-detail-panel id to beat the base .project-detail-inner width. */
.standalone-detail-page .project-detail-inner.about-page,
#project-detail-panel .project-detail-inner.about-page {
  max-width: 1500px;
}

/* ===== Mosaic page: a rounded amber-bordered card holding a left intro
   column and the hub network on the right. ===== */

body:has(.mosaic-page) .standalone-detail-page {
  padding: 0;
  overflow-x: hidden;
}

/* Edge-to-edge (no card): full-width two columns — hub left, intro right. */
.standalone-detail-page .project-detail-inner.mosaic-page,
#project-detail-panel .project-detail-inner.mosaic-page {
  max-width: none;
  width: 100%;
  margin: 0;
  padding: 0;
  height: calc(100vh - 176px); /* viewport − header(86) − footer(90) */
  display: flex;
  align-items: stretch;
  background:
    radial-gradient(circle at 30% 46%, rgba(var(--accent-rgb), 0.06), transparent 55%),
    var(--panel);
  border: none;
  border-radius: 0;
  box-shadow: none;
  overflow: hidden;
  position: relative;
}

/* The dedicated Mosaic page hides the hub's own "The Mosaic" heading (the intro
   column titles it); the homepage Cloud view keeps its heading. */
.mosaic-page .hub-head { display: none; }

/* Mosaic: project nodes show just the thumbnail — hide the project name label. */
.mosaic-page .hub-project .hub-node-label { display: none; }

/* Right column — the hub fills the remaining space (flex, like the cloud view). */
.mosaic-page .about-hub {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.mosaic-page .hub-stage {
  flex: 1 1 auto;
  width: 100%;
  height: auto;
  min-height: 0;
  margin-left: 0;
}

/* Right column — intro. (DOM order is hub then intro, so no order flip needed;
   the hub sits on the left, the intro on the right.) */
.mosaic-intro {
  position: relative;
  flex: 0 0 clamp(360px, 34%, 620px);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding: 52px 48px;
  overflow-y: auto;
  z-index: 2;
}
/* Auto margins center the content when it fits, but collapse to 0 when it's
   taller than the column — so on resize it flows DOWNWARD from the top (never
   pushing up under the header) and stays scrollable. */
.mosaic-intro-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 28px;
  margin: auto 0;
}
/* Vertical divider between hub + text: runs from the heading down to a little
   past the stat cards, tracking the content. left:-48px reaches the column's
   left edge (matches .mosaic-intro padding-left). */
.mosaic-intro-inner::before {
  content: "";
  position: absolute;
  left: -48px;
  top: 0;
  bottom: -18px;
  width: 1px;
  background: rgba(var(--accent-rgb), 0.28);
  pointer-events: none;
}

/* Eyebrow with a trailing glowing line */
.mosaic-intro-eyebrow {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-bottom: 26px;
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 4px;
  text-transform: uppercase;
}
.mosaic-intro-eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(var(--accent-rgb), 0.7), rgba(var(--accent-rgb), 0));
  box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.5);
}

/* Styled like the site's gold section headings (.project-detail-heading /
   .about-eyebrow). Scoped with .mosaic-page to beat `.project-detail-inner h1`
   (clamp 40–72px), which was overriding this and keeping the heading huge. */
.mosaic-page .mosaic-intro-title {
  margin: 0 0 20px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent);
}
.mosaic-intro-title .mi-amp { color: inherit; }

.mosaic-intro-rule {
  display: block;
  width: 132px;
  height: 3px;
  margin: 22px 0 26px;
  border-radius: 3px;
  background: var(--accent);
}

.mosaic-intro-body p {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.65;
}
.mosaic-intro-body p:last-child { margin-bottom: 0; }
.mosaic-intro-body strong { color: inherit; font-weight: inherit; }

/* Stat cards row */
.mosaic-stats { display: flex; gap: 16px; flex-wrap: wrap; }
.mosaic-stat {
  flex: 1 1 0;
  min-width: 150px;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  border: 1px solid rgba(var(--accent-rgb), 0.28);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
}
.mosaic-stat-icon {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(var(--accent-rgb), 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
}
.mosaic-stat-icon svg { width: 22px; height: 22px; }
.mosaic-stat-text { display: flex; flex-direction: column; line-height: 1.1; }
.mosaic-stat-count { color: var(--accent); font-size: 20px; font-weight: 800; }
.mosaic-stat-label { color: var(--muted); font-size: 13px; margin-top: 3px; }
.mosaic-stat-chev { margin-left: auto; color: rgba(var(--accent-rgb), 0.7); display: flex; }
.mosaic-stat-chev svg { width: 18px; height: 18px; }

/* iPad / tablet: stack the network on top and the intro text below it. */
@media (max-width: 1024px) {
  .standalone-detail-page .project-detail-inner.mosaic-page,
  #project-detail-panel .project-detail-inner.mosaic-page {
    flex-direction: column;
    height: auto;
    overflow-y: hidden;
  }
  /* Homepage Cloud overlay: key the stacked treatment to the STATIC
     .project-cloud.mosaic-page classes, NOT the toggled .cloud-view. Gating it on
     .cloud-view meant leaving Mosaic stripped these rules mid-slide, snapping the
     overlay back to the desktop row layout so the text jumped up over the icons.
     Keeping them static holds the stacked layout steady while it slides away.
     - Explicit height (independent of the inline `top` pinMosaicOverlay sets from
       the grid's frozen scrollTop) prevents the collapse seen when entering from a
       bottom-scrolled grid; matches the old height when scrollTop is 0.
     - overflow-y:auto makes the overlay the vertical touch-scroll container so the
       tall stacked content scrolls on iPad/phone; scrollbars hidden for a clean look.
     Scoped to ≤1024px, so desktop and the standalone mosaic page are untouched. */
  .project-cloud.mosaic-page {
    flex-direction: column;
    height: calc(100% - var(--sa-controls-h, 0px));
    bottom: auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;   /* iOS momentum scrolling */
    scrollbar-width: none;               /* Firefox: no visible scrollbar */
    -ms-overflow-style: none;            /* legacy Edge/IE */
  }
  .project-cloud.mosaic-page::-webkit-scrollbar {
    width: 0;
    height: 0;
    display: none;                       /* WebKit/Blink: no visible scrollbar */
  }
  .mosaic-page .about-hub {
    flex: 0 0 auto;
    height: 58vh;
    min-height: 400px;
  }
  .mosaic-intro {
    flex: 0 0 auto;
    width: 100%;
    overflow-y: visible;
    padding: 28px 28px 40px;
  }
  .mosaic-intro-inner { margin: 0; }        /* top-align when stacked */
  .mosaic-intro-inner::before { display: none; } /* no vertical divider when stacked */
}

/* ===========================================================================
   About page — editorial dark layout: hero portrait, philosophy + stat row,
   and application expertise. Rendered standalone (about.php) AND inside the
   index panel (id=about). Constrained to a centred column (see the 1400px
   .about-page rule above) so it matches the project-detail description width
   instead of running edge to edge.
   =========================================================================== */

.about2 { color: var(--text); padding-bottom: 56px; }
.about2-eyebrow {
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  margin-bottom: 18px;
}

/* ---- Hero: full-width photo banner with the text overlaid on the left ---- */
.about2-hero {
  position: relative;
  display: flex;              /* stretch the text panel to the full banner height */
  align-items: stretch;
  min-height: clamp(300px, 46vh, 520px);   /* match the project-detail banner height */
  overflow: hidden;
  border-radius: 12px;
}
.about2-hero-text {
  position: relative;   /* sits over the photo as a solid panel on the left */
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex: 0 0 auto;
  width: clamp(300px, 32vw, 440px);   /* narrower left panel */
  padding: clamp(30px, 4vh, 56px) clamp(28px, 4.5vw, 72px);
  background: var(--bg);   /* solid site background panel */
}
/* Vertical rhythm inside the hero banner: headline → paragraph → résumé button. */
.about2-hero-text .about2-phil-head { margin-bottom: 22px; }
.about2-hero-text .about2-intro { margin-bottom: 30px; }
.about2-title {
  margin: 0;
  font-weight: 700;
  font-size: clamp(32px, 4vw, 60px);
  line-height: 1.02;
  letter-spacing: -1px;
  text-transform: uppercase;
}
.about2-rule { display: block; width: 64px; height: 2px; margin: 26px 0 22px; background: var(--accent); }
.about2-lede {
  margin: 0 0 22px;
  max-width: 30ch;
  color: var(--accent);
  font-size: clamp(15px, 1.35vw, 19px);
  font-weight: 600;
  letter-spacing: 2px;
  line-height: 1.5;
  text-transform: uppercase;
}
.about2-intro { margin: 0; max-width: 46ch; color: var(--muted); font-size: 15px; line-height: 1.75; }
.about2-sign {
  margin: 34px 0 0;
  font-size: 22px;
  font-weight: 600;
  font-style: italic;
  letter-spacing: 0.3px;
  color: var(--accent);
}
.about2-social { list-style: none; display: flex; gap: 26px; margin: 26px 0 0; padding: 0; }
.about2-social a {
  color: var(--text);
  text-decoration: none;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  padding-bottom: 4px;
  border-bottom: 1px solid rgba(var(--accent-rgb), 0.5);
  transition: color 0.2s ease;
}
.about2-social a:hover { color: var(--accent); }

.about2-hero-photo { position: relative; flex: 1 1 auto; overflow: hidden; }   /* fills the space to the right of the text panel */
.about2-hero-photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* left-edge fade so the photo melts into the text side */

/* ---- About Me: bio paragraph (left) + statement (right), split by a rule ---- */
.about2-bio {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;   /* no gap: the divider (border-left + paddings) provides the column separation */
  align-items: stretch;   /* both columns match the taller one so the divider rule spans full height */
  padding: clamp(40px, 6vh, 80px) 0;
}
.about2-bio-text {
  display: flex;
  flex-direction: column;
  align-items: flex-end;   /* push the block to the right edge of its column */
  text-align: right;
  padding-right: 33px;
}
.about2-bio-spacer { visibility: hidden; }   /* reserves eyebrow height; aligns the paragraph with the headline */
.about2-bio-text p {
  margin: 0 0 0 auto;   /* right-align the capped block within the column */
  max-width: 57ch;      /* narrower than the column so the block clearly hugs the right */
  color: var(--muted);
  font-size: 15px;
  line-height: 1.85;
}
.about2-bio-text p + p { margin-top: 18px; }   /* gap between stacked bio paragraphs */
.about2-bio-statement {
  border-left: 1px solid var(--border);
  padding-left: 30px;
}
.about2-bio-head {
  margin: 18px 0 0;
  font-weight: 400;
  font-size: clamp(22px, 2.2vw, 32px);   /* match .about2-phil-head */
  line-height: 1.24;
  letter-spacing: -0.5px;
  color: var(--text);
  width: 88%;
}
.about2-bio-head strong { font-weight: 700; }

/* ---- Philosophy + stats ---- */
.about2-mid {
  display: grid;
  grid-template-columns0px fr 1.1fr 1.5fr;
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
  margin-top: clamp(0px , 5vh, 72px);
  padding: 34px 40px 0px 40px;   /* no bottom padding — the last app row's band runs to the card edge */
  border: 1px solid var(--border);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.015);
  margin-top: 0px;
}
.about2-phil-head {
  margin: 0;
  display: flex;
  flex-direction: column;
  font-weight: 700;
  font-size: clamp(22px, 2.2vw, 32px);
  line-height: 1.12;
  letter-spacing: -0.5px;
}
.about2-phil-body p { margin: 0 0 24px; max-width: 42ch; color: var(--muted); font-size: 14.5px; line-height: 1.75; }
.about2-resume {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 13px 22px;
  border: 1px solid var(--accent);
  color: var(--accent);
  text-decoration: none;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  transition: background 0.2s ease, color 0.2s ease;
}
.about2-resume svg { width: 16px; height: 16px; }
.about2-resume:hover { background: var(--accent); color: var(--bg); }

.about2-stats { display: grid; grid-template-columns: repeat(4, 1fr); grid-column: 2 / -1; }
.about2-stat { padding: 4px 18px; border-left: 1px solid var(--border); }
.about2-stat:first-child { border-left: none; padding-left: 0; }
.about2-stat-icon { display: block; margin-bottom: 14px; color: var(--accent); }
.about2-stat-icon svg { width: 26px; height: 26px; }
.about2-stat-value { display: block; font-weight: 800; font-size: clamp(30px, 3vw, 44px); line-height: 1; letter-spacing: -0.5px; }
.about2-stat-label { display: block; margin-top: 8px; font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--muted); line-height: 1.5; }

/* ---- Application Expertise ---- */
.about2-apps {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
  padding: clamp(28px, 3.5vh, 48px) 0;   /* no horizontal padding → list/intro match .about2-mid width */
}
.about2-apps-eyebrow { display: flex; align-items: center; gap: 16px; margin-bottom: 18px; }
.about2-apps-eyebrow span { color: var(--accent); font-weight: 700; font-size: 15px; letter-spacing: 1px; }
.about2-apps-eyebrow i { height: 1px; width: 130px; background: var(--accent); opacity: 0.5; }
.about2-apps-title { margin: 0; font-weight: 700; font-size: clamp(24px, 2.4vw, 34px); line-height: 1.05; letter-spacing: -0.3px; text-transform: uppercase; }
.about2-apps-sub { margin: 16px 0 18px; color: var(--accent); font-size: 13px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; }
.about2-apps-lead { margin: 0 0 26px; max-width: 44ch; color: var(--muted); font-size: 14px; line-height: 1.7; }
.about2-apps-note { display: flex; align-items: center; gap: 14px; margin-bottom: 24px; font-size: 14px; line-height: 1.4; }
.about2-apps-note-icon { display: inline-flex; color: var(--accent); }
.about2-apps-note-icon svg { width: 24px; height: 24px; }
.about2-apps-badge { display: flex; gap: 14px; padding: 18px 20px; border: 1px solid var(--border); border-radius: 8px; background: rgba(255, 255, 255, 0.02); }
.about2-apps-badge-icon { flex: 0 0 auto; display: inline-flex; color: var(--accent); }
.about2-apps-badge-icon svg { width: 22px; height: 22px; }
.about2-apps-badge-head { margin-bottom: 6px; color: var(--accent); font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; }
.about2-apps-badge p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.55; }

.about2-apps-list {
  display: flex;
  flex-direction: column;
}
/* The app-list now lives inside the .about2-mid grid — span it full width on its own row. */
.about2-mid .about2-apps-list { grid-column: 1 / -1; margin-top: clamp(24px, 4vh, 48px); }
.about2-app-row {
  display: grid;
  grid-template-columns: minmax(210px, 1.5fr) 0.7fr 0.7fr 1fr 1.5fr;
  gap: clamp(16px, 1.6vw, 28px);
  align-items: center;
  padding: 22px 0;
  border-bottom: 1px solid var(--border);
}
.about2-app-row:first-child { padding-top: 0; }
.about2-app-row:last-child { border-bottom: none; }   /* keep bottom padding so the tinted band is full height */
.about2-app-id { display: flex; align-items: center; gap: 14px; }
.about2-app-icon { flex: 0 0 auto; width: 56px; height: 56px; border-radius: 12px; overflow: hidden; display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border); font-weight: 700; font-size: 15px; text-transform: uppercase; color: var(--muted); }
.about2-app-icon img { width: 100%; height: 100%; object-fit: cover; }
.about2-app-name { font-weight: 700; font-size: 16px; }
.about2-app-desc { margin-top: 3px; color: var(--muted); font-size: 12.5px; line-height: 1.4; }
.about2-app-metric-label { margin-bottom: 8px; font-size: 10px; font-weight: 700; letter-spacing: 1.2px; text-transform: uppercase; color: var(--muted); opacity: 0.85; }
.about2-app-metric-value { font-weight: 800; font-size: clamp(22px, 2vw, 30px); line-height: 1; letter-spacing: -0.5px; }
.about2-app-metric-unit { margin-top: 5px; font-size: 11px; color: var(--muted); }
.about2-app-bars { display: flex; gap: 5px; margin-bottom: 9px; }
.about2-app-bar { width: 22px; height: 8px; border-radius: 2px; background: rgba(255, 255, 255, 0.12); }
.about2-app-bar.is-on { background: var(--accent); }
.about2-app-level-label { font-size: 13px; font-weight: 600; }
.about2-app-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 7px; }
.about2-app-chips li { padding: 5px 11px; border: 1px solid var(--border); border-radius: 6px; font-size: 11.5px; color: var(--text); background: rgba(255, 255, 255, 0.02); }

/* Application Expertise — table header: filter (App column) + clickable sort headers.
   Mirrors the Projects filter/sort UX (funnel + dropdown, direction arrows). */
.about2-apps-head {
  display: grid;
  grid-template-columns: minmax(210px, 1.5fr) 0.7fr 0.7fr 1fr 1.5fr;
  gap: clamp(16px, 1.6vw, 28px);
  align-items: center;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);   /* match the app-row separators */
}
.about2-apps-head-filter { display: flex; align-items: center; gap: 10px; }
.about2-apps-funnel { display: inline-flex; color: var(--accent); }
.about2-apps-funnel svg { width: 15px; height: 15px; }
.about2-appfilter { position: relative; }
.about2-appfilter-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 14px; border: 1px solid var(--border); border-radius: 8px;
  background: rgba(255, 255, 255, 0.02); color: var(--text); cursor: pointer;
  font-family: inherit; font-size: 16px; font-weight: 600;
  transition: border-color 0.2s ease;
}
.about2-appfilter-btn:hover { border-color: var(--accent-border); }
.about2-appfilter-caret { width: 13px; height: 13px; transition: transform 0.2s ease; }
.about2-appfilter.is-open .about2-appfilter-caret { transform: rotate(180deg); }
.about2-appfilter-menu {
  position: absolute; z-index: 6; top: calc(100% + 6px); left: 0; min-width: 168px;
  display: none; flex-direction: column; padding: 6px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
}
.about2-appfilter.is-open .about2-appfilter-menu { display: flex; }
.about2-appfilter-menu button {
  text-align: left; padding: 10px 14px; border: none; border-radius: 6px; background: transparent;
  color: var(--muted); cursor: pointer; font-family: inherit; font-size: 16px; font-weight: 600;
  transition: color 0.15s ease, background 0.15s ease;
}
.about2-appfilter-menu button:hover { background: rgba(255, 255, 255, 0.05); color: var(--text); }
.about2-appfilter-menu button.is-active { color: var(--accent); }
/* sort options folded into the filter dropdown — hidden until mobile (see 560px block) */
.about2-appfilter-sortgroup { display: none; flex-direction: column; margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--border); }
.about2-appfilter-grouplabel { padding: 8px 14px 4px; font-size: 10px; font-weight: 700; letter-spacing: 1.2px; text-transform: uppercase; color: var(--muted); opacity: 0.8; }

.about2-th {
  display: inline-flex; align-items: center; gap: 6px; justify-self: start;
  padding: 0; border: none; background: transparent; cursor: pointer;
  font-family: inherit; font-size: 16px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--muted); transition: color 0.2s ease;
}
.about2-th:hover { color: var(--text); }
.about2-th.is-active { color: var(--accent); }
.about2-th::after { content: "\2195"; opacity: 0.35; }              /* ↕ sortable */
.about2-th.is-active::after { content: "\2193"; opacity: 1; }       /* ↓ high→low */
.about2-th.is-active[data-dir="asc"]::after { content: "\2191"; }   /* ↑ low→high */
.about2-th--static { cursor: default; }
.about2-th--static::after { content: none; }

/* headers name the columns on desktop → drop the repeated per-row labels there */
.about2-app-metric-label { display: none; }

.about2-app-row.is-hidden { display: none; }

.about2-apps-intro {
  padding-top: 0;
  display: grid;
  grid-template-columns: auto 1fr;   /* left: title + sub stacked | right: full-width lead */
  column-gap: clamp(28px, 4vw, 64px);
  align-items: start;
}
.about2-apps-intro .about2-apps-title { grid-column: 1; grid-row: 1; }
.about2-apps-intro .about2-apps-sub   { grid-column: 1; grid-row: 2; }
.about2-apps-intro .about2-apps-lead  { grid-column: 2; grid-row: 1 / 3; max-width: none; align-self: center; }

@media (max-width: 1150px) {
  .about2-apps { grid-template-columns: 1fr; }
}
@media (max-width: 860px) {
  /* Application Expertise becomes a grid of rounded app cards — 2 per row through
     the tablet range (down to the mobile breakpoint, where it drops to 1). Two
     columns keeps the grid's min-content low enough that the cards + the header
     resize with the .about2-mid box instead of overflowing it. */
  .about2-apps-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
  /* header collapses to a single toolbar row (filter + sort) spread evenly across the card columns */
  .about2-apps-head { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; }
  .about2-apps-head-filter { flex: 0 1 auto; }
  .about2-th--static { display: none; }
  .about2-app-metric-label { display: block; }

  .about2-app-row {
    grid-template-columns: 1fr 1fr;
    gap: 16px 18px;
    align-items: start;
    padding: 20px;
    border: 1px solid var(--border);
    border-radius: 14px;
  }
  .about2-app-row:first-child { padding-top: 20px; }                                    /* undo desktop first-row reset */
  .about2-app-row:last-child { border-bottom: 1px solid var(--border); padding-bottom: 20px; }
  .about2-app-id,
  .about2-app-level,
  .about2-app-focus { grid-column: 1 / -1; }
}

/* On desktop the tinted app rows full-bleed to the mid card's edges (past its
   40px padding) so the icon-derived colour band spans the full width; the
   matching padding keeps the row content aligned where it was. */
@media (min-width: 861px) {
  .about2-app-row,
  .about2-apps-head { margin-left: -40px; margin-right: -40px; padding-left: 40px; padding-right: 40px; }
}

/* ---- About page — responsive tiers ---- */
@media (max-width: 1150px) {
  .about2-mid { grid-template-columns: 1fr 1fr; row-gap: 40px; }
  .about2-stats { grid-column: 1 / -1; }
}
@media (max-width: 860px) {
  /* hero stays a side-by-side banner on tablets (incl. iPad portrait); it only
     stacks at phone widths — see the 560px block below */
  .about2-hero-text { width: clamp(240px, 40vw, 360px); }
  /* .about2-bio keeps its two-column split + divider on tablets; tighten the
     paddings so it still fits — it collapses to one column at phone widths */
  .about2-bio { padding: clamp(30px, 5vh, 60px) 0; }
  .about2-bio-text { padding-right: 22px; }
  .about2-bio-statement { padding-left: 22px; }
  .about2-mid { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
  .about2-hero { flex-direction: column; }        /* stack: text panel on top, photo below */
  .about2-hero-text { width: 100%; }
  .about2-hero-photo { min-height: 260px; }
  .about2-bio { grid-template-columns: 1fr; gap: 24px; }
  .about2-bio-statement { border-left: none; padding-left: 0; order: -1; }   /* large statement leads on mobile */
  .about2-bio-spacer { display: none; }
  .about2-bio-text { align-items: flex-start; text-align: left; padding-right: 0; }
  .about2-bio-text p { max-width: none; margin: 0; }   /* paragraph spans full width on mobile */
  .about2-bio-head { width: 100%; }                    /* statement headline spans full width too */
  /* no card on mobile — drop the box and let the content breathe full width */
  .about2-mid { padding: 8px 0 0; border: none; border-radius: 0; background: none; }
  /* one app at a time in mobile portrait — drop the bounding boxes, use horizontal rule separators */
  .about2-apps-list { grid-template-columns: 1fr; }
  .about2-app-row { border: none; border-bottom: 1px solid var(--border); border-radius: 0; padding: 22px 0; }
  .about2-app-row:first-child { padding-top: 0; }
  .about2-app-row:last-child { border-bottom: none; }   /* keep bottom padding for a full-height tinted band */
  /* fold filter + sort into a single dropdown: hide the separate sort buttons, show the in-menu sort group */
  .about2-apps-head { border-bottom: none; padding-bottom: 0; }
  .about2-apps-head .about2-th[data-app-sort] { display: none; }
  /* Sort as a standalone full-width row of 3 horizontal buttons sitting ABOVE
     the filter dropdown (order:-1), OUTSIDE the dropdown menu. */
  .about2-appfilter-sortgroup {
    display: flex;
    order: -1;
    flex: 1 1 100%;
    flex-flow: row nowrap;
    gap: 6px;
    margin: 0;
    padding: 0;
    border: none;
  }
  .about2-appfilter-grouplabel { display: none; }   /* just the 3 buttons on top */
  .about2-appfilter-sortgroup button {
    flex: 1 1 0;
    min-width: 0;
    padding: 10px 6px;
    text-align: center;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.02);
    color: var(--muted);
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
  }
  .about2-appfilter-sortgroup button:hover { color: var(--text); }
  .about2-appfilter-sortgroup button.is-active {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), 0.14);
    color: var(--accent);
  }
  .about2-appfilter-sortgroup button.is-active::after { content: " \2193"; }        /* ↓ high→low */
  .about2-appfilter-sortgroup button.is-active[data-dir="asc"]::after { content: " \2191"; }  /* ↑ low→high */
  /* full-width filter control on mobile */
  .about2-apps-head-filter { flex: 1 1 100%; width: 100%; }
  .about2-appfilter { width: 100%; }
  .about2-appfilter-btn { width: 100%; }
  .about2-appfilter-btn .about2-appfilter-caret { margin-left: auto; }
  .about2-appfilter-menu { width: 100%; }
  /* stats as rounded boxes, 2×2, on mobile (dividers → borders) */
  .about2-stats { grid-template-columns: 1fr 1fr; gap: 12px; }
  .about2-stat,
  .about2-stat:first-child { border: 1px solid var(--border); border-radius: 12px; padding: 18px 16px; }
}

/* iPad / touch tablets (wider than a phone): the sort column-headers are still
   the sort control here — give them the same button look as the phone sort row.
   They're already <button>s, so this only adds a border/bg/padding; everything
   else (positions, arrows, active accent) stays as is. */
@media (min-width: 561px) and (pointer: coarse) {
  .about2-apps-head .about2-th[data-app-sort] {
    padding: 8px 14px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.02);
    transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
  }
  .about2-apps-head .about2-th[data-app-sort].is-active {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), 0.14);
  }
}

/* ===========================================================================
   Résumé page — two-column editorial CV. On screen it reuses the site theme
   (dark panel, Sora, gold accent) so it stays consistent with the rest of the
   site; the @media print block at the end flips it to a clean light layout for
   "Save as PDF" / printing. Rendered standalone (resume.php) AND inside the
   index panel (id=resume). Full-bleed like the About page.
   =========================================================================== */
.standalone-detail-page .project-detail-inner.resume-page,
#project-detail-panel .project-detail-inner.resume-page {
  max-width: none;
  width: 100%;
  margin: 0;
  padding: 0;
}

.resume-doc {
  color: var(--text);
  /* Rounded card capped at 1500px to match the project description section. */
  max-width: 1500px;
  margin: 0 auto;
  padding: clamp(26px, 4vh, 54px) clamp(24px, 4.5vw, 72px) clamp(40px, 6vh, 80px);
  border: 1px solid var(--border);
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.015);
}

/* "Back to Projects" — top of the résumé, in a centered container that mirrors
   .resume-doc's padding + 1500px width, so the button left-aligns with the
   letterhead while the container stays centered. */
.resume-page .resume-back {
  display: flex;
  justify-content: center;
  /* no horizontal padding → the inner box lines up with the .resume-doc card edge.
     bottom pad = gap between the Back button and the résumé card below. */
  padding: clamp(26px, 4vh, 54px) 0 clamp(16px, 2.4vh, 26px);
}
.resume-page .resume-back-inner { width: 100%; max-width: 1500px; }   /* match .resume-doc max-width */
.resume-page .resume-back .project-back-button { margin-bottom: 0; }
.resume-grid {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: clamp(30px, 3.4vw, 60px);
  align-items: start;
  max-width: 1500px;
  margin: 0 auto;
}
.resume-num { color: var(--accent); font-weight: 700; margin-right: 8px; }

/* ---- Full-width letterhead: logo | gold rule | name + titles + statement.
   Spans the same max-width as the grid below so it lines up with the columns. */
.resume-letterhead {
  max-width: 1500px;
  margin: 0 auto clamp(30px, 4vh, 50px);   /* container centered; inner content left-aligned via flex */
  padding-bottom: clamp(22px, 3vh, 34px);
  display: flex;
  align-items: center;
  gap: clamp(18px, 2.6vw, 40px);
}
.resume-photo {
  flex: 0 0 auto;
  width: clamp(72px, 7vw, 96px); height: clamp(72px, 7vw, 96px);
  display: flex; align-items: center; justify-content: center;
}
.resume-photo img { width: 100%; height: 100%; object-fit: contain; }
.resume-lh-divider { flex: 0 0 auto; align-self: stretch; width: 2px; margin: 4px 0; background: var(--accent); opacity: 0.7; }
.resume-lh-text { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
/* Scoped under .project-detail-inner so it beats the generic ".project-detail-inner h1" rule. */
.project-detail-inner .resume-name { margin: 0; font-weight: 800; font-size: 52px; line-height: 1.0; letter-spacing: -0.5px; }
.resume-role { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.4; color: var(--text); }
.resume-tagline { margin: 0; max-width: none; color: var(--accent); font-style: italic; font-size: 14px; line-height: 1.5; }

/* ---- Sidebar ---- */
/* Full-width hairline between sidebar sections (Contact | About | Core Expertise). */
/* Line removed (header underlines now separate the sections) — kept as an
   invisible spacer so the gaps between sidebar sections stay the same. */
.resume-divider { display: block; width: 100%; height: 2px; margin: 24px 0; background: none; }

.resume-contacts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.resume-contacts li { display: flex; align-items: center; gap: 11px; font-size: 13.5px; color: var(--muted); }
.resume-contact-icon { flex: 0 0 auto; display: inline-flex; color: var(--accent); }
.resume-contact-icon svg { width: 16px; height: 16px; }
.resume-contacts a { color: var(--muted); text-decoration: none; transition: color 0.2s ease; }
.resume-contacts a:hover { color: var(--accent); }

/* Sidebar sections are separated by .resume-divider now, so no bottom margin. */
.resume-block { margin: 0; }
/* Unified section header — icon + underline, identical for the sidebar
   (Contact / About / Core Expertise) and the main column (Experience / Tools / Beyond). */
.resume-side-head,
.resume-main-head {
  display: flex; align-items: center;
  font-size: 12px; font-weight: 700; letter-spacing: 2.5px; text-transform: uppercase;
  padding-bottom: 14px; margin-bottom: 26px;
  border-bottom: 3px solid var(--border);
}
.resume-about-p { margin: 0 0 14px; color: var(--muted); font-size: 13.5px; line-height: 1.72; }
.resume-about-p:last-child { margin-bottom: 0; }

/* Education (sidebar, below About) */
.resume-edu { display: flex; flex-direction: column; gap: 18px; }
.resume-edu-school { font-weight: 700; font-size: 14px; color: var(--text); }
.resume-edu-program { margin-top: 3px; font-size: 13px; font-weight: 600; color: var(--accent); }
.resume-edu-dates { margin-top: 3px; font-size: 12.5px; color: var(--muted); }
.resume-edu-detail { margin-top: 4px; font-size: 12.5px; font-style: italic; color: var(--muted); }

/* Core Expertise: one bordered card per group (icon + heading + its own chips). */
.resume-expertise { display: flex; flex-direction: column; }
/* No boxes — each group is a plain block separated by a hairline. */
.resume-exp-card { padding: 16px 0; }
.resume-exp-card:first-child { padding-top: 0; }
.resume-exp-card + .resume-exp-card { border-top: 1px solid var(--border); }
.resume-exp-head { display: flex; align-items: center; gap: 9px; margin-bottom: 11px; }
.resume-exp-icon { flex: 0 0 auto; display: inline-flex; color: var(--accent); }
.resume-exp-icon svg { width: 17px; height: 17px; }
.resume-exp-title { font-size: 12.5px; font-weight: 700; letter-spacing: 0.2px; color: var(--text); }

/* Core Expertise items: plain text separated by a subtle "|" (no boxes). */
.resume-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center; row-gap: 3px; }
.resume-chip { display: inline-flex; align-items: center; padding: 0; border: none; border-radius: 0; background: none; font-size: 11.5px; font-weight: 600; color: var(--muted); }
.resume-chip:not(:last-child)::after { content: "|"; margin: 0 9px; color: var(--muted); opacity: 0.45; font-weight: 400; }

/* ---- Main column ---- */
/* It's a <main>, so reset the global `main { padding: 28px … }` rule (keep a small left inset). */
.resume-main { padding: 0 0 0 10px; }
.resume-section { margin-bottom: clamp(30px, 4vh, 48px); }
.resume-section:last-child { margin-bottom: 0; }
.resume-main-icon { flex: 0 0 auto; display: inline-flex; margin-right: 11px; color: var(--accent); }
.resume-main-icon svg { width: 19px; height: 19px; }

.resume-timeline { list-style: none; margin: 0; padding: 0 0 0 26px; }
.resume-job { position: relative; padding-bottom: 30px; }
.resume-job:last-child { padding-bottom: 0; }
.resume-job-dot { position: absolute; left: -26px; top: 6px; width: 11px; height: 11px; border-radius: 50%; border: 2px solid var(--accent); background: var(--bg); }
.resume-job:not(:last-child)::before { content: ""; position: absolute; left: -21px; top: 18px; bottom: 0; width: 1px; background: var(--border); }
.resume-job-top { display: flex; justify-content: space-between; gap: 16px; align-items: baseline; }
.resume-job-company { font-weight: 700; font-size: 16px; letter-spacing: 0.2px; }
.resume-job-role { margin-top: 8px; font-size: 14px; font-weight: 600; color: var(--accent); }
.resume-job-dates { flex: 0 0 auto; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.resume-job-place { margin: 7px 0 11px; font-size: 12.5px; font-style: italic; color: var(--muted); }
.resume-job-bullets { list-style: none; margin: 0; padding: 0; }
.resume-job-bullets li { position: relative; padding-left: 18px; margin-bottom: 6px; font-size: 13.5px; line-height: 1.6; color: var(--muted); }
.resume-job-bullets li:last-child { margin-bottom: 0; }
.resume-job-bullets li::before { content: ""; position: absolute; left: 2px; top: 8px; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }

.resume-tools { display: flex; flex-direction: column; gap: 14px; }
.resume-tool-row { display: grid; grid-template-columns: 190px 1fr; gap: 16px; align-items: start; }
.resume-tool-label { padding-top: 6px; font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--text); }
.resume-tool-items { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.resume-tool-pill { padding: 6px 12px; border: 1px solid var(--border); border-radius: 6px; font-size: 12px; color: var(--muted); }

.resume-beyond { margin: 0; max-width: 66ch; color: var(--muted); font-size: 14px; line-height: 1.72; }

/* Download button lives OUTSIDE the résumé card, centered at the bottom. */
.resume-download-wrap { display: flex; justify-content: center; margin-top: clamp(26px, 3.5vh, 44px); padding-bottom: clamp(30px, 5vh, 64px); }
.resume-download {
  display: inline-flex; align-items: center; gap: 12px;
  padding: 13px 22px; border: 1px solid var(--accent); background: transparent;
  color: var(--accent); cursor: pointer; text-decoration: none;
  font-family: inherit; font-size: 12px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase;
  transition: background 0.2s ease, color 0.2s ease;
}
.resume-download svg { width: 16px; height: 16px; }
.resume-download:hover { background: var(--accent); color: var(--bg); }

/* ---- Résumé — responsive ---- */
@media (max-width: 900px) {
  .resume-grid { grid-template-columns: 1fr; gap: 34px; }
  .resume-tool-row { grid-template-columns: 1fr; gap: 8px; }
}

/* iPad + mobile (touch): drop the résumé card box — no border/bg/rounded corners. */
@media (pointer: coarse) {
  .resume-doc { border: 0; background: none; border-radius: 0; }
  .resume-main { padding: 0; }   /* drop the 10px left inset on touch (single-column) */
}

/* ---- Print / Save-as-PDF: flip the dark site skin to a clean light résumé ---- */
@media print {
  @page { margin: 10mm; }

  header, footer, .portfolio-footer,
  .project-back-button, .resume-back, .project-detail-nav, .project-nav-link,
  .portfolio-side, .hero, .hero-side, .project-cloud,
  .resume-download, .resume-download-wrap { display: none !important; }

  html, body {
    background: #fff !important;
    color: #14161a !important;
    overflow: visible !important;
    height: auto !important;
    display: block !important;
  }

  /* Neutralise the SPA panel positioning so the résumé flows onto the page. */
  .portfolio-layout, .portfolio-main, .project-detail-panel,
  #project-detail-panel, .standalone-detail-page,
  .project-detail-inner, .resume-page {
    position: static !important;
    transform: none !important;
    inset: auto !important;
    width: auto !important;
    max-width: none !important;
    height: auto !important;
    overflow: visible !important;
    background: #fff !important;
    box-shadow: none !important;
    opacity: 1 !important;
    filter: none !important;
  }

  .resume-doc { padding: 0 !important; color: #14161a !important; border: 0 !important; background: #fff !important; border-radius: 0 !important; max-width: none !important; }
  /* A CSS grid is one unbreakable row across print pages, which pushed the whole
     grid to page 2 and orphaned the letterhead. Flow it as a single column so it
     fragments naturally (letterhead → sidebar → experience). */
  .resume-grid { display: block !important; max-width: none !important; }
  .resume-side { margin-bottom: 22px !important; }
  .resume-letterhead { break-after: avoid; }

  .resume-name, .resume-role, .resume-job-company,
  .resume-side-head, .resume-main-head, .resume-chip, .resume-tool-label,
  .resume-exp-title, .resume-edu-school { color: #14161a !important; }
  .resume-about-p, .resume-tagline, .resume-job-role, .resume-job-dates,
  .resume-job-place, .resume-job-bullets li, .resume-tool-pill,
  .resume-contacts li, .resume-contacts a, .resume-beyond,
  .resume-edu-dates, .resume-edu-detail { color: #3a3d42 !important; }
  .resume-num, .resume-job-role, .resume-tagline, .resume-edu-program { color: #a9782a !important; }
  .resume-rule, .resume-job-dot, .resume-job-bullets li::before { background: #a9782a !important; }
  .resume-job-dot { border-color: #a9782a !important; }
  .resume-contact-icon, .resume-exp-icon, .resume-main-icon { color: #a9782a !important; }

  .resume-chip, .resume-tool-pill, .resume-exp-card { border-color: #ccc !important; background: #fff !important; }
  .resume-chip:not(:last-child)::after { color: #b3b3b3 !important; }
  .resume-side-head, .resume-main-head { border-color: #ddd !important; background: none !important; }
  .resume-divider { background: none !important; }
  .resume-letterhead { border-bottom-color: #ddd !important; }
  .resume-lh-divider { background: #a9782a !important; }
  .resume-job:not(:last-child)::before { background: #ddd !important; }
  .resume-exp-card { break-inside: avoid; }
  /* the white logo would vanish on white paper — give it a small dark chip in
     print only (on screen the box is removed). */
  .resume-photo { background: #14161a !important; border-radius: 12px !important; padding: 12px !important; }

  .resume-job, .resume-tool-row { break-inside: avoid; page-break-inside: avoid; }
  /* Keep a section heading (e.g. Experience, Core Expertise, Tools) with the
     content that follows it, so it never sits alone at the foot of a page. */
  .resume-main-head, .resume-side-head { break-after: avoid !important; page-break-after: avoid !important; break-inside: avoid !important; }

  /* The résumé flows as one continuous column (letterhead → sidebar →
     Experience → Tools → Beyond) with NO forced page breaks and NO whole-block
     "keep together" rules, so every section fills the room left on the current
     page instead of jumping to a fresh one and leaving a gap. This is what makes
     Firefox match Chrome — Firefox strictly honours break-inside:avoid, so any
     block-level avoid (sidebar, Core Expertise, Tools…) pushed the whole block
     to the next page whenever it didn't quite fit, orphaning the page before it.
     Nothing gets orphaned even without block-level avoids, because:
       • every heading stays with its first item (break-after:avoid, above), and
       • the atomic items never split — .resume-job, .resume-exp-card (Core
         Expertise), .resume-tool-row (Tools) all have break-inside:avoid above.
     No block-level keep-whole rule at all — that's deliberate. */

  /* ---- Compact the printed PDF (screen spacing is too airy on paper) ---- */
  .resume-name { font-size: 30px !important; }
  .resume-letterhead { margin: 0 0 14px !important; padding-bottom: 12px !important; gap: 16px !important; }
  .resume-lh-text { gap: 3px !important; }
  .resume-photo { width: 62px !important; height: 62px !important; padding: 8px !important; }
  .resume-tagline { font-size: 12px !important; }
  .resume-role { font-size: 13px !important; }

  .resume-side { margin-bottom: 14px !important; }
  .resume-divider { margin: 12px 0 !important; }
  .resume-side-head { margin-bottom: 12px !important; padding-bottom: 8px !important; }   /* match main-head underline spacing */
  .resume-contacts { gap: 6px !important; }
  .resume-contacts li { font-size: 12px !important; }
  .resume-about-p { margin-bottom: 7px !important; font-size: 12px !important; line-height: 1.45 !important; }
  .resume-exp-card { padding: 9px 0 !important; }
  .resume-exp-head { margin-bottom: 6px !important; }
  .resume-chip { font-size: 11px !important; }

  .resume-section { margin-bottom: 15px !important; }
  .resume-main-head { margin-bottom: 12px !important; padding-bottom: 8px !important; }
  .resume-timeline { padding-left: 20px !important; }
  .resume-job { padding-bottom: 12px !important; }
  .resume-job-role { margin-top: 3px !important; }
  .resume-job-place { margin: 4px 0 6px !important; }
  .resume-job-bullets li { margin-bottom: 3px !important; font-size: 12px !important; line-height: 1.4 !important; }
  .resume-tools { gap: 8px !important; }
  .resume-tool-pill { font-size: 11px !important; padding: 4px 9px !important; }
  .resume-beyond { font-size: 12px !important; line-height: 1.5 !important; }

  * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
}

/* ===========================================================================
   Contact page — centered "Get in touch": two sections, Contact Information and
   Connect Online. Rendered both standalone (contact.php) and inside the index
   panel (id=contact).
   =========================================================================== */
.contact-layout {
  max-width: 560px;
  margin: 8px auto 0;
  text-align: center;
}

.contact-eyebrow {
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  margin-bottom: 16px;
}
.contact-title {
  margin: 0;
  font-size: clamp(30px, 3.4vw, 46px);
  line-height: 1.02;
  letter-spacing: -1px;
}
.contact-lead {
  margin: 16px auto 34px;
  max-width: 44ch;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.6;
}

.contact-cards {
  display: flex;
  flex-direction: column;
  gap: 20px;
  text-align: left;
}

.contact-card {
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01));
}
.contact-card-head {
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  margin-bottom: 16px;
  text-align: center;
}

.contact-info-list { list-style: none; margin: 0; padding: 0; }
.contact-info-row {
  display: flex;
  align-items: center;
  gap: 15px;
  padding: 13px 0;
}
.contact-info-row + .contact-info-row { border-top: 1px solid var(--border); }
.contact-info-icon {
  flex: none;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.14);
  border: 1px solid rgba(var(--accent-rgb), 0.30);
}
.contact-info-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.contact-info-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--muted);
}
.contact-info-value { font-size: 14.5px; color: var(--text); word-break: break-word; }
.contact-link { color: var(--accent-light); text-decoration: none; transition: color 0.15s ease; }
.contact-link:hover { color: var(--accent); text-decoration: underline; }

.contact-svg { width: 20px; height: 20px; display: block; }

.contact-social-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 13px 16px;
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  text-decoration: none;
  background: rgba(0, 0, 0, 0.22);
  transition: border-color 0.18s ease, transform 0.18s ease, color 0.18s ease;
}
.contact-social-link:hover {
  border-color: rgba(var(--accent-rgb), 0.55);
  color: var(--accent-light);
  transform: translateY(-1px);
}
.contact-social-icon { color: var(--accent); display: flex; }
.contact-social-name { font-size: 14px; font-weight: 600; }

/* Contact page — responsive */
@media (max-width: 620px) {
  .contact-card { padding: 20px; }
}

/* ===========================================================================
   Project Network: logo hub with projects radiating outward (straight links).
   Node + link positions are computed responsively in js/timeline.js.
   =========================================================================== */
.about-hub {
  margin-top: 0;
  padding-top: 0;
}
.hub-head { text-align: center; margin-bottom: 6px; }
.hub-head-label {
  display: inline-block;
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
}

.hub-stage {
  position: relative;
  width: 100vw;                    /* full window width, regardless of the */
  margin-left: calc(50% - 50vw);   /* centred container it lives in (full-bleed) */
  height: clamp(520px, 82vh, 900px);
  /* Kill the mobile press-hold selection / blue tap highlight / image callout */
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.hub-node, .hub-center { -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; }
.hub-node img, .hub-center img { -webkit-user-drag: none; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }

.hub-links {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 1;
}
.hub-links line {
  stroke: rgba(var(--accent-rgb), 0.42);
  stroke-width: 1.5;
  stroke-linecap: round;
  /* Dark and quiet by default (same hue via brightness); the glow is dropped
     until hover so the resting network isn't distracting. */
  filter: brightness(0.49);
  transition: opacity 0.9s ease, stroke 0.9s ease, filter 0.9s ease;
}
.hub-links line.hub-link--app { stroke: rgba(255, 255, 255, 0.20); stroke-width: 1; }
.hub-links line.hub-link--year { stroke: rgba(var(--accent-rgb), 0.4); stroke-width: 1; stroke-dasharray: 3 5; }

.hub-center {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  width: 134px;
  height: 134px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--panel);
  border: 2px solid rgba(var(--accent-rgb), 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
}
.hub-center img { max-width: 66%; max-height: 66%; width: auto; height: auto; object-fit: contain; pointer-events: none; }
.hub-center { cursor: grab; touch-action: none; }
.hub-center.is-dragging { cursor: grabbing; }

.hub-node {
  position: absolute;
  transform: translate(-50%, -50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 132px;
  text-decoration: none;
  color: inherit;
  cursor: grab;
  touch-action: none; /* let pointer-drag work on touch without scrolling */
}
.hub-node.is-dragging { cursor: grabbing; z-index: 5; }
.hub-node-circle {
  width: 82px;
  height: 82px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--panel);
  border: 2px solid rgba(var(--accent-rgb), 0.5);
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.hub-node-circle img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hub-node-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  text-align: center;
  line-height: 1.25;
  max-width: 132px;
}
.hub-node:hover .hub-node-circle {
  transform: scale(1.1);
  border-color: var(--accent);
}
/* Peers gathering around a hovered project shrink a little so they reposition
   cleanly without overlapping each other or the logo, and ignore the mouse while
   travelling so they can't steal the hover (which caused jitter).
   NOTE: the scale here must match ORBIT_SCALE in js/timeline.js. */
.hub-node.is-orbit { pointer-events: none; }
.hub-node.is-orbit .hub-node-circle {
  transform: scale(0.82);
  border-color: rgba(var(--accent-rgb), 0.8);
}

/* The hovered project rides above every other node/link, so its open icon and
   connecting line are always on top and actually receive the mouse. */
.hub-node.is-anchor { z-index: 12; }

/* --- Hover "open" action icon -------------------------------------------------
   A link icon that emerges from within a hovered project, on a short stalk, so
   the project clearly exposes an "open me" action. Only the actively hovered
   project (.is-anchor, set in timeline.js) shows it. It's a child of the node,
   so moving the mouse onto it keeps the project hovered (it won't vanish) and it
   stays clickable. --hub-r (circle radius) is set per node in timeline.js. */
.hub-open {
  --tx: calc((var(--hub-r, 41px) + 40px) * 0.7071);
  --ty: calc((var(--hub-r, 41px) + 40px) * -0.7071);
  position: absolute;
  top: var(--hub-r, 41px); /* circle centre (the circle sits at the node's top) */
  left: 50%;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #0b0d10;
  background: var(--accent);
  border: 2px solid rgba(255, 255, 255, 0.92);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45), 0 0 16px rgba(var(--accent-rgb), 0.6);
  cursor: pointer;
  z-index: 6;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%) translate(var(--tx), var(--ty)) scale(0);
  transform-origin: calc(50% - var(--tx)) calc(50% - var(--ty)); /* grows out from the circle */
  transition: transform 0.5s cubic-bezier(0.34, 1.4, 0.5, 1), opacity 0.34s ease;
}
.hub-open-icon { width: 17px; height: 17px; display: block; }
/* Connecting line: a separate element anchored at the CIRCLE CENTRE and rotated
   toward the icon (direction/length set in timeline.js). It sits behind the
   circle (only the gap portion shows) and scales out with the icon on hover — so
   its inner end stays pinned to the circle while it stretches to follow the icon. */
.hub-link-line {
  position: absolute;
  top: var(--hub-r, 41px); /* circle centre */
  left: 50%;
  width: 2px;
  height: var(--open-len, 60px);
  border-radius: 2px;
  background: var(--accent);
  box-shadow: 0 0 6px rgba(var(--accent-rgb), 0.6);
  transform-origin: top center; /* pinned at the circle centre */
  transform: translate(-50%, 0) rotate(var(--open-rot, -135deg)) scaleY(0);
  opacity: 0;
  pointer-events: none;
  z-index: -1; /* behind the circle so only the gap portion shows */
  transition: opacity 0.34s ease, transform 0.5s cubic-bezier(0.34, 1.4, 0.5, 1);
}
/* Invisible hit-bridge over the gap between the circle and the icon, so the mouse
   can travel to the icon without leaving the node. Centred on the midpoint toward
   the circle, following the icon's actual direction (--tx/--ty set in JS). */
.hub-open::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 46px;
  height: 46px;
  transform: translate(-50%, -50%) translate(calc(var(--tx, 0px) / -2), calc(var(--ty, 0px) / -2));
}
.hub-project.is-anchor .hub-open,
.hub-project:focus-within .hub-open {
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, -50%) translate(var(--tx), var(--ty)) scale(1);
}
.hub-project.is-anchor .hub-link-line,
.hub-project:focus-within .hub-link-line {
  opacity: 1;
  transform: translate(-50%, 0) rotate(var(--open-rot, -135deg)) scaleY(1);
}
/* Hover feedback is colour + glow only — the icon does NOT move/scale, otherwise
   it would slide out from under the cursor and flicker between hover states. */
.hub-project.is-anchor .hub-open:hover,
.hub-project .hub-open:focus-visible {
  background: var(--accent-light);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5), 0 0 24px rgba(var(--accent-rgb), 0.9);
}

/* App nodes: smaller logo circles (no label) */
.hub-app { width: auto; }
.hub-node-circle--app {
  width: 54px;
  height: 54px;
  border-width: 1px;
  background: rgba(255, 255, 255, 0.05);
  display: flex;
  align-items: center;
  justify-content: center;
}
.hub-node-circle--app img { object-fit: contain; padding: 20%; }
.hub-fallback { color: var(--accent); font-size: 12px; font-weight: 700; }

/* Year nodes: sized by project count in JS; year number inside */
.hub-node-circle--year {
  border-radius: 50%;
  border: 1px solid rgba(var(--accent-rgb), 0.5);
  background: rgba(var(--accent-rgb), 0.10);
  color: var(--accent);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Featured projects: larger (sized in JS) with a stronger ring */
.hub-featured .hub-node-circle {
  border-width: 3px;
  border-color: rgba(var(--accent-rgb), 0.75);
}
.hub-featured .hub-node-label { font-size: 13px; }

/* Entrance: centre + nodes fade/scale in once positioned (JS adds .is-live) */
.about-hub .hub-center,
.about-hub .hub-node {
  opacity: 0;
  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.2, 1.4, 0.4, 1);
}
.about-hub .hub-center { transform: translate(-50%, -50%) scale(0.7); }
.about-hub .hub-node { transform: translate(-50%, -50%) scale(0.6); }
.about-hub.is-live .hub-center { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.about-hub.is-live .hub-node { opacity: 1; transform: translate(-50%, -50%) scale(1); }

/* While the loop is actively animating (hover gather / return), drive motion
   with transform on a compositor layer and drop the transform transition so the
   rAF loop isn't fighting the 0.5s CSS ease — keeps large project images smooth.
   Opacity still eases for the spotlight dim. */
.hub-stage.hub-animating .hub-node,
.hub-stage.hub-animating .hub-center { transition: opacity 0.9s ease; will-change: transform; }

/* Project Cloud filtering: nodes removed by the filter fade + scale away. High
   specificity so it wins over .is-live and the animating loop, and keeps its own
   transition so it eases out even while the survivors are being repositioned. */
.about-hub.is-live .hub-node.hub-filtered-out,
.hub-stage.hub-animating .hub-node.hub-filtered-out {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0);
  pointer-events: none;
  /* Fade + scale out in place; slower opacity fade. */
  transition: opacity 0.6s ease, transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Hover a node to spotlight only its connections */
.hub-stage.hub-focus .hub-links line { opacity: 0.08; }
/* Hovered/active connections return to their full original colour + glow. */
.hub-stage.hub-focus .hub-links line.is-hot { opacity: 1; stroke: var(--accent); filter: none; }
/* Hovering the logo lights the whole network back to its full default colour.
   No drop-shadow: an SVG filter on every moving line re-rasterises each frame,
   which is what made the logo hover chug. Lines just brighten to full colour. */
.hub-stage.hub-center-focus .hub-links line { opacity: 1; filter: none; }
.hub-stage.hub-focus .hub-links line.hub-link--app.is-hot { stroke: rgba(255, 255, 255, 0.9); }
.about-hub.is-live .hub-stage.hub-focus .hub-node { opacity: 0.3; }
.about-hub.is-live .hub-stage.hub-focus .hub-node.is-hot { opacity: 1; }

@media (max-width: 700px) {
  .hub-center { width: 100px; height: 100px; }
  .hub-node { width: 100px; }
  .hub-node-circle { width: 60px; height: 60px; }
  .hub-node-label { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
  .about-hub .hub-center,
  .about-hub .hub-node { opacity: 1; transition: none; }
  .about-hub .hub-center { transform: translate(-50%, -50%); }
  .about-hub .hub-node { transform: translate(-50%, -50%); }
}

/* Fine-print disclaimer paragraph: lighter, smaller and italic */
.project-detail-body.project-detail-disclaimer {
  color: #888888;
  font-size: 12px;
  font-style: italic;
}

/* Stand-alone "early concept" section: full width, video left + copy right */
.project-detail-concept,
.project-detail-concept-alt {
  margin-top: 54px;
  padding-top: 46px;
  border-top: 1px solid var(--border);
}

.project-detail-concept-cols {
  display: flex;
  align-items: flex-start;
  gap: 48px;
  margin-top: 26px;
}

.project-detail-concept-media,
.project-detail-concept-copy {
  flex: 1 1 0;
  min-width: 0;
}

/* Alternate container: video on the right, copy on the left */
.project-detail-concept-alt .project-detail-concept-cols {
  flex-direction: row-reverse;
}

/* Supporting write-up next to an early-concept video */
.project-detail-body.project-detail-video-note {
  margin-top: 20px;
  font-size: 12px;
  color: var(--muted);
}

/* Heading now lives with the copy so the title + description stay on the same
   side (video on the other). Tighten the gap under the heading. */
.project-detail-concept-copy .project-detail-heading {
  margin-bottom: 16px;
}

.project-detail-concept-copy .project-detail-video-note:first-child,
.project-detail-concept-copy .project-detail-heading + .project-detail-video-note {
  margin-top: 0;
}

/* ---- Talk & Presentation (project detail; sits below Credits) ------------
   Divider rule + two columns: image montage left, write-up + meta right. */
.project-detail-sep {
  border: 0;
  height: 1px;
  background: var(--border);
  margin: 54px 0 0;
}

.project-detail-talk {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1fr);
  gap: 48px;
  align-items: center;
  margin-top: 46px;
}

.talk-slideshow {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 14px;
  border: 1px solid var(--border);
  overflow: hidden;
  background: var(--panel);
}
/* Keep the crossfade on the GPU compositor (smooth opacity fade, no repaint). */
.talk-slideshow .hero-poster-slide { will-change: opacity; }
/* Single-image fallback (no images array) */
.talk-media img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 14px;
  border: 1px solid var(--border);
}

.talk-eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 12px;
}
.talk-title {
  font-size: clamp(22px, 2.4vw, 30px);
  font-weight: 700;
  line-height: 1.2;
  color: var(--text);
  margin: 0 0 6px;
}
.talk-subtitle {
  font-size: 16px;
  font-weight: 600;
  color: var(--accent);
  margin-bottom: 18px;
}
.talk-para {
  font-size: 14px;
  line-height: 1.7;
  color: var(--muted);
  margin: 0 0 14px;
}
.talk-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  border: 1px solid var(--accent-border);
  border-radius: 999px;
  padding: 9px 18px;
  transition: background 0.18s ease, color 0.18s ease;
}
.talk-link svg { width: 16px; height: 16px; }
.talk-link:hover { background: var(--accent); color: #0a0a0a; }

.talk-meta {
  grid-column: 1 / -1;   /* full-width row below image + text (no top rule) */
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px 0;
}
.talk-meta-item { display: flex; align-items: center; gap: 12px; padding: 0 22px; }
/* Vertical separators between items (not before the first item in a row). */
.talk-meta-item:first-child { padding-left: 0; }
.talk-meta-item:not(:first-child) { border-left: 1px solid var(--border); }
.talk-meta-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--accent-soft);
  border: 1px solid var(--border);
  color: var(--accent);
}
.talk-meta-icon svg { width: 22px; height: 22px; display: block; }
.talk-meta-text { display: flex; flex-direction: column; min-width: 0; }
.talk-meta-label { font-size: 11px; letter-spacing: 0.04em; color: var(--muted); }
.talk-meta-value { font-size: 13.5px; font-weight: 600; color: var(--text); line-height: 1.3; }

/* Section stacks before the 4-across meta row gets too tight. */
@media (max-width: 1024px) {
  .talk-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* 2 per row: divider only before the second item in each row. */
  .talk-meta-item:nth-child(odd) { border-left: 0; padding-left: 0; }
  .talk-meta-item:nth-child(even) { border-left: 1px solid var(--border); padding-left: 22px; }
}
@media (max-width: 860px) {
  .project-detail-talk { grid-template-columns: 1fr; gap: 26px; }
}
@media (max-width: 520px) {
  /* Single column: no vertical dividers. */
  .talk-meta { grid-template-columns: 1fr; }
  .talk-meta-item { border-left: 0; padding-left: 0; }
}

/* ===================== Project detail page redesign ===================== */

/* Cinematic hero. Change the 2.4 below to retune the banner aspect ratio
   (e.g. 16 / 9, 2 / 1, 21 / 9). The video is letterboxed, never cropped. */
.project-hero-video,
.project-hero-banner {
  width: 100%;
  aspect-ratio: 2.4 / 1;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--border);
}

.project-hero-video {
  position: relative;
  background: #000;
}

/* The 16:9 video sits centered in the wide frame, leaving black side bars */
.project-hero-video-frame {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  height: 100%;
  max-width: 100%;
  aspect-ratio: 16 / 9;
}

.project-hero-video-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Poster covers the full cinematic frame (cropped), above the 16:9 player frame */
.project-hero-video > .video-poster {
  z-index: 2;
}

/* Poster crossfades through the gallery images like a banner */
.hero-poster-slides {
  position: absolute;
  inset: 0;
}

.hero-poster-slide {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 1s ease;
}

.hero-poster-slide.active {
  opacity: 1;
}

/* Close button overlay — shown only while the video is playing */
.project-hero-close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 3;

  width: 40px;
  height: 40px;
  border-radius: 50%;

  display: flex;
  align-items: center;
  justify-content: center;

  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(0, 0, 0, 0.55);
  color: #ffffff;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(6px);

  transition:
    background 0.2s ease,
    border-color 0.2s ease,
    transform 0.2s ease;
}

.project-hero-close:hover {
  background: rgba(0, 0, 0, 0.78);
  border-color: rgba(var(--accent-rgb), 0.6);
  transform: scale(1.05);
}

.project-hero-close[hidden] {
  display: none;
}

/* While playing, the video fills the whole cinematic frame (cover/crop) instead
   of letterboxing — the native Vimeo UI is hidden, so nothing gets clipped. */
.project-hero-video.is-playing .project-hero-video-frame {
  inset: 0;
  top: 0;
  left: 0;
  transform: none;
  width: 100%;
  height: 100%;
  max-width: none;
  aspect-ratio: auto;
}

.project-hero-video.is-playing .project-hero-video-frame iframe {
  inset: auto;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
}

/* Fullscreen: let the container fill the screen instead of keeping the 2.4:1 */
.project-hero-video:fullscreen {
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
}

.project-hero-video:-webkit-full-screen {
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
}

/* Custom control bar (play/pause · timeline · volume · fullscreen) */
.project-hero-controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;

  display: flex;
  align-items: center;
  gap: 14px;
  padding: 30px 20px 16px;

  background: linear-gradient(to top, rgba(5, 6, 8, 0.9), rgba(5, 6, 8, 0));
}

.project-hero-controls[hidden] {
  display: none;
}

/* Transparent layer over the video: captures click-to-pause and reports mouse
   movement for the idle auto-hide (the iframe itself swallows both). Sits above
   the video but below the control bar / action stack (z-index 4). */
.phc-clickcatch {
  position: absolute;
  inset: 0;
  z-index: 3;
  cursor: pointer;
  background: transparent;
}

/* The catcher persists across replays, so disable it while the poster is
   showing — otherwise it would block the poster's play button. */
.project-hero-video:not(.is-playing) .phc-clickcatch,
.sa-video:not(.is-playing) .phc-clickcatch {
  display: none;
}

.phc-btn {
  flex: none;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: none;
  border: none;
  color: #ffffff;
  cursor: pointer;
  transition: color 0.2s ease, transform 0.2s ease;
}

.phc-btn:hover {
  color: var(--accent-light);
  transform: scale(1.08);
}

.phc-ico {
  width: 20px;
  height: 20px;
  fill: currentColor;
  display: block;
}

/* Play/pause icon swap */
.phc-play .phc-ico-pause {
  display: none;
}

.phc-play[data-playing="true"] .phc-ico-play {
  display: none;
}

.phc-play[data-playing="true"] .phc-ico-pause {
  display: block;
}

/* Timeline */
.phc-progress {
  flex: 1 1 auto;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.25);
  cursor: pointer;
  touch-action: none;
}

.phc-progress-fill {
  width: 0;
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
}

.phc-time {
  flex: none;
  min-width: 76px;
  text-align: center;
  font-size: 12px;
  color: rgba(255, 255, 255, 0.85);
  font-variant-numeric: tabular-nums;
}

/* Volume */
.phc-volume {
  flex: none;
  width: 68px;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.25);
  cursor: pointer;
  touch-action: none;
}

.phc-volume-fill {
  width: 100%;
  height: 100%;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.8);
}

.phc-mute.is-muted {
  color: rgba(255, 255, 255, 0.4);
}

/* Top-right action stack: close · like · share (vertical) */
.project-hero-actions {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.project-hero-actions[hidden] {
  display: none;
}

.phc-round {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;

  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(0, 0, 0, 0.55);
  color: #ffffff;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(6px);

  transition:
    background 0.2s ease,
    border-color 0.2s ease,
    transform 0.2s ease,
    color 0.2s ease;
}

.phc-round:hover {
  background: rgba(0, 0, 0, 0.78);
  border-color: rgba(var(--accent-rgb), 0.6);
  transform: scale(1.06);
}

.phc-round .phc-ico {
  width: 18px;
  height: 18px;
  fill: currentColor;
}

/* Like heart — outline by default, filled yellow when liked */
.phc-like .phc-heart {
  fill: none;
  stroke: currentColor;
  stroke-width: 2px;
}

.phc-like.is-liked {
  color: #ffd21e;
}

.phc-like.is-liked .phc-heart {
  fill: currentColor;
  stroke: none;
}

/* Share "copied" tooltip (appears to the left of the button) */
.phc-share {
  position: relative;
}

.phc-share.is-copied::after {
  content: "Link copied";
  position: absolute;
  top: 50%;
  right: 118%;
  transform: translateY(-50%);
  white-space: nowrap;
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 11px;
  color: #ffffff;
  background: rgba(5, 6, 8, 0.92);
  border: 1px solid var(--border);
}

/* Close button only makes sense while a video is playing */
.project-hero-video:not(.is-playing) .phc-close {
  display: none;
}

/* While playing, the whole overlay UI (bottom control bar + top-right action
   stack) auto-hides after 3s of mouse stillness — JS toggles .ui-visible on
   movement. It also stays up while paused or while a control has focus. */
.project-hero-video.is-playing .project-hero-controls,
.project-hero-video.is-playing .project-hero-actions {
  opacity: 0;
  transition: opacity 0.3s ease;
}

.project-hero-video.is-playing.ui-visible .project-hero-controls,
.project-hero-video.is-playing.is-paused .project-hero-controls,
.project-hero-video.is-playing .project-hero-controls:focus-within,
.project-hero-video.is-playing.ui-visible .project-hero-actions,
.project-hero-video.is-playing.is-paused .project-hero-actions,
.project-hero-video.is-playing .project-hero-actions:focus-within {
  opacity: 1;
}

/* Supporting videos reuse the hero's custom control bar for a consistent look.
   They keep their 16:9 frame (no crop-fill) — only the bar and its auto-hide
   behaviour are shared. The bar is clipped to the frame's rounded corners by
   .video-embed's overflow:hidden. */
.sa-video .project-hero-controls {
  opacity: 0;
  transition: opacity 0.3s ease;
}

.sa-video.ui-visible .project-hero-controls,
.sa-video.is-paused .project-hero-controls,
.sa-video .project-hero-controls:focus-within {
  opacity: 1;
}

/* Fullscreen: drop the 16:9 padding box so the video fills the screen */
.sa-video:fullscreen,
.sa-video:-webkit-full-screen {
  padding-top: 0;
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 0;
}

.project-hero-banner {
  position: relative;
  background-size: cover;
  background-position: center;
}

/* The title + 3-column info sit together in one rounded card */
.project-detail-card {
  margin-top: 28px;
  padding: 34px 40px 40px;
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: 18px;
}

/* ---- Standalone image viewer (zoom / pan / fullscreen) -------------------- */
/* Reuses the .project-detail-card box; the heading uses .project-detail-heading. */
.image-viewer-card .project-detail-heading {
  margin-bottom: 20px;
}

/* One rounded container around the WHOLE shader section — the "Shader Graph
   Breakdown" heading, the section tabs, the canvas and the write-up column all
   sit inside it, so the lab reads as a single unit rather than loose parts.
   (Scoped via :has; plain image-viewer cards keep the default card box.) */
.image-viewer-card:has(.shader-graph-viewer) {
  margin-top: 28px;
  padding: 24px 24px 26px;
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow: none;
}
.image-viewer-card:has(.shader-graph-viewer) .project-detail-heading {
  margin-bottom: 16px;
}
/* Height is owned by whichever element is the row. With no section column the
   viewer is a direct child and sets its own height; with one, the .sg-stage row
   sets it and BOTH children stretch to match, so the canvas and the write-up
   column are always exactly the same height. */
.image-viewer-card:has(.shader-graph-viewer) > .shader-graph-viewer { height: clamp(315px, 55.5vh, 675px); }
/* (the .sg-stage row sets its own height in the section-tour block below, kept at
   low specificity so fullscreen and the stacked breakpoint can override it) */

.image-zoom-viewer {
  position: relative;
  overflow: hidden;
  height: clamp(320px, 52vh, 640px);
  border-radius: 14px;
  border: 1px solid var(--border);
  /* Matches the Shader Graph canvas colour so the image blends into the viewer
     with no visible boundary in the letterbox gutters. */
  background: #202020;
  /* Let the page scroll past the image until it's zoomed; once zoomed we own the
     gestures so a drag pans instead of scrolling the page. */
  touch-action: pan-y;
  user-select: none;
}

.image-zoom-viewer.is-zoomed { touch-action: none; }

/* Hovering the viewer arms scroll-to-zoom (and suppresses page scroll); a subtle
   accent ring signals it's now in control of the wheel. */
.image-zoom-viewer:hover {
  border-color: var(--accent-border);
  box-shadow: 0 0 0 1px var(--accent-soft);
}

.izv-stage {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: default;
}

.image-zoom-viewer.is-zoomed .izv-stage { cursor: grab; }
.image-zoom-viewer.is-zoomed .izv-stage.is-grabbing { cursor: grabbing; }

.izv-image {
  max-width: 100%;
  max-height: 100%;
  display: block;
  transform: translate(0px, 0px) scale(1);
  transform-origin: center center;
  /* Motion is driven by JS (eased zoom + momentum pan) — no CSS transition. */
  will-change: transform;
  -webkit-user-drag: none;
}

/* Floating control pill, bottom-centered over the stage */
.izv-toolbar {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px;
  border-radius: 999px;
  background: rgba(10, 12, 16, 0.72);
  border: 1px solid var(--border);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.izv-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, opacity 0.15s ease;
}

.izv-btn svg { width: 18px; height: 18px; }

.izv-btn:hover {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent);
}

.izv-btn:disabled { opacity: 0.35; cursor: default; }
.izv-btn:disabled:hover { background: transparent; border-color: transparent; color: var(--text); }

.izv-zoom-level {
  min-width: 46px;
  padding: 0 4px;
  text-align: center;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  opacity: 0.85;
}

/* Enter/exit fullscreen icon swap */
.izv-ico-exit { display: none; }
.image-zoom-viewer.is-fullscreen .izv-ico-enter { display: none; }
.image-zoom-viewer.is-fullscreen .izv-ico-exit { display: block; }

.izv-hint {
  position: absolute;
  left: 14px;
  bottom: 20px;
  z-index: 2;
  font-size: 11px;
  letter-spacing: 0.2px;
  color: var(--text);
  opacity: 0.5;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

.image-zoom-viewer.is-zoomed .izv-hint { opacity: 0; }

/* Native fullscreen: fill the screen, dark backdrop, keep the pill reachable */
.image-zoom-viewer:fullscreen,
.image-zoom-viewer:-webkit-full-screen {
  width: 100vw;
  height: 100vh;
  border-radius: 0;
  border: none;
  background: #202020;
}

.image-viewer-caption {
  margin: 16px 2px 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
  opacity: 0.62;
}

@media (max-width: 600px) {
  .image-zoom-viewer { height: clamp(240px, 44vh, 420px); }
  .izv-toolbar { bottom: 12px; }
  .izv-hint { display: none; }
}

/* ---- Shader image viewer — hover regions & tooltips ----------------------- */
/* Driven by js/shaders/shader-viewer.js. The highlight lives inside the stage
   (clipped with the image); the tooltip floats on <body> (or inside the viewer
   while full-screen) and is positioned in JS. Rounded to match the site cards. */
.svt-highlight {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  border: 1.5px solid var(--accent-border);
  border-radius: 7px;
  background: var(--accent-soft);
  box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.25), 0 0 22px rgba(var(--accent-rgb), 0.28);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.14s ease;
  z-index: 4;
}
.svt-highlight.is-visible { opacity: 1; }

.svt-tip {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 100000;
  width: max-content;
  max-width: 320px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--panel-soft);
  color: var(--text);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.16s ease, transform 0.16s ease;
}
.svt-tip.is-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }

/* Official Shader Graph category — small label above the node name. */
.svt-tip-cat {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 3px;
}
.svt-tip-title {
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.2px;
  line-height: 1.2;
}
.svt-tip-sub {
  margin-top: 3px;
  font-size: 11px;
  opacity: 0.55;
}

/* "Share Shader" toolbar button (reuses .izv-btn) + copied-confirmation bubble */
.svt-share-btn { position: relative; }
.svt-share-btn.is-copied {
  color: var(--accent);
  border-color: var(--accent-border);
  background: var(--accent-soft);
}
.svt-share-btn.is-copied::after {
  content: attr(data-copied);
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 11px;
  font-weight: 500;
  padding: 4px 9px;
  border-radius: 7px;
  background: rgba(10, 12, 16, 0.94);
  border: 1px solid var(--accent-border);
  color: var(--accent);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
  pointer-events: none;
}
.svt-tip-row { margin-top: 9px; }
.svt-tip-label {
  display: block;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.9;
  margin-bottom: 2px;
}
.svt-tip-text {
  display: block;
  font-size: 12.5px;
  line-height: 1.45;
  opacity: 0.85;
}

@media (max-width: 600px) {
  .svt-tip { max-width: 260px; padding: 11px 13px; }
}

/* ---- Property legend (left) + chain highlighting ------------------------- */
/* Unity Shader Graph "Blackboard" look: dark panel, name + subtitle + add glyph,
   property rows with a type dot on the left and the type right-aligned. */
.svt-legend {
  position: absolute; left: 10px; top: 10px;
  max-height: calc(100% - 20px); z-index: 6;
  width: 214px; min-width: 150px; max-width: 340px; min-height: 90px;
  display: flex; flex-direction: column; padding: 0;
  border: 1px solid #3d3d3d; border-radius: 6px;
  background: #2e2e2e;
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5);
  overflow: auto;                  /* non-visible overflow required for resize */
  overscroll-behavior: contain;
  resize: both;
}
.svt-bb-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 10px 12px 9px; border-bottom: 1px solid #3d3d3d;
  position: sticky; top: 0; background: #2a2a2a; border-radius: 6px 6px 0 0;
}
.svt-bb-titles { min-width: 0; }
.svt-bb-title { font-size: 14px; font-weight: 600; color: #e9e9e9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.svt-bb-sub { font-size: 10.5px; color: #8c8c8c; margin-top: 1px; }
.svt-bb-add {
  flex: 0 0 auto; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
  font-size: 16px; line-height: 1; color: #b3b3b3; border: 1px solid #454545; border-radius: 4px; background: #333;
}
.svt-bb-section { font-size: 11px; color: #cfcfcf; padding: 8px 12px 4px; display: flex; align-items: center; gap: 6px; }
.svt-bb-section::before { content: "\25BE"; font-size: 9px; color: #9a9a9a; }
/* Row = a rounded "pill" (dot + name) with the type label outside on the right,
   matching Unity's Blackboard property buttons. Colours sampled from the editor. */
.svt-legend-btn.svt-bb-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  text-align: left; font-size: 12px; line-height: 1.2;
  padding: 3px 12px; border: 0; border-radius: 0; background: transparent;
  color: #d2d2d2; cursor: pointer; opacity: 1;
}
.svt-bb-pill {
  display: flex; align-items: center; gap: 8px; min-width: 0; flex: 0 1 auto;
  padding: 4px 13px; border-radius: 999px;
  background: #505051; border: 1px solid #626262;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.svt-bb-rdot { width: 5px; height: 5px; border-radius: 50%; flex: 0 0 auto; background: #98ee66; }
.svt-bb-rname { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #dcdcdc; }
.svt-bb-rtype { flex: 0 0 auto; margin-left: auto; padding-left: 8px; color: #9a9a9a; font-size: 11px; }
.svt-legend-btn.svt-bb-row:hover .svt-bb-pill { background: #5c5c5d; border-color: #6f6f6f; }
.svt-legend-btn.svt-bb-row.is-active .svt-bb-pill { background: rgba(57, 144, 189, 0.55); border-color: #3990bd; }
.svt-legend-btn.svt-bb-row.is-active .svt-bb-rname { color: #fff; }
.svt-legend-btn.svt-bb-row.is-active .svt-bb-rtype { color: #cfe6f2; }

/* Collapsible Blackboard categories — Unity groups the properties into named,
   foldable sections. Header = chevron · name · property count. Default collapsed. */
.svt-bb-cat {
  display: flex; align-items: center; gap: 8px; width: 100%;
  margin: 5px 0 1px; padding: 6px 10px; border: 0; border-radius: 6px;
  background: rgba(255, 255, 255, 0.045); color: #d0d0d0; cursor: pointer;
  font-size: 11px; font-weight: 600; line-height: 1.2; text-align: left;
}
.svt-bb-cat:hover { background: rgba(255, 255, 255, 0.09); }
.svt-bb-cat-chev { display: inline-block; flex: 0 0 auto; font-size: 9px; color: #9a9a9a; transition: transform .15s ease; }
.svt-bb-cat.is-open .svt-bb-cat-chev { transform: rotate(90deg); }
.svt-bb-cat-name { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.svt-bb-cat-count { flex: 0 0 auto; font-size: 10px; font-weight: 500; color: #8f8f8f; background: rgba(255, 255, 255, 0.07); border-radius: 999px; padding: 1px 8px; }
.svt-bb-cat.is-open .svt-bb-cat-count { color: #cfe6f2; background: rgba(57, 144, 189, 0.28); }
.svt-bb-catbody { display: flex; flex-direction: column; gap: 3px; padding: 2px 0 4px 8px; }

.svt-chain-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}
.svt-chain-box {
  position: absolute;
  border: 2px solid var(--accent);
  border-radius: 7px;
  background: rgba(var(--accent-rgb), 0.12);
  box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.2), 0 0 18px rgba(var(--accent-rgb), 0.22);
  pointer-events: none;
  animation: svt-pop 0.18s ease both;
}
/* Source = the Blackboard property node; Sink = the Fragment output. */
.svt-chain-box.is-source {
  border-width: 3px;
  border-color: var(--accent-light);
  background: rgba(var(--accent-rgb), 0.24);
  box-shadow: 0 0 0 2px rgba(var(--accent-rgb), 0.3), 0 0 26px rgba(var(--accent-rgb), 0.4);
}
.svt-chain-box.is-sink {
  border-style: dashed;
  border-color: var(--accent-light);
}
@keyframes svt-pop {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: scale(1); }
}

@media (max-width: 600px) {
  .svt-legend { width: 132px; padding: 7px; resize: none; }
  .svt-legend-btn { font-size: 10px; padding: 4px 6px; }
}

/* ---- Property explanation panel (bottom-left, wide) --------------------- */
.svt-explain {
  position: absolute;
  left: 10px;
  bottom: 10px;
  right: auto; top: auto;
  max-height: 46%;                 /* wide + short, not a tall column */
  z-index: 7;
  width: min(720px, calc(100% - 20px));
  max-width: calc(100% - 20px);
  display: flex;
  flex-direction: column;
  padding: 15px 16px 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(10, 12, 16, 0.9);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
  overflow: hidden;               /* the body scrolls, not the whole panel */
  min-width: 240px;
  min-height: 130px;
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  resize: both;                    /* user-resizable (drag the bottom-right corner) */
}
.svt-explain.svt-dragging { transition: none; }
/* Scrollable content area below the fixed (draggable) header. */
.svt-explain-body {
  flex: 1 1 auto;
  min-height: 0;                  /* allow the body to shrink + scroll in the flex column */
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Wide, short layout: flow the sections into columns instead of one tall list. */
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 0 24px;
  align-content: start;
}
/* Panel headers double as drag handles. */
.svt-legend-title,
.svt-explain-head {
  cursor: move;
  touch-action: none;
}
.svt-explain.is-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }

.svt-explain-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
.svt-explain-title {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.3px;
  line-height: 1.15;
  color: var(--text);
}
.svt-explain-close {
  flex: none;
  width: 26px;
  height: 26px;
  margin: -3px -4px 0 0;
  padding: 0;
  font-size: 20px;
  line-height: 1;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--text);
  opacity: 0.6;
  cursor: pointer;
  transition: background 0.14s ease, border-color 0.14s ease, opacity 0.14s ease, color 0.14s ease;
}
.svt-explain-close:hover {
  opacity: 1;
  color: var(--accent);
  border-color: var(--accent-border);
  background: var(--accent-soft);
}
.svt-explain-row { margin-top: 11px; }
.svt-explain-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.9;
  margin-bottom: 3px;
}
.svt-explain-text {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text);
  opacity: 0.88;
}

@media (max-width: 600px) {
  /* Bottom sheet on phones so it doesn't fight the left legend. */
  .svt-explain {
    right: 8px;
    left: 8px;
    bottom: 8px;
    top: auto;
    width: auto;
    max-width: none;
    max-height: 56%;
    transform: translateY(10px);
    resize: none;
  }
  .svt-explain.is-visible { transform: translateY(0); }
  .svt-explain-title { font-size: 15px; }
}

/* ==========================================================================
   Live Shader Graph (js/shader-graph.js) — a DOM recreation of a Unity URP
   Shader Graph: two-tone operation nodes, rounded property tokens, the master
   stack, and type-coloured routed wires. Shares the .svt-* tooltip / legend /
   explain panels above. Class prefix: .sg-*
   ========================================================================== */
.shader-graph-viewer {
  position: relative;
  overflow: hidden;
  height: clamp(360px, 60vh, 720px);
  border-radius: 14px;
  border: 1px solid var(--border);
  background: #202020;
  user-select: none;
  touch-action: none;
  /* Unity uses Myriad Pro across the graph UI. */
  font-family: "Myriad Pro", "Myriad", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.shader-graph-viewer:hover {
  border-color: var(--accent-border);
  box-shadow: 0 0 0 1px var(--accent-soft);
}
.shader-graph-viewer.is-fullscreen {
  width: 100vw; height: 100vh; border-radius: 0; border: 0;
}
/* With sections present it's .sg-lab that goes fullscreen, so the tabs, the
   Blackboard and the write-up all travel with the canvas. The viewer is then a
   flex child and must fill what's left between the two columns — not 100vw,
   which would shove them off screen.
   Whole lab maximised: tabs across the top, row filling everything under them. */
.sg-lab.is-fullscreen {
  width: 100vw; height: 100vh;
  padding: 14px; gap: 12px;
  background: var(--bg);
  overflow: hidden;
}
.sg-lab.is-fullscreen > .sg-sections { flex: none; margin-bottom: 0; }
.sg-lab.is-fullscreen > .sg-stage { flex: 1 1 auto; height: auto; min-height: 0; }

/* Row maximised on its own (a shader registered without sections). */
.sg-stage.is-fullscreen {
  width: 100vw; height: 100vh;
  padding: 14px; gap: 14px;
  background: var(--bg);
}
.sg-lab.is-fullscreen > .sg-stage > .shader-graph-viewer,
.sg-stage.is-fullscreen > .shader-graph-viewer {
  width: auto; height: auto;
  border-radius: 12px;
  border: 1px solid var(--border);
}

/* Pan surface + faint two-tier grid, like the Shader Graph canvas. */
.sg-canvas {
  position: absolute; inset: 0; cursor: grab; touch-action: none;
  background-color: #202020;
  background-image:
    linear-gradient(rgba(255,255,255,.022) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.022) 1px, transparent 1px),
    linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
  background-size: 22px 22px, 22px 22px, 110px 110px, 110px 110px;
}
.sg-canvas.sg-panning { cursor: grabbing; }
.sg-canvas.sg-nogrid { background-image: none; }   /* grid toggle (off by default) */

/* Shader-graph toolbar: vertical, pinned top-right (overrides the image viewer's
   bottom-centre horizontal pill). */
.izv-toolbar.sg-toolbar {
  left: auto; bottom: auto;
  top: 12px; right: 12px;
  transform: none;
  flex-direction: column;
}
.sg-layer { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; }

/* Wires */
.sg-wires { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
.sg-wires path {
  fill: none; stroke-width: 2.6; stroke-linecap: round;
  /* Keep wires crisp at any zoom: without this the strokes scale down with the
     layer transform and antialias into faint dotted lines when zoomed out. */
  vector-effect: non-scaling-stroke; shape-rendering: geometricPrecision;
  transition: stroke-width .12s, filter .12s, opacity .12s;
}
.sg-wires path.sg-wire-hi { stroke-width: 3.6; filter: brightness(1.55); }
/* Easy-to-hover invisible hit stroke + hover-thicken on the visible wire (Unity-style) */
.sg-wires path.sg-wire-hit { stroke: transparent; stroke-width: 14; pointer-events: stroke; }
.sg-wires path.sg-wire-hover { stroke-width: 4.8; }
.sg-stacklink { stroke: #8f8f8f; stroke-width: 4; fill: none; stroke-linecap: round;
  vector-effect: non-scaling-stroke; shape-rendering: geometricPrecision; }
.sg-stacknub  { stroke: #8f8f8f; stroke-width: 2.5; fill: #202020;
  vector-effect: non-scaling-stroke; }

/* Operation / input node — near-straight edges, two-tone body */
.sg-node {
  position: absolute; min-width: 190px; background: #2b2b2b;
  border: 1px solid #1e1e1e; border-radius: 3px;
  box-shadow: 0 7px 18px rgba(0,0,0,.5); color: #c3c3c3;
}
/* Slight transparency so the wires behind read through the nodes (Unity-style). */
.sg-node, .sg-pill, .sg-master { opacity: 0.85; }
.sg-head {
  padding: 9px 15px; background: #393939; color: #bdbdbd; font-size: 15px; font-weight: 500;
  border-bottom: 1px solid #1e1e1e; border-radius: 2px 2px 0 0; cursor: grab; white-space: nowrap;
}
.sg-head:active { cursor: grabbing; }
.sg-body { display: flex; align-items: stretch; }
.sg-colin  { background: #393939; padding: 7px 0; }
.sg-colout { background: #2b2b2b; padding: 7px 0; flex: 1 1 auto; }
.sg-row { display: flex; align-items: center; height: 27px; font-size: 14px; color: #c3c3c3; white-space: nowrap; }
.sg-row.in  { padding: 0 16px; gap: 10px; }
.sg-row.out { padding: 0 16px 0 18px; gap: 14px; justify-content: space-between; }
/* Preview panel revealed when a node is expanded via the footer chevron.
   Collapsed = 0 height; expanding animates it open. White placeholder for now —
   swap the .sg-preview-img background for a per-node preview graphic later. */
.sg-preview {
  height: 0; overflow: hidden; background: #2b2b2b;
  transition: height .3s cubic-bezier(.4, 0, .2, 1);
}
.sg-preview-img {
  width: 100%; height: 100%;
  background-color: #313131;      /* grey backdrop so transparent sphere PNGs read on grey; no-preview nodes get white inline */
  background-size: cover; background-position: center; background-repeat: no-repeat;
  border-top: 1px solid #1e1e1e;
}
/* An expanded node reads solid (no wire-through) and floats above its neighbours. */
.sg-node.sg-expanded { opacity: 1; }
.sg-foot {
  height: 18px; display: flex; align-items: center; justify-content: center;
  background: #2b2b2b; border-top: 1px solid #1e1e1e; border-radius: 0 0 2px 2px;
  cursor: pointer;
}
.sg-foot:hover { background: #343434; }
.sg-foot::before {
  content: ""; width: 6px; height: 6px; border-right: 1.6px solid #b7b7b7;
  border-bottom: 1.6px solid #b7b7b7; transform: translateY(-2px) rotate(45deg);
  transition: transform .3s cubic-bezier(.4, 0, .2, 1);
}
/* Chevron flips to point up while expanded. */
.sg-node.sg-expanded .sg-foot::before { transform: translateY(2px) rotate(225deg); }

/* Per-node info button (top-right corner). Subtle until the node is hovered;
   click toggles that node's info bubble. */
.sg-info {
  position: absolute; top: 5px; right: 5px; z-index: 3;
  width: 16px; height: 16px; padding: 0; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid #6f6f6f; border-radius: 50%;
  background: rgba(43, 43, 43, .9); color: #d0d0d0;
  font: italic 700 11px/1 Georgia, "Times New Roman", serif;
  cursor: pointer; opacity: .4;
  transition: opacity .15s, background .15s, border-color .15s, color .15s;
}
.sg-node:hover .sg-info, .sg-master:hover .sg-info { opacity: .95; }
.sg-info:hover, .sg-info:focus-visible { opacity: 1; background: #3990bd; border-color: #3990bd; color: #fff; outline: none; }
/* On the big master box, clear the rounded corner. */
.sg-master .sg-info { top: 14px; right: 16px; }

/* Port ring (shared by nodes, pills and master fields) */
.sg-slot {
  position: relative; width: 13px; height: 13px; border-radius: 50%;
  background: #202020; border: 2.5px solid var(--sc, #84e4e7); flex: 0 0 auto;
}
.sg-row.in .sg-slot  { margin-left: -11px; }
.sg-row.out .sg-slot { margin-right: -11px; }
.sg-slot.filled::after { content: ""; position: absolute; inset: 2px; border-radius: 50%; background: var(--sc, #84e4e7); }

/* Exposed property token — rounded pill, set min-width, single line */
.sg-pill {
  position: absolute; display: flex; align-items: center; gap: 11px; height: 36px;
  padding: 0 15px; min-width: 210px; background: #5a5b5c; border: 1px solid #1e1e1e;
  border-radius: 12px; box-shadow: 0 6px 16px rgba(0,0,0,.45); cursor: grab;
  white-space: nowrap; font-size: 14px; color: #d3d3d3;
  z-index: 3;                       /* properties render above nodes, never pushed behind them */
}
.sg-pill:active { cursor: grabbing; }
.sg-pdot { width: 11px; height: 11px; border-radius: 50%; background: #83cf60; flex: 0 0 auto; }
.sg-plabel { flex: 1 1 auto; white-space: nowrap; }
.sg-pill .sg-slot { margin-right: -9px; }

/* Master stack — big rounded containers */
.sg-master { position: absolute; }
.sg-mbox {
  position: relative; background: #252525; border: 1.5px solid #3d3d3d; border-radius: 18px;
  padding: 16px 18px 18px; min-width: 340px; box-shadow: 0 9px 26px rgba(0,0,0,.5);
}
.sg-mtitle { font-size: 22px; font-weight: 500; color: #cfcfcf; margin: 2px 4px 14px; cursor: grab; }
.sg-mtitle:active { cursor: grabbing; }
.sg-mfield {
  position: relative; display: flex; align-items: center; gap: 12px; height: 38px; margin: 11px 0;
  background: #333; border-radius: 6px; padding: 0 16px; color: #c3c3c3; font-size: 15px; white-space: nowrap;
}
/* First child is the port ring; hang it just outside the field's left edge. */
.sg-mfield .sg-slot { width: 15px; height: 15px; margin-left: -24px; border-color: var(--sc, #e0e88d); }
.sg-mdef {
  position: absolute; right: calc(100% + 24px); display: flex; align-items: center; height: 32px;
  background: #333; border-radius: 4px; padding: 0 12px; color: #c3c3c3; font-size: 14px; white-space: nowrap;
}
.sg-caret { margin-left: 10px; color: #8f8f8f; font-size: 11px; }
.sg-ddot { position: absolute; right: -6px; top: 50%; transform: translateY(-50%); width: 9px; height: 9px; border-radius: 50%; background: var(--sc, #e0e88d); }
.sg-mdef::after { content: ""; position: absolute; left: 100%; top: 50%; transform: translateY(-50%); width: 24px; height: 2.5px; background: var(--sc, #e0e88d); }
.sg-swatch { width: 52px; height: 18px; border-radius: 3px; background: #8f8f8f; }
.sg-xf { display: flex; align-items: center; gap: 9px; }
.sg-xf b { color: #9a9a9a; font-weight: 600; }
.sg-xv { background: #2a2a2a; border-radius: 3px; padding: 2px 12px; color: #c3c3c3; }

/* Hover + drag + chain highlight — Unity-style blue outline on hover */
.sg-node.sg-hover, .sg-pill.sg-hover { box-shadow: 0 0 0 4px #3990bd, 0 8px 20px rgba(0,0,0,.55); }
.sg-master.sg-hover .sg-mbox { box-shadow: 0 0 0 4px #3990bd, 0 10px 28px rgba(0,0,0,.55); }
.sg-node.sg-grab, .sg-pill.sg-grab { z-index: 20; }   /* grabbed item always on top while it pushes others aside */
.sg-canvas.sg-has-chain .sg-node,
.sg-canvas.sg-has-chain .sg-pill,
.sg-canvas.sg-has-chain .sg-master { opacity: .3; transition: opacity .16s; }
.sg-canvas.sg-has-chain .sg-node.sg-hi,
.sg-canvas.sg-has-chain .sg-pill.sg-hi,
.sg-canvas.sg-has-chain .sg-master.sg-hi { opacity: 1; }
.sg-canvas.sg-has-chain .sg-node.sg-hi,
.sg-canvas.sg-has-chain .sg-pill.sg-hi { outline: 2px solid var(--accent); outline-offset: 1px; }
.sg-canvas.sg-has-chain .sg-master.sg-hi .sg-mbox { border-color: var(--accent); }
.sg-canvas.sg-has-chain .sg-pill.sg-hi-src { box-shadow: 0 0 0 2px var(--accent), 0 6px 16px rgba(0,0,0,.5); }
.sg-canvas.sg-has-chain .sg-wires path { opacity: .28; }
.sg-canvas.sg-has-chain .sg-wires path.sg-wire-hi { opacity: 1; }

/* Fit button lives in the shared toolbar; fullscreen icon swap */
.shader-graph-viewer .izv-ico-exit { display: none; }
.shader-graph-viewer.is-fullscreen .izv-ico-enter { display: none; }
.shader-graph-viewer.is-fullscreen .izv-ico-exit  { display: block; }

/* Hint — top-centre so it never collides with the bottom toolbar. Fades on
   hover, but stays put while editing (that's when the message matters most). */
.sg-hint {
  /* Bottom of the canvas, riding just above the toolbar. The toolbar sits at
     16px and is ~48px tall, so its top edge is ~64px — 70px keeps them clear
     without a big gap. */
  position: absolute; left: 50%; bottom: 70px; transform: translateX(-50%); z-index: 4;
  max-width: calc(100% - 24px); overflow: hidden; text-overflow: ellipsis;
  font-size: 11px; color: #b7b7b7; background: rgba(20,20,20,.72); padding: 4px 12px;
  border-radius: 999px; pointer-events: none; white-space: nowrap;
  opacity: 1; transition: opacity .3s;
}
.shader-graph-viewer:hover .sg-hint { opacity: 0; }
.shader-graph-viewer.sg-editing .sg-hint { opacity: 1; color: var(--accent); }

/* "Interactive Shader Graph · LIVE" badge, top-right of the canvas. Purely
   informational, so it never takes pointer events away from the graph. */
.sg-live {
  position: absolute;
  left: 12px;
  top: 12px;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 13px;
  border-radius: 999px;
  background: rgba(16, 18, 22, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(6px);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: #b9b9b9;
  white-space: nowrap;
  pointer-events: none;
}
.sg-live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #2dff8c;
  /* Halo ring + outer bloom — the bloom is what reads as neon rather than
     just "a green circle". */
  box-shadow:
    0 0 0 3px rgba(45, 255, 140, 0.18),
    0 0 10px rgba(45, 255, 140, 0.9),
    0 0 20px rgba(45, 255, 140, 0.45);
  animation: sgLivePulse 2.2s ease-in-out infinite;
}
.sg-live-text {
  color: #2dff8c;
  text-shadow: 0 0 8px rgba(45, 255, 140, 0.55);
}
@keyframes sgLivePulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
@media (prefers-reduced-motion: reduce) {
  .sg-live-dot { animation: none; }
}
@media (max-width: 1100px) {
  /* Drop the long label, keep the live signal. */
  .sg-live-label { display: none; }
}

/* Icon-pill form of the hint: a small segmented bar of interaction affordances
   across the top of the canvas. Stays legible on hover (just dimmed) instead of
   vanishing, since it's chrome rather than a one-time tip. */
.sg-hint.sg-hintbar {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 5px 7px;
  background: rgba(16, 18, 22, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(6px);
}
/* NEVER dimmed. This bar is how anyone discovers the graph is interactive at all,
   so it stays at full opacity in every state — hover, editing, fullscreen.
   Outranks the generic `.shader-graph-viewer:hover .sg-hint { opacity: 0 }` above. */
.shader-graph-viewer .sg-hint.sg-hintbar,
.shader-graph-viewer:hover .sg-hint.sg-hintbar,
.shader-graph-viewer.sg-editing .sg-hint.sg-hintbar,
.shader-graph-viewer.is-fullscreen .sg-hint.sg-hintbar { opacity: 1; }

.sg-hintpill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 13px;
  border-radius: 999px;
  color: #c2c2c2;
  white-space: nowrap;
}
.sg-hintpill + .sg-hintpill { border-left: 1px solid rgba(255, 255, 255, 0.09); }
/* Gold glyphs against the neutral labels — the icons carry the recognition. */
.sg-hintpill svg { width: 13px; height: 13px; flex: none; color: var(--accent); opacity: 1; }

@media (max-width: 1100px) {
  /* Drop the labels, never the pills — every affordance stays on screen.
     Each pill keeps a title attribute so the wording is still reachable. */
  .sg-hintpill span { display: none; }
  .sg-hintpill { padding: 5px 10px; }
  .sg-hintpill svg { width: 14px; height: 14px; }
}

/* Edit-layout tools */
/* Reset button is always available (there is no Save button — drags auto-save). */
.shader-graph-viewer .izv-btn.is-active {
  color: var(--accent);
  border-color: var(--accent-border);
  background: var(--accent-soft);
}
/* Reset + Fullscreen also exist as labelled buttons in the lab header, so the
   toolbar copies are hidden — EXCEPT in fullscreen, where the header is off
   screen and these become the only way out. fsBtn swaps to its exit icon
   automatically via .is-fullscreen, giving a minimise control in the panel. */
.shader-graph-viewer .izv-btn.sg-dupbtn { display: none; }
.shader-graph-viewer.is-fullscreen .izv-btn.sg-dupbtn { display: inline-flex; }
/* While editing, node title bars advertise that they're draggable. */
.shader-graph-viewer.sg-editing .sg-node,
.shader-graph-viewer.sg-editing .sg-pill { outline: 1px dashed rgba(var(--accent-rgb), 0.5); outline-offset: 2px; }

/* Multi-select (Unity Shader Graph look): blue selection outline on nodes and on
   the wires running between selected nodes; a yellow dashed marquee box. */
.sg-node.sg-sel, .sg-pill.sg-sel, .sg-master.sg-sel { box-shadow: 0 0 0 2px #3990bd, 0 8px 22px rgba(0,0,0,.5); }
.sg-wires path.sg-wire-sel { stroke: #3990bd; stroke-width: 3.4; }
/* Dashed border drawn with gradients so the dash length is controllable (--d).
   ~3x the length of a default CSS dashed border. */
.sg-marquee { position: absolute; z-index: 4; pointer-events: none;
  --d: 18px; --g: 7px;   /* dash length / gap */
  background:
    repeating-linear-gradient(90deg,#ebc500 0 var(--d),transparent var(--d) calc(var(--d) + var(--g))) left top/100% 1px no-repeat,
    repeating-linear-gradient(90deg,#ebc500 0 var(--d),transparent var(--d) calc(var(--d) + var(--g))) left bottom/100% 1px no-repeat,
    repeating-linear-gradient(0deg,#ebc500 0 var(--d),transparent var(--d) calc(var(--d) + var(--g))) left top/1px 100% no-repeat,
    repeating-linear-gradient(0deg,#ebc500 0 var(--d),transparent var(--d) calc(var(--d) + var(--g))) right top/1px 100% no-repeat,
    rgba(235,197,0,0.07); }

/* ---------------------------------------------------------------------------
   Section tour — the tab strip above the graph. Each tab names one stage of the
   shader; clicking it dims the rest of the graph, flies the view to those nodes
   and opens the section write-up in the explain panel (.svt-explain.is-section).
   Built by js/shader-graph.js from map.sections.
   ------------------------------------------------------------------------- */
/* Header row inside the section container: heading left, actions right.
   Named .sg-lab-head, NOT .sg-head — that one is the Unity node title bar
   (#393939 / #bdbdbd) further down this file. */
.sg-lab-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  /* Full-width rule under the header block (i.e. below the intro copy),
     separating it from the section tabs. */
  margin: 0 0 18px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--border);
}
.sg-head-main { min-width: 0; }
.sg-lab-head .project-detail-heading { margin: 0 0 8px; }
/* Lab title + intro, below the eyebrow. */
/* Matches .project-detail-card .project-detail-title so every heading in the
   detail view reads at the same level. */
.sg-lab-title {
  margin: 0;
  font-size: 36px;
  font-weight: 600;
  letter-spacing: -1px;
  line-height: 1.04;
  color: var(--text);
}
.sg-lab-intro {
  margin: 9px 0 0;
  max-width: 62ch;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text);
  opacity: 0.62;
}
.sg-head-actions { display: flex; gap: 8px; flex: none; }
.sg-headbtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 15px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text);
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
  border-radius: 9px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .2s, border-color .2s, color .2s;
}
.sg-headbtn svg { width: 15px; height: 15px; flex: none; }
.sg-headbtn:hover { background: rgba(255, 255, 255, 0.07); border-color: rgba(255, 255, 255, 0.24); }
.sg-headbtn.is-accent { color: var(--accent); border-color: var(--accent-border); background: rgba(var(--accent-rgb), 0.08); }
.sg-headbtn.is-accent:hover { background: rgba(var(--accent-rgb), 0.18); }

.sg-sections {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 14px;
}
.sg-sec {
  flex: 1 1 190px;
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
  padding: 11px 14px;
  text-align: left;
  font: inherit;
  color: var(--text);
  background: rgba(255, 255, 255, 0.028);
  border: 1px solid var(--border);
  border-radius: 11px;
  cursor: pointer;
  transition: background .2s, border-color .2s, transform .2s;
}
/* "Full Graph" is an overview, not a stage — keep it compact so the four stage
   tabs share the remaining width evenly. */
.sg-sec-all { flex: 0 0 auto; }
.sg-sec:hover { background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.22); }
.sg-sec:active { transform: translateY(1px); }
.sg-sec.is-active {
  border-color: var(--accent-border);
  background: var(--accent-soft);
  box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.25);
}
.sg-sec-ico {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 8px;
  color: #9a9a9a;
  background: rgba(255, 255, 255, 0.05);
  transition: color .2s, background .2s;
}
.sg-sec-ico svg { width: 16px; height: 16px; }
.sg-sec.is-active .sg-sec-ico { color: var(--accent); background: rgba(var(--accent-rgb), 0.14); }
.sg-sec-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sg-sec-title {
  font-size: 13px; font-weight: 600; letter-spacing: -0.1px; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sg-sec.is-active .sg-sec-title { color: var(--accent); }
.sg-sec-sub {
  font-size: 11px; line-height: 1.25; color: var(--text); opacity: 0.55;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Canvas + write-up sit side by side. The panel is a sibling of the viewer, not
   an overlay inside it, so opening a section NARROWS the graph rather than
   covering it — the graph then re-fits into what's left. */
/* PLACEHOLDER three-column block under the lab. Black boxes stand in for stills
   until real images exist — replace .sg-col-img with an <img> then. */
/* Rule sits on this wrapper rather than on .sg-stage, so it reads as a separator
   below the graph row without following it into fullscreen. */
.sg-colsblock {
  margin: 22px 0 0;
  padding-top: 22px;
  border-top: 1px solid var(--border);
}
/* Gold eyebrow; .project-detail-heading supplies the 13px uppercase accent
   styling, this only fixes the spacing to the heading below it. */
.sg-cols-eyebrow { margin: 0 0 10px; }

/* Same size as .project-detail-title / .sg-lab-title. */
.sg-cols-title {
  margin: 0 0 18px;
  font-size: 36px;
  font-weight: 600;
  letter-spacing: -1px;
  line-height: 1.04;
  color: var(--text);
}
.sg-cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Gap matches the rule's padding below, so each divider sits centred in its
     gutter with equal air either side. */
  gap: 32px;
}
.sg-col + .sg-col {
  border-left: 1px solid var(--border);
  padding-left: 32px;
}
/* Columns are flex so a bottom placeholder can be pushed to the true bottom,
   keeping the two images aligned to the block's edges rather than floating
   wherever the copy happens to end. */
.sg-col { min-width: 0; display: flex; flex-direction: column; }
.sg-col-img {
  /* Narrower than the column and a squatter ratio, so the stills read as
     supporting the copy rather than dominating it. Works for both the black
     placeholder <div> and a real <img>. */
  display: block;
  width: 100%;
  aspect-ratio: 2 / 1;
  object-fit: cover;
  background: #000;
  border: 1px solid var(--border);
  border-radius: 10px;
}
/* Real stills show whole and uncropped at full column width — the fixed 2:1 box
   is only there to give the black placeholder <div>s a size. */
img.sg-col-img {
  height: auto;
  aspect-ratio: auto;
  object-fit: fill;
}
.sg-col-img-top { margin: 0 0 14px; }
.sg-col-img-middle { margin: 3px 0 14px; }
.sg-col-img-bottom { margin: auto 0 0; padding-top: 14px; }
.sg-col-text {
  margin: 0 0 11px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text);
  opacity: 0.62;
}
.sg-col-text:last-child { margin-bottom: 0; }
@media (max-width: 820px) {
  /* Stacked: the divider becomes a horizontal rule between rows. */
  .sg-cols { grid-template-columns: 1fr; gap: 26px; }
  .sg-col + .sg-col {
    border-left: 0;
    padding-left: 0;
    border-top: 1px solid var(--border);
    padding-top: 26px;
  }
}

/* .sg-lab = tabs + row. This is the element that goes fullscreen, so the section
   tabs travel with the graph instead of being stranded on the page behind it. */
.sg-lab {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.sg-stage {
  position: relative;        /* nothing absolute inside can escape to the page */
  display: flex;
  align-items: stretch;      /* every column shares the row's height */
  gap: 14px;
  min-height: 0;
  height: clamp(315px, 55.5vh, 675px);
}
.sg-stage > .shader-graph-viewer {
  flex: 1 1 auto;
  min-width: 0;
  height: auto;              /* stretch to the row instead of its own clamp */
  align-self: stretch;
}

/* Blackboard as the LEFT column — same treatment as the write-up on the right.
   .svt-legend supplies the Unity chrome; everything positional is overridden so
   it becomes a real flex child, exactly the height of the canvas beside it. */
.sg-stage > .svt-legend {
  position: static;
  flex: 0 0 232px;
  align-self: stretch;
  width: 232px;
  max-width: none;
  height: auto;
  max-height: none;
  min-height: 0;
  resize: none;              /* the row owns its size now */
  border-radius: 12px;
  box-shadow: none;
}
.sg-stage > .svt-legend .svt-bb-head {
  cursor: default;           /* not a drag handle any more */
  border-radius: 12px 12px 0 0;
}

/* Collapsed to zero width until a section is picked; .svt-explain supplies the
   card chrome, everything positional is overridden here. */
.sg-secpanel {
  position: static;
  flex: 0 0 0px;
  align-self: stretch;       /* exactly the canvas height, never taller */
  width: 0;
  height: auto;
  max-width: none;
  max-height: none;
  padding: 0;
  border-width: 0;
  overflow: hidden;
  opacity: 0;
  transform: none;
  pointer-events: none;
  transition: flex-basis .26s ease, width .26s ease, padding .26s ease, opacity .18s ease;
}
.sg-secpanel.is-visible {
  flex: 0 0 330px;
  width: 330px;
  padding: 15px 16px 16px;
  border-width: 1px;
  opacity: 1;
  pointer-events: auto;
}
/* Head pinned, body scrolls — a section carries an image and a node table. */
.sg-secpanel .svt-explain-head { flex: none; cursor: default; }
.sg-secpanel .svt-explain-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* No close button on the column — it's part of the layout, not a dismissible
   overlay. Deselecting the active tab is what closes it. (The floating overlay
   form, used by any shader without sections, keeps its ×.) */
.sg-secpanel .svt-explain-close { display: none; }
.svt-sec-desc { margin: 6px 0 0; font-size: 12.5px; line-height: 1.55; color: var(--text); opacity: 0.85; }
/* Content swap inside the write-up column. Section → property → overview all
   reuse the same panel, so without this the text just teleports while the graph
   behind it is still gliding. Short and small-travel on purpose — this runs on
   every selection, so anything longer would feel sluggish. */
@keyframes svtBodyIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
/* Slow ease-out: moves quickly at first then settles, which reads as calm rather
   than sluggish even at 450ms. Roughly matches the 560ms view glide underneath,
   so the panel and the graph feel like one motion. */
.svt-body-in { animation: svtBodyIn 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; }
@media (prefers-reduced-motion: reduce) {
  .svt-body-in { animation: none; }
}

/* Derived counts under the overview description. */
.svt-sec-stats {
  margin: 10px 0 0;
  padding: 7px 10px;
  font-size: 11px;
  letter-spacing: 0.2px;
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.07);
  border: 1px solid rgba(var(--accent-rgb), 0.22);
  border-radius: 7px;
}
.svt-sec-figure {
  margin: 12px 0 0;
  border: 1px solid var(--border);
  border-radius: 9px;
  overflow: hidden;
  background: #0a0c10;
}
.svt-sec-figure img { display: block; width: 100%; height: auto; }
.svt-sec-keyhead {
  margin: 15px 0 7px;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.7px; text-transform: uppercase;
  color: var(--accent); opacity: 0.9;
}
.svt-sec-keys { display: flex; flex-direction: column; gap: 1px; }
.svt-sec-key {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 5px 6px;
  font: inherit; text-align: left; color: var(--text);
  background: none; border: 0; border-radius: 6px; cursor: pointer;
  transition: background .16s;
}
.svt-sec-key:hover { background: rgba(255, 255, 255, 0.06); }
.svt-sec-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.svt-sec-kname { flex: 1 1 auto; min-width: 0; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.svt-sec-krole { flex: none; font-size: 11px; opacity: 0.5; }
.svt-sec-focus {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; margin-top: 14px; padding: 9px 12px;
  font: inherit; font-size: 12.5px; font-weight: 600;
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.1);
  border: 1px solid var(--accent-border);
  border-radius: 9px;
  cursor: pointer;
  transition: background .2s;
}
.svt-sec-focus:hover { background: rgba(var(--accent-rgb), 0.2); }

@media (max-width: 900px) {
  .sg-sec { flex: 1 1 calc(50% - 8px); }
}

@media (max-width: 600px) {
  .shader-graph-viewer { height: clamp(300px, 52vh, 520px); }
  .sg-hint { display: none; }
  .sg-sec { flex: 1 1 100%; }
  .sg-sec-sub { display: none; }
  /* Actions drop under the heading rather than squeezing it. */
  .sg-lab-head { flex-direction: column; align-items: stretch; gap: 12px; }
  .sg-head-actions { justify-content: flex-start; }
  .sg-headbtn { flex: 1 1 auto; justify-content: center; }
}

/* Below ~820px a 330px column starves the canvas, so stack instead: graph on
   top, write-up beneath it at full width. */
@media (max-width: 820px) {
  /* Stacked, the row can no longer be one fixed height — the canvas keeps the
     clamp and the panels add their own height around it. */
  .sg-stage { flex-direction: column; height: auto; }
  .sg-stage > .shader-graph-viewer { height: clamp(300px, 52vh, 520px); flex: 0 0 auto; }
  /* Blackboard goes full width above the canvas, capped so it can't eat the page. */
  .sg-stage > .svt-legend {
    width: auto; flex: 0 0 auto; align-self: auto; max-height: 34vh;
  }
  .sg-secpanel { width: auto; flex: 0 0 0px; }
  .sg-secpanel.is-visible {
    width: auto;
    flex: 0 0 auto;
    align-self: auto;
    max-height: 60vh;
  }
}

/* On the standalone project page, drop the outer panel so the video and card
   read as separate boxes (the card provides its own box). about/contact pages
   have no .project-detail-card, so they keep their panel. */
.standalone-detail-page .project-detail-inner:has(.project-detail-card) {
  max-width: 1280px;
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Title block (top-left of the card) */
.project-detail-headline {
  margin-top: 0;
}

.project-detail-card .project-detail-title {
  font-size: 36px;
  letter-spacing: -1px;
  line-height: 1.04;
}

.project-detail-headline .project-detail-category {
  margin-bottom: 14px;
}

.project-detail-headline .project-detail-subtitle {
  margin-top: 14px;
  font-size: 15px;
}

/* About | Video Details | Gallery — separated by vertical divider lines */
.project-detail-cols {
  display: flex;
  align-items: stretch;
  gap: 0;
  margin-top: 30px;
}

.project-detail-col {
  min-width: 0;
  padding: 0 34px;
}

.project-detail-col:first-child {
  padding-left: 0;
}

.project-detail-col:last-child {
  padding-right: 0;
}

.project-detail-col + .project-detail-col {
  border-left: 1px solid var(--border);
}

.project-detail-col--about {
  flex: 1.2 1 0;
}

.project-detail-col--details {
  flex: 1 1 0;
}

.project-detail-col--gallery {
  flex: 1.5 1 0;
}

.project-detail-col--about .project-detail-body {
  font-size: 12px;
  margin-top: 18px;
}

.project-detail-col--about .project-detail-body.project-detail-disclaimer {
  font-size: 12px;
}

/* Video details meta list */
.project-meta {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
}

.project-meta-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}

.project-meta-row:last-child {
  border-bottom: none;
}

.project-meta-icon {
  flex: none;
  width: 10px;
  height: 10px;
  margin-top: 5px;
  border-radius: 50%;
  background: var(--accent);
}

.project-meta-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.project-meta-label {
  font-size: 12px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--muted);
}

.project-meta-value {
  font-size: 15px;
  color: var(--text);
}

/* Project gallery (2-up in its column, first image spans full width) */
.project-detail-col--gallery .gallery-grid {
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 104px;
}

.project-detail-col--gallery .gallery-item:first-child {
  grid-row: span 1;
}

/* Pipeline & Workflow */
.project-detail-workflow {
  margin-top: 52px;
  padding-top: 44px;
  border-top: 1px solid var(--border);
}

.workflow-flow {
  --wf-cols: 4;
  --wf-col-gap: 44px; /* room for the horizontal arrows between cards */
  --wf-row-gap: 52px; /* room for the downward arrows at each turn */

  list-style: none;
  margin: 26px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(var(--wf-cols), 1fr);
  align-items: stretch;
  gap: var(--wf-row-gap) var(--wf-col-gap);
}

.workflow-step {
  /* grid-column / grid-row are set inline per step so the flow snakes */
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 14px;

  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}

/* Serpentine connectors, drawn in the grid gaps as decorative pseudo-elements.
   Each step carries at most one of these classes (the last step carries none). */
.workflow-step.wf-next-right::after,
.workflow-step.wf-next-left::after,
.workflow-step.wf-next-down::after {
  position: absolute;
  color: var(--accent);
  font-size: 32px;
  font-weight: 700;
  line-height: 1;
  text-align: center;
  text-shadow: 0 0 12px rgba(var(--accent-rgb), 0.45);
  pointer-events: none;
}

/* Horizontal arrow into the next card on the right (left→right rows) */
.workflow-step.wf-next-right::after {
  content: "→";
  top: 50%;
  left: 100%;
  width: var(--wf-col-gap);
  transform: translateY(-50%);
}

/* Horizontal arrow into the next card on the left (reversed right→left rows) */
.workflow-step.wf-next-left::after {
  content: "←";
  top: 50%;
  right: 100%;
  width: var(--wf-col-gap);
  transform: translateY(-50%);
}

/* Downward arrow at a turn, centred under the card that ends the row */
.workflow-step.wf-next-down::after {
  content: "↓";
  left: 0;
  right: 0;
  top: 100%;
  height: var(--wf-row-gap);
  display: flex;
  align-items: center;
  justify-content: center;
}

.workflow-icon {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  background: rgba(var(--accent-rgb), 0.16);
  border: 1px solid rgba(var(--accent-rgb), 0.30);

  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  overflow: hidden;
}

/* White skill icon tinted with the gold theme. Sized larger than the box so it
   overflows ~20% and gets clipped (box has overflow: hidden) — makes it bigger. */
.workflow-icon-img {
  width: 120%;
  height: 120%;

  background-color: var(--accent-light);

  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}

.workflow-label {
  font-size: 12px;
  line-height: 1.4;
  color: var(--text);

  /* Cap at 2 lines (full text available via the title tooltip) */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Tools & Technologies badges */
.project-detail-tools {
  margin-top: 52px;
  padding-top: 44px;
  border-top: 1px solid var(--border);
}

.tool-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 22px;
}

.tool-badge {
  width: 58px;
  height: 58px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;

  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);

  transition:
    border-color 0.22s ease,
    background 0.22s ease,
    transform 0.22s ease;
}

/* App logo sits inside the badge box, contained (no overflow), ~20% larger */
.tool-badge-icon {
  width: 77%;
  height: 77%;
  object-fit: contain;
  display: block;
}

.tool-badge:hover {
  border-color: rgba(var(--accent-rgb), 0.40);
  background: rgba(var(--accent-rgb), 0.08);
  transform: translateY(-2px);
}

.tool-badge-label {
  font-weight: 700;
  font-size: 18px;
  color: var(--accent-light);
}

/* Credits & Collaborators — 2-up cards: gold icon + name/role/description */
.project-detail-credits {
  margin-top: 52px;
  padding-top: 44px;
  border-top: 1px solid var(--border);
}

.project-credits-intro {
  margin: 10px 0 0;
  color: var(--muted);
  font-size: 14px;
}

.credits-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-top: 26px;
}

.credit-card {
  display: flex;
  align-items: flex-start;
  gap: 16px;

  padding: 22px 24px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
}

.credit-icon {
  flex: none;
  width: 62px;
  height: 62px;
  border-radius: 12px;

  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;

  background: rgba(var(--accent-rgb), 0.16);
  border: 1px solid rgba(var(--accent-rgb), 0.30);
}

/* White skill icon tinted with the gold theme (same treatment as workflow) */
.credit-icon-img {
  width: 50px;
  height: 50px;

  background-color: var(--accent-light);

  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}

.credit-text {
  min-width: 0;
}

.credit-name {
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
}

.credit-role {
  margin-top: 4px;
  font-size: 11px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--accent);
}

.credit-desc {
  margin-top: 10px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}

@media (max-width: 700px) {
  .credits-grid {
    grid-template-columns: 1fr;
  }
}

/* Below-banner two-column body: About + Tools (left), Gallery (right) */

.project-detail-body-cols {
  display: flex;
  align-items: flex-start;
  gap: 56px;
  margin-top: 8px;
}

.project-detail-col-left {
  flex: 1 1 0;
  min-width: 0;
}

.project-detail-col-right {
  flex: 1.6 1 0;
  min-width: 0;
}

.project-detail-block {
  margin-bottom: 46px;
}

.project-detail-block:last-child {
  margin-bottom: 0;
}

.project-detail-heading {
  margin: 0 0 22px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent);
}

/* Live-site link in the Project Highlights: a standard meta row (bullet) whose
   value is a clickable URL. */
.project-meta-link {
  color: var(--accent-light);
  text-decoration: none;
  transition: color 0.2s ease;
}

.project-meta-link:hover {
  color: var(--accent);
}

/* Related projects carousel (bottom of the project detail page) */
.related-projects {
  margin-top: 52px;
  padding-top: 44px;
  border-top: 1px solid var(--border);
}

.related-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
}

.related-head .project-detail-heading {
  margin-bottom: 6px;
}

.related-head-text {
  flex: 1 1 auto;
  min-width: 0;
}

.related-track {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  margin-top: 22px;
}

.related-card {
  position: relative;   /* anchor for the in-card loading overlay (showBoxLoading) */
  flex: 1 1 240px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
  cursor: pointer;
  transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

.related-card:hover,
.related-card:focus-visible {
  border-color: rgba(var(--accent-rgb), 0.6);
  transform: translateY(-3px);
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.35);
  outline: none;
}

.related-card-media {
  position: relative;
  aspect-ratio: 16 / 9;
  background-size: cover;
  background-position: center;
  background-color: #000;
}

.related-card-body {
  display: flex;
  flex-direction: column;
  padding: 20px;
  flex: 1 1 auto;
}

.related-card-title {
  margin: 0 0 5px;          /* tight to the tagline */
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
}

.related-card-tag {
  margin: 0 0 14px;         /* gap before the blurb */
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--accent-light);
}

.related-card-blurb {
  margin: 0 0 16px;         /* gap before the tool pills */
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.related-card-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 18px;         /* gap before View Project */
}

.related-pill {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 3px 8px;
}

.related-card-link {
  margin-top: auto;                 /* pin to the bottom, aligned across cards */
  display: flex;
  align-items: center;
  justify-content: space-between;   /* text left, arrow pushed to the far right */
  width: 100%;
  padding-top: 16px;
  border-top: 1px solid var(--border);   /* divider line above View Project */
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--accent-light);
}

.related-card:hover .related-card-link {
  color: var(--accent);
}

@media (max-width: 640px) {
  .related-card {
    flex-basis: 100%;
  }
}

/* iPad / tablet: lay the related projects out 2 per row (4 → 2x2). */
@media (min-width: 641px) and (max-width: 1024px) {
  .related-track { display: grid; grid-template-columns: 1fr 1fr; }
}

.project-detail-lead {
  margin: 0 0 18px;
  color: var(--text);
  font-size: 18px;
  line-height: 1.55;
}

/* Tools & technologies icon tiles */

.tool-list {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

.tool-item {
  position: relative;
  width: 66px;
  height: 66px;

  display: flex;
  align-items: center;
  justify-content: center;

  border: 1px solid var(--border);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.03);

  transition:
    transform 0.22s ease,
    border-color 0.22s ease,
    background 0.22s ease;
}

.tool-item:hover {
  transform: translateY(-3px);
  border-color: var(--accent-border);
  background: rgba(255, 255, 255, 0.06);
}

.tool-item img {
  position: absolute;
  width: 40px;
  height: 40px;
  object-fit: contain;
  transition: opacity 0.25s ease;
}

.tool-icon-color {
  opacity: 0;
}

.tool-item:hover .tool-icon-mono {
  opacity: 0;
}

.tool-item:hover .tool-icon-color {
  opacity: 1;
}

/* Video embed (responsive 16:9) */

.video-embed {
  position: relative;
  width: 100%;
  padding-top: 56.25%;

  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: #000000;
}

.video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Full-size holder the supporting-video iframe is injected into on play */
.sa-video-frame {
  position: absolute;
  inset: 0;
}

.video-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;

  padding: 0;
  border: 0;
  cursor: pointer;

  background-size: cover;
  background-position: center;

  display: flex;
  align-items: center;
  justify-content: center;
}

.video-poster::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.28);
  transition: background 0.25s ease;
}

.video-poster:hover::after {
  background: rgba(0, 0, 0, 0.12);
}

.video-play-btn {
  position: relative;
  z-index: 1;

  width: 76px;
  height: 76px;
  border-radius: 50%;

  display: flex;
  align-items: center;
  justify-content: center;

  background: rgba(var(--accent-rgb), 0.92);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);

  transition:
    transform 0.25s ease,
    background 0.25s ease;
}

.video-poster:hover .video-play-btn {
  transform: scale(1.08);
  background: var(--accent);
}

.video-play-btn::before {
  content: "";
  width: 0;
  height: 0;
  margin-left: 6px;
  border-style: solid;
  border-width: 13px 0 13px 22px;
  border-color: transparent transparent transparent #ffffff;
}

/* Gallery mosaic */

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 150px;
  gap: 14px;
}

.gallery-item {
  display: block;
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--panel-soft);
}

.gallery-item:first-child {
  grid-column: span 2;
  grid-row: span 2;
}

.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0.92;

  transition:
    transform 0.35s ease,
    opacity 0.35s ease;
}

.gallery-item:hover {
  border-color: var(--accent-border);
}

.gallery-item:hover img {
  transform: scale(1.05);
  opacity: 1;
}

/* ---- Photography collection: immersive full-bleed masonry ----------------
   Dark, edge-to-edge grid that lazy-loads as the panel scrolls; row spans are
   set by JS from each image's height (masonry) and very wide shots span the full
   width. Location/title show on hover. Ported from the reference photography.php,
   adapted to this site's detail panel. */
.photography-page {
  background: #000;
  min-height: 100%;
}
/* Full-bleed like the reference photography.php: beat the base .project-detail-inner
   (max-width:1500 + margin:auto + padding:48) with the same id / standalone
   specificity the About & Mosaic pages use, so the gallery fills the whole panel
   instead of a narrow centred column (which made the images render huge). */
.standalone-detail-page .project-detail-inner.photography-page,
#project-detail-panel .project-detail-inner.photography-page {
  max-width: none;
  width: 100%;
  margin: 0;
  padding: 0;
  background: #000;
}
.photography-page .photo-back {
  position: sticky;
  top: 14px;
  left: 14px;
  z-index: 6;
  margin: 14px 0 0 14px;
}
/* Packed grid masonry. A fixed 5-track grid with a tiny row unit (8px); JS sizes
   each tile's row-span from its aspect ratio (see layoutPhotoGrid). grid-auto-flow:
   dense keeps the columns filling continuously LEFT-TO-RIGHT so short runs never
   leave voids, and the full-width dividers (.photo-wide → grid-column:1/-1) drop in
   without forcing the balance gaps that column-span:all did in a multi-column. */
.photo-gallery {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-auto-rows: 8px;
  grid-auto-flow: row dense;
  gap: 5px;
  padding: 5px;
}
.photo-item,
.photo-wide {
  position: relative;
  overflow: hidden;                /* mask: clips rounded corners + hover zoom */
  border-radius: 8px;
  grid-row-end: span 30;           /* ~245px fallback until JS sets the real span */
}
/* Full-width divider: a wide panorama or wide banner spanning all columns.
   Negative side margins cancel the gallery's 5px padding so the banner bleeds
   flush to the panel edges (the tiles keep the 5px gutter); square corners so
   the flush edge stays clean. */
.photo-wide {
  grid-column: 1 / -1;
  /* 0 top/bottom resets the <figure> user-agent margin (was ~16px, making the
     vertical gap around banners bigger than the 5px between photos); -5px sides
     cancel the gallery padding so the banner bleeds flush to the edges. */
  margin: 0 -5px;
  border-radius: 0;
}
.photo-wide img { border-radius: 0; }
.photo-item,
.photo-wide {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s ease, transform 0.7s ease, filter 0.45s ease;
  will-change: opacity, transform;
}
.photo-item.loadedVisible,         /* fade/slide in on scroll */
.photo-wide.loadedVisible {
  opacity: 1;
  transform: translateY(0);
}
.photo-item img,
.photo-wide img {
  width: 100%;
  height: 100%;
  object-fit: cover;               /* fill the row-span box exactly, no letterbox */
  display: block;
  border-radius: 8px;
  transition: transform 0.45s ease;
}
.photo-item:hover img { transform: scale(1.03); }
.photo-wide:hover img { transform: scale(1.02); }
.photo-wide--banner:hover img { transform: none; }   /* graphics: no zoom */
.photo-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.82);
  color: #fff;
  box-sizing: border-box;
  transform: translateY(100%);
  opacity: 0;
  transition: transform 0.35s ease, opacity 0.35s ease;
  pointer-events: none;
}
.photo-item:hover .photo-caption,
.photo-wide:hover .photo-caption {
  transform: translateY(0);
  opacity: 1;
}
.photo-caption-title { font-size: 15px; font-weight: 700; }
.photo-caption-loc { font-size: 12px; color: #ddd; margin-top: 3px; }

/* Decorative filler tiles — fillPhotoGaps() in portfolio.js drops these into the
   leftover grid gaps (around banners / uneven column bottoms) with explicit grid
   placement, so holes read as intentional themed accents instead of blank space. */
.photo-filler {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px;
  text-align: center;
  border-radius: 8px;
  color: var(--accent);
  background:
    radial-gradient(120% 120% at 50% 0%, rgba(var(--accent-rgb), 0.12), transparent 62%),
    linear-gradient(160deg, rgba(255, 255, 255, 0.045), rgba(0, 0, 0, 0.28));
  border: 1px solid rgba(var(--accent-rgb), 0.20);
  overflow: hidden;
}
.photo-filler svg { width: clamp(24px, 42%, 52px); height: auto; opacity: 0.92; }
.photo-filler .pf-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1.4;
}
.photo-filler .pf-sub { font-size: 9.5px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--muted); opacity: 0.75; }

/* Large screens: 5 columns (the base rule above). iPads — portrait ~744px up
   through the 12.9" Pro's 1366px landscape — get 4 columns. Phones (≤700px) use
   2 columns. */
@media (max-width: 1366px) { .photo-gallery { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 700px)  { .photo-gallery { grid-template-columns: repeat(2, 1fr); } }

/* ---- Gallery viewer (slides in like a project) --------------------------- */

/* In-layout panel (sits between header and footer, next to the project panel)
   — slides in from the right like a project instead of overlaying the site. */
.gallery-viewer {
  position: absolute;
  top: 0;
  left: 100vw;

  width: 100vw;
  height: 100%;
  z-index: 20;

  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 74px 24px calc(24px + var(--footer-h));

  background:
    radial-gradient(circle at top right, var(--accent-soft), transparent 42%),
    var(--panel);

  overflow: hidden;

  transition: left 0.75s cubic-bezier(0.77, 0, 0.175, 1);
}

.gallery-viewer[hidden] {
  display: none;
}

/* Gallery open: the viewer slides in from the right while the project detail
   panel slides out to the left — one smooth page transition. */
.portfolio-layout.gallery-open .gallery-viewer {
  left: 0;
}

.portfolio-layout.gallery-open .project-detail-panel {
  left: -100vw;
}

.gv-back {
  position: absolute;
  top: 20px;
  left: 24px;
  z-index: 3;
}

.gv-counter {
  position: absolute;
  top: 26px;
  right: 26px;
  z-index: 3;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 1px;
  color: var(--muted);
}

.gv-counter #gv-index {
  color: var(--accent);
}

.gv-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: 18px;
}

/* Main stage holds the sliding image layers */
.gv-stage {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  border-radius: 12px;
}

.gv-slide {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  will-change: opacity;
}

.gv-slide img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 12px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
}

/* Prev / next arrows */
.gv-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 4;

  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  font-size: 26px;
  color: var(--text);
  background: rgba(5, 6, 8, 0.55);
  border: 1px solid var(--border);
  border-radius: 50%;
  cursor: pointer;

  transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}

.gv-nav:hover {
  color: var(--accent);
  border-color: var(--accent-border);
  background: rgba(var(--accent-rgb), 0.12);
}

.gv-prev {
  left: 12px;
}

.gv-next {
  right: 12px;
}

/* Vertical thumbnail rail on the right */
.gv-rail {
  flex: 0 0 120px;
  display: flex;
  flex-direction: column;
  gap: 12px;

  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-right: 2px;
}

.gv-rail::-webkit-scrollbar {
  width: 0;
  height: 0;
}

.gv-thumb {
  flex: 0 0 auto;
  width: 100%;
  height: 82px;
  padding: 0;
  overflow: hidden;

  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  opacity: 0.6;

  transition: opacity 0.2s ease, border-color 0.2s ease;
}

.gv-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.gv-thumb:hover {
  opacity: 0.9;
  border-color: var(--accent-border);
}

.gv-thumb.is-active {
  opacity: 1;
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

/* On phones and touch tablets (iPad etc.) the thumbnail rail is cramped — hide
   it and give the image the full width. Navigation stays via the on-screen
   arrows, plus left/right swipe (see the gv-stage touch handlers). */
@media (max-width: 600px), (pointer: coarse) {
  .gv-rail {
    display: none;
  }

  .gv-body {
    gap: 0;
  }

  /* Full-bleed width, but keep bottom room for the footer so it sits below the
     image instead of covering it. The image crops at the edges (object-fit:
     cover); back button + counter float on top (absolutely positioned). */
  .gallery-viewer {
    padding: 0 0 var(--footer-h);
  }

  .gv-stage {
    border-radius: 0;
  }

  .gv-slide img {
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    object-fit: cover;
    border-radius: 0;
    box-shadow: none;
  }
}

/* Mobile / touch: the footer is fixed and slides up over the bottom of the page.
   Give every scroll area enough bottom room (footer height + buffer + iOS safe
   area) so content can scroll clear of it instead of hiding behind it. This also
   restores the reservation that ".project-detail-inner { padding: 40px }" drops
   on tablet widths — the description, mosaic and contact pages all live here. */
@media (max-width: 950px), (pointer: coarse) {
  /* Detail panel is the scroll container for description / mosaic / contact;
     detail-layer is the scroller mid slide-transition. */
  .project-detail-panel,
  .project-detail-panel .detail-layer {
    padding-bottom: calc(var(--footer-h) + 48px + env(safe-area-inset-bottom, 0px));
  }

  /* Home grid (scrolls inside .portfolio-side). */
  #portfolio-grid {
    padding-bottom: calc(var(--footer-h) + 48px + env(safe-area-inset-bottom, 0px));
  }
}


/* Previous / next project bar */

.project-detail-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;

  padding: 22px 48px 40px;
  border-top: 1px solid var(--border);
}

.project-detail-nav-left {
  display: flex;
  align-items: center;
  gap: 24px;
  min-width: 0;
}

.project-detail-nav-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.project-detail-nav-right {
  display: flex;
  justify-content: flex-end;
  min-width: 0;
}

.project-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
  text-decoration: none;
  color: var(--text);
  transition: color 0.2s ease;
  position: relative;   /* anchor for the in-element loading overlay (showBoxLoading) */
}

.project-nav-link[hidden] {
  display: none;
}

/* While the destination project preloads, showBoxLoading() drops a spinner +
   "Loading content…" overlay INSIDE the clicked prev/next nav link or Related
   card (same as the grid thumbnails). Just block further clicks here; the
   overlay itself is the visible feedback. */
.project-nav-link.is-loading,
.related-card.is-loading,
.menu a.is-loading {
  pointer-events: none;
}

.project-nav-link:hover {
  color: var(--accent);
}

.project-nav-arrow {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;

  font-size: 22px;
  color: var(--accent);

  border: 1px solid var(--border);
  border-radius: 10px;

  transition:
    border-color 0.2s ease,
    background 0.2s ease;
}

.project-nav-link:hover .project-nav-arrow {
  border-color: var(--accent-border);
  background: rgba(var(--accent-rgb), 0.10);
}

.project-nav-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.project-nav-link--next .project-nav-meta {
  align-items: flex-end;
  text-align: right;
}

.project-nav-label {
  font-size: 12px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--muted);
}

.project-nav-title {
  font-size: 17px;
  font-weight: 600;
  color: var(--accent);
  max-width: 22ch;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Filters dropdown (opens upward, above the bar) */
.project-nav-filter {
  position: relative;
  flex: 0 0 auto;
}

.project-nav-filter-toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px;

  background: rgba(255, 255, 255, 0.035);
  color: var(--accent-light);
  border: 1px solid rgba(var(--accent-rgb), 0.28);
  border-radius: 10px;
  cursor: default;

  font: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}

.project-nav-filter-icon {
  display: inline-flex;
  width: 16px;
  height: 16px;
  color: var(--accent);
}

.project-nav-filter-icon svg {
  width: 100%;
  height: 100%;
}

/* Active sort shown in the toggle's first row: the sort-arrows icon + white
   value, mirroring the grid controls' sort section. */
.project-nav-filter-sep {
  flex: 0 0 auto;
  color: rgba(255, 255, 255, 0.30);
  font-weight: 400;
}

.project-nav-filter-sort {
  flex: 0 1 auto;
  color: #ffffff;
  font-weight: 600;
  white-space: nowrap;
  text-transform: none;
  letter-spacing: normal;
}

/* Sort-arrows glyph replacing the old "Sort by" text — same 16px box as the
   funnel filter icon so the two read as a matched pair. */
.project-nav-filter-sort-icon {
  flex: 0 0 auto;
  display: inline-flex;
  width: 16px;
  height: 16px;
  color: var(--accent);
}
.project-nav-filter-sort-icon svg { width: 100%; height: 100%; }

/* Active filter label ("All", "Cinema", …) — normal case like the mobile
   toggle; the button's uppercase/wide tracking is only for the SORT BY label. */
.project-nav-filter-text {
  text-transform: none;
  letter-spacing: normal;
  font-weight: 600;
}

.project-nav-filter-menu {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(100% + 12px);
  z-index: 30;

  min-width: 232px;
  padding: 8px;

  background: #12151b;
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);

  display: none;
  flex-direction: column;
  gap: 2px;
}

.project-nav-filter.open .project-nav-filter-menu {
  display: flex;
}

/* Section heading inside the in-panel menu ("Sort by" / "Category"), mirroring
   the mobile filter dropdown's .filter-section-label. */
.project-nav-section-label {
  padding: 8px 8px 2px;

  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-light);
}

/* First section label sits flush with the menu's top padding. */
.project-nav-filter-menu .project-nav-section-label:first-child {
  padding-top: 2px;
}

.project-nav-filter-option,
.project-nav-sort-option {
  display: flex;
  align-items: center;
  gap: 12px;

  width: 100%;
  padding: 11px 14px;

  background: none;
  border: none;
  border-radius: 9px;
  cursor: pointer;

  color: rgba(255, 255, 255, 0.78);
  font: inherit;
  font-size: 14px;
  text-align: left;

  transition:
    background 0.16s ease,
    color 0.16s ease;
}

.project-nav-filter-option:hover,
.project-nav-sort-option:hover {
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
}

.project-nav-filter-option .opt-label,
.project-nav-sort-option .opt-label {
  flex: 1 1 auto;
}

.project-nav-filter-option .opt-check,
.project-nav-sort-option .opt-check {
  flex: 0 0 auto;
  color: var(--accent);
  opacity: 0;
}

.project-nav-filter-option.is-active,
.project-nav-sort-option.is-active {
  color: var(--accent-light);
}

.project-nav-filter-option.is-active .opt-check,
.project-nav-sort-option.is-active .opt-check {
  opacity: 1;
}

/* Page counter, inline on the right of the filter button */
.project-nav-filter-count {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;

  padding-left: 12px;
  border-left: 1px solid rgba(var(--accent-rgb), 0.30);

  font-size: 13px;
  font-weight: 700;
  letter-spacing: 1px;
  color: var(--muted);
}

.project-nav-filter-count [data-nav-index] {
  color: var(--accent);
}

.project-nav-filter-count .count-sep {
  color: rgba(255, 255, 255, 0.4);
}

/* Standalone detail fallback */

.standalone-detail-page {
  width: 100%;
  min-height: calc(100vh - 176px);
  padding: 42px 28px;
  background:
    radial-gradient(circle at top left, var(--accent-soft), transparent 34%),
    var(--bg);
}

.standalone-detail-page .project-detail-inner {
  max-width: 1180px;
  margin: 0 auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
}

/* Footer */

footer {
  display: none;
}

/* Static-page footer (about, contact, standalone detail) */

.site-footer {
  display: flex;
  align-items: center;
  justify-content: center;

  width: 100%;
  min-height: var(--footer-h);

  background-color: #050608;
  color: var(--muted);
  border-top: 1px solid var(--border);

  font-size: 14px;
  padding: 0 24px;
  text-align: center;
}

.portfolio-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 25;

  width: 100%;
  min-height: var(--footer-h);
  background-color: #050608;
  color: var(--muted);
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  padding: 0 24px;
  text-align: center;

  transform: translateY(100%);

  transition: transform 0.45s cubic-bezier(0.77, 0, 0.175, 1);
}

body.show-footer .portfolio-footer {
  transform: translateY(0);
}

/* The gallery viewer is a full page view — keep the footer pinned at the bottom
   while it's open (it reserves room via its bottom padding). */
.portfolio-layout.gallery-open ~ .portfolio-footer {
  transform: translateY(0);
}

/* Lift the hero slider controls so the footer doesn't cover them */

body.show-footer .hero-slider-controls {
  bottom: calc(34px + var(--footer-h) + env(safe-area-inset-bottom, 0px));
}

body.show-footer .hero-slider-dots {
  bottom: calc(36px + var(--footer-h) + env(safe-area-inset-bottom, 0px));
}

/* iPad landscape (uses the desktop two-column layout, so the ≤950px mobile block
   doesn't reach it): keep the banner nav clear of the fixed footer here too.
   Scoped to touch (pointer: coarse) so real laptops in this width are unaffected. */
@media (min-width: 951px) and (max-width: 1024px) and (pointer: coarse) {
  .hero-slider-controls {
    bottom: calc(max(var(--footer-actual-h, 0px), var(--footer-h)) + 24px + env(safe-area-inset-bottom, 0px));
  }
  .hero-slider-dots {
    bottom: calc(max(var(--footer-actual-h, 0px), var(--footer-h)) + 26px + env(safe-area-inset-bottom, 0px));
  }
}

/* Medium screens */

@media (max-width: 1300px) {
  .portfolio-side {
    width: 54vw;
  }

  .hero-side {
    left: 54vw;
    width: 46vw;
  }

  .filter-bar {
    grid-template-columns: repeat(3, 1fr);
  }

  /* Categories wrap to 3 columns here; the sort control flows into the empty
     cell right beside "Personal" on the last row (no full-width span, so it
     isn't pushed down onto its own row). */
  .sort-wrap {
    padding-left: 0;
    border-left: none;
  }

  .main-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .hero h1 {
    font-size: clamp(40px, 4vw, 64px);
  }

  .project-detail-inner {
    padding: 40px;
  }
}

/* Tablet / mobile horizontal sliding layout */

@media (max-width: 950px) {
  :root {
    /* Full-screen banner: fill the scroll viewport (viewport − header) so the
       hero is the whole first "page" on phones/tablets and the grid scrolls up
       under it. 100% resolves against .portfolio-side, which the JS makes the
       hero's parent on mobile (see portfolio.js). */
    --mobile-hero-height: 100%;
  }

  body {
    overflow: hidden;
    /* dvh tracks the *visible* viewport as iOS Safari's toolbar collapses, so
       the banner's bottom (View Project button) isn't hidden behind it. vh is
       the fallback for browsers without dvh. */
    height: 100vh;
    height: 100dvh;
  }

  /* From tablet down, prev/next show only their arrows — the titles overlap
     the counter/filter otherwise. */
  .project-nav-meta {
    display: none;
  }

  .project-nav-link {
    gap: 0;
  }

  header {
    position: relative;
    min-height: auto;
  }

  .header-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 24px;
    padding: 20px 0;
  }

  .brand {
    gap: 14px;
  }

  .brand-logo {
    width: 45px;
    height: 45px;
  }

  .brand-title {
    font-size: 22px;
    letter-spacing: 4px;
  }

  .brand-subtitle {
    font-size: 13px;
  }

  .menu {
    width: 100%;
    display: grid;
    /* All FIVE items stay on ONE row (Home / Projects / About / Mosaic /
       Contact); the columns shrink to fit the width. */
    grid-template-columns: repeat(5, 1fr);
    gap: 8px;
  }

  .menu a {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 3px;
    text-align: center;
    white-space: nowrap;
    font-size: clamp(10px, 2.6vw, 14px);
    letter-spacing: 0.3px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Mobile has no underline indicator, so show the active page as a filled
     accent pill instead. */
  .menu a.active {
    background: rgba(var(--accent-rgb), 0.16);
    border-color: var(--accent-border);
    color: var(--accent-light);
  }

  .menu a.active::after,
  .menu a:hover::after {
    display: none;
  }

  /* Mobile marks the active page with the filled pill above, so the sliding
     bar has no role here (and the menu is a 5-column grid, not a row). */
  .menu-underline { display: none; }

  .portfolio-layout {
    position: relative;
    display: block;
    width: 100%;
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }

  /* Mobile hero is a top banner that scrolls away with the grid. The JS moves
     it to be the first child of .portfolio-side so it shares its scroll. */

  .hero,
  .hero-side {
    display: flex;
    position: relative;
    left: 0;
    top: 0;

    width: 100%;
    height: var(--mobile-hero-height);
    min-height: var(--mobile-hero-height);

    border-left: none;
    border-right: none;
    border-bottom: 1px solid var(--border);

    z-index: 5;
  }

  /* Hero now rides inside .portfolio-side, so it slides with it on project-open
     instead of transforming on its own. */
  .portfolio-layout.project-open .hero-side {
    transform: none;
    pointer-events: none;
  }

  .hero-content {
    height: 100%;
    display: flex;
    flex-direction: column;
    /* Extra bottom padding clears the fixed footer (+ iOS safe area) so the
       bottom-anchored "View Project" button isn't hidden behind it. */
    padding: 48px 32px calc(32px + var(--footer-h) + env(safe-area-inset-bottom, 0px));
  }

  .hero h1 {
    font-size: clamp(34px, 8vw, 58px);
  }

  /* Show a short description on the (now full-screen) mobile/tablet banner, like
     the desktop banner. The <p> already holds the ~180-char excerpt; clamp it to
     a few lines so it stays tidy on the smaller banner. */
  .hero p {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
    max-width: 34rem;
    margin: 28px 0 0;   /* match the desktop banner's title→description gap */
    font-size: clamp(15px, 3.6vw, 18px);
    line-height: 1.5;
    color: var(--muted);
  }

  /* Sit the View Project button just below the description text, matching the
     desktop banner's gap (34px), instead of anchoring it to the bottom. */
  .hero-actions {
    margin-top: 34px;
  }

  /* Show the banner nav (prev/next arrows + active-slide dots) on the full-screen
     mobile/tablet banner. They sit near the bottom and are lifted clear of the
     footer by the body.show-footer rule (below) when it slides up. */
  .hero-slider-controls {
    display: flex;
    right: 20px;
    /* max() floors it at the footer height even if --footer-actual-h is missing
       or misreported, so the nav can never land inside the footer's band. */
    bottom: calc(max(var(--footer-actual-h, 0px), var(--footer-h)) + 24px + env(safe-area-inset-bottom, 0px));
  }

  .hero-slider-dots {
    display: flex;
    left: 32px;   /* line up with the hero-content padding */
    bottom: calc(max(var(--footer-actual-h, 0px), var(--footer-h)) + 26px + env(safe-area-inset-bottom, 0px));
  }

  .portfolio-side {
    position: absolute;
    left: 0;
    top: 0;

    width: 100%;
    height: 100%;

    overflow-y: auto;
    overflow-x: hidden;

    transform: translateX(0);
    opacity: 1;
    visibility: visible;
    pointer-events: auto;

    transition:
      transform 0.75s cubic-bezier(0.77, 0, 0.175, 1);
  }

  .portfolio-layout.project-open .portfolio-side {
    transform: translateX(-105%);
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    opacity: 1;
    visibility: visible;
    pointer-events: none;
  }

  .project-detail-panel {
    position: absolute;
    left: 100%;
    top: 0;

    width: 100%;
    height: 100%;

    border-left: none;
    border-top: none;

    overflow-y: auto;
    overflow-x: hidden;

    opacity: 1;
    visibility: visible;
    transform: translateX(0);

    z-index: 10;

    transition:
      left 0.75s cubic-bezier(0.77, 0, 0.175, 1);
  }

  .portfolio-layout.project-open .project-detail-panel {
    left: 0;
    width: 100%;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    opacity: 1;
    visibility: visible;
    transform: translateX(0);
  }

  /* Gallery open on mobile: slide the detail panel out to the left */
  .portfolio-layout.gallery-open .project-detail-panel {
    left: -100%;
  }

  .filter-wrap {
    position: sticky;
    top: 0;
    padding: 18px 24px;

    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 12px;
  }

  /* Collapse the filter grid into a dropdown menu to free up scroll space */
  .filter-toggle {
    order: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex: 1 1 auto;
    width: auto;
    min-height: 46px;
    padding: 12px 16px;

    background: rgba(255, 255, 255, 0.035);
    color: rgba(255, 255, 255, 0.82);

    border: 1px solid rgba(255, 255, 255, 0.055);
    border-radius: 8px;

    cursor: pointer;

    font-family: "Sora", Arial, sans-serif;
    font-size: 14px;
    font-weight: 600;
    text-align: left;

    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.035),
      0 8px 18px rgba(0, 0, 0, 0.28);

    transition:
      background 0.22s ease,
      border-color 0.22s ease,
      box-shadow 0.22s ease;
  }

  .filter-toggle[aria-expanded="true"] {
    border-color: rgba(var(--accent-rgb), 0.22);
  }

  .filter-toggle-icon {
    display: inline-flex;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    color: var(--accent);
  }

  .filter-toggle-icon svg {
    width: 100%;
    height: 100%;
  }

  .filter-toggle-caret {
    margin-left: auto;
    color: var(--accent-light);
    font-size: 16px;
    line-height: 1;
    transition: transform 0.22s ease;
  }

  .filter-toggle[aria-expanded="true"] .filter-toggle-caret {
    transform: rotate(180deg);
  }

  /* Active filter | SORT BY <active sort>, shown inline next to the funnel icon.
     "SORT BY" is gold (theme accent); the selection value is white. */
  .filter-toggle-sep {
    flex: 0 0 auto;
    color: rgba(255, 255, 255, 0.45);
    font-weight: 400;
  }

  .filter-toggle-sort-label {
    flex: 0 0 auto;
    color: var(--accent-light);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  .filter-toggle-sort {
    flex: 0 1 auto;
    color: #ffffff;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .filter-bar {
    order: 3;
    flex-basis: 100%;
    width: 100%;
    display: none;
    flex-direction: column;
    gap: 8px;

    margin-top: 0;
    padding: 8px;

    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 10px;

    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
  }

  .filter-bar.open {
    display: flex;
  }

  /* Sort moves INTO the filter dropdown on mobile: hide the standalone control
     and reveal the in-dropdown sort group + section labels at the top. */
  .sort-wrap {
    display: none;
  }

  .filter-section-label {
    display: block;
    padding: 8px 6px 2px;

    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent-light);
  }

  .filter-scroll > .sort-option {
    display: block;
  }

  /* In the mobile dropdown the filter list stacks its items in a single column. */
  .filter-scroll {
    flex-direction: column;
    width: 100%;
    gap: 8px;
  }

  .filter-bar button {
    width: 100%;
    min-height: 46px;
    padding: 12px 14px;
    font-size: 13px;
    text-align: left;
  }

  main {
    padding: 24px;
  }

  .main-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 16px;
  }

  .content-box,
  .content-box img {
    min-height: 230px;
  }

  .project-detail-inner {
    padding: 36px 28px;
  }

  .project-detail-body-cols,
  .project-detail-concept-cols,
  .project-detail-cols {
    flex-direction: column;
    gap: 40px;
  }

  /* Keep the alternate concept layout stacked (not row-reverse) on mobile */
  .project-detail-concept-alt .project-detail-concept-cols {
    flex-direction: column;
  }

  .project-detail-col-left,
  .project-detail-col-right,
  .project-detail-concept-media,
  .project-detail-concept-copy,
  .project-detail-col {
    flex-basis: auto;
    width: 100%;
  }

  /* Stack the workflow vertically and turn the arrows downward */
  .workflow-flow {
    grid-template-columns: 1fr;
  }

  /* Override the inline serpentine placement: stack in reading order */
  .workflow-step {
    grid-column: 1 !important;
    grid-row: auto !important;
  }

  /* Every horizontal connector becomes a downward arrow between stacked cards
     (.wf-next-down already points down and needs no change) */
  .workflow-step.wf-next-right::after,
  .workflow-step.wf-next-left::after {
    content: "↓";
    top: 100%;
    left: 0;
    right: 0;
    width: auto;
    height: var(--wf-row-gap);
    transform: none;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  /* Drop the cinematic letterbox to a full-width 16:9 on small screens */
  .project-hero-video,
  .project-hero-banner {
    aspect-ratio: 16 / 9;
  }

  /* Stacked columns: no vertical dividers or side padding */
  .project-detail-col {
    padding: 0;
  }

  .project-detail-col + .project-detail-col {
    border-left: none;
  }
}

/* --- iPad / tablet (768–950px): the ≤950px block stacks the header into a
   column (menu under the brand) for phones, but iPad (portrait 768px) + landscape
   phones have room for the desktop-style single row. Keep brand + menu on ONE
   row with the five nav buttons inline. Applied from 768px up so the brand + five
   compact buttons never overflow on the narrower widths. --- */
@media (min-width: 768px) and (max-width: 950px) {
  .header-inner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 0;
  }
  .brand-title { font-size: 19px; letter-spacing: 3px; }
  .menu {
    width: auto;
    display: flex;
    gap: 6px;
  }
  .menu a {
    padding: 8px 10px;
    font-size: 12.5px;
    white-space: nowrap;
    text-align: center;
  }
}

/* --- iPad / tablet (touch, 601–1024px): the unified filter box hugs the left
   (content width) with the view toggles hidden on touch, wasting the right half
   of the row. Make it span the FULL width, with the two controls sharing it as
   equal halves (divider centred) so the bar stays balanced. --- */
@media (min-width: 601px) and (max-width: 1024px) and (pointer: coarse) {
  .control-bar .ctrl-unified {
    flex: 1 1 100%;
    width: 100%;
  }
  .ctrl-unified .ctrl-group { flex: 1 1 0; min-width: 0; }
  .ctrl-unified .ctrl-box { width: auto; flex: 1 1 auto; min-width: 0; }
}

/* Small mobile */

@media (max-width: 600px) {
  :root {
    --mobile-hero-height: 100%;  /* full-screen banner (see 950px query) */
    --footer-h: 78px;
  }

  .header-inner {
    max-width: 92%;
    gap: 18px;
    padding: 18px 0;
  }

  .brand {
    width: 100%;
    gap: 12px;
  }

  .brand-logo {
    width: 40px;
    height: 40px;
  }

  .brand-title {
    font-size: 19px;
    letter-spacing: 3px;
  }

  .brand-subtitle {
    margin-top: 5px;
    font-size: 12px;
  }

  .menu {
    /* Keep all five items on one line here too. */
    grid-template-columns: repeat(5, 1fr);
    gap: 5px;
  }

  .menu a {
    font-size: clamp(10px, 2.6vw, 13px);
    padding: 10px 2px;
    border-radius: 7px;
    letter-spacing: 0.2px;
  }

  .hero-content {
    padding: 42px 24px calc(24px + var(--footer-h) + env(safe-area-inset-bottom, 0px));
  }

  .hero h1 {
    font-size: 38px;
    letter-spacing: -1.5px;
  }

  .hero p {
    font-size: 16px;
  }

  /* Banner nav is tablet-only (shown in the 601–950px block); hide it again on
     phones — too cramped, and the grid is right below anyway. */
  .hero-slider-controls,
  .hero-slider-dots {
    display: none;
  }

  .hero-actions {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  .hero-button {
    width: 100%;
    text-align: center;
    padding: 15px 20px;
  }

  .hero-slider-controls {
    right: 20px;
    bottom: 24px;
  }

  .hero-slider-controls button {
    width: 40px;
    height: 40px;
  }

  .hero-slider-dots {
    left: 24px;
    bottom: 30px;
  }

  .filter-wrap {
    padding: 16px;
  }

  .filter-bar {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }

  .filter-bar button {
    min-height: 44px;
    padding: 11px 12px;
    font-size: 13px;
    border-radius: 8px;
  }

  main {
    padding: 20px;
  }

  .main-grid {
    grid-template-columns: 1fr;
    gap: 16px;
  }

  .content-box,
  .content-box img {
    min-height: 240px;
  }

  .card-content {
    left: 18px;
    right: 18px;
    bottom: 18px;
  }

  .card-title {
    font-size: 20px;
  }

  .card-subtitle {
    font-size: 13px;
  }

  .project-detail-inner {
    padding: 30px 22px;
  }

  .project-detail-inner h1 {
    font-size: 38px;
  }

  .project-detail-subtitle {
    font-size: 17px;
  }

  .project-detail-body {
    font-size: 16px;
  }

  .gallery-grid {
    grid-auto-rows: 120px;
  }

  .gallery-item:first-child {
    grid-column: span 2;
    grid-row: span 1;
  }

  /* Prev · filters · next all fit on one row on mobile (arrows only). */
  .project-detail-nav {
    gap: 10px;
    padding: 20px 12px 30px;
  }

  .project-nav-arrow {
    width: 44px;
    height: 44px;
    font-size: 20px;
  }

  .portfolio-footer {
    min-height: 78px;
    padding: 0 20px;
    text-align: center;
    font-size: 13px;
  }
}

/* Extra small phones */

@media (max-width: 420px) {
  :root {
    --mobile-hero-height: 100%;  /* full-screen banner (see 950px query) */
  }

  .brand-title {
    font-size: 17px;
    letter-spacing: 2px;
  }

  .brand-subtitle {
    font-size: 11px;
  }

  .menu {
    gap: 6px;
  }

  .menu a {
    font-size: clamp(9px, 2.4vw, 12px);
    padding: 9px 2px;
    letter-spacing: 0.2px;
  }

  .hero-content {
    padding: 36px 20px calc(20px + var(--footer-h) + env(safe-area-inset-bottom, 0px));
  }

  .hero h1 {
    font-size: 32px;
  }

  .hero p {
    font-size: 15px;
  }

  .filter-wrap {
    padding: 14px;
  }

  .filter-bar {
    grid-template-columns: repeat(2, 1fr);
    gap: 9px;
  }

  .filter-bar button {
    min-height: 42px;
    font-size: 12px;
    padding: 10px 8px;
  }

  main {
    padding: 16px;
  }

  .content-box,
  .content-box img {
    min-height: 220px;
  }
}
/* ---- Interactive Unity / WebGPU build in the hero ------------------------ */
/* The build shares the video's 16:9 frame. A second CTA sits beside the play
   button; launching swaps the frame to the build iframe (see playUnityHero). */

/* "Launch Interactive Build" button, centered just below the play circle. */
/* Launch CTA + its WebGPU bubble, centered as a group below the play button. */
.hero-build-row {
  position: absolute;
  left: 50%;
  bottom: 15%;
  transform: translateX(-50%);
  z-index: 3; /* above the poster (z-index 2) so it's independently clickable */
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
  max-width: 92%;
}

.hero-build-launch {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 11px 20px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #10130f;
  background: var(--accent);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
  transition: background 0.18s ease, transform 0.18s ease;
}

.hero-build-launch:hover {
  background: var(--accent-light);
  transform: translateY(-1px);
}

.hero-build-launch-ico {
  width: 15px;
  height: 15px;
}

/* The "requires WebGPU" speech bubble that sits beside the launch button, with
   a little tail on its left pointing back toward the button. */
.hero-build-note {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: #f2fff4;
  background: rgba(60, 143, 90, 0.95); /* algae green */
  border: 1px solid rgba(150, 214, 170, 0.55);
  border-radius: 12px;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

/* Tail: a small triangle on the left edge. ::after is the border, ::before the
   fill sitting 1px in front of it. */
.hero-build-note::after {
  content: "";
  position: absolute;
  left: -8px;
  top: 50%;
  transform: translateY(-50%);
  border-style: solid;
  border-width: 7px 8px 7px 0;
  border-color: transparent rgba(150, 214, 170, 0.55) transparent transparent;
}

.hero-build-note::before {
  content: "";
  position: absolute;
  left: -6px;
  top: 50%;
  transform: translateY(-50%);
  border-style: solid;
  border-width: 6px 7px 6px 0;
  border-color: transparent rgba(60, 143, 90, 0.95) transparent transparent;
  z-index: 1;
}

.hero-build-note-ico {
  width: 15px;
  height: 15px;
  color: #d6f5df;
  flex: 0 0 auto;
}

/* Hide the whole CTA row once the video or the build is playing. */
.project-hero-video.is-playing .hero-build-row,
.project-hero-video.is-unity-playing .hero-build-row {
  display: none;
}

/* iPads / phones: the interactive build is desktop-only (WebGPU), so hide the
   launch row. JS also removes it (see initProjectVideoPlayers) to catch iPadOS
   with a trackpad, which reports a fine pointer and slips past this query. */
@media (pointer: coarse) {
  .hero-build-row {
    display: none;
  }
}

/* Notice shown in the frame when the build can't run (mobile / no WebGPU). */
.unity-hero-notice {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
  padding: 24px;
  background: #0a0c10;
}

.unity-hero-notice h3 {
  margin: 0;
  font-size: 18px;
  color: var(--accent);
}

.unity-hero-notice p {
  margin: 0;
  max-width: 460px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
}

/* While the build runs, keep the banner's exact size/shape (no size-up). The
   build fills the frame WITHOUT the 2.4:1 crop that clipped its menu — use the
   maximize/fullscreen button for a larger interactive view. */
.project-hero-video.is-unity-playing .project-hero-video-frame {
  inset: 0;
  top: 0;
  left: 0;
  transform: none;
  width: 100%;
  height: 100%;
  max-width: none;
  aspect-ratio: auto;
}

.project-hero-video.is-unity-playing .unity-hero-iframe {
  position: absolute;
  inset: 0;
  top: 0;
  left: 0;
  transform: none;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  border: 0;
  display: block;
  background: #000;
  /* Fade the build in on launch — no scale/resize (the container size is fixed). */
  animation: unityBuildIn 0.35s ease both;
}

@keyframes unityBuildIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Don't show the video's like/share/close stack over the running build. */
.project-hero-video.is-unity-playing .project-hero-actions {
  display: none;
}

/* Corner controls for the build — hidden until it's running. */
.unity-hero-ctl {
  position: absolute;
  z-index: 5;
  display: none;
  width: 40px;
  height: 40px;
  align-items: center;
  justify-content: center;
  color: var(--text);
  background: rgba(3, 4, 6, 0.55);
  border: 1px solid var(--border);
  border-radius: 50%;
  cursor: pointer;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}

.unity-hero-ctl:hover {
  border-color: var(--accent-border);
  background: rgba(3, 4, 6, 0.75);
}

.unity-hero-ctl .phc-ico {
  width: 18px;
  height: 18px;
}

.project-hero-video.is-unity-playing .unity-hero-ctl {
  display: inline-flex;
}

.unity-hero-close {
  top: 14px;
  right: 14px;
  font-size: 16px;
  line-height: 1;
}

.unity-hero-max {
  right: 14px;
  bottom: 14px;
}

/* Narrow desktop windows: the hero frame gets short, so the centered launch row
   would ride up over the play button. Shrink it, tuck it into the bottom-left
   corner (clear of the centered play button), and stack the bubble UNDER the
   button — with its tail flipped to point up at the button. (On phones/tablets
   the row is removed entirely, so this only affects small desktop windows.) */
@media (max-width: 900px) {
  .hero-build-row {
    left: 18px;
    bottom: 16px;
    transform: none;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: 8px;
    max-width: calc(100% - 36px);
  }

  .hero-build-launch {
    padding: 9px 15px;
    font-size: 13px;
  }

  .hero-build-note {
    padding: 6px 11px;
    font-size: 11px;
  }

  /* Bubble now sits below the button — point the tail up (top edge) instead of
     left. ::after is the border, ::before the fill 1px in front. */
  .hero-build-note::after {
    left: 18px;
    top: -8px;
    transform: none;
    border-width: 0 7px 8px 7px;
    border-color: transparent transparent rgba(150, 214, 170, 0.55) transparent;
  }

  .hero-build-note::before {
    left: 19px;
    top: -6px;
    transform: none;
    border-width: 0 6px 7px 6px;
    border-color: transparent transparent rgba(60, 143, 90, 0.95) transparent;
  }
}

/* The downloadable build link reuses the .project-meta-row--website / .project-meta-link
   styling (it sits in the highlights list where the live link used to be). */


/* =====================================================================
   Home / Projects view modes + Projects browsing grid
   (visual direction: Downloads/projectPageConcept.png)
   Home = full-width hero banner; Projects = full-width browsing grid.
   ===================================================================== */

/* --- View-mode transition (desktop): Home <-> Projects SLIDE horizontally ---
   Both views are full-width and stay in the DOM; the inactive one parks
   off-screen (hero to the left, grid to the right) and slides in/out via
   transform, matching the project panel's slide. left/width stay constant so
   only transform animates. Opening a project (project-open) layers on top via
   the higher-specificity rules at the end. */
@media (min-width: 951px) {
  .portfolio-layout.home-mode .hero-side,
  .portfolio-layout.projects-mode .hero-side,
  .portfolio-layout.home-mode .portfolio-side,
  .portfolio-layout.projects-mode .portfolio-side {
    left: 0;
    width: 100vw;
  }
  .portfolio-layout.home-mode .hero-side,
  .portfolio-layout.projects-mode .hero-side { border-left: none; }

  /* Resting positions: hero is the left slide, grid the right slide. */
  .portfolio-layout.home-mode .hero-side { transform: translateX(0); }
  .portfolio-layout.home-mode .portfolio-side { transform: translateX(100%); pointer-events: none; }
  .portfolio-layout.projects-mode .portfolio-side { transform: translateX(0); }
  .portfolio-layout.projects-mode .hero-side { transform: translateX(-100%); pointer-events: none; }

  /* With a project open, the ACTIVE view slides fully out to the left (matching
     the panel entering from the right); the inactive view stays parked. Higher
     specificity (mode + project-open) so these win over the resting positions. */
  .portfolio-layout.home-mode.project-open .hero-side { transform: translateX(-105%); }
  .portfolio-layout.home-mode.project-open .portfolio-side { transform: translateX(100%); }
  .portfolio-layout.projects-mode.project-open .portfolio-side { transform: translateX(-105%); }
  .portfolio-layout.projects-mode.project-open .hero-side { transform: translateX(-100%); }
}

/* While switching Home <-> Projects with a project open, snap the side views to
   their new positions instantly (hidden behind the full-screen detail panel) so
   they don't sweep across the viewport; only the panel then slides out. */
.portfolio-layout.mode-instant .portfolio-side,
.portfolio-layout.mode-instant .hero-side {
  transition: none !important;
}

/* Keep each sliding view on a stable, pre-rasterized GPU layer so sliding it
   just moves the existing texture instead of re-painting mid-animation — that
   re-paint is what flashed a faint white seam across the thumbnails ("as if the
   CSS dropped") during the transition back to Home. */
.portfolio-side,
.hero-side {
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  will-change: transform;
}

/* --- View-mode visibility (mobile / stacked <=950px). JS relocates the hero
   into .portfolio-side here, so toggle the browsing cluster vs the hero in
   place rather than hiding the whole side. --- */
@media (max-width: 950px) {
  .portfolio-layout.home-mode #portfolio-grid,
  .portfolio-layout.home-mode .filter-wrap,
  .portfolio-layout.home-mode .about-collection,
  .portfolio-layout.home-mode .project-cloud { display: none; }

  .portfolio-layout.projects-mode .hero-side { display: none; }
}

/* --- Control bar: one unified Filter+Sort box on the left (Sort split from
   Filter by a divider), view toggles pushed to the right. --- */
.portfolio-side .control-bar { align-items: center; }
.control-bar .ctrl-sort {
  margin-left: 18px;
  padding-left: 18px;
  border-left: 1px solid var(--border);
}
/* Push the view toggles to the far right (no-op on touch, where they're hidden). */
.portfolio-side .control-bar .ctrl-view:first-of-type { margin-left: auto; }

/* --- Projects grid: plain row-major CSS grid, image on top, text below ---
   A regular grid: cards are placed strictly left-to-right, top-to-bottom (one card
   per cell), so the order is preserved at any card count. The category info card is
   the first child, so it reliably holds row 1 / column 1 and the newest project
   starts at row 1 / column 2. Cards keep their own height (align-items:start). */
.portfolio-side .main-grid {
  display: block;
  position: relative;                   /* anchor for the absolutely-positioned masonry cards */
  --cols: 4;                            /* JS reads this; kept in sync with the breakpoints below */
}
/* Ease the section height during category transitions (class added by JS after the
   first layout, so the initial paint doesn't animate the height in from 0). */
.portfolio-side .main-grid.pg-anim { transition: height 0.45s cubic-bezier(0.2, 0.8, 0.2, 1); }

/* The cards are position:absolute and sized/placed by JS (layoutProjectsGrid). On a
   refresh the browser paints before that JS runs, which flashed an unpositioned,
   full-size thumbnail. Hide the cards until JS adds .pg-ready after the first layout
   (this rule applies at first paint since it's in the stylesheet). */
.portfolio-side .main-grid:not(.pg-ready) .content-box,
.portfolio-side .main-grid:not(.pg-ready) .category-card { visibility: hidden; }

.portfolio-side .content-box {
  position: absolute;                   /* JS masonry sets top / left / width */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Gentle, controlled hover — same easing both ways, so leaving is as smooth as
     entering (no snap/bounce). top/left are intentionally NOT transitioned here, so
     masonry re-layout/resize stays instant; the category-change slide sets a
     temporary top/left transition inline. */
  transition:
    transform 0.45s cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 0.45s cubic-bezier(0.4, 0, 0.2, 1),
    border-color 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}

.portfolio-side .content-box:hover {
  transform: translateY(-3px);          /* gentler lift (was -5px) */
  border-color: var(--accent-border);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.28);
}

.portfolio-side .content-box.batch-hidden { display: none; }   /* infinite-scroll cap */
.portfolio-side .content-box::after { display: none; }         /* drop the full-card scrim */

.portfolio-side .card-thumb {
  position: relative;
  width: 100%;
  height: 0;
  /* 16:9 (56.25% of width) minus a fixed 40px → shorter thumbnails so more cards
     pack in. Cropping (object-fit:cover), never zoom/stretch, so no pixelation. */
  padding-bottom: calc(56.25% - 40px);
  overflow: hidden;
  background: #05070a;
}

.portfolio-side .card-thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: cover;
  opacity: 1;
  transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);   /* matches the card's hover timing */
}
.portfolio-side .content-box:hover .card-thumb img { transform: scale(1.03); opacity: 1; }  /* gentler zoom */

.portfolio-side .card-content {
  position: static;
  inset: auto;
  padding: 15px 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.portfolio-side .card-category { margin-bottom: 0; font-size: 11px; }
.portfolio-side .card-title {
  margin-bottom: 0;
  font-size: 19px;
  line-height: 1.2;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;      /* max 2 lines (natural height; masonry handles the variation) */
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.portfolio-side .card-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.portfolio-side .card-date {
  margin-top: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
}
.portfolio-side .card-date svg { width: 14px; height: 14px; opacity: 0.75; }

.portfolio-side .card-tag {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: rgba(var(--accent-rgb), 0.08);
  color: var(--muted);
  font-family: "Sora", Arial, sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.portfolio-side .card-desc {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: 15px;                          /* matches the mosaic body text (was 13px, too small) */
  line-height: 1.6;
  display: -webkit-box;
  -webkit-line-clamp: var(--cc-lines, 3);   /* per-card 2-7 lines → varied heights (masonry stacks them) */
  line-clamp: var(--cc-lines, 3);
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: calc(var(--cc-lines, 3) * 1.6em);
}

/* Clear the fixed slide-up footer so the lowest thumbnail row isn't covered
   (the old 14px was left over from when the About bar sat below the grid). This
   selector out-specifies the plain #portfolio-grid rules, so include the iOS
   safe-area here too (it wins on touch as well). */
.portfolio-side #portfolio-grid { padding-bottom: calc(var(--footer-h) + 52px + env(safe-area-inset-bottom, 0px)); }

/* --- About This Collection bar (projects grid view only) --- */
.about-collection { display: none; }
.portfolio-side .about-collection {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin: 4px 28px calc(28px + var(--footer-h));
  padding: 22px 26px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background:
    radial-gradient(circle at left, var(--accent-soft), transparent 62%),
    var(--panel);
}
.portfolio-layout.cloud-view .about-collection { display: none !important; }

.about-collection-brand { display: flex; align-items: center; gap: 18px; max-width: 640px; }
.about-collection-logo {
  width: 46px; height: 46px; flex: 0 0 auto;
  border-radius: 50%;
  border: 1px solid var(--accent-border);
  padding: 8px;
  background: rgba(var(--accent-rgb), 0.06);
  object-fit: contain;
}
.about-collection-title {
  color: var(--accent);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 12px;
  margin-bottom: 6px;
}
.about-collection-copy p { color: var(--muted); font-size: 13px; line-height: 1.55; margin: 0; }
.about-collection-stats { display: flex; gap: 34px; }
.ac-stat { display: flex; flex-direction: column; align-items: flex-end; }
.ac-num { color: var(--accent); font-size: 26px; font-weight: 800; line-height: 1; }
.ac-label { color: var(--muted); font-size: 12px; letter-spacing: 0.04em; margin-top: 4px; }

/* --- Responsive columns for the projects grid --- */
@media (max-width: 1300px) { .portfolio-side .main-grid { --cols: 3; } }
@media (max-width: 900px)  { .portfolio-side .main-grid { --cols: 2; } }
@media (max-width: 560px)  {
  .portfolio-side .main-grid { --cols: 1; }
  /* Hide the category info card on phones (kept on iPad/tablet+). layoutProjectsGrid
     also treats it as not-shown here, so column 0 isn't reserved. */
  .portfolio-side .category-card,
  .portfolio-side .category-card.is-shown { display: none !important; }
  .portfolio-side .about-collection { flex-direction: column; align-items: flex-start; gap: 18px; }
  .about-collection-stats { gap: 24px; }
  .ac-stat { align-items: flex-start; }
}

/* --- Home hero: muted looping video preview (featured slides only) ---
   The video is the primary banner; the still image (.hero-bg) sits behind it as
   a placeholder that only shows while the video loads (or when there is no video
   / autoplay is blocked). The video fades in once it's ready and covers it. */
.hero-video-preview {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.7s ease;
}
.hero-video-preview.is-playing { opacity: 1; }

.hero-video-preview iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100vw;
  height: 56.25vw;      /* 16:9 from width */
  min-width: 177.78vh;  /* 16:9 from height — together these cover the banner */
  min-height: 100vh;
  transform: translate(-50%, -50%);
  border: 0;
  pointer-events: none;
}

/* Keep the left-aligned copy readable over the moving video. */
.hero-video-preview::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    90deg,
    rgba(5, 6, 8, 0.72) 0%,
    rgba(5, 6, 8, 0.38) 55%,
    rgba(5, 6, 8, 0.08) 100%
  );
}

/* ==== Category info card ==================================================
   The first tile in the Projects grid when a single category is filtered
   (hidden for "All Categories"). Typography + icons only, no project imagery.
   Built + row-span sized by portfolio.js (ccRenderCard / ccSizeCard). It's a
   normal grid item, so it matches the thumbnail column width automatically. */
.category-card {
  display: none;            /* hidden until a category is selected */
}
.category-card.is-shown {   /* first masonry cell: top of column 1 (JS sets width/top/left) */
  display: block;
  position: absolute;
  box-sizing: border-box;
  top: 0;
  left: 0;
}

.category-card .cc-inner {
  display: flex;
  flex-direction: column;
  gap: 13px;
  padding: 26px 24px 22px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background:
    radial-gradient(130% 90% at 0% 0%, rgba(var(--accent-rgb), 0.10), transparent 55%),
    var(--panel);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.28);
}

.category-card .cc-head { display: flex; align-items: center; gap: 14px; }
.category-card .cc-icon { color: var(--text); flex: 0 0 auto; display: flex; }
.category-card .cc-icon svg { width: 40px; height: 40px; display: block; }
.category-card .cc-title {
  margin: 0;
  font-size: 27px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text);
  line-height: 1;
}
.category-card .cc-tagline {
  margin: 0;
  color: var(--accent);
  font-weight: 700;
  font-size: 14px;
}
.category-card .cc-desc {
  margin: 0;
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.6;
}

.category-card .cc-divider {
  height: 1px;
  background: var(--border);
  margin: 5px 0;
}

.category-card .cc-section-label {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.category-card .cc-count {
  margin-left: auto;
  color: var(--text);
  background: rgba(var(--accent-rgb), 0.14);
  border: 1px solid var(--accent-border);
  border-radius: 999px;
  min-width: 24px;
  height: 22px;
  padding: 0 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0;
}

.category-card .cc-techs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(58px, 1fr));
  gap: 15px 10px;
}
.category-card .cc-tech { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.category-card .cc-tech-icon { color: var(--muted); display: flex; }
.category-card .cc-tech-icon svg { width: 26px; height: 26px; display: block; }
.category-card .cc-tech-name {
  color: var(--text);
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1.2;
}
.category-card .cc-tech-sub {
  color: var(--muted);
  font-size: 10.5px;
  line-height: 1.3;
}

.category-card .cc-projects { display: flex; flex-direction: column; }
.category-card .cc-project {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 6px;
  border-radius: 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
  transition: background 0.18s ease;
}
.category-card .cc-project:first-child { border-top: none; }
.category-card .cc-project:hover { background: rgba(var(--accent-rgb), 0.10); }
/* "All Projects" overview rows are informational only — no click affordance. */
.category-card .cc-project.is-static { cursor: default; }
.category-card .cc-project.is-static:hover { background: none; }
.category-card .cc-project-icon { color: var(--muted); flex: 0 0 auto; display: flex; }
.category-card .cc-project-icon svg { width: 20px; height: 20px; display: block; }
.category-card .cc-project-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.category-card .cc-project-tag {
  color: var(--muted);
  font-size: 11px;
  flex: 0 0 auto;
  white-space: nowrap;
}
.category-card .cc-project-badge {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 11px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 999px;
  padding: 3px 9px;
}
.category-card .cc-empty {
  color: var(--muted);
  font-size: 12.5px;
  padding: 6px 2px;
}

