/* ==========================================================================
   Reset & Base Styles
   ========================================================================== */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: #c8ccd0; /* Dawn gray background matching PALETTE.bg */
  font-family: 'Inter', sans-serif;
  color: #111111;
}

/* 3D Canvas */
#webgl-canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  display: block;
}

/* Desktop's first 3D entry must never expose the canvas while Three.js is still assembling its
   geometry/post-processing chain. This deliberately shares the WORK page's solid surface instead of
   introducing a new branded screen; the only visible indicator is a thin, measured progress bar. */
.gallery-loading {
  --gallery-load-progress: 0%;
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--view2d-bg, #171718);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 180ms ease, visibility 0s linear 180ms;
}
html.desktop-gallery-boot .gallery-loading,
body.initial-gallery-loading .gallery-loading {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 120ms ease;
}
.gallery-loading-track {
  width: clamp(108px, 12vw, 176px);
  height: 2px;
  overflow: hidden;
  background: rgba(232, 233, 235, 0.22);
}
.gallery-loading-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: #e8e9eb;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
.gallery-loading[style*='--gallery-load-progress'] .gallery-loading-fill {
  transform: scaleX(var(--gallery-load-progress-decimal, 0));
}
body.initial-gallery-loading #webgl-canvas {
  visibility: hidden;
}

/* Mobile browsers treat a long press on the WebGL canvas like pressing an image/link: Safari can show
   its callout and Chromium briefly paints the selected element blue. The gallery owns this interaction,
   so suppress only those browser affordances without preventing vertical touch movement/navigation. */
body.view-3d,
body.view-3d #webgl-canvas {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-drag: none;
}
body.view-3d #webgl-canvas {
  /* Gallery navigation owns touch movement in js/navigation.js; prevent the browser from treating a
     stationary mirror hold as a selectable/downloadable image gesture. */
  touch-action: none;
}

/* ==========================================================================
   Premium Top Bar UI (Overlay)
   ========================================================================== */
.top-bar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 60px;
  background-color: #ffffff;
  border-bottom: 1px solid #111111;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 40px;
  z-index: 10;
  pointer-events: auto;
  transform: translate3d(0, 0, 0); /* Force layer isolation to prevent WebGL hover flickering */
  will-change: transform;
  transition: opacity 0.45s ease;
}

/* First paint: keep the bar hidden until the initial view (scene or list) is drawn, so the static bar
   doesn't flash in before the WebGL scene. JS drops .booting once the first frame is painted. */
body.booting .top-bar {
  opacity: 0;
  pointer-events: none;
}

/* Mobile's bare URL resolves to the video-reel home. Keep the WebGL canvas hidden during boot so its
   temporary default scene cannot flash before the router switches to that home view. Explicit ?view=3d
   still reveals the same canvas once the initial 3D frame is ready. Desktop is intentionally unchanged. */
@media (hover: none) and (pointer: coarse) {
  body.booting #webgl-canvas { visibility: hidden; }
}

/* UA check runs synchronously in <head>, before app.js or the canvas can paint. The media-query rule
   above remains as a fallback for touch devices with an unusual UA string. */
html.mobile-boot body.booting #webgl-canvas { visibility: hidden; }

.top-bar-left {
  display: flex;
  align-items: center;
  gap: 20px;                /* visual text spacing ≈34px once the items' hit-area padding (7px) is added */
}

.designer-name {
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 1px; /* Reduced by half per user request */
  padding: 13px 7px;   /* ~44px tall touch target (home button) — text stays centered in the 60px bar */
  transition: color 0.2s ease;
}
/* Desktop shows the wordmark; the favicon logo is a mobile-only swap (see the max-width:768 block). */
.designer-name .brand-logo { display: none; }
.designer-name:hover {
  color: #888888;
}

/* INFO + WORKS (=view-portal) are one unified nav form: same size/weight/spacing/color/hover. The
   13px vertical padding grows the tap target to ~44px without moving the (centered) text. */
.nav-link {
  font-family: 'Inter', sans-serif;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #111111;
  text-decoration: none;
  cursor: pointer;
  padding: 13px 7px;
  transition: color 0.2s ease;
}
.nav-link:hover {
  color: #888888;
}
/* Hover/press underline: a thin line that wipes in from the left and out to the right. Uses currentColor
   so it reads correctly on every bar skin (white info / dark detail-list / frosted 3D). */
.nav-link { position: relative; }
.nav-link::after {
  content: '';
  position: absolute;
  left: 7px;
  right: 7px;
  bottom: 11px;
  height: 1px;
  background: currentColor;
  opacity: 0.55;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform 0.25s ease;
}
.nav-link:hover::after,
.nav-link:active::after {
  transform: scaleX(1);
  transform-origin: left center;
}
/* Active (current-page) nav item: white text + persistent underline, on every bar skin. The per-view
   prefixes give higher specificity than the per-view color rules so white always wins. */
body.view-3d .nav-link.active,
body.view-detail .nav-link.active,
body.view-list .nav-link.active,
body.view-info .nav-link.active { color: #ffffff; }
.nav-link.active::after {
  transform: scaleX(1);
  transform-origin: left center;
  opacity: 0.9;
}
/* Tighten the "3D GALLERY" inter-word gap (was carried by the old .view-portal-label). */
#gallery-link { word-spacing: -2px; }

/* ==========================================================================
   3D scene chrome: the SAME bar geometry dressed as a full-width frosted band (translucent
   dark + light backdrop blur + hairline light border), reusing the scroll-hint's frosted
   language. Because only the skin changes, name/INFO/portal sit at identical coordinates in
   both views. On the 2D list / detail / info the bar stays the default flat white (rules above).
   ========================================================================== */
body.view-3d .top-bar {
  background-color: rgba(6, 8, 11, 0.28);
  border-bottom: 1px solid rgba(255, 255, 255, 0.175);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
/* Match the work/detail palette (name #e8e9eb, nav #b4b7bd) while KEEPING the translucent frosted bar
   and the legibility text-shadow over the scene. */
body.view-3d .designer-name {
  color: #e8e9eb;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.55);
}
body.view-3d .designer-name:hover {
  color: #ffffff;
}
body.view-3d .nav-link {
  color: #b4b7bd;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.55);
}
body.view-3d .nav-link:hover {
  color: #ffffff;
}

/* ==========================================================================
   WORKS / 3D GALLERY portal — the single view-switch, now a peer nav item sitting right after
   INFO (typography comes from .nav-link above; the 3D-band light text from body.view-3d .nav-link).
   It's a <button> whose label swaps by view. Hidden on detail/info.
   ========================================================================== */
.view-portal {
  /* padding comes from .nav-link (13px 7px) → ~44px tap target; only reset the button chrome here */
  background: none;
  border: none;
  line-height: 1;
}
/* Tighten only the inter-word gap in "3D GALLERY" (no effect on the single-word "WORKS"). */
.view-portal-label {
  word-spacing: -2px;
}

/* The WORKS portal stays visible in EVERY view — on a detail/info page it reads "WORKS" and links
   back to the 2D list (label set in JS). */

/* ==========================================================================
   First-visit scroll hint — a soft frosted tile that melds into the 3D fog
   (translucent dark surface + light backdrop blur + thin mirror-colored outline,
   static RGB film grain, light thin label, animated mouse icon). Present from the
   first frame (no fade-in); fades out slowly on the first scroll/interaction.
   ========================================================================== */
.scroll-hint {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;                 /* above the canvas, below #app-content (5) and the top-bar (10) */
  pointer-events: none;       /* never block clicking the mirrors underneath */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 30px 46px;
  background-color: rgba(6, 8, 11, 0.23);   /* subtle black tint (half of the previous 0.46) */
  border: 1px solid #ffffff;                /* thin white outline */
  backdrop-filter: blur(1px);               /* very light blur */
  -webkit-backdrop-filter: blur(1px);
  color: #f0f0f0;
  overflow: hidden;                         /* clip the grain overlay to the tile */
  opacity: 0;                               /* hidden until JS activates it (guards mobile/deep-link) */
}
.scroll-hint.visible {
  opacity: 1;                               /* shown INSTANTLY — no fade-in */
}
.scroll-hint.visible.dismissed {
  opacity: 0;
  transition: opacity 1.3s ease;            /* relaxed fade-out on first scroll */
}

/* Static, non-animated RGB film grain baked onto the popup — denser (~2x) than the scene grain. */
.scroll-hint::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='170' height='170'%3E%3Cfilter id='sg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='170' height='170' filter='url(%23sg)'/%3E%3C/svg%3E");
  opacity: 0.22;
  mix-blend-mode: overlay;
  pointer-events: none;
}

.scroll-mouse {
  width: 26px;
  height: 42px;
  border: 1.5px solid #f0f0f0;
  border-radius: 13px;
  display: flex;
  justify-content: center;
  padding-top: 7px;
  box-sizing: border-box;
  filter: drop-shadow(0 1px 5px rgba(0, 0, 0, 0.45));
}
.scroll-swipe {
  display: none;
  width: 40px;
  height: 56px;
  filter: drop-shadow(0 1px 5px rgba(0, 0, 0, 0.45));
}
.scroll-swipe svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: none;
  stroke: #f0f0f0;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.scroll-swipe-finger { stroke-width: 1.55; }
.scroll-swipe-arrow {
  stroke-width: 1.35;
}
.scroll-swipe-arrow-top {
  animation: scroll-swipe-top 1.6s ease-in-out infinite;
}
@keyframes scroll-swipe-top {
  0%, 100% { opacity: 0.32; transform: translateY(1px); }
  50% { opacity: 1; transform: translateY(-2px); }
}
.scroll-wheel {
  width: 3px;
  height: 8px;
  border-radius: 2px;
  background: #f0f0f0;
  animation: scroll-wheel-move 1.6s ease-in-out infinite;
}
@keyframes scroll-wheel-move {
  0%   { opacity: 0; transform: translateY(-3px); }
  30%  { opacity: 1; }
  70%  { opacity: 1; transform: translateY(7px); }
  100% { opacity: 0; transform: translateY(9px); }
}

.scroll-hint-text {
  font-family: 'Inter', sans-serif;
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 1px;
  text-transform: uppercase;
  white-space: nowrap;
  text-shadow: .5px 0 rgba(255, 255, 255, 0.3), -.5px 0 rgba(180, 180, 180, 0.3), 0 1px 6px rgba(0, 0, 0, 0.5);
}

/* Glitch layers — a noise-frayed outline overlay and a container for the flickering grain chips (JS-driven). */
.scroll-hint-edge {
  position: absolute;
  inset: 0;
  border: 1px solid #ffffff;
  opacity: 0.32;
  pointer-events: none;
}
.scroll-hint-shards {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* Ambient/exit burst states (toggled by JS): monochrome chroma-less split on the label + brighter frayed edge. */
.scroll-hint.sh-blip .scroll-hint-text {
  text-shadow: 2px 0 rgba(255, 255, 255, 0.62), -2px 0 rgba(150, 150, 150, 0.5), 0 1px 6px rgba(0, 0, 0, 0.5);
}
.scroll-hint.sh-wild .scroll-hint-text {
  text-shadow: 4px 0 rgba(255, 255, 255, 0.8), -4px 0 rgba(140, 140, 140, 0.65), 0 1px 6px rgba(0, 0, 0, 0.5);
  transform: translateX(-1px);
}
.scroll-hint.sh-blip .scroll-hint-edge,
.scroll-hint.sh-wild .scroll-hint-edge {
  opacity: 0.92;
}
.scroll-hint.sh-wild {
  box-shadow: -4px 0 rgba(255, 255, 255, 0.45), 4px 0 rgba(255, 255, 255, 0.3);
}

/* ==========================================================================
   Info Page Layout Styles (standalone ?view=info overlay)
   ========================================================================== */
/* Back bar mirrors the detail page: same top offset + left-aligned < Back link
   (the link itself inherits the shared #app-content .back-link styling). */
.info-back-bar {
  width: 100%;
  max-width: 1400px;
  margin: 40px auto 0 auto;
  padding: 25px 12px 0 12px;
  box-sizing: border-box;
}

.info-view-container {
  width: 100%;
  max-width: 800px; /* Increased from 720px to allow 4-line paragraph wrapping */
  margin: 30px auto 90px auto;
  padding: 0 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  box-sizing: border-box;
}

.info-title {
  font-size: 32px;
  font-weight: 500;
  letter-spacing: -0.5px;
  color: #111111;
  margin: 0 0 56px 0;
}

.info-profile-wrapper {
  margin-bottom: 92px;
  /* Cube face length — a touch under the midpoint of the old flat image's rendered
     width (240px) and height (~319px). */
  --cube-size: 250px;
}

/* Lightweight CSS 3D cube replacing the flat profile image. Only the cube rotates;
   its position is fixed. The scene reserves extra height so rotated corners don't
   collide with the text below. */
.info-cube-scene {
  width: 100%;
  height: calc(var(--cube-size) * 1.4);
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 1000px;
  opacity: 0; /* Hidden by default until textures are fully loaded */
  transition: opacity 0.4s ease;
}

/* A finger that begins on the cube belongs to the cube: do not let the browser hand that gesture to
   the Info page scroll. Touches that begin anywhere else in the page keep their normal scrolling. */
@media (max-width: 768px) {
  .info-cube-scene {
    touch-action: none;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
}

.info-cube {
  position: relative;
  width: var(--cube-size);
  height: var(--cube-size);
  margin: 0;
  transform-style: preserve-3d;
  will-change: transform;
  /* Hover is only a visual glance. The grab cursor appears after the pointer is actually pressed. */
  cursor: default;
  -webkit-user-select: none;
  user-select: none;
}
.info-cube.is-dragging { cursor: grabbing; }

.info-cube-face {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--cube-size);
  height: var(--cube-size);
  background-color: #d8d8d8; /* fallback tint until a texture loads */
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover; /* crop the longer side of non-square images */
  backface-visibility: hidden;
  -webkit-user-drag: none;
}

.info-cube-face--front  { transform: rotateY(0deg)   translateZ(calc(var(--cube-size) / 2)); }
.info-cube-face--back   { transform: rotateY(180deg) translateZ(calc(var(--cube-size) / 2)); filter: brightness(0.82); }
.info-cube-face--right  { transform: rotateY(90deg)  translateZ(calc(var(--cube-size) / 2)); filter: brightness(0.9); }
.info-cube-face--left   { transform: rotateY(-90deg) translateZ(calc(var(--cube-size) / 2)); filter: brightness(0.9); }
.info-cube-face--top    { transform: rotateX(90deg)  translateZ(calc(var(--cube-size) / 2)); filter: brightness(1.08); }
.info-cube-face--bottom { transform: rotateX(-90deg) translateZ(calc(var(--cube-size) / 2)); filter: brightness(0.72); }

.info-body {
  max-width: 800px; /* Increased from 720px to match the container width */
  margin-bottom: 60px;
}

.info-body p {
  font-size: 15px;
  line-height: 1.9;
  color: #555555;
  font-weight: 400;
  margin: 0;
}

.info-email {
  display: inline-block;
  font-size: 15px;
  color: #111111;
  text-decoration: none;
  font-weight: 500;
  margin-bottom: 60px;
}

.info-social-links {
  display: flex;
  gap: 30px;
}

.info-social-link {
  font-size: 15px;
  color: #111111;
  text-decoration: underline;
  text-underline-offset: 4px;
  font-weight: 400;
  transition: color 0.2s ease;
}
.info-social-link:hover {
  color: #888888;
}

@media (max-width: 768px) {
  .info-back-bar {
    margin-top: 24px;
    padding-top: 20px;
    padding-left: 24px;   /* nudge the "<" inward so it isn't flush against the edge on mobile */
  }
  .info-view-container {
    margin: 26px auto 70px auto;
  }
  .info-title {
    font-size: 26px;
    margin-bottom: 44px;
  }
  .info-profile-wrapper {
    margin-bottom: 74px;
    /* Slightly reduced mobile size. */
    --cube-size: 198px;
  }
  .info-body {
    margin-bottom: 46px;
  }
  .info-body p {
    font-size: 14px;
    line-height: 1.8;
  }
  .info-email {
    margin-bottom: 46px;
  }
}

/* ==========================================================================
   2D Grid List View styles
   ========================================================================== */
/* Full-bleed, immersive list (reference: haramjung.tv): the grid runs edge-to-edge with no margins;
   only the WORK title + filter header keep side padding. */
.list-view-container {
  width: 100%;
  max-width: none;
  margin: 40px 0 0 0;
  padding: 0;
}

/* Close symbol inside active filter tags */
.list-filter-tag .filter-close {
  margin-left: 6px;
  font-size: 11px;
  font-weight: bold;
  opacity: 0.8;
}

/* Work page title (in-flow, top-left) — replaces the old "Works" label that sat beside the filters.
   Thin editorial wordmark; color is set per-theme in the dark-theme block. */
.list-brand {
  font-size: 26px;
  font-weight: 300;
  letter-spacing: normal;
  text-transform: uppercase;
  color: #e8e9eb;
  padding-top: 25px;
  padding-left: 20px;   /* the grid is full-bleed; the header keeps its own inset */
}

.list-header-bar {
  display: flex;
  justify-content: flex-end;     /* filters sit RIGHT-aligned, directly above the first thumbnail */
  align-items: center;
  /* header divider line removed per request (was border-bottom: 1px solid #e2e2e2) */
  padding: 40px 20px 0 20px;     /* clear air below the WORK title; inset matches .list-brand */
  margin-bottom: 14px;           /* … and a tight seat right on top of the grid */
  box-sizing: border-box;
}

.list-title {
  font-size: 16px; /* Matched with back-link font-size */
  font-weight: 500; /* Matched with back-link font-weight */
  color: #555555; /* Matched with back-link text color */
  text-transform: uppercase;
}

/* Category Filter Tags inside List page */
.list-filter-tags {
  display: flex;
  gap: 10px;
}

/* Compact solid chips (reference: GRAPHIC DRUMMER's 2D/3D/BRANDING tags) — no outline boxes. */
.list-filter-tag {
  background-color: #26282d;
  border: none;
  color: #b4b7bd;
  padding: 6px 12px;
  font-family: 'Inter', sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  cursor: pointer;
  outline: none;
  transition: background-color 0.2s, color 0.2s;
}

.list-filter-tag.active {
  background-color: #e8e9eb;
  color: #131418;
}

.list-filter-tag:hover:not(.active) {
  background-color: #33363c;
}

/* 3-column portfolio grid (desktop) — previous card structure: image on top, title + meta below. */
.portfolio-list-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;               /* thumbnails butt directly against each other (full-bleed tiling) */
  width: 100%;
}

/* Intermediate desktop width: drop from 3 → 2 columns before the mobile (1-column) breakpoint at 768px,
   so the cards never get uncomfortably narrow. This is still the DESKTOP layout (top bar unchanged) — the
   mobile view only kicks in at ≤768px. */
@media (min-width: 769px) and (max-width: 1024px) {
  .portfolio-list-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.portfolio-card {
  border: none;
  background-color: transparent;
  cursor: pointer;
  display: block;
  text-decoration: none;
  color: inherit;
}

.card-thumbnail-wrapper {
  position: relative;            /* anchors the hover-video + title-overlay layers */
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background-color: #1a1c21;
}
.card-thumbnail {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Hover/touch preview: the video layer WIPES in from the left over the still (clip-path reveal —
   the same left→right wipe language as the nav underlines; mirrored burger direction). The video
   itself doesn't translate (a full spatial slide of a wide image read as clumsy), so it lands as a
   clean curtain reveal. Wipes back out the way it came on leave. */
.card-hover-poster,
.card-hover-video {
  position: absolute;
  /* Overscan 1px on each side so the video ALWAYS fully covers the still beneath it. In the desktop
     3-column grid the middle column's edges land on fractional pixels, so a width:100% video and the
     width:100% <img> snap to device pixels differently → a ~1px sliver of the still leaks on the right.
     The wrapper's overflow:hidden clips the overscan, so this only closes the seam (no visual bleed). */
  top: 0;
  left: -1px;
  width: calc(100% + 2px);
  height: 100%;
  object-fit: cover;
  clip-path: inset(0 100% 0 0);  /* hidden at rest (everything right of x=0 clipped) */
  transition: clip-path 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
  pointer-events: none;          /* clicks always hit the card link */
}
.card-hover-poster {
  z-index: 1;                    /* instant desktop fallback above the WORK still */
}
.card-hover-video {
  z-index: 2;                    /* replaces the fallback as soon as its first frame decodes */
}
.portfolio-card.peek .card-hover-poster,
.portfolio-card.peek .card-hover-video {
  clip-path: inset(0 0 0 0);
  will-change: clip-path;        /* allocated only while a card is actively revealing */
}

/* Title lives ON the thumbnail now: hidden at rest, fades in with the video preview on hover/touch
   (reference: haramjung.tv). Text shadow keeps it legible over bright frames — no gradient bar. */
.card-title-overlay {
  position: absolute;
  left: 22px;
  bottom: 20px;
  right: 22px;
  z-index: 3;
  color: #f2f2f3;
  font-family: 'Inter', sans-serif;
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -0.3px;
  line-height: 1.2;
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.7);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.portfolio-card.peek .card-title-overlay {
  opacity: 1;
}

/* ==========================================================================
   SPA Content Overlays (List & Detail Pages)
   ========================================================================== */
#app-content {
  position: fixed;
  top: 60px;
  left: 0;
  width: 100%;
  height: calc(100% - 60px);
  z-index: 5;
  background-color: #ffffff;
  overflow-y: auto;
  pointer-events: auto;
  opacity: 1;
  transition: opacity 0.5s ease;
}

#app-content.hidden {
  display: none;
  opacity: 0;
  pointer-events: none;
}

/* lil-gui custom styling to match the editorial flat layout */
.lil-gui {
  --background-color: #ffffff !important;
  --text-color: #111111 !important;
  --title-background-color: #ffffff !important;
  --widget-color: #f0f2f5 !important;
  --hover-color: #e0e2e5 !important;
  --focus-color: #d0d2d5 !important;
  --number-color: #111111 !important;
  --string-color: #111111 !important;
  font-family: 'Inter', sans-serif !important;
  border: 1px solid #111111 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  top: 80px !important;
  right: 40px !important;
  z-index: 100 !important;
}

.lil-gui .title {
  font-weight: 700 !important;
  border-bottom: 1px solid #111111 !important;
  text-transform: uppercase !important;
  letter-spacing: 1px !important;
}

/* Responsive adjustment */
/* Responsive adjustment */
@media (max-width: 768px) {
  .top-bar {
    padding: 0 16px;
  }
  /* Mobile swaps the "HAENA JANG" wordmark for the favicon logo — the long, space-containing name read
     too close to INFO / WORKS. The logo is the home button (same click handler on .designer-name). */
  .designer-name {
    font-size: 15px;
    padding: 8px 5px;
    display: flex;
    align-items: center;
  }
  .designer-name .brand-text { display: none; }
  .designer-name .brand-logo {
    display: block;
    width: 30px;
    height: 30px;
    object-fit: cover;
    border-radius: 22%;   /* slight rounding; corners fall outside the radius → transparent (alpha) */
  }
  .nav-link {
    font-size: 14px;       /* bump the top menu up one px on mobile (was the 13px base) */
    padding: 13px 5px;
  }
  .top-bar-left {
    gap: 8px;              /* visual text spacing ≈18px with the 5px hit-area padding */
  }
}

/* ==========================================================================
   2D Detail Page Layout Styles
   ========================================================================== */

/* Enforce NO rounding and NO shadows globally inside details */
#app-content *, #app-content *::before, #app-content *::after {
  border-radius: 0 !important;
  box-shadow: none !important;
}

#app-content .container {
  width: 100%;
  max-width: 1400px;
  display: flex;
  flex-direction: column;
  margin: 72px auto 60px auto;  /* more space between the fixed top bar and the title (was 40px) */
  padding: 0 12px;
}

#app-content .header-section {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 60px;
  margin-bottom: 36px;  /* title → CATEGORY/YEAR spec gap (the spec block owns the gap to the video) */
  padding-top: 25px;
  padding-bottom: 20px;
  padding-left: 12px;  /* left inset matches the mobile container inset */
  box-sizing: border-box;
}

/* CATEGORY / YEAR two-column spec between the title and the video — labels + values, no lines.
   Generous air on both sides (36px from the title above, 96px to the video below). */
#app-content .detail-meta {
  display: flex;
  gap: 90px;
  padding-left: 12px;
  margin: 0 0 96px 0;
}
#app-content .detail-meta-label {
  display: block;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: uppercase;
  color: #7d8087;
  margin-bottom: 9px;
}
#app-content .detail-meta-value {
  font-size: 15px;
  font-weight: 300;
  color: #e8e9eb;
}

/* The back control is just "<" in every view (the word "Back" is always hidden), sized relative to
   the page title so it reads as a peer of it rather than a tiny link. */
.back-link .back-label { display: none; }
#app-content .back-link {
  font-size: 30px;               /* 3/4 of the 40px desktop title (same ratio as mobile) */
  font-weight: 400;
  letter-spacing: normal;
  line-height: 1;
  color: #555555;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  padding: 8px 16px 8px 2px;     /* enlarge the tap area (right/vertical) without breaking the left inset */
  transition: color 0.2s ease;
  cursor: pointer;
}

#app-content .back-link:hover {
  color: #222222;
}

#app-content h1.project-title {
  font-size: 60px;           /* bigger hero title (one-line fit shrinks it if it would overflow) */
  font-weight: 500;          /* lighter, editorial (was a heavy 700) */
  letter-spacing: -0.5px;
  text-transform: uppercase;
  color: #111111;
  margin: 0;
  line-height: 1.2; /* Fixed title line-height to maintain consistent header height */
  /* Flex-item measurement fix: without min-width:0 the h1 grows to its full nowrap text width
     (min-width:auto), so clientWidth === scrollWidth and the one-line shrink loop never fires —
     the title just clipped off-screen. flex:1 + min-width:0 pins clientWidth to the available
     space so fitDetailTitleToOneLine() can detect overflow and also re-grow on wider windows. */
  flex: 1 1 auto;
  min-width: 0;
}

#app-content .video-wrapper {
  position: relative;            /* anchor the absolutely-filled iframe */
  width: 100%;
  aspect-ratio: 16/9;
  background-color: #111111;
  margin-bottom: 140px;
  /* 1px frame drawn with OUTLINE (not border) so it doesn't shrink the content box. A border makes the
     inner box slightly wider-ratio than the video, so the Vimeo player pillarboxes it (~1–2px black bars
     left/right). Outline overlays the edge without changing layout → the iframe fills the exact ratio. */
  outline: 1px solid #222222;
  outline-offset: -1px;
}

/* Absolutely fill the wrapper so the iframe can't push it taller than its aspect-ratio. A static
   iframe falls back to its 150px intrinsic height when height:100% can't resolve, which for the short
   ultrawide THE TREES ratio (2.37:1 → ~145px) made the box too tall and letterboxed the video with
   black bars top/bottom. Absolute positioning removes it from flow so the box keeps its exact ratio. */
#app-content .video-wrapper iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
  display: block;
}

#app-content .content-description {
  text-align: center;
  max-width: 760px;
  margin: 0 auto 140px auto;
  font-size: 16px;
  color: #555555;
  font-weight: 400;
  line-height: 1.8;
}

/* The Trees overview reads wider so each of its 3 paragraphs lands in ≤2 lines (not 3). */
#app-content .content-description.wide {
  max-width: 940px;
}

#app-content .styleframes-grid {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 5px;
  row-gap: 8px;
  margin-bottom: 140px;
}

#app-content .styleframe-item {
  width: 100%;
  border: 1px solid #e2e2e2;
  overflow: hidden;
}

#app-content .styleframe-item img {
  width: 100%;
  height: auto;
  display: block;
}

/* Process Section Title (1px larger than Back link) */
#app-content .detail-section-title {
  font-size: 17px;
  font-weight: 700;
  text-align: left;
  text-transform: uppercase;
  letter-spacing: normal;
  color: #222222;
  margin-top: 40px;
  margin-bottom: 24px;
  width: 100%;
}

/* Team Wang Special Layout: 7 above, 6(L) and 5(R) below with matching heights */
#app-content .teamwang-special-layout {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 8px;
}

#app-content .teamwang-special-layout .special-row-top {
  width: 100%;
}

#app-content .teamwang-special-layout .special-row-bottom {
  display: flex;
  gap: 5px;
  width: 100%;
  align-items: stretch;
}

#app-content .teamwang-special-layout .special-row-bottom .item-left {
  /* Process_06 (1300x2000): aspect ratio 0.65 */
  flex: 0.65 1 0%;
}

#app-content .teamwang-special-layout .special-row-bottom .item-right {
  /* Process_05 (1920x1080): aspect ratio 1.777 */
  flex: 1.777 1 0%;
}

#app-content .teamwang-special-layout img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

#app-content .section-divider {
  width: 60px;
  height: 1px;
  background-color: #e2e2e2;
  margin: 60px auto;
}

#app-content .info-section {
  width: 100%;
  max-width: 760px;
  margin: 0 auto 60px auto;
}

#app-content .info-section h3 {
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: normal;
  color: #222222;
  margin-bottom: 30px;
  text-align: center;
}

#app-content .credits-list {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;
}

#app-content .credit-row {
  display: flex;
  width: 100%;
  max-width: 600px;
  justify-content: center;
  line-height: 1.6;
}

#app-content .credit-key {
  width: 50%;
  text-align: right;
  padding-right: 20px;
  font-size: 12px;
  font-weight: 700;
  color: #888888;
  letter-spacing: normal;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

#app-content .credit-value {
  width: 50%;
  text-align: left;
  padding-left: 20px;
  font-size: 14px;
  font-weight: 400;
  color: #444444;
  display: block;
}

#app-content .footer-section {
  width: 100%;
  max-width: 1280px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  border-top: 1px solid #e2e2e2;
  padding-top: 30px;
  margin: 40px auto 0 auto;
}

@media (max-width: 768px) {
  #app-content .container {
    padding: 0 8px;
    margin: 40px auto 48px auto; /* more gap between the top bar and the title on mobile too (was 16px) */
  }
  /* The back "<" is gone; the title now sits alone in the header, with a left inset off the screen edge. */
  #app-content .header-section {
    flex-direction: row;
    align-items: center;
    height: auto;
    padding-top: 10px;
    padding-bottom: 18px;
    padding-left: 8px;
    margin-bottom: 26px;  /* title → spec gap on mobile (spec block owns the gap to the video) */
  }
  #app-content .detail-meta {
    gap: 56px;
    padding-left: 8px;
    margin-bottom: 64px;
  }
  #app-content .back-link {
    font-size: 21px;      /* ≈ 3/4 of the 28px mobile title */
  }
  #app-content h1.project-title {
    font-size: 42px;
    line-height: 1.12;
  }
  #app-content .styleframes-grid {
    grid-template-columns: 1fr;
    gap: 15px;
  }
  /* Tighten the vertical rhythm on mobile — the desktop 140px section gaps read as too airy on a phone.
     Reference (tendril image 5): moderate breaks. Roughly halve the between-section spacing. */
  #app-content .video-wrapper { margin-bottom: 64px; }
  body.view-detail #app-content .content-description { margin-bottom: 64px; }
  /* Overview: left-aligned on mobile (desktop stays centered via the base rule). */
  #app-content .content-description { text-align: left; }
  #app-content .styleframes-grid { margin-bottom: 64px; }
  #app-content .detail-section-title { margin-top: 24px; margin-bottom: 16px; }
  #app-content .section-divider { margin: 40px auto; }
  #app-content .info-section { margin-bottom: 40px; }
  #app-content .footer-section { padding-top: 24px; }
}

/* ===== Mobile 2D list view + top-bar toggle (Phase 4 polish) ===== */
@media (max-width: 768px) {
  /* Top bar spacing on the 2D list / detail. */
  .top-bar { padding: 0 16px; }

  /* List container: comfortable editorial margins (keeps the tight flat tone). */
  .list-view-container {
    margin: 18px auto 48px auto;
    padding: 0;
  }
  /* "Work" title on its own line at the top, then the filter row below it. */
  .list-brand {
    font-size: 22px;
    padding-top: 10px;
    padding-left: 8px;
  }
  .list-header-bar {
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;   /* right-aligned, directly above the first thumbnail (matches desktop) */
    flex-wrap: nowrap;
    height: auto;
    padding: 28px 8px 0 8px;
    margin-bottom: 12px;
    gap: 8px;
  }
  .list-filter-tags { flex-wrap: nowrap; gap: 5px; flex-shrink: 0; }
  /* Same compact chip proportions as desktop (short and wide — never tall-box + tiny text). */
  .list-filter-tag { padding: 6px 11px; font-size: 11px; white-space: nowrap; }

  /* Single column, full-bleed, no gaps — thumbnails tile edge-to-edge (title appears on touch). */
  .portfolio-list-grid {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .card-thumbnail-wrapper { aspect-ratio: 16 / 9; }
  .card-title-overlay { left: 18px; right: 18px; bottom: 16px; font-size: 22px; }
}

/* Lightbox Styles for SPA and general views */
.lightbox-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(10, 10, 10, 0.95);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 10000;
  display: none;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1);
}
.lightbox-overlay.active {
  opacity: 1;
}
.lightbox-content {
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10010;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  padding: 40px; /* 여백 유지 */
  box-sizing: border-box;
}
#lightbox-img {
  width: auto;
  height: auto;
  max-width: 100%; /* 화면보다 클 땐 비율 유지 축소 */
  max-height: 90vh; /* 화면 높이에 맞추어 자연스럽게 축소 */
  object-fit: contain; /* 비율 유지하며 영역 채움 */
  cursor: zoom-out;
  user-select: none;
  border: 1px solid rgba(255, 255, 255, 0.088);
}
.lightbox-close {
  position: absolute;
  top: 30px;
  right: 40px;
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.6);
  font-size: 40px;
  cursor: pointer;
  transition: color 0.2s, transform 0.2s;
  padding: 10px;
  line-height: 1;
  z-index: 10030;
}
.lightbox-close:hover {
  color: #ffffff;
  transform: scale(1.1);
}
.lightbox-arrow {
  position: absolute;
  top: 0;
  width: 120px;
  height: 100%;
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.3);
  font-size: 48px;
  font-weight: 300;
  cursor: pointer;
  transition: color 0.2s ease;
  z-index: 10020;
  display: flex;
  align-items: center;
  justify-content: center;
  outline: none;
  user-select: none;
}
.lightbox-prev {
  left: 0;
}
.lightbox-next {
  right: 0;
}
.lightbox-arrow:hover {
  color: #ffffff;
}

@media (max-width: 768px) {
  .lightbox-arrow {
    width: 60px;
    font-size: 32px;
  }
  .lightbox-close {
    top: 20px;
    right: 20px;
    font-size: 32px;
  }
  .lightbox-content {
    padding: 20px; /* 모바일 대응 여백 축소 */
  }
}

/* 애니메이션은 종류(프로젝트)별 의도된 비율로 표시한다. 기본은 원본 비율(width:100%/height:auto);
   AOM2 애니메이션만 16:9로 표시(원본 오프닝 아트워크는 16:9 — 원본 standalone 페이지와 동일). AOM2
   프로세스(2단 비교 이미지)는 규칙 없이 자연 비율. 라이트박스는 항상 원본 크기(업스케일 없음). */

/* gifAsVideo 프로젝트의 그리드 <video>는 이미지와 동일하게 채움 */
#app-content .styleframe-item video {
  width: 100%;
  height: auto;
  display: block;
}

/* AOM2 애니메이션(gif·mp4)만 16:9 — Netflix 오프닝 아트워크의 원래 와이드 비율 */
#app-content .styleframe-item img[src*="Agents_of_Mystery_2/Animation_"],
#app-content .styleframe-item video[src*="Agents_of_Mystery_2/Animation_"] {
  aspect-ratio: 16 / 9;
  object-fit: fill;
}

/* 라이트박스 <video>: #lightbox-img와 동일한 규격 — 원본 크기, 화면보다 크면 비율 유지 축소 */
#lightbox-video {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 90vh;
  object-fit: contain;
  cursor: zoom-out;
  user-select: none;
  border: 1px solid rgba(255, 255, 255, 0.088);
}

/* 한 줄에 3개씩 배치하는 변형 (예: Digital Mirage Animation, Still Life Process) */
.styleframes-grid.cols-3 {
  grid-template-columns: 1fr 1fr 1fr;
}
/* 인앱(#app-content)에서는 ID 접두사로 명시도를 올려야 함 — 안 그러면 위의
   `#app-content .styleframes-grid { 1fr 1fr }`(id 우선)에 밀려 2열로 떨어짐(3장 → 2+1). */
#app-content .styleframes-grid.cols-3 {
  grid-template-columns: 1fr 1fr 1fr;
}

/* 같은 그리드 안에서 처음 3개는 3-up, 이후는 2-up (예: The Waltz Stills).
   반드시 #app-content 접두사로 명시도를 올려야 함 — 안 그러면 위의
   `#app-content .styleframes-grid { 1fr 1fr }`(id 우선)에 밀려 인앱에서 무시됨. */
#app-content .styleframes-grid.mixed-3-2 {
  grid-template-columns: repeat(6, 1fr);
}
#app-content .styleframes-grid.mixed-3-2 > .styleframe-item:nth-child(-n + 3) {
  grid-column: span 2;   /* Still 1–3 → 3 across */
}
#app-content .styleframes-grid.mixed-3-2 > .styleframe-item:nth-child(n + 4) {
  grid-column: span 3;   /* Still 4+ → 2 across */
}
/* 좁은 화면(인앱/프리뷰 패널은 < 768px)에서도 3-2 레이아웃 유지 — 1열 붕괴 제거 */

/* 규격이 제각각인 이미지(예: FILA Process 05–07)를 가운데 정렬 + 높이 제한으로 자연스럽게 배치 */
.process-mixed {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 100px;
}
.process-mixed .styleframe-item {
  border: 1px solid #e2e2e2;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.process-mixed .styleframe-item img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 80vh;
  display: block;
}

@media (max-width: 768px) {
  .styleframes-grid.cols-3,
  #app-content .styleframes-grid.cols-3 {
    grid-template-columns: 1fr;
  }
  .process-mixed .styleframe-item img {
    max-height: 60vh;
  }
  /* Mobile: every detail grid collapses to a SINGLE column (incl. the Waltz 3-2 mixed grid). */
  #app-content .styleframes-grid.mixed-3-2 {
    grid-template-columns: 1fr;
  }
  #app-content .styleframes-grid.mixed-3-2 > .styleframe-item {
    grid-column: span 1 !important;
  }
}

/* 한 줄에 1개씩 배치하는 변형 (예: LCK 2026 Still, THE TREES Still / Process) */
.styleframes-grid.cols-1,
#app-content .styleframes-grid.cols-1 {
  grid-template-columns: 1fr;
}

/* STUDIO Dragon case-study media: trim exactly 2 CSS px from every edge while keeping the
   artwork filling its existing frame. The class is opt-in so no other project's stills/processes change. */
#app-content .styleframes-grid.crop-2px .styleframe-item img {
  width: calc(100% + 4px);
  max-width: none;
  margin: -2px;
}



/* ==========================================================================
   Mobile HOME — fullscreen horizontal video reel (reference: tendril.studio /
   ernestdesumbila.com mobile landings). Minimal: snap slides, a title, an index.
   No decorative motion beyond the swipe itself.
   ========================================================================== */
.mobile-home {
  position: fixed;
  inset: 0;
  z-index: 5;                 /* same layer as #app-content; under the top bar (10) */
  background: #0c0e12;
}
.mobile-home.hidden { display: none; }
/* The bare mobile route is HOME. This image is parsed before the JS reel exists, preventing a black
   landing surface while the first MP4 is fetched and its opening seek is resolved. */
html.mobile-home-boot .mobile-home.hidden { display: block; }
.mobile-home-initial-poster {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 66% 50%;
  display: block;
}
.mh-slide-poster {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

.mh-track {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  overflow: hidden;
  touch-action: none;
}
.mh-track::-webkit-scrollbar { display: none; }

.mh-slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  z-index: 1;
  visibility: hidden;
  transform: none;
  overflow: hidden;
  transition: transform 0.45s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.45s ease, visibility 0.45s;
}
.mh-slide.active {
  opacity: 1;
  z-index: 2;
  visibility: visible;
  transform: translate(0, 0) !important;
}

/* 슬라이드 방향별 초기 진입 위치 지정 (transition 비활성화된 상태에서 지정) */
.mh-slide.prepare-left { transform: translate(100%, 0); transition: none; visibility: visible; opacity: 1; z-index: 3; }
.mh-slide.prepare-right { transform: translate(-100%, 0); transition: none; visibility: visible; opacity: 1; z-index: 3; }
.mh-slide.prepare-up { transform: translate(0, 100%); transition: none; visibility: visible; opacity: 1; z-index: 3; }
.mh-slide.prepare-down { transform: translate(0, -100%); transition: none; visibility: visible; opacity: 1; z-index: 3; }

/* 이전 슬라이드가 나가는 모션 */
.mh-slide.out-left { transform: translate(-100%, 0); opacity: 0; z-index: 1; }
.mh-slide.out-right { transform: translate(100%, 0); opacity: 0; z-index: 1; }
.mh-slide.out-up { transform: translate(0, -100%); opacity: 0; z-index: 1; }
.mh-slide.out-down { transform: translate(0, 100%); opacity: 0; z-index: 1; }
.mh-slide video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;          /* fills the phone screen; landscape sources get center-cropped */
  background: transparent;
  z-index: 2;
}
/* Let the real slide-owned still remain visible beneath every movie until its first decoded frame. */
.mh-slide video.mh-initial-video { background: transparent; }
/* Cool dark gray shade over the playing video (user spec: ~50% opacity) */
.mh-shade {
  position: absolute;
  inset: 0;
  background: #262b33;
  opacity: 0.5;
  pointer-events: none;
}
.mh-info-overlay {
  position: absolute;
  left: 20px;
  bottom: 44px;
  right: 60px;
  z-index: 10;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.mh-designer-name {
  color: #ffffff;
  font-family: 'Inter', sans-serif;
  font-size: 26px; /* Work page hover title scale */
  font-weight: 700;
  letter-spacing: -0.3px;
  line-height: 1.1;
  text-transform: uppercase;
}
.mh-project-title-wrapper {
  position: relative;
  height: 20px;
  overflow: hidden;
  width: 100%;
}
.mh-project-title {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  color: #b9bcc2;
  font-family: 'Inter', sans-serif;
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.2px;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  animation-duration: 0.35s;
  animation-fill-mode: forwards;
  animation-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
}

/* Slide-in Keyframes */
@keyframes slideInLeft {
  from { transform: translateX(30px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
@keyframes slideInRight {
  from { transform: translateX(-30px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
@keyframes slideInUp {
  from { transform: translateY(100%); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
@keyframes slideInDown {
  from { transform: translateY(-100%); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

/* Slide-out Keyframes */
@keyframes slideOutLeft {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(-30px); opacity: 0; }
}
@keyframes slideOutRight {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(30px); opacity: 0; }
}
@keyframes slideOutUp {
  from { transform: translateY(0); opacity: 1; }
  to { transform: translateY(-100%); opacity: 0; }
}
@keyframes slideOutDown {
  from { transform: translateY(0); opacity: 1; }
  to { transform: translateY(100%); opacity: 0; }
}

.mh-project-title.slide-in-left { animation-name: slideInLeft; }
.mh-project-title.slide-in-right { animation-name: slideInRight; }
.mh-project-title.slide-in-up { animation-name: slideInUp; }
.mh-project-title.slide-in-down { animation-name: slideInDown; }

.mh-project-title.slide-out-left { animation-name: slideOutLeft; }
.mh-project-title.slide-out-right { animation-name: slideOutRight; }
.mh-project-title.slide-out-up { animation-name: slideOutUp; }
.mh-project-title.slide-out-down { animation-name: slideOutDown; }
/* Left-edge slide progress: a thin vertical track at 50% opacity, with a bright (100%) segment sized
   to 1/N of its height that slides up/down to the active slide. No numbers. */
.mh-index {
  position: absolute;
  left: 22px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 92px;
  background: rgba(255, 255, 255, 0.5);
  z-index: 10;
  overflow: hidden;
  pointer-events: none;
}
.mh-index-thumb {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  /* height set inline to 100%/N; transform (translateY in whole steps) set by mhSync */
  background: #ffffff;
  transition: transform 0.35s ease;
}

/* Top bar over the home reel: fully transparent (no white band, no border) */
body.view-home .top-bar {
  background: transparent;
  border-bottom: none;
}
body.view-home .nav-link,
body.view-home .designer-name { display: none; }   /* home nav lives in the burger menu; no logo either */

/* Burger icon — two long thin lines (reference style: tendril/psyop), light not chunky.
   Hidden everywhere except the mobile home / mobile detail. */
.menu-burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: flex-end;
  gap: 7px;
  background: none;
  border: none;
  padding: 14px 5px;          /* ~44px touch target, matches the nav-link hit areas */
  cursor: pointer;
}
.menu-burger span {
  display: block;
  width: 27px;
  height: 1.5px;
  background: #e8e9eb;
}
body.view-home .menu-burger { display: flex; }

/* Close (×): two thin lines crossed — SAME 1.5px stroke as the burger, not the chunky glyph. */
.mobile-menu-close {
  position: absolute;
  top: 8px;
  right: 12px;
  width: 44px;
  height: 44px;             /* ~44px tap target, mirrors the burger */
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}
.mobile-menu-close span {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 27px;
  height: 1.5px;
  background: #e8e9eb;
}
.mobile-menu-close span:nth-child(1) { transform: translate(-50%, -50%) rotate(45deg); }
.mobile-menu-close span:nth-child(2) { transform: translate(-50%, -50%) rotate(-45deg); }

/* Fullscreen solid menu overlay — slides in from the right (the burger's side). One clean
   0.3s slide; no other motion. Items stacked bottom-right. */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 30;                /* above the top bar */
  background: #101216;
  transform: translateX(100%);
  visibility: hidden;
  /* Close = reverse of open at the SAME speed: slide back out, then hide (visibility delayed by the
     slide duration so the panel stays visible while it slides away). */
  transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1), visibility 0s linear 0.45s;
}
.mobile-menu.open {
  transform: translateX(0);
  visibility: visible;
  transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1), visibility 0s;
}
.mobile-menu-items {
  position: absolute;
  right: 24px;
  bottom: 44px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 26px;
}
/* Each item lives in an overflow-hidden clip: it slides UP on open and back DOWN on close. ALL items
   share the same timing (no stagger) and the same speed both directions (transition on the base rule). */
.mobile-menu-items .mm-clip {
  overflow: hidden;
}
.mobile-menu-note {
  margin: 3px 0 0;
  color: rgba(232, 233, 235, 0.62);
  font-family: 'Inter', sans-serif;
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.1px;
  line-height: 1.25;
  text-align: right;
  transform: translateY(110%);
  opacity: 0;
  transition: transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.2s ease;
}
/* Big, quiet nav type (reference: psyop's WORK/STUDIOS/ABOUT menu scale). */
.mobile-menu-items button {
  position: relative;
  display: block;
  background: none;
  border: none;
  color: #b4b7bd;
  font-family: 'Inter', sans-serif;
  font-size: 38px;
  font-weight: 400;
  letter-spacing: 0.5px;
  line-height: 1.1;
  text-transform: uppercase;
  padding: 4px 0;
  cursor: pointer;
  transform: translateY(110%);
  transition: transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1), color 0.2s ease;
}
.mobile-menu-items button:hover {
  color: #ffffff;
}
/* Hover / press underline (frosted style, matching top-bar), wipes in from left, out to right. */
.mobile-menu-items button::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0px;
  height: 1px;
  background: currentColor;
  opacity: 0.55;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform 0.25s ease;
}
.mobile-menu-items button:hover::after,
.mobile-menu-items button:active::after {
  transform: scaleX(1);
  transform-origin: left center;
}
/* Current-page item: white + persistent underline (mobile equivalent of the top-bar active). */
.mobile-menu-items button.active { color: #ffffff; }
.mobile-menu-items button.active::after {
  transform: scaleX(1);
  transform-origin: left center;
  opacity: 0.9;
}
/* Each line rises inside its OWN clip (individual), but all three start at the SAME time (no stagger).
   A small shared delay lets the solid panel arrive first, so the rise reads as its own phase rather
   than the whole block sliding. */
.mobile-menu.open .mm-clip button {
  transform: translateY(0);
  transition-delay: 0.14s;
}
.mobile-menu.open .mobile-menu-note {
  transform: translateY(0);
  opacity: 1;
  transition-delay: 0.14s;
}

/* ==========================================================================
   DARK THEME — detail pages + works list (reference mood: tendril.studio / psyop.com).
   Near-black cool dark gray, no rules/lines anywhere: hierarchy comes from type scale,
   weight, and whitespace only. The Info page and the 3D scene are untouched.
   ========================================================================== */

/* 2D pages background — tunable live from the "2D" folder in the key-1 panel. Default is a lower-
   saturation near-neutral dark (was the slightly blue #131418). */
:root { --view2d-bg: #171718; }

/* Page surfaces — the BODY too, so overscroll/rubber-band and the strip behind the (transparent)
   bar show the same dark, never the default light page background. */
body.view-detail,
body.view-list,
body.view-info {
  background-color: var(--view2d-bg);
}
body.view-detail #app-content,
body.view-list #app-content,
body.view-info #app-content {
  background-color: var(--view2d-bg);
  color: #e8e9eb;
}

/* Top bar melts into the page: same dark surface, no border line, light text.
   (It stays fixed, so the brand/burger float in place while the page scrolls.) */
body.view-detail .top-bar,
body.view-list .top-bar,
body.view-info .top-bar {
  background-color: var(--view2d-bg);
  border-bottom: none;
}
body.view-detail .designer-name,
body.view-list .designer-name,
body.view-info .designer-name {
  color: #e8e9eb;
}
body.view-detail .designer-name:hover,
body.view-list .designer-name:hover,
body.view-info .designer-name:hover { color: #ffffff; }
body.view-detail .nav-link,
body.view-list .nav-link,
body.view-info .nav-link {
  color: #b4b7bd;
}
body.view-detail .nav-link:hover,
body.view-list .nav-link:hover,
body.view-info .nav-link:hover { color: #ffffff; }

/* Mobile detail + work: NO bar at all — the burger is the only floating (fixed) chrome. The bar element
   stays in the DOM only as the burger's anchor: transparent, no fill, and pointer-events:none so taps
   in the empty bar area reach the page; the burger itself re-enables pointer-events, so tapping it
   always hits the burger (never the content behind it). The page title ("Work") lives IN the page flow
   and scrolls away with the content. */
@media (max-width: 768px) {
  body.view-detail .top-bar,
  body.view-list .top-bar,
  body.view-info .top-bar,
  body.view-3d .top-bar {
    background: transparent;
    border-bottom: none;
    backdrop-filter: none;            /* drop the 3D frosted blur too (no bar on mobile) */
    -webkit-backdrop-filter: none;
    pointer-events: none;
  }
  body.view-detail .designer-name,
  body.view-detail .nav-link,
  body.view-list .designer-name,
  body.view-list .nav-link,
  body.view-info .designer-name,
  body.view-info .nav-link,
  body.view-3d .designer-name,
  body.view-3d .nav-link { display: none; }
  body.view-detail .menu-burger,
  body.view-list .menu-burger,
  body.view-info .menu-burger,
  body.view-3d .menu-burger {
    display: flex;
    pointer-events: auto;
  }
  /* Burger sits over the live scene on 3D — a soft shadow keeps the thin light lines legible on bright frames. */
  body.view-3d .menu-burger span {
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
  }
  /* No bar → the content area owns the full screen; content starts near the top and scrolls under the
     floating burger. */
  body.view-detail #app-content,
  body.view-list #app-content,
  body.view-info #app-content {
    top: 0;
    height: 100%;
  }
  /* Clear the floating burger (top:8px + 44px tall): content starts BELOW it, with breathing room
     so the title doesn't kiss the ceiling or run under the icon. */
  body.view-detail #app-content .container {
    margin-top: 76px;
  }
  body.view-list .list-view-container {
    margin-top: 16px;
  }
  body.view-info .info-view-container {
    margin-top: 104px;   /* clear the floating burger + a bit more breathing room from the ceiling */
  }
}

/* ---- Info page: dark palette + more top air (matches work/detail) ---- */
/* Desktop only (min-width 769px) — the mobile top margin is set in the max-width:768px block above.
   The #app-content ID keeps this above the base .info-view-container rule; the min-width media query
   keeps it from leaking onto mobile (where it would otherwise override the mobile value). */
@media (min-width: 769px) {
  body.view-info #app-content .info-view-container {
    margin-top: 150px;       /* more breathing room below the top bar (was crowding the ceiling) */
  }
}

/* Info page: pin the copyright to the very bottom of the viewport, even when the page content is
   shorter than one screen. #app-content becomes a flex column; the content keeps its natural height
   (scrolls if it ever overflows) and the copyright's margin-top:auto absorbs the leftover space. */
body.view-info #app-content {
  display: flex;
  flex-direction: column;
}
body.view-info #app-content .info-view-container {
  flex-shrink: 0;
}
body.view-info #app-content .site-copyright {
  margin-top: auto;
}
body.view-info .info-title { color: #e8e9eb; }
body.view-info .info-body p { color: #b9bcc2; }
body.view-info .info-email { color: #e8e9eb; }
body.view-info .info-email:hover { color: #ffffff; }
body.view-info .info-social-link { color: #cfd1d5; }
body.view-info .info-social-link:hover { color: #ffffff; }
/* Cube face fallback tint on the dark surface (before textures load) */
body.view-info .info-cube-face { background-color: #23262b; }

/* Title */
body.view-detail #app-content h1.project-title {
  color: #e8e9eb;
}

/* Video: no frame line; letterboxing sits on pure black */
body.view-detail #app-content .video-wrapper {
  outline: none;
  background-color: #000000;
}

/* Overview: left-aligned editorial block (was centered), soft light gray */
/* Dark theme only recolors the overview — its layout (centered, max-width 760, margin auto → widens
   fluidly with the window up to the pre-renewal width) is inherited from the base rule. */
body.view-detail #app-content .content-description {
  color: #b9bcc2;
}

/* Section labels (Process 등): small quiet caps instead of bold headings — no tracking */
body.view-detail #app-content .detail-section-title {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: normal;
  color: #7d8087;
  padding-left: 12px;
}

/* Grids: drop the 1px item frames — images sit directly on the dark surface */
body.view-detail #app-content .styleframe-item {
  border: none;
  background-color: #1a1c21;
}

/* Dividers: keep the rhythm (spacing) but no visible line */
body.view-detail #app-content .section-divider {
  background-color: transparent;
}

/* Credits: same centered key/value, colors flipped, no lines */
body.view-detail #app-content .info-section h3 {
  color: #7d8087;
  font-size: 12px;
  letter-spacing: normal;
}
body.view-detail #app-content .info-section.credits {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  box-sizing: border-box;
  padding: 0 20px;
}
body.view-detail #app-content .credits-list {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: 28px;
  row-gap: 12px;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  align-items: start;
}
body.view-detail #app-content .credit-row {
  display: contents;
}
body.view-detail #app-content .credit-key {
  grid-column: 1;
  width: auto;
  text-align: right;
  justify-content: flex-end;
  padding-right: 0;
  color: #6f727a;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: normal;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  white-space: nowrap;
}
body.view-detail #app-content .credit-value {
  grid-column: 2;
  width: auto;
  text-align: left;
  padding-left: 0;
  color: #d6d8db;
  font-size: 13px;
  font-weight: 400;
  display: block;
  white-space: normal;
}

/* Footer back: no top rule; quiet arrow that brightens on hover */
body.view-detail #app-content .footer-section {
  border-top: none;
}
body.view-detail #app-content .back-link {
  color: #9a9da3;
}
body.view-detail #app-content .back-link:hover {
  color: #e8e9eb;
}

/* ---- Works list (dark skin + compact chips) ---- */
body.view-list #app-content .list-brand {
  color: #e8e9eb;
}
/* (Card borders/title-below were removed with the full-bleed grid — the base rules are dark already.) */

/* ==========================================================================
   Detail footer nav — a divider under the credits, then Prev / Work / Next.
   ========================================================================== */
.detail-nav {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-top: 1px solid #26282d;   /* the divider under the credits (user-requested line) */
  margin-top: 90px;
  padding: 26px 4px 40px 4px;
}
.detail-nav-btn {
  background: none;
  border: none;
  font-family: 'Inter', sans-serif;
  font-size: 17px;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: uppercase;
  color: #9a9da3;
  padding: 14px 14px;              /* comfortable tap targets */
  cursor: pointer;
  transition: color 0.2s ease;
}
.detail-nav-btn:hover {
  color: #e8e9eb;
}
@media (max-width: 768px) {
  /* These are controls and preview media, not selectable content. Suppress the native long-press
     selection tint / image callout while leaving page scrolling and normal taps untouched. */
  .menu-burger,
  .menu-burger *,
  .mobile-menu,
  .mobile-menu *,
  .portfolio-card,
  .portfolio-card * {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
    -webkit-user-drag: none;
  }
  .detail-nav {
    margin-top: 56px;
    padding-bottom: 20px;   /* trim the bottom whitespace a touch on mobile (desktop unchanged) */
  }
  .detail-nav-btn {
    font-size: 14px;        /* desktop stays 17px */
    padding: 14px 10px;
  }
  .scroll-hint {
    padding: 20px 30px;
    gap: 12px;
  }
  .scroll-hint-text {
    font-size: 11px;
    letter-spacing: 0.5px;
  }
  .scroll-hint .scroll-mouse { display: none; }
  .scroll-hint .scroll-swipe { display: block; }
}

/* ==========================================================================
   Bottom-left copyright — quiet muted footer on Info / Work / detail (all dark).
   ========================================================================== */
.site-copyright {
  font-family: 'Inter', sans-serif;
  font-size: 12px;
  font-weight: 400;
  letter-spacing: normal;
  color: #7d8087;
  text-align: left;
}
/* Detail: the .container already insets the left edge (12px), so align with the content column. */
body.view-detail .site-copyright { padding: 6px 0 46px 0; }
/* Work: the grid is full-bleed, so the footer carries its own 20px inset (matches the WORK title). */
body.view-list .site-copyright { padding: 44px 0 46px 20px; }
/* Info: page-level footer at the bottom-left edge (reference: tendril). */
body.view-info .site-copyright { padding: 28px 0 60px 20px; }

@media (max-width: 768px) {
  body.view-detail .site-copyright { padding: 6px 0 28px 0; }
  body.view-list .site-copyright { padding: 34px 0 34px 16px; }
  body.view-info .site-copyright { padding: 22px 0 44px 16px; }
}

/* Keyboard navigation remains discoverable without changing the pointer-led visual direction. A number
   of bespoke controls intentionally suppress the browser's default outline, so restore one only for
   :focus-visible (keyboard focus, never a normal tap/click). */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid #f4f7ff;
  outline-offset: 4px;
}
