: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, 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;
}

.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);
}

/* 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 {
  transition: none !important;
}

.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: center;   /* center the filter/sort box in the bar */
}
.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 {
  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 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 34px;

  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: 1400px;
}

/* ===== 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: full-bleed hero portrait, philosophy +
   stat row, journey timeline, and a quote overlaid on a landscape. Rendered
   standalone (about.php) AND inside the index panel (id=about). Full-bleed:
   override the padded .project-detail-inner so the sections run edge to edge.
   =========================================================================== */
.standalone-detail-page .project-detail-inner.about-page,
#project-detail-panel .project-detail-inner.about-page {
  max-width: none;
  width: 100%;
  margin: 0;
  padding: 0;
}

.about2 { color: var(--text); }
.about2-eyebrow {
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  margin-bottom: 18px;
}

/* ---- Hero ---- */
.about2-hero {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  min-height: 440px;          /* content-driven height — no huge empty vh gap */
}
.about2-hero-text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(30px, 4vh, 56px) clamp(28px, 4.5vw, 72px);
}
.about2-title {
  margin: 0;
  font-weight: 800;
  font-size: clamp(44px, 5.6vw, 88px);
  line-height: 0.98;
  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; overflow: hidden; min-height: 380px; }
.about2-hero-photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.04);
}
/* left-edge fade so the photo melts into the text side */
.about2-hero-photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(90deg, var(--bg) 0%, rgba(0, 0, 0, 0) 34%);
}

/* ---- Philosophy + stats ---- */
.about2-mid {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr 1.5fr;
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
  padding: clamp(28px, 3.5vh, 48px) clamp(28px, 4.5vw, 72px);
  border-top: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.015);
}
.about2-phil-head {
  margin: 0;
  display: flex;
  flex-direction: column;
  font-weight: 800;
  font-size: clamp(26px, 2.6vw, 40px);
  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); }
.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; }

/* ---- Journey + quote ---- */
.about2-foot {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(28px, 4vw, 64px);
  align-items: stretch;
  padding: clamp(28px, 3.5vh, 48px) clamp(28px, 4.5vw, 72px);
  border-top: 1px solid var(--border);
}
.about2-foot-photo { position: relative; margin: 0; min-height: 260px; overflow: hidden; border-radius: 4px; }
.about2-foot-photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: grayscale(0.35) brightness(0.68); }
.about2-quote { position: absolute; left: 0; bottom: 0; z-index: 1; margin: 0; padding: 26px 30px; max-width: 88%; }
.about2-quote-mark { display: block; color: var(--accent); font-weight: 800; font-size: 60px; line-height: 0.3; }
.about2-quote p {
  margin: 12px 0 0;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(18px, 1.8vw, 26px);
  line-height: 1.4;
  color: #fff;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.65);
}

.about2-timeline { list-style: none; margin: 0; padding: 0; }
.about2-tl-item { position: relative; display: grid; grid-template-columns: 54px 22px 1fr; gap: 12px; padding-bottom: 16px; }
.about2-tl-item:last-child { padding-bottom: 0; }
.about2-tl-year { color: var(--accent); font-weight: 700; font-size: 14px; padding-top: 1px; }
.about2-tl-dot { position: relative; justify-self: center; width: 11px; height: 11px; margin-top: 5px; border-radius: 50%; border: 2px solid var(--accent); background: var(--bg); }
.about2-tl-item:not(:last-child) .about2-tl-dot::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 11px;
  transform: translateX(-50%);
  width: 1px;
  height: calc(100% + 16px);
  background: var(--border);
}
.about2-tl-title { font-weight: 700; font-size: 14px; letter-spacing: 0.3px; text-transform: uppercase; }
.about2-tl-text { margin: 5px 0 0; color: var(--muted); font-size: 13.5px; line-height: 1.55; }

/* ---- 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) {
  .about2-hero { grid-template-columns: 1fr; min-height: 0; }
  .about2-hero-photo { order: -1; min-height: 320px; }
  .about2-hero-photo::before { background: linear-gradient(0deg, var(--bg) 0%, rgba(0, 0, 0, 0) 42%); }
  .about2-mid,
  .about2-foot { grid-template-columns: 1fr; }
  .about2-foot-photo { min-height: 300px; }
}
@media (max-width: 560px) {
  .about2-stats { grid-template-columns: 1fr 1fr; gap: 26px 0; }
  .about2-stat { padding: 0 16px; }
  .about2-stat:nth-child(3) { border-left: none; padding-left: 0; }
}

/* ===========================================================================
   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;
}

/* 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%;
  }
}

.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;
  }

  .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: uniform 4-column cards, image on top, text below --- */
.portfolio-side .main-grid {
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: auto;
  gap: 20px;
  align-items: start;
}

.portfolio-side .content-box {
  grid-row: auto;                 /* cancel the masonry row-spans */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.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%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #05070a;
}

.portfolio-side .card-thumb img {
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: cover;
  opacity: 1;
}
.portfolio-side .content-box:hover .card-thumb img { transform: scale(1.05); opacity: 1; }

.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); }

.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: 13px;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* 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 { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 900px)  { .portfolio-side .main-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  {
  .portfolio-side .main-grid { grid-template-columns: 1fr; }
  .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%
  );
}
