/* =====================
   GOOGLE FONTS — Bauhaus-flavored display face for the K logo
   Rubik Mono One: heavy, geometric, block-letter sans. Loaded site-wide
   so every page's nav and footer K renders consistently.
   ===================== */
@import url('https://fonts.googleapis.com/css2?family=Rubik+Mono+One&display=swap');

/* =====================
   CSS VARIABLES
   ===================== */
:root {
  --bg: #F7F7F2;
  --gunmetal: #1E2022;
  --gunmetal-light: #3A3D40;
  --red: #004225;
  --text: #1E2022;
  --text-muted: #6B6B6B;
  --font: Helvetica, Arial, sans-serif;
}

/* =====================
   CROSS-DOCUMENT VIEW TRANSITIONS — hero image morph from index
   =====================
   The landing page's centered card and each project's hero image
   share the view-transition-name `hero-morph`. With navigation: auto
   set on both pages, the browser snapshots the source image, snaps it
   over to the destination's hero position, and animates the FLIP
   between them — the image grows from the centered card and lands on
   the project page's real hero. Surrounding chrome crossfades.
   ===================== */
@view-transition { navigation: auto; }

.project-hero-img {
  view-transition-name: hero-morph;
}
/* The transition needs to operate on a non-inline element for the
   capture to be reliable. .project-hero-img-wrap is the block-level
   container; the <img> inside it inherits the morph naturally. */
.project-hero-img-wrap {
  contain: layout;
}

/* ---- INBOUND MORPH SETTLE-IN ----
   When the destination page loads after a "click to morph"
   navigation from the index, script.js adds .is-morphing-in to
   <body> (only when cross-document view transitions are NOT
   supported, to avoid double-animating on Chrome). The page then
   plays a brief settle-in: the hero image starts slightly zoomed
   (looking like the just-grown index card) and eases to its real
   position, while the surrounding chrome fades up underneath. */
@keyframes morph-hero-settle {
  from {
    transform: scale(1.18);
    opacity: 1;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}
@keyframes morph-fade-up {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@keyframes morph-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

body.is-morphing-in .project-hero-img-wrap {
  animation: morph-hero-settle 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  transform-origin: center center;
}
body.is-morphing-in nav,
body.is-morphing-in footer {
  animation: morph-fade-in 0.5s ease 0.05s both;
}
body.is-morphing-in .project-hero-titles,
body.is-morphing-in .project-hero-bottom {
  animation: morph-fade-up 0.55s cubic-bezier(0.2, 0.8, 0.2, 1) 0.15s both;
}
body.is-morphing-in .project-section,
body.is-morphing-in .project-cta {
  animation: morph-fade-in 0.4s ease 0.25s both;
}

@media (prefers-reduced-motion: reduce) {
  body.is-morphing-in .project-hero-img-wrap,
  body.is-morphing-in nav,
  body.is-morphing-in footer,
  body.is-morphing-in .project-hero-titles,
  body.is-morphing-in .project-hero-bottom,
  body.is-morphing-in .project-section,
  body.is-morphing-in .project-cta {
    animation: none !important;
  }
}

/* =====================
   RESET & BASE
   ===================== */
*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  cursor: none;
}

html {
  scroll-behavior: smooth;
}

body {
  background-color: var(--bg);
  font-family: var(--font);
  color: var(--text);
  overflow-x: hidden;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

a {
  color: inherit;
  text-decoration: none;
}

/* =====================
   CUSTOM CURSOR
   ===================== */
.cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  width: 36px;
  height: 36px;
  border: 3px solid white;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%);
  background-color: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(6px) brightness(1.08);
  -webkit-backdrop-filter: blur(6px) brightness(1.08);
  transition: width 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              height 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              border-radius 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              border-color 0.3s ease,
              background-color 0.3s ease,
              backdrop-filter 0.3s ease,
              opacity 0.3s ease;
  z-index: 9999;
  mix-blend-mode: difference;
}

.cursor-dot {
  display: none;
}

.cursor-ring.hover {
  width: 64px;
  height: 64px;
  border-color: white;
  background-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.cursor-ring.text-hover {
  border-radius: 3px;
  border-color: #FFD600;
  background-color: rgba(255, 214, 0, 0.15);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  mix-blend-mode: normal;
}

/* Custom text selection color */
::selection {
  background-color: #FFD600;
  color: var(--gunmetal);
}

.cursor-dot.hover {
  background-color: var(--gunmetal-light);
}

.cursor-ring.hidden,
.cursor-dot.hidden {
  opacity: 0;
}

/* =====================
   NAVIGATION
   ===================== */
nav {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 28px 56px;
  z-index: 100;
  background-color: transparent;
  /* Color the whole nav white and blend it with whatever sits behind via
     `difference`. The result is always the inverse of the page background:
     dark text on light backgrounds, light text on dark ones — the same
     trick the custom cursor uses.
     No transform / will-change / opacity here on purpose — any of those
     would re-trap the blend and stop it from reaching the page bg. */
  color: #fff;
  mix-blend-mode: difference;
}

.nav-logo {
  font-size: 0; /* Hides any whitespace; logo is the K glyph */
  line-height: 0;
  /* Color is fixed white so the difference blend on <nav> always inverts
     against the page bg. !important guards against per-page anchor styles
     (e.g. `.aurora-page a`) from repainting the K. */
  color: #fff !important;
  /* Square box around the K. Border uses currentColor so it inverts with
     the rest of the nav under mix-blend-mode. Slight corner radius keeps
     the form geometric while softening the right-angle stamp. */
  width: 56px;
  height: 56px;
  border: 1.5px solid currentColor;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.3s ease;
}

/* Render logo as a Bauhaus-style block "K" — Rubik Mono One is a heavy
   geometric monospace face that recalls Bayer's Universal alphabet. The
   nav's `mix-blend-mode: difference` handles inversion against the page
   bg, so this just stays plain white. */
.logo-img {
  display: inline-block;
  font-family: 'Rubik Mono One', 'Major Mono Display', 'Bungee', Helvetica, Arial, sans-serif;
  font-weight: 400; /* Rubik Mono One ships at a single 400 weight */
  line-height: 1;
  letter-spacing: 0;
  transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.logo-img::before {
  content: "K";
}

.nav-logo .logo-img {
  /* Rubik Mono One has wider proportions than Helvetica; size down so the
     glyph still has breathing room inside the 56px square. */
  font-size: 28px;
  /* Re-declare here so this beats per-page `.<page> span` selectors
     (specificity 0,1,1) that would otherwise repaint the K in Helvetica. */
  font-family: 'Rubik Mono One', 'Major Mono Display', 'Bungee', Helvetica, Arial, sans-serif;
}

/* Hover feedback comes from rotation only — the nav uses mix-blend-mode
   so a colored hover would invert against the bg unpredictably. */
.nav-logo:hover .logo-img {
  transform: rotate(-4deg);
  transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Click spin — JS adds .spinning to the inner .logo-img, then removes it
   on animationend so the K can spin again on the next click. */
@keyframes k-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.logo-img.spinning {
  animation: k-spin 0.55s cubic-bezier(0.65, 0, 0.35, 1);
}

.nav-links {
  display: flex;
  gap: 48px;
  align-items: center;
}

.nav-links a {
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* color inherited from <nav> (#fff) — the difference blend on the parent
     gives us "opposite of bg" for free. !important guards against per-page
     overrides like `.aurora-page a { color: ... }`. */
  color: #fff !important;
  position: relative;
}

.nav-links a::after {
  content: '';
  position: absolute;
  bottom: -3px;
  left: 50%;
  width: 0;
  height: 1px;
  background-color: #fff;
  transform: translateX(-50%);
  transition: width 0.4s cubic-bezier(0.19, 1, 0.22, 1);
}

.nav-links a:hover::after {
  width: 100%;
}

.nav-links a.active {
  /* same color as inactive — the underline alone differentiates */
}

.nav-links a.active::after {
  width: 100%;
  background-color: #fff;
}

/* Mobile nav toggle */
.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: none;
  padding: 4px;
}

.nav-toggle span {
  display: block;
  width: 24px;
  height: 1.5px;
  background-color: #fff;
  transition: transform 0.3s ease, opacity 0.3s ease;
}

/* =====================
   HERO SECTION
   ===================== */
.hero {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 140px 56px 80px;
}

.hero-label {
  font-size: 18px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 56px;
  text-align: center;
}

.hero-objects {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 24px;
  width: 100%;
}

.object-item {
  flex: 0 0 calc(20% - 20px);
}

.object-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  text-decoration: none;
  color: var(--text);
  transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              opacity 0.4s ease;
}

.object-img-wrap {
  width: 100%;
  aspect-ratio: 1 / 1.1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              filter 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  will-change: transform;
}

/*
  Replace the .object-placeholder with an <img> tag once PNGs are ready:
  <img src="images/ticket.png" alt="Rendeview" class="object-img">
*/
.object-placeholder {
  font-size: clamp(48px, 6vw, 88px);
  line-height: 1;
  user-select: none;
  display: block;
  transition: font-size 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              filter 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.object-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.object-title {
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gunmetal-light);
  transition: color 0.25s ease, font-size 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  text-align: center;
}

.object-item:hover .object-title {
  font-size: 16px;
  color: var(--gunmetal);
}

.object-item:hover .object-placeholder {
  font-size: clamp(96px, 12vw, 160px);
  transform: translateY(-20px);
  filter: drop-shadow(0 32px 48px rgba(0, 0, 0, 0.3));
}

.object-item:hover .object-title {
  color: var(--gunmetal);
}

/* =====================
   FOOTER
   ===================== */
footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 28px 56px;
  border-top: 1px solid rgba(30, 32, 34, 0.1);
}

.footer-logo {
  font-size: 0;
  line-height: 0;
  color: var(--gunmetal);
  /* Match the nav K — square box around the glyph, with the same slight
     corner radius. Slightly smaller radius to scale with the smaller box. */
  width: 42px;
  height: 42px;
  border: 1.5px solid currentColor;
  border-radius: 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 0.2s ease;
}

.footer-logo .logo-img {
  /* Match the nav: scale down so Rubik Mono One's wide K fits the box. */
  font-size: 20px;
  /* Re-declare here so this beats per-page `.<page> span` selectors
     (specificity 0,1,1) that would otherwise repaint the K in Helvetica. */
  font-family: 'Rubik Mono One', 'Major Mono Display', 'Bungee', Helvetica, Arial, sans-serif;
}

.footer-logo:hover {
  color: var(--red);
}

.footer-links {
  display: flex;
  gap: 40px;
  align-items: center;
}

.footer-links a {
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  transition: color 0.2s ease;
}

.footer-links a:hover {
  color: var(--gunmetal);
}

.footer-copy {
  font-size: 11px;
  color: var(--text-muted);
  letter-spacing: 0.05em;
}

/* =====================
   HOME PAGE — dark footer
   The landing page sits on a light cream bg; we drop the footer onto a
   near-black slab so it reads as a separate end-of-page band. Footer
   text and the K logo invert to light tones so they remain legible.
   ===================== */
body.home footer {
  background-color: #1A1C1E;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

body.home .footer-logo {
  color: #F7F7F2;
}

body.home .footer-logo:hover {
  color: #EDEDE6;
}

body.home .footer-links a {
  color: rgba(247, 247, 242, 0.6);
}

body.home .footer-links a:hover {
  color: #F7F7F2;
}

body.home .footer-copy {
  color: rgba(247, 247, 242, 0.55);
}

/* =====================
   ABOUT PAGE
   ===================== */

/* Hero: centered image + name */
.about-hero {
  background-color: var(--gunmetal);
  padding-top: 88px;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 64px;
}

.about-hero-img {
  width: clamp(200px, 28vw, 360px);
  aspect-ratio: 3 / 4;
  background-color: #3A3D40;
  margin-top: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(247, 247, 242, 0.3);
  overflow: hidden;
}

.about-hero-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.about-name {
  font-family: Helvetica, "Helvetica Neue", Arial, sans-serif;
  font-size: clamp(40px, 7vw, 96px);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1;
  color: var(--bg);
  text-align: center;
  margin-top: 36px;
}

/* About page — Motives + Synesthesia rows ride a charcoal background
   that's distinct from the gunmetal hero above. */
.about-page .content-row.dark {
  background-color: #2C2E30;
}

.about-page .content-row.dark .row-image {
  background-color: #3D3F42;
}

/* About-page nav overrides removed — the global `mix-blend-mode: difference`
   on <nav> already makes the K and links read as the inverse of the dark
   gunmetal hero. */

/* Section group heading — full width label before a group of rows */
.section-group-heading {
  padding: 56px 56px 0;
  border-top: 1px solid rgba(30, 32, 34, 0.1);
}

.section-group-title {
  font-size: clamp(20px, 2.5vw, 32px);
  font-weight: 300;
  letter-spacing: -0.01em;
  color: var(--gunmetal);
  padding-bottom: 48px;
  border-bottom: 1px solid rgba(30, 32, 34, 0.08);
}

/* Alternating image + text rows */
.content-row {
  display: flex;
  align-items: center;
  gap: 72px;
  padding: 72px 56px;
  border-top: 1px solid rgba(30, 32, 34, 0.1);
}

.content-row.reverse {
  flex-direction: row-reverse;
}

.row-image {
  position: relative;
  flex: 0 0 58%;
  aspect-ratio: 4 / 3;
  background-color: #E8E8E2;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-muted);
  overflow: hidden;
}

.row-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* =========================================
   Aurora problem-section carousel
   Layout: buttons OVERLAY the image (left/right edges)
   ========================================= */
.aurora-carousel {
  position: relative;
}

/* Blurred backdrop that mirrors the current slide */
.aurora-carousel-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(34px) saturate(1.1);
  transform: scale(1.2);
  z-index: 0;
  pointer-events: none;
  transition: opacity 0.35s ease;
}

.aurora-carousel-track {
  position: relative;
  display: flex;
  width: 100%;
  height: 100%;
  transition: transform 0.55s cubic-bezier(0.65, 0, 0.35, 1);
  will-change: transform;
  /* No z-index here — keeping the track out of its own stacking context lets
     the buttons' mix-blend-mode reach through to the image pixels. */
}

.row-image img.aurora-carousel-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  position: relative;
  z-index: 1;
}

/* Video carousel slide — custom-controls wrapper around a <video>.
   No native browser controls (kills timeline + fullscreen). */
.row-image .aurora-video-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  position: relative;
  display: block;
  z-index: 1;
  background-color: #000;
}

.aurora-video-slide .aurora-video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  background-color: #000;
}

/* Caption that sits in the top letterbox gap above each video. */
.aurora-video-title {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  margin: 0;
  padding: 0 12px;
  color: #FFFFFF;
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  font-style: italic;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

/* Hide any native UI fragments WebKit/Blink might still expose. */
.aurora-video-slide .aurora-video::-webkit-media-controls,
.aurora-video-slide .aurora-video::-webkit-media-controls-enclosure,
.aurora-video-slide .aurora-video::-webkit-media-controls-fullscreen-button,
.aurora-video-slide .aurora-video::-webkit-media-controls-panel {
  display: none !important;
  -webkit-appearance: none;
}

/* Custom video control bar — anchored to the bottom of the slide, sitting
   directly above the carousel pagination dots. Horizontally centered, well
   under 1/3 of the carousel width, laid out as
     [play/pause][progress timeline][replay]                              */
.aurora-video-controls-bar {
  position: absolute;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%);
  width: 30%;
  min-width: 220px;
  max-width: 380px;
  height: 30px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background-color: rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(12px);
          backdrop-filter: blur(12px);
  border-radius: 999px;
  z-index: 2;
  opacity: 1;
  transition: opacity 0.45s ease;
}

/* Push the dots pill lower on the video carousel only so it tucks closer to
   the bottom edge, leaving room for the control bar above it. */
.aurora-carousel:has(.aurora-video-slide) .aurora-carousel-dots {
  bottom: 6px;
}

/* JS adds .controls-hidden after a 1s idle window during playback. */
.aurora-video-slide.controls-hidden .aurora-video-controls-bar,
.aurora-video-slide.controls-hidden .aurora-video-toggle--center {
  opacity: 0;
  pointer-events: none;
}

/* Big secondary play/pause sitting dead-center on the video. Inherits the
   .aurora-video-toggle class (so click + icon-swap behavior is shared) and
   adds a standalone circular surface. */
.aurora-video-toggle--center {
  position: absolute;
  top: 50%;
  left: 50%;
  /* translate(-50%, -50%) centers regardless of size/border/box-sizing. */
  transform: translate(-50%, -50%);
  /* Sized as a fraction of the video's width — aspect-ratio keeps it a circle. */
  width: 18%;
  height: auto;
  aspect-ratio: 1 / 1;
  border: 1.5px solid rgba(255, 255, 255, 0.55);
  background-color: rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(12px);
          backdrop-filter: blur(12px);
  border-radius: 999px;
  z-index: 2;
  opacity: 1;
  transition: opacity 0.45s ease;
}

/* Center button intentionally has no hover state — it's already the
   focal element on the video, so additional hover affordance would feel
   noisy. The :not() guards below keep the small bar toggle's hover. */

.aurora-video-toggle--center .av-icon {
  width: 58%;
  height: 58%;
}

.aurora-video-toggle,
.aurora-video-replay {
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  background-color: transparent;
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: none;
  border-radius: 999px;
  transition: transform 0.2s ease, background-color 0.2s ease;
}

/* Small toggles in the bottom control bar — fixed pixel size. The center
   variant deliberately escapes this (it sets its own width via percentage). */
.aurora-video-toggle:not(.aurora-video-toggle--center),
.aurora-video-replay {
  width: 22px;
  height: 22px;
}

.aurora-video-toggle:not(.aurora-video-toggle--center):hover,
.aurora-video-replay:hover {
  background-color: rgba(255, 255, 255, 0.18);
  transform: scale(1.08);
}

.aurora-video-toggle:not(.aurora-video-toggle--center) .av-icon,
.aurora-video-replay .av-icon {
  width: 70%;
  height: 70%;
  fill: #FFFFFF;
  display: block;
}

/* Center button icon — own sizing, shared white fill. */
.aurora-video-toggle--center .av-icon {
  fill: #FFFFFF;
  display: block;
}

.aurora-video-replay .av-icon {
  width: 80%;
  height: 80%;
}

/* Toggle icon: show play by default, swap to pause when playing. */
.aurora-video-toggle .av-pause { display: none; }
.aurora-video-slide.is-playing .aurora-video-toggle .av-play { display: none; }
.aurora-video-slide.is-playing .aurora-video-toggle .av-pause { display: block; }

/* Timeline — the click-to-seek track that fills the middle of the bar. */
.aurora-video-progress {
  flex: 1 1 auto;
  height: 4px;
  background-color: rgba(255, 255, 255, 0.25);
  border-radius: 999px;
  position: relative;
  cursor: none;
}

.aurora-video-progress-fill {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 0%;
  background-color: #FFFFFF;
  border-radius: 999px;
  pointer-events: none;
}

/* Buttons overlay the image — no border, no fill (idle).
   IMPORTANT: no z-index, no backdrop-filter, no isolation property on the button
   itself. Any of those would create a stacking context that blocks the arrow's
   mix-blend-mode from reaching the image. The buttons still paint above the
   track because they come after it in document order. */
.aurora-carousel-btn {
  position: absolute;
  top: 0;
  height: 100%;
  width: 64px;
  border: none;
  background-color: transparent;
  background-image: none;
  padding: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: none;
  transition: opacity 0.25s ease;
}

/* Gradient layer (smooth opacity fade) */
.aurora-carousel-btn::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.55s ease;
}

/* Backdrop-blur layer — separate pseudo so the button itself doesn't isolate */
.aurora-carousel-btn::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  -webkit-backdrop-filter: blur(0px);
          backdrop-filter: blur(0px);
  transition: backdrop-filter 0.55s ease,
              -webkit-backdrop-filter 0.55s ease;
}

.aurora-carousel-prev::before {
  background-image: linear-gradient(
    to right,
    rgba(30, 32, 34, 0.75) 0%,
    rgba(30, 32, 34, 0) 100%
  );
}

.aurora-carousel-next::before {
  background-image: linear-gradient(
    to left,
    rgba(30, 32, 34, 0.75) 0%,
    rgba(30, 32, 34, 0) 100%
  );
}

.aurora-carousel-btn:hover:not(:disabled)::before {
  opacity: 1;
}

.aurora-carousel-btn:hover:not(:disabled)::after {
  -webkit-backdrop-filter: blur(10px);
          backdrop-filter: blur(10px);
}

.aurora-carousel-btn:disabled {
  opacity: 0.25;
  cursor: none;
}

.aurora-carousel-prev { left: 0; }
.aurora-carousel-next { right: 0; }

/* Arrow shape — white fill blended with the image via difference (same trick
   the cursor ring uses), giving a true inverse-color chevron over whatever
   pixels sit directly behind it. Sits above the button's pseudo-layers. */
.aurora-carousel-arrow {
  display: block;
  position: relative;
  z-index: 1;
  width: 36px;
  height: 36px;
  background-color: #FFFFFF;
  mix-blend-mode: difference;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: contain;
          mask-size: contain;
  transition: width 0.35s ease, height 0.35s ease;
}

.aurora-carousel-arrow--prev {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='15 6 9 12 15 18'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='15 6 9 12 15 18'/></svg>");
}

.aurora-carousel-arrow--next {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='9 6 15 12 9 18'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='9 6 15 12 9 18'/></svg>");
}

/* Bigger arrows on hover — and drop the difference blend so the chevron reads
   as plain white over the gradient/blur. */
.aurora-carousel-btn:hover:not(:disabled) .aurora-carousel-arrow {
  width: 50px;
  height: 50px;
  mix-blend-mode: normal;
}

/* Dot indicator stays anchored at the bottom of the image */
.aurora-carousel-dots {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 999px;
  background-color: rgba(0, 0, 0, 0.32);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 3;
}

.aurora-carousel-dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background-color: rgba(255, 255, 255, 0.5);
  cursor: none;
  transition: background-color 0.25s ease, transform 0.25s ease;
}

.aurora-carousel-dot:hover {
  background-color: rgba(255, 255, 255, 0.8);
}

.aurora-carousel-dot.is-active {
  background-color: rgba(255, 255, 255, 1);
  transform: scale(1.25);
}

.row-text {
  flex: 1;
}

/* Text-only row variant — the .row-image has been removed so the
   block centers horizontally under the hero name. Text inside stays
   left-aligned for readability. */
.content-row.text-only {
  justify-content: center;
}
.content-row.text-only .row-text {
  flex: 0 1 760px;
  max-width: 760px;
}

.row-subtitle {
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-muted);
  display: block;
  margin-bottom: 14px;
}

.row-title {
  font-size: clamp(18px, 2vw, 26px);
  font-weight: 400;
  color: var(--gunmetal);
  line-height: 1.2;
  margin-bottom: 8px;
  letter-spacing: -0.01em;
}

.row-title-note {
  font-size: clamp(13px, 1.2vw, 15px);
  font-weight: 300;
  color: var(--text-muted);
  display: block;
  margin-bottom: 20px;
  line-height: 1.5;
}

.row-body {
  font-size: 15px;
  line-height: 1.85;
  color: var(--gunmetal-light);
}

/* Philosophy group — no internal dividers, condensed */
.philosophy-group {
  display: flex;
  flex-direction: column;
}

.philosophy-row {
  border-top: none;
  padding-top: 40px;
  padding-bottom: 40px;
}

/* Synesthesia row — dark background variant */
.content-row.dark {
  background-color: var(--gunmetal);
  border-top: none;
}

.content-row.dark .row-image {
  background-color: #3A3D40;
  color: rgba(247, 247, 242, 0.25);
}

.content-row.dark .row-subtitle {
  color: rgba(247, 247, 242, 0.35);
}

.content-row.dark .row-title {
  color: var(--bg);
}

.content-row.dark .row-body {
  color: rgba(247, 247, 242, 0.65);
}

/* Motives row (Row 2: reverse + dark) — justify body text so the
   right edge is flush instead of ragged. Hyphenation lets the
   browser break long words to keep word-spacing even rather than
   creating wide gaps between words. */
.content-row.reverse.dark .row-body {
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
}

/* Rainbow text */
.rainbow-text {
  background: linear-gradient(
    90deg,
    hsl(0,   100%, 58%),
    hsl(33,  100%, 55%),
    hsl(60,  100%, 50%),
    hsl(90,  100%, 45%),
    hsl(120, 100%, 42%),
    hsl(150, 100%, 44%),
    hsl(180, 100%, 46%),
    hsl(210, 100%, 56%),
    hsl(240, 100%, 62%),
    hsl(270, 100%, 60%),
    hsl(300, 100%, 56%),
    hsl(330, 100%, 56%),
    hsl(360, 100%, 58%)
  );
  background-size: 200% auto;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  animation: rainbow-shift 20s linear infinite;
}

@keyframes rainbow-shift {
  0%   { background-position: 0% center; }
  100% { background-position: 200% center; }
}

/* Hobbies infinite marquee */
.hobbies-section {
  padding: 72px 0 72px 56px;
  border-top: 1px solid rgba(30, 32, 34, 0.1);
  overflow: hidden;
}

.hobbies-label {
  display: block;
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 40px;
}

.hobbies-marquee-wrap {
  overflow: hidden;
  margin-left: -56px;
  width: calc(100% + 56px);
}

.hobbies-marquee-track {
  display: flex;
  gap: 16px;
  width: max-content;
  animation: hobbies-scroll 40s linear infinite;
}

.hobby-card {
  flex: 0 0 220px;
  aspect-ratio: 3 / 4;
  background-color: #E8E8E2;
  overflow: hidden;
  transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              filter 0.4s ease;
}

.hobby-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}

.hobby-card:hover {
  transform: scale(1.04);
  filter: brightness(0.9);
}

@keyframes hobbies-scroll {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}

/* About page responsive */
@media (max-width: 1024px) {
  .hobby-card {
    flex: 0 0 180px;
  }
}

@media (max-width: 768px) {
  .about-hero-img {
    width: 60vw;
  }

  .section-group-heading {
    padding: 40px 28px 0;
  }

  .section-group-title {
    padding-bottom: 32px;
  }

  .content-row,
  .content-row.reverse {
    flex-direction: column;
    padding: 48px 28px;
    gap: 36px;
  }

  .row-image {
    flex: 0 0 auto;
    width: 100%;
    aspect-ratio: 3 / 2;
  }

  .hobbies-section {
    padding: 48px 0 48px 28px;
  }

  .hobbies-marquee-wrap {
    margin-left: -28px;
    width: calc(100% + 28px);
  }

  .hobby-card {
    flex: 0 0 160px;
    font-size: 56px;
  }
}

/* =====================
   PROJECTS PAGE
   ===================== */
.projects-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  margin-top: 56px;
}

.project-card {
  aspect-ratio: 4 / 3;
  background-color: #EAEAE4;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 28px;
  position: relative;
  overflow: hidden;
  transition: background-color 0.3s ease;
  text-decoration: none;
  color: var(--text);
}

.project-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(30,32,34,0.5) 0%, transparent 60%);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.project-card:hover::before {
  opacity: 1;
}

.project-card-emoji {
  font-size: 48px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -60%);
  transition: transform 0.3s ease;
}

.project-card:hover .project-card-emoji {
  transform: translate(-50%, -65%) scale(1.05);
}

.project-card-title {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  position: relative;
  z-index: 1;
  transition: color 0.3s ease;
}

.project-card:hover .project-card-title {
  color: white;
}

.project-card-sub {
  font-size: 11px;
  color: var(--text-muted);
  letter-spacing: 0.08em;
  margin-top: 4px;
  position: relative;
  z-index: 1;
  transition: color 0.3s ease;
}

.project-card:hover .project-card-sub {
  color: rgba(255,255,255,0.7);
}

/* =====================
   PROJECT PAGES
   ===================== */

.project-hero {
  padding-top: 88px;
}

/* Hero top — title/subtitle row + full-span image */
.project-hero-top {
  padding: 48px 56px 0;
  display: flex;
  flex-direction: column;
  min-height: 75vh;
}

.project-hero-titles {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin-bottom: 64px;
  gap: 40px;
}

.project-title {
  font-size: clamp(36px, 6vw, 84px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 0.9;
  color: var(--gunmetal);
}

.project-subtitle {
  font-size: clamp(13px, 1.4vw, 17px);
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  max-width: 280px;
  text-align: right;
  line-height: 1.6;
  flex-shrink: 0;
}

.project-hero-img-wrap {
  flex: 1;
  overflow: hidden;
  min-height: 44vh;
}

.project-hero-img {
  width: 100%;
  height: 130%;
  background-color: #E4E4DE;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-muted);
  will-change: transform;
  transform-origin: top center;
}

.project-hero-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Hero bottom — meta + role */
.project-hero-bottom {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 80px;
  padding: 48px 56px;
  border-top: 1px solid rgba(30, 32, 34, 0.1);
  min-height: calc(75vh / 3);
  align-items: start;
}

.project-meta-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 28px 40px;
  align-content: start;
}

.meta-item {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.meta-label {
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.meta-value {
  font-size: 14px;
  color: var(--gunmetal);
  line-height: 1.45;
}

.project-role-title {
  font-size: clamp(16px, 1.8vw, 22px);
  font-weight: 400;
  color: var(--gunmetal);
  margin-bottom: 18px;
  letter-spacing: -0.01em;
}

.project-role p {
  font-size: 15px;
  line-height: 1.85;
  color: var(--gunmetal-light);
}

/* Content sections */
.project-section {
  border-top: 1px solid rgba(30, 32, 34, 0.1);
  padding: 56px 56px 0;
}

.project-section-header {
  display: flex;
  align-items: baseline;
  gap: 20px;
  margin-bottom: 48px;
}

.project-section-num {
  font-size: 11px;
  letter-spacing: 0.18em;
  color: var(--text-muted);
}

.project-section-title {
  font-size: clamp(28px, 4vw, 52px);
  font-weight: 300;
  letter-spacing: -0.02em;
  color: var(--gunmetal);
  line-height: 1;
}

.project-section .content-row {
  border-top: none;
  padding-top: 0;
  padding-left: 0;
  padding-right: 0;
}

/* Project page responsive */
@media (max-width: 768px) {
  .project-hero-top {
    padding: 32px 28px 0;
    min-height: auto;
  }

  .project-hero-titles {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }

  .project-subtitle {
    text-align: left;
  }

  .project-hero-img-wrap {
    min-height: 36vh;
  }

  .project-hero-bottom {
    grid-template-columns: 1fr;
    gap: 40px;
    padding: 40px 28px;
  }

  .project-meta-grid {
    grid-template-columns: 1fr 1fr;
  }

  .project-section {
    padding: 40px 28px 0;
  }
}

/* =====================
   PROJECT PAGES — DYNAMIC SCROLL REVEAL
   =====================
   Sections of every project page slide upward and fade in as they
   enter the viewport. The IntersectionObserver in script.js attaches
   `.scroll-reveal` to candidate elements on load and toggles
   `.is-visible` on each one as it crosses the viewport threshold,
   creating a staggered, page-wide unfurl while the user scrolls.

   The hero titles, hero image, hero meta block, and every numbered
   .project-section slide up with their own reveal. The Rendeview
   screens carousel and the Mzizi photo grid get the same treatment
   so the effect carries through to the bottom of every page. */
.scroll-reveal {
  opacity: 0;
  transform: translate3d(0, 32px, 0);
  transition: opacity 0.85s cubic-bezier(0.2, 0.7, 0.2, 1),
              transform 0.85s cubic-bezier(0.2, 0.7, 0.2, 1);
  will-change: opacity, transform;
}

.scroll-reveal.scroll-reveal--soft {
  transform: translate3d(0, 18px, 0);
}

.scroll-reveal.scroll-reveal--zoom {
  transform: translate3d(0, 0, 0) scale(1.04);
  transform-origin: center center;
}

.scroll-reveal.is-visible {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

/* Re-enable parallax friendliness — the inline transform written by the
   parallax handler on .project-hero-img wins because it's set on style.
   The reveal targets the wrapping element instead, so the two don't fight. */
.project-hero-img-wrap.scroll-reveal {
  transform: translate3d(0, 24px, 0);
}
.project-hero-img-wrap.scroll-reveal.is-visible {
  transform: translate3d(0, 0, 0);
}

@media (prefers-reduced-motion: reduce) {
  .scroll-reveal,
  .scroll-reveal.scroll-reveal--soft,
  .scroll-reveal.scroll-reveal--zoom,
  .project-hero-img-wrap.scroll-reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* The morph-in animation already controls the hero on first paint when
   the user navigates from the projects index. Suppress the reveal there
   so we don't get a double-animation for the same elements. */
body.is-morphing-in .scroll-reveal {
  opacity: 1;
  transform: none;
}

/* =====================
   CTA BUTTON
   ===================== */
.btn-cta {
  display: inline-block;
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--bg);
  background-color: var(--red);
  padding: 14px 32px;
  transition: background-color 0.25s ease, transform 0.25s ease;
}

.btn-cta:hover {
  background-color: var(--gunmetal);
  transform: translateY(-2px);
}

/* =====================
   RESPONSIVE
   ===================== */
@media (max-width: 1100px) {
  .hero-objects {
    gap: 32px;
  }

  .object-item {
    flex: 0 0 calc(33.333% - 22px);
  }

  .projects-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 768px) {
  nav {
    padding: 20px 28px;
  }

  .nav-links {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100vh;
    background-color: var(--bg);
    flex-direction: column;
    justify-content: center;
    gap: 32px;
    z-index: 99;
  }

  .nav-links.open {
    display: flex;
  }

  .nav-links a {
    font-size: 18px;
    letter-spacing: 0.12em;
  }

  .nav-toggle {
    display: flex;
    z-index: 100;
  }

  .nav-toggle.open span:nth-child(1) {
    transform: translateY(6.5px) rotate(45deg);
  }

  .nav-toggle.open span:nth-child(2) {
    opacity: 0;
  }

  .nav-toggle.open span:nth-child(3) {
    transform: translateY(-6.5px) rotate(-45deg);
  }

  .hero {
    padding: 120px 28px 60px;
  }

  .hero-objects {
    gap: 28px;
  }

  .object-item {
    flex: 0 0 calc(50% - 14px);
  }

  .page-section {
    padding: 120px 28px 60px;
  }

  .about-grid {
    grid-template-columns: 1fr;
    gap: 48px;
  }

  .projects-grid {
    grid-template-columns: 1fr;
  }

  footer {
    padding: 24px 28px;
    flex-direction: column;
    gap: 20px;
    text-align: center;
  }

  .footer-links {
    gap: 24px;
  }

  /* Hide cursor ring on touch devices */
  .cursor-ring,
  .cursor-dot {
    display: none;
  }
}

@media (max-width: 480px) {
  .hero-objects {
    gap: 20px;
  }
}

/* =====================
   AURORA PAGE THEME (scoped to .aurora-page only)
   ===================== */
body.aurora-page {
  background-color: #0C0C0C;
  color: #D3D3D3;
}

/* Borealis-blue highlighter cursor + text selection on the Aurora page,
   replacing the default yellow so the accent matches Aurora's palette.
   Toned down a notch from the original #5DD8FF — still cyan-blue but
   easier on the eye against the dark page. */
body.aurora-page .cursor-ring.text-hover {
  border-color: #3FA8D0;
  background-color: rgba(63, 168, 208, 0.18);
}
body.aurora-page ::selection {
  background-color: #3FA8D0;
  color: #0C0C0C;
}

/* Body copy + general text. The nav is intentionally excluded — it relies
   on `mix-blend-mode: difference` (set on <nav>) plus white !important on
   .nav-logo / .nav-links a to invert against the page bg. The remaining
   `.aurora-page a` rule below would otherwise repaint the nav links. */
.aurora-page,
.aurora-page p,
.aurora-page a,
.aurora-page span,
.aurora-page li,
.aurora-page .footer-logo,
.aurora-page .footer-links a,
.aurora-page .footer-copy,
.aurora-page .project-subtitle,
.aurora-page .meta-label,
.aurora-page .meta-value,
.aurora-page .project-section-num,
.aurora-page .row-subtitle,
.aurora-page .row-title-note,
.aurora-page .row-body,
.aurora-page .project-role p {
  color: #D3D3D3;
}

/* Justified, square paragraphs with a touch more line height on Aurora */
.aurora-page .row-body,
.aurora-page .project-role p {
  text-align: justify;
  text-justify: inter-word;
  hyphens: auto;
  -webkit-hyphens: auto;
  line-height: 2.05;
}

/* Bold every title on the Aurora page (incl. meta labels: Date, Length, etc.) */
.aurora-page .project-title,
.aurora-page .project-section-title,
.aurora-page .project-role-title,
.aurora-page .row-title,
.aurora-page .meta-label,
.aurora-page .project-section-num,
.aurora-page .row-subtitle {
  font-weight: 700;
}

/* Highlight bolded callouts inside Aurora body paragraphs — luminous
   borealis-blue swatch with deep-navy text so the phrase reads like a
   marker stroke against the dark page background.
   box-decoration-break: clone keeps the highlight tight to each wrapped line
   instead of stretching across the gap when the strong text breaks lines. */
.aurora-page .row-body strong,
.aurora-page .project-role p strong {
  background-color: #3FA8D0;
  color: #0A1A3A;
  font-weight: inherit;
  padding: 0.05em 0.2em;
  border-radius: 2px;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* Solid-color titles a touch brighter than body text */
.aurora-page .project-section-title,
.aurora-page .project-role-title,
.aurora-page .row-title,
.aurora-page .meta-label,
.aurora-page .project-section-num {
  color: #FFFFFF;
}

/* Sub-section titles a touch smaller on the Aurora page */
.aurora-page .project-section-title {
  font-size: clamp(22px, 3vw, 40px);
}

/* Underline the project-description meta titles (Date, Length, Role, etc.) */
.aurora-page .meta-label {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

/* Placeholder carousel slides — same look as the bare "Image" placeholders */
.aurora-page .aurora-slide-placeholder {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(211, 211, 211, 0.04);
  color: rgba(211, 211, 211, 0.4);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* Aurora main title — Havelock Titling, solid white */
.aurora-page .project-title {
  font-family: 'Havelock Titling', 'Halvar Tilting', 'Audiowide', 'Helvetica Neue', Helvetica, sans-serif;
  font-size: clamp(36px, 6vw, 84px);
  letter-spacing: -0.2em;
  padding-right: 0.2em;
  background-image: none;
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  color: #FFFFFF;
  -webkit-text-fill-color: #FFFFFF;
  animation: none;
  filter: none;
}

/* Soften borders for the dark background */
.aurora-page .project-hero-bottom,
.aurora-page .project-section,
.aurora-page .content-row,
.aurora-page footer {
  border-color: rgba(211, 211, 211, 0.08);
}

/* Image placeholder backgrounds for dark mode */
.aurora-page .row-image,
.aurora-page .project-hero-img {
  background-color: rgba(211, 211, 211, 0.04);
  color: rgba(211, 211, 211, 0.4);
}

/* Real hero <img> on Aurora — fill the wrap, cover, no placeholder text.
   Parallax is disabled on Aurora, so the image stays static and we don't
   need a GPU promotion or extra height. */
.aurora-page img.project-hero-img {
  height: 100%;
  width: 100%;
  object-fit: cover;
  background-color: transparent;
  transform: none;
  will-change: auto;
  animation: none;
}

/* Mobile nav drawer needs a dark backdrop too */
@media (max-width: 768px) {
  .aurora-page .nav-links {
    background-color: #0C0C0C;
  }
}

/* Racecar that drives across the top edge of the footer on the Aurora page.
   Footer keeps overflow visible so the car can ride above it; horizontal
   containment is handled by body's overflow-x: hidden. */
.aurora-page footer {
  position: relative;
  overflow: visible;
  background-color: #0C0C0C;   /* asphalt grey, near-black */
}

.aurora-racecar {
  position: absolute;
  top: -32px;            /* sits just above the footer's top edge */
  left: 0;
  font-size: 32px;
  line-height: 1;
  pointer-events: none;
  z-index: 5;
  animation: aurora-racecar-drive 7s linear infinite,
             aurora-racecar-bob 0.4s ease-in-out infinite;
  /* The 🏎️ glyph on most platforms faces left, so a leftward leg uses
     scaleX(1) and the rightward return leg flips to scaleX(-1). The flips
     happen while the car is off-screen so the snap isn't visible. */
}

@keyframes aurora-racecar-drive {
  0%   { transform: translateX(calc(100vw + 60px)) scaleX(1); }
  48%  { transform: translateX(-80px)              scaleX(1); }
  50%  { transform: translateX(-80px)              scaleX(-1); }
  98%  { transform: translateX(calc(100vw + 60px)) scaleX(-1); }
  100% { transform: translateX(calc(100vw + 60px)) scaleX(1); }
}

@keyframes aurora-racecar-bob {
  0%, 100% { margin-top: 0; }
  50%      { margin-top: -2px; }
}

/* Engine smoke puffing out the back of the car. The smoke is a child of the
   racecar so it inherits the scaleX flip that happens at each turn-around —
   it always appears behind the car relative to its direction of travel. */
.aurora-racecar-smoke {
  position: absolute;
  left: 100%;
  top: 50%;
  font-size: 0.65em;
  line-height: 1;
  pointer-events: none;
  opacity: 0;
  transform: translate(0, -50%) scale(0.3);
  animation-iteration-count: infinite;
  animation-timing-function: ease-out;
  animation-name: aurora-racecar-smoke;
}

/* Two staggered puffs so the smoke pattern doesn't feel mechanically periodic */
.aurora-racecar-smoke--a {
  animation-duration: 3.2s;
  animation-delay: 0s;
}

.aurora-racecar-smoke--b {
  animation-duration: 4.7s;
  animation-delay: 1.6s;
  font-size: 0.55em;
}

@keyframes aurora-racecar-smoke {
  0%   { opacity: 0;    transform: translate(2px,  -50%) scale(0.3); }
  4%   { opacity: 0.9;  transform: translate(4px,  -52%) scale(0.6); }
  18%  { opacity: 0.45; transform: translate(20px, -58%) scale(1.0); }
  30%  { opacity: 0;    transform: translate(38px, -64%) scale(1.4); }
  100% { opacity: 0;    transform: translate(38px, -64%) scale(1.4); }
}

/* Aurora mobile only — hide the subtitle that sits under the title */
@media (max-width: 768px) {
  .aurora-page .project-subtitle {
    display: none;
  }
}

/* Aurora page — gutters match the rest of the project pages (56px on
   desktop, set by the base .project-hero-top / -bottom / .project-section
   rules). Keeping them consistent across all four project case studies. */

/* Aurora page — extra breathing room between each section title and its content */
.aurora-page .project-section-header {
  margin-bottom: 80px;
}

/* When a row has more than one body paragraph, separate them. The base
   reset zeroes out <p> margins, so without this they'd run together. */
.row-text .row-body + .row-body {
  margin-top: 1em;
}

/* Aurora — keep image and text aligned to the top of each content row so
   the image doesn't drift to the vertical middle when text wraps to more
   lines at narrower widths. Otherwise the "container" appears to grow as
   the viewport shrinks. */
.aurora-page .project-section .content-row {
  align-items: flex-start;
}

/* Aurora mobile — tighten the gap between the hero image and the first
   numbered project section. */
@media (max-width: 768px) {
  .aurora-page .project-hero-bottom {
    padding-top: 24px;
    padding-bottom: 24px;
    gap: 24px;
  }
  .aurora-page .project-section {
    padding-top: 20px;
  }
  .aurora-page .project-section-header {
    margin-bottom: 36px;
  }
}

/* Aurora intermediate widths (tablet / narrow desktop) — scale spacing down
   so it doesn't balloon between the mobile breakpoint and full desktop.
   Without this, hero-bottom grows as the role text wraps and the section
   paddings stay at their wide-desktop values. */
@media (min-width: 769px) and (max-width: 1100px) {
  .aurora-page .project-hero-top {
    min-height: 60vh;
    padding-left: 56px;
    padding-right: 56px;
  }
  .aurora-page .project-hero-bottom {
    min-height: auto;
    padding: 32px 56px;
    gap: 24px 48px;
  }
  .aurora-page .project-section {
    padding: 36px 56px 0;
  }
  .aurora-page .project-section-header {
    margin-bottom: 48px;
  }
  .aurora-page .project-section .content-row {
    padding-bottom: 48px;
    gap: 48px;
  }
}

/* =====================
   Aurora Solution tabs
   Tabs sit above the carousel, swapping between Gauge Cluster and HUD views.
   The wrap takes the same flex slot the lone .row-image used to occupy.
   ===================== */
.aurora-tabs-wrap {
  flex: 0 0 58%;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.aurora-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid rgba(211, 211, 211, 0.18);
}

.aurora-tab {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 10px 18px 12px;
  margin-bottom: -1px;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(211, 211, 211, 0.55);
  cursor: none;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.aurora-tab:hover {
  color: rgba(255, 255, 255, 0.9);
}

.aurora-tab.is-active {
  color: #FFFFFF;
  border-bottom-color: var(--red, #004225);
}

.aurora-tab:focus-visible {
  outline: 2px solid var(--red, #004225);
  outline-offset: 2px;
}

.aurora-tab-panels {
  position: relative;
  width: 100%;
}

.aurora-tab-panel {
  display: none;
  width: 100%;
}

.aurora-tab-panel.is-active {
  display: block;
}

/* The .row-image inside each panel keeps its own aspect-ratio + styling. */
.aurora-tab-panel > .row-image {
  width: 100%;
  flex: none;
}

/* On mobile, the wrap drops into the column flow like .row-image does. */
@media (max-width: 768px) {
  .aurora-tabs-wrap {
    flex: 0 0 auto;
    width: 100%;
  }

  .aurora-tab {
    flex: 1 1 0;
    text-align: center;
    padding: 10px 8px 12px;
  }
}

/* =====================
   PHOTOGRAPHY PAGE — NEOPLASTIC GRID
   The Photography page uses a CSS Grid where each photo is a hard-
   edged rectangle of varying size — squares, tall rects, wide rects
   and the occasional feature block — packed densely so the result
   reads as a Mondrian-style composition. No titles, just shapes
   filled with photographs. NYC skyline anchors the composition as a
   hero banner above the two sections.
   ===================== */
.photo-main {
  padding: 140px 56px 80px;
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.photo-page-label {
  text-align: left;
  margin-bottom: 8px;
}

/* ----- NYC skyline hero ----- */
.photo-hero {
  width: 100%;
  display: block;
  position: relative;
  margin: 0;
  padding: 0;
}

.photo-hero-tile {
  display: block;
  width: 100%;
  aspect-ratio: 21 / 9;
  overflow: hidden;
  background-color: #1E2022;
  border: none;
  padding: 0;
  cursor: none;
  position: relative;
}

.photo-hero-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 60%;
  display: block;
  transition: transform 0.9s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.photo-hero-tile:hover img {
  transform: scale(1.02);
}

@media (max-width: 820px) {
  .photo-hero-tile { aspect-ratio: 16 / 9; }
}

@media (max-width: 480px) {
  .photo-hero-tile { aspect-ratio: 4 / 3; }
}

/* ----- Mondrian grid ----- */
.photo-mosaic {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-auto-rows: minmax(0, 1fr);
  grid-auto-flow: dense;
  gap: 6px;
  width: 100%;
  /* Force square base cells via aspect-ratio on the container */
  --cell-aspect: 1;
}

/* Square base cells: each row's height equals one column's width.
   We achieve this by making the grid container aspect track the
   number of rows. Instead we set explicit grid-auto-rows in each
   breakpoint based on a calc that mirrors the column width. */
.photo-mosaic {
  grid-auto-rows: calc((100vw - 56px * 2 - 6px * 5) / 6);
}

@media (max-width: 1280px) {
  .photo-mosaic {
    grid-template-columns: repeat(6, 1fr);
    grid-auto-rows: calc((100vw - 56px * 2 - 6px * 5) / 6);
  }
}

@media (max-width: 820px) {
  .photo-main { padding: 120px 28px 60px; gap: 22px; }
  .photo-mosaic {
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: calc((100vw - 28px * 2 - 6px * 3) / 4);
    gap: 6px;
  }
}

@media (max-width: 520px) {
  .photo-mosaic {
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: calc((100vw - 28px * 2 - 6px * 2) / 3);
  }
}

.photo-tile {
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: none;
  position: relative;
  overflow: hidden;
  background-color: #1E2022;
  cursor: none;
  /* Default 1x1 — overridden by .span-* classes below */
  grid-column: span 1;
  grid-row: span 1;
}

.photo-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              filter 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              opacity 0.5s ease;
  opacity: 0;
  will-change: transform, filter;
}

.photo-tile img.is-loaded { opacity: 1; }

.photo-tile:hover img {
  transform: scale(1.04);
  filter: brightness(1.05);
}

/* Scroll-reveal: tile slides up and fades in as it enters the viewport.
   The animation is intentionally slow (1.1s) and lightly staggered for
   that "developing photograph" feel. Once a tile gets .is-visible, the
   class never comes off — so re-scrolling doesn't replay the anim. */
.photo-tile.reveal {
  opacity: 0;
  transform: translateY(28px) scale(0.985);
  transition: opacity 1.1s cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 1.1s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: opacity, transform;
}

.photo-tile.reveal.is-visible {
  opacity: 1;
  transform: translateY(0) scale(1);
}

/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .photo-tile.reveal,
  .photo-tile.reveal.is-visible {
    transition: none;
    opacity: 1;
    transform: none;
  }
}

/* Mondrian span classes — assigned by JS from the data's `s` field. */
.photo-tile.span-1x1 { grid-column: span 1; grid-row: span 1; }
.photo-tile.span-2x1 { grid-column: span 2; grid-row: span 1; }
.photo-tile.span-1x2 { grid-column: span 1; grid-row: span 2; }
.photo-tile.span-2x2 { grid-column: span 2; grid-row: span 2; }
.photo-tile.span-3x1 { grid-column: span 3; grid-row: span 1; }
.photo-tile.span-1x3 { grid-column: span 1; grid-row: span 3; }
.photo-tile.span-2x3 { grid-column: span 2; grid-row: span 3; }
.photo-tile.span-3x2 { grid-column: span 3; grid-row: span 2; }

/* On the narrowest viewports, collapse oversized blocks so they
   never exceed the column count. */
@media (max-width: 820px) {
  .photo-tile.span-3x2 { grid-column: span 3; grid-row: span 2; }
  .photo-tile.span-3x1 { grid-column: span 2; grid-row: span 1; }
}

@media (max-width: 520px) {
  .photo-tile.span-3x2 { grid-column: span 3; grid-row: span 2; }
  .photo-tile.span-3x1 { grid-column: span 3; grid-row: span 1; }
  .photo-tile.span-2x3 { grid-column: span 2; grid-row: span 3; }
}

/* Section spacing — no title between, just rhythmic whitespace */
.photo-mosaic + .photo-mosaic {
  margin-top: 8px;
}

/* =====================
   PHOTO LIGHTBOX
   ===================== */
.photo-lightbox {
  position: fixed;
  inset: 0;
  background-color: rgba(15, 15, 17, 0.94);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 9000;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.photo-lightbox.is-open {
  display: flex;
  opacity: 1;
}

.photo-lightbox-img {
  max-width: 92vw;
  max-height: 88vh;
  object-fit: contain;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
  user-select: none;
  -webkit-user-drag: none;
}

.photo-lightbox-close,
.photo-lightbox-nav {
  position: absolute;
  background: transparent;
  border: none;
  color: #f7f7f2;
  font-family: var(--font);
  cursor: none;
  padding: 12px 16px;
  line-height: 1;
  transition: opacity 0.2s ease, transform 0.2s ease;
  opacity: 0.7;
}

.photo-lightbox-close:hover,
.photo-lightbox-nav:hover {
  opacity: 1;
}

.photo-lightbox-close {
  top: 24px;
  right: 28px;
  font-size: 36px;
  font-weight: 200;
}

.photo-lightbox-nav {
  top: 50%;
  transform: translateY(-50%);
  font-size: 28px;
}

.photo-lightbox-nav:hover {
  transform: translateY(-50%) scale(1.1);
}

.photo-lightbox-prev { left: 28px; }
.photo-lightbox-next { right: 28px; }

@media (max-width: 768px) {
  .photo-lightbox-close { top: 16px; right: 16px; font-size: 30px; }
  .photo-lightbox-prev  { left: 12px;  }
  .photo-lightbox-next  { right: 12px; }
  .photo-lightbox-nav   { font-size: 22px; padding: 10px 12px; }
}

/* When lightbox is open, lock background scroll */
body.photo-lightbox-open {
  overflow: hidden;
}

/* =====================
   MZIZI PROJECT PAGE
   Brand-identity case study — Bag Design, Website & Phone, Photography.
   Sizing borrowed from the Aurora page so images stay contained (never
   cropped) and the hero stays compact. Helvetica Bold across all titles.
   ===================== */

/* ----- TYPOGRAPHY: Helvetica Bold across every title on this page ----- */
.mzizi-page,
.mzizi-page p,
.mzizi-page a,
.mzizi-page span,
.mzizi-page li {
  font-family: Helvetica, Arial, sans-serif;
}

.mzizi-page .project-title,
.mzizi-page .project-section-title,
.mzizi-page .project-role-title,
.mzizi-page .row-title,
.mzizi-page .row-subtitle,
.mzizi-page .meta-label,
.mzizi-page .project-section-num {
  font-family: Helvetica, Arial, sans-serif;
  font-weight: 700;
}

/* ----- HERO: matches Aurora's sizing exactly — default 75vh min-height
   on the top, flex:1 image wrap, image fills with object-fit: cover.
   Parallax is disabled so the image stays static. ----- */
.mzizi-page img.project-hero-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
  display: block;
  background-color: transparent;
  transform: none;
  will-change: auto;
  animation: none;
}

/* ----- ROW IMAGES: never crop. Aurora's carousel uses object-fit: contain
   for the same reason. The cell background matches the page so contained
   images float on the cream paper rather than sitting in a colored box. ----- */
.mzizi-page .project-section .row-image {
  background-color: rgba(255, 255, 255, 0.05);
  aspect-ratio: 4 / 3;
}

.mzizi-page .project-section .row-image img {
  object-fit: contain;
  width: 100%;
  height: 100%;
}

/* Bag detail — the studio shot is on white; let it sit on the page bg
   with object-fit: contain so the whole bag is visible, top to bottom. */
.mzizi-page .row-image.mzizi-bag {
  background-color: rgba(255, 255, 255, 0.05);
}

/* Devices row — phone-and-web mockup already includes a warm-wood backdrop.
   Show the whole composition (contain) so neither the laptop nor the phone
   gets sliced. */
.mzizi-page .row-image.mzizi-devices {
  background-color: rgba(255, 255, 255, 0.05);
}

.row-image.mzizi-devices img {
  object-fit: contain;
  width: 100%;
  height: 100%;
}

/* Photography section intro — reuses .row-body sizing but sits flush with
   the section header (no .content-row wrapper, so we re-add the padding). */
.mzizi-photo-intro {
  max-width: 720px;
  margin-bottom: 48px;
}

/* Photo mosaic — square base cells, dense packing, subtle gap. Matches the
   look of the dedicated photography page but lives inline as a section. */
.mzizi-photo-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: calc((100vw - 56px * 2 - 6px * 3) / 4);
  grid-auto-flow: dense;
  gap: 6px;
  padding-bottom: 80px;
}

.mzizi-photo-tile {
  display: block;
  position: relative;
  overflow: hidden;
  background-color: #1E2022;
  grid-column: span 1;
  grid-row: span 1;
}

.mzizi-photo-tile.span-2x1 { grid-column: span 2; grid-row: span 1; }
.mzizi-photo-tile.span-1x2 { grid-column: span 1; grid-row: span 2; }
.mzizi-photo-tile.span-2x2 { grid-column: span 2; grid-row: span 2; }

.mzizi-photo-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              filter 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.mzizi-photo-tile:hover img {
  transform: scale(1.04);
  filter: brightness(1.05);
}

@media (max-width: 1100px) {
  .mzizi-photo-grid {
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: calc((100vw - 56px * 2 - 6px * 3) / 4);
  }
}

@media (max-width: 768px) {
  .mzizi-photo-grid {
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: calc((100vw - 28px * 2 - 6px * 2) / 3);
    padding-bottom: 60px;
  }
  .mzizi-photo-tile.span-2x2 { grid-column: span 2; grid-row: span 2; }
  .mzizi-photo-tile.span-2x1 { grid-column: span 2; grid-row: span 1; }
}

@media (max-width: 480px) {
  .mzizi-photo-grid {
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: calc((100vw - 28px * 2 - 6px) / 2);
  }
  .mzizi-photo-tile.span-2x2 { grid-column: span 2; grid-row: span 2; }
  .mzizi-photo-tile.span-2x1 { grid-column: span 2; grid-row: span 1; }
}

/* =====================
   SYNESTHESIA VISUALIZER (About page, Row 3)
   Live audio→color element. The .row-image cell hosts a canvas that
   draws a chromatic-circle bloom — each musical note class lights up
   its own colored petal in real time, driven by the FFT of the
   currently playing track. Search box + result list float above the
   canvas; the now-playing card replaces them once a track is chosen.
   ===================== */
.synesthesia-stage {
  /* Override the default cream .row-image background — the canvas
     draws its own backdrop on top, but the underlying stage stays
     black so any sliver behind the canvas matches. */
  background-color: #000;
  position: relative;
}

.synesthesia-visualizer {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.synesthesia-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Floating UI on top of the canvas — search + results, then now-playing */
.synesthesia-controls {
  position: absolute;
  top: 18px;
  left: 18px;
  right: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 2;
  pointer-events: none; /* children opt back in */
}

.synesthesia-search {
  display: flex;
  gap: 6px;
  pointer-events: auto;
}

.synesthesia-input {
  flex: 1;
  appearance: none;
  background: rgba(20, 22, 28, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.18);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: #f7f7f2;
  font-family: inherit;
  font-size: 14px;
  letter-spacing: 0.02em;
  padding: 10px 14px;
  border-radius: 999px;
  outline: none;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}
.synesthesia-input::placeholder {
  color: rgba(255, 255, 255, 0.55);
}
.synesthesia-input:focus {
  border-color: rgba(255, 255, 255, 0.55);
  background: rgba(20, 22, 28, 0.78);
}

.synesthesia-search-btn {
  appearance: none;
  background: rgba(255, 255, 255, 0.92);
  color: #0F1014;
  border: none;
  font-family: inherit;
  font-size: 15px;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  cursor: pointer;
  transition: transform 0.15s ease, background-color 0.2s ease;
}
.synesthesia-search-btn:hover {
  background: #fff;
  transform: scale(1.05);
}

/* Search results list — translucent cards with album art */
.synesthesia-results {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  pointer-events: auto;
  max-height: 60%;
  overflow-y: auto;
  scrollbar-width: thin;
}
.synesthesia-results:empty {
  display: none;
}
.synesthesia-result {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: rgba(20, 22, 28, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  color: #f7f7f2;
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.15s ease;
}
.synesthesia-result:hover {
  background: rgba(40, 44, 56, 0.85);
  transform: translateX(2px);
}
.synesthesia-result img {
  width: 36px;
  height: 36px;
  border-radius: 6px;
  object-fit: cover;
  flex-shrink: 0;
}
.synesthesia-result-meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.synesthesia-result-title {
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.synesthesia-result-artist {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.6);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.synesthesia-result.no-preview {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Now-playing card pinned to the bottom-left corner of the stage so
   the album cover anchors there, with title/artist + play control
   trailing to its right. Sized to content so it never reaches the
   right edge — leaves the rest of the bottom row clear for the EQ. */
.synesthesia-now {
  position: absolute;
  left: 14px;
  bottom: 14px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px 8px 8px;
  background: rgba(20, 22, 28, 0.78);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  color: #f7f7f2;
  z-index: 3;
  max-width: calc(100% - 28px);
}
.synesthesia-now[hidden] { display: none; }
.synesthesia-art {
  width: 56px;
  height: 56px;
  border-radius: 8px;
  object-fit: cover;
  flex-shrink: 0;
}
.synesthesia-meta {
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-width: 0;
  max-width: 180px;
}
.synesthesia-title {
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.synesthesia-artist {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.65);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.synesthesia-toggle {
  appearance: none;
  background: #fff;
  color: #0F1014;
  border: none;
  font-family: inherit;
  font-size: 16px;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}
.synesthesia-toggle:hover { transform: scale(1.06); }

/* Subtle hint near the bottom while no track is playing */
.synesthesia-hint {
  position: absolute;
  bottom: 16px;
  left: 22px;
  right: 22px;
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.55);
  text-transform: none;
  pointer-events: none;
  z-index: 1;
  transition: opacity 0.4s ease;
}
.synesthesia-visualizer.is-playing .synesthesia-hint {
  opacity: 0;
}

.synesthesia-audio { display: none; }

/* Mobile: keep usable when the row stacks */
@media (max-width: 768px) {
  .synesthesia-input { font-size: 13px; padding: 9px 12px; }
  .synesthesia-search-btn { width: 36px; height: 36px; }
  .synesthesia-art { width: 40px; height: 40px; }
  .synesthesia-toggle { width: 36px; height: 36px; }
}

/* =====================
   RENDEVIEW — hero still image
   The hero image slot is a single uncropped still. Drop every inherited
   constraint that could clip the image (130% height, max-height vh
   bounds, flex stretching, overflow: hidden) and let the wrap size
   itself to the image's natural aspect ratio. The hero section grows
   as tall as needed so the image is fully visible — never cropped.
   ===================== */
.rendeview-page .project-hero-top {
  min-height: 0;
}

.rendeview-page .project-hero-img-wrap {
  flex: 0 0 auto;
  min-height: 0;
  max-height: none;
  height: auto;
  overflow: visible;
  contain: none;
}

.rendeview-page .project-hero-img.rendeview-hero-still {
  width: 100%;
  height: auto;
  display: block;
  background-color: rgba(255, 255, 255, 0.05);
  font-size: 0;
  letter-spacing: 0;
  text-transform: none;
  color: inherit;
  will-change: auto;
  transform: none;
  overflow: visible;
}

.rendeview-page .project-hero-img.rendeview-hero-still img {
  width: 100%;
  height: auto;
  max-width: 100%;
  object-fit: contain;
  object-position: center;
  display: block;
}

/* Dedicated screens carousel — sits beneath the hero/description.
   Container is portrait so the long scrollable Mac captures show at a
   readable size; object-fit: contain guarantees nothing is cropped. */
.rendeview-page .rendeview-screens-section {
  padding: 56px 56px 80px;
}

.rendeview-page .rendeview-screens-carousel {
  position: relative;
  width: 100%;
  max-width: 880px;
  margin: 0 auto;
  aspect-ratio: 3 / 5;
  background-color: rgba(255, 255, 255, 0.05);
  overflow: hidden;
}

.rendeview-page .rendeview-screens-carousel .aurora-carousel-track {
  height: 100%;
}

.rendeview-page .rendeview-screens-carousel img.aurora-carousel-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: top center;
  display: block;
  position: relative;
  z-index: 1;
}

.rendeview-page .rendeview-screens-carousel .aurora-carousel-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

@media (max-width: 768px) {
  .rendeview-page .rendeview-screens-section {
    padding: 40px 28px 64px;
  }
  .rendeview-page .rendeview-screens-carousel {
    aspect-ratio: 3 / 5.5;
  }
}

/* =========================================
   Generic blurred image backdrop for non-carousel .row-image
   containers. Mirrors the aurora-carousel-bg pattern so every
   project-page image cell has a consistent size (4 / 3) with
   the foreground image floating on a soft, blurred copy of
   itself instead of a flat color block.

   Doubled class names (.row-image-bg.row-image-bg) bump the
   selector to specificity (0,3,1) so it wins over per-project
   overrides like `.mzizi-page .project-section .row-image img`
   without resorting to !important.
   ========================================= */
.row-image > img.row-image-bg.row-image-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(34px) saturate(1.1);
  transform: scale(1.2);
  z-index: 0;
  pointer-events: none;
}

.row-image > img.row-image-fg.row-image-fg {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* =========================================
   Shared project-page typography
   ----------------------------------------
   Mirrors the Aurora reading rhythm on every project page
   (Sage, Mzizi, Rendeview, Threadly, Web Espresso Martini, etc.)
   so all case studies share the same body/text styles. Title
   font-family is intentionally NOT touched here — each project
   keeps its own typeface override (Aurora=Havelock,
   Mzizi=Helvetica, Sage uses the page default).
   ========================================= */

/* Justified body copy with hyphenation and a generous line-height */
.project-hero .project-role p,
.project-section .row-body {
  text-align: justify;
  text-justify: inter-word;
  hyphens: auto;
  -webkit-hyphens: auto;
  line-height: 2.05;
}

/* All titles bold across project pages */
.project-hero .project-title,
.project-hero .project-role-title,
.project-hero .meta-label,
.project-section .project-section-title,
.project-section .project-section-num,
.project-section .row-title,
.project-section .row-subtitle {
  font-weight: 700;
}

/* Section titles size-matched to Aurora */
.project-section .project-section-title {
  font-size: clamp(22px, 3vw, 40px);
}

/* Underline meta labels (Date, Length, Role, etc.) */
.project-hero .meta-label {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

/* Top-align image and text inside content rows so the image
   doesn't drift to the vertical middle when text wraps long. */
.project-section .content-row {
  align-items: flex-start;
}

/* =========================================
   Project page themes — gradient backgrounds matching the
   index-page card gradients. Each project gets a single
   monochrome text tone tuned for legibility against its
   gradient. Aurora is intentionally left untouched (keeps
   its solid #0C0C0C asphalt-grey backdrop).
   ========================================= */

/* Mzizi — dark brown to tan; warm beige body text */
body.mzizi-page {
  background-color: #140905;
  background-image: linear-gradient(155deg, #140905 0%, #2A170B 55%, #4D2914 100%);
  background-attachment: fixed;
  background-repeat: no-repeat;
  background-size: cover;
  color: #E8DAC1;
}

.mzizi-page,
.mzizi-page p,
.mzizi-page a,
.mzizi-page span,
.mzizi-page li,
.mzizi-page .footer-logo,
.mzizi-page .footer-links a,
.mzizi-page .footer-copy,
.mzizi-page .project-subtitle,
.mzizi-page .meta-label,
.mzizi-page .meta-value,
.mzizi-page .project-section-num,
.mzizi-page .row-subtitle,
.mzizi-page .row-title-note,
.mzizi-page .row-body,
.mzizi-page .project-role p {
  color: #E8DAC1;
}

.mzizi-page .project-title,
.mzizi-page .project-section-title,
.mzizi-page .project-role-title,
.mzizi-page .row-title,
.mzizi-page .meta-label,
.mzizi-page .project-section-num {
  color: #F5EAD2;
}

/* Sage — dark olive to sage green; pale cream-green body text */
body.sage-page {
  background-color: #161D11;
  background-image: linear-gradient(155deg, #161D11 0%, #2C3A22 55%, #4E6238 100%);
  background-attachment: fixed;
  background-repeat: no-repeat;
  background-size: cover;
  color: #E2E5D0;
}

.sage-page,
.sage-page p,
.sage-page a,
.sage-page span,
.sage-page li,
.sage-page .footer-logo,
.sage-page .footer-links a,
.sage-page .footer-copy,
.sage-page .project-subtitle,
.sage-page .meta-label,
.sage-page .meta-value,
.sage-page .project-section-num,
.sage-page .row-subtitle,
.sage-page .row-title-note,
.sage-page .row-body,
.sage-page .project-role p {
  color: #E2E5D0;
}

.sage-page .project-title,
.sage-page .project-section-title,
.sage-page .project-role-title,
.sage-page .row-title,
.sage-page .meta-label,
.sage-page .project-section-num {
  color: #F0F0DC;
}

/* Rendeview — dark burnt orange; warm beige body text */
body.rendeview-page {
  background-color: #170804;
  background-image: linear-gradient(155deg, #170804 0%, #3F1608 60%, #6E2510 100%);
  background-attachment: fixed;
  background-repeat: no-repeat;
  background-size: cover;
  color: #ECD2B8;
}

.rendeview-page,
.rendeview-page p,
.rendeview-page a,
.rendeview-page span,
.rendeview-page li,
.rendeview-page .footer-logo,
.rendeview-page .footer-links a,
.rendeview-page .footer-copy,
.rendeview-page .project-subtitle,
.rendeview-page .meta-label,
.rendeview-page .meta-value,
.rendeview-page .project-section-num,
.rendeview-page .row-subtitle,
.rendeview-page .row-title-note,
.rendeview-page .row-body,
.rendeview-page .project-role p {
  color: #ECD2B8;
}

.rendeview-page .project-title,
.rendeview-page .project-section-title,
.rendeview-page .project-role-title,
.rendeview-page .row-title,
.rendeview-page .meta-label,
.rendeview-page .project-section-num {
  color: #F8E5CC;
}

/* Soften borders on the gradient backgrounds */
.mzizi-page .project-hero-bottom,
.mzizi-page .project-section,
.mzizi-page .content-row,
.mzizi-page footer,
.sage-page .project-hero-bottom,
.sage-page .project-section,
.sage-page .content-row,
.sage-page footer,
.rendeview-page .project-hero-bottom,
.rendeview-page .project-section,
.rendeview-page .content-row,
.rendeview-page footer {
  border-color: rgba(255, 255, 255, 0.12);
}

/* Subtle dark placeholders for image cells. The blurred row-image-bg
   layer still covers these for real images — this is just the fallback
   tone for empty placeholder cells. */
.mzizi-page .row-image,
.mzizi-page .project-hero-img,
.sage-page .row-image,
.sage-page .project-hero-img,
.rendeview-page .row-image,
.rendeview-page .project-hero-img {
  background-color: rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.4);
}

/* =====================
   SAGE PAGE — Libre Baskerville for titles/headings, Helvetica for body
   The serif is reserved for the project title, section titles, role
   titles, row titles, meta labels, and section numbers. Body copy,
   subtitles, meta values, and the nav stay in Helvetica for readability.
   ===================== */
.sage-page .project-title,
.sage-page .project-section-title,
.sage-page .project-role-title,
.sage-page .row-title,
.sage-page .row-subtitle,
.sage-page .meta-label,
.sage-page .project-section-num,
.sage-page h1,
.sage-page h2,
.sage-page h3,
.sage-page h4,
.sage-page h5,
.sage-page h6 {
  font-family: 'Libre Baskerville', Georgia, 'Times New Roman', serif;
}

.sage-page,
.sage-page p,
.sage-page span,
.sage-page li,
.sage-page .row-body,
.sage-page .project-subtitle,
.sage-page .meta-value,
.sage-page .project-role p,
.sage-page .nav-links a,
.sage-page .footer-links a,
.sage-page .footer-copy {
  font-family: Helvetica, "Helvetica Neue", Arial, sans-serif;
}

/* Keep the K logo in Rubik Mono One on the sage page — the rule above
   would otherwise inherit Helvetica into .logo-img. */
.sage-page .logo-img {
  font-family: 'Rubik Mono One', 'Major Mono Display', 'Bungee', Helvetica, Arial, sans-serif;
}

