/* ───────────────────────────────────────────
   RESET & TOKENS
─────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  /* DESIGNER REVIEW: all colour values are placeholder */
  --paper:       #DBD0B3;
  --paper-dark:  #e8e0d0;
  --amber:       #5C6437; /* was gold #c8892a — now aliased to the hover green so every old accent use follows the new palette */
  --hover:       #5C6437; /* the one hover-state color, used everywhere something is hovered */
  --amber-light: #e8b84b;
  --rust:        #b84c2e;
  --pink:        #d4897a;
  --sage:        #7a9b80;
  --ink:         #1e1710;
  --ink-mid:     #3d3022;
  --ink-faint:   rgba(30,23,16,0.35);
  --electric:    #3a7dff;

  /* shadow system */
  --sh-base: 0 4px 18px rgba(30,23,16,0.13), 0 1px 5px rgba(30,23,16,0.08);
  --sh-lift: 0 16px 44px rgba(30,23,16,0.21), 0 4px 12px rgba(30,23,16,0.12);

  /* Argent Pixel CF — loaded via the Adobe Fonts (Typekit) kit link in
     index.html <head>. One weight only (400, roman + italic), used for
     the name-mark, all headings, and object labels. Mono stays on
     Google Fonts for small utility text (tabs, meta, captions). */
  --font-serif: 'argent-pixel-cf', Georgia, serif;
  --font-mono:  'DM Mono', 'Courier New', monospace;

  --header-h: 84px; /* persistent top bar height, used to offset content below it */
}

html, body {
  width: 100%; height: 100%;
  overflow: hidden;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-serif);
}

/* paper grain — layered noise texture */
body::after {
  content: '';
  position: fixed; inset: 0; z-index: 999;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.055'/%3E%3C/svg%3E");
  background-size: 200px 200px;
  mix-blend-mode: multiply;
}

/* ───────────────────────────────────────────
   LOADER SCREEN
─────────────────────────────────────────── */
#loader {
  position: fixed; inset: 0; z-index: 800;
  background: var(--paper);
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
#loader-name {
  font-family: var(--font-serif);
  font-size: clamp(28px, 5vw, 48px);
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--ink);
  opacity: 0;
}

/* ───────────────────────────────────────────
   PERSISTENT HEADER — name (click = home), socials, nav trigger.
   Fixed above every view. The social icons and nav trigger stay the
   same size on every breakpoint per feedback: nothing there shrinks
   on mobile. The name-mark is the one exception — on a narrow phone
   viewport "abhas misraraj" was wrapping to two lines, and the fix
   requested is to shrink the text (via clamp()) rather than ever let
   it wrap (see .name-mark below). The bar itself ignores pointer
   events so it never blocks clicks on content underneath; only its
   buttons (name, social links, nav trigger) opt back in.
─────────────────────────────────────────── */
#top-bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 210;
  height: var(--header-h);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 28px;
  pointer-events: none;
  opacity: 0; /* revealed by GSAP */
  background: transparent;
  transition: background 0.3s ease;
}
/* Solid only over scrollable content (domain views, galleries, the
   skills tracker) so there's always a legible surface to read the
   name/icons against while scrolling — on the home desk it stays
   transparent so it doesn't read as a bar sitting over the objects. */
#top-bar.solid { background: var(--paper); }

.name-mark {
  pointer-events: auto;
  background: none; border: none; cursor: pointer;
  display: flex; align-items: center;
  font-family: var(--font-serif);
  font-size: clamp(15px, 4.5vw, 28px);
  white-space: nowrap;
  letter-spacing: 0.02em;
  color: var(--ink);
  opacity: 0.88;
  padding: 10px 6px;
  transition: opacity 0.2s;
}
.name-mark:hover { opacity: 1; color: var(--hover); }
.top-right-cluster {
  pointer-events: auto;
  display: flex; align-items: center;
  gap: 6px;
}
@media (min-width: 681px) {
  .top-right-cluster { gap: 34px; }
}

/* ───────────────────────────────────────────
   SOCIAL LINKS — 44×44 min tap targets, vertically
   centered with the nav trigger via the flex cluster above.
─────────────────────────────────────────── */
.social-row {
  display: flex; align-items: center; gap: 6px;
}
.social-row a {
  width: 44px; height: 44px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  opacity: 0.55;
  transition: opacity 0.2s, background 0.2s;
}
.social-row a:hover { opacity: 1; background: rgba(30,23,16,0.06); }
.social-row img {
  width: 22px; height: 22px;
  display: block;
}

/* ───────────────────────────────────────────
   NAV TRIGGER — 44×44 tap target
─────────────────────────────────────────── */
.nav-trigger {
  width: 44px; height: 44px;
  background: none; border: none; cursor: pointer;
  padding: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px;
  transition: opacity 0.2s;
}
.nav-trigger span {
  display: block; width: 24px; height: 1.5px;
  background: var(--ink);
  opacity: 0.5;
  transform-origin: center;
  transition: opacity 0.25s, transform 0.35s cubic-bezier(0.34,1.2,0.64,1), width 0.25s;
}
.nav-trigger:hover span { opacity: 0.8; }
.nav-trigger.open span:nth-child(1) { transform: rotate(45deg) translate(4px, 4px); opacity: 0.8; }
.nav-trigger.open span:nth-child(2) { width: 0; opacity: 0; }
.nav-trigger.open span:nth-child(3) { transform: rotate(-45deg) translate(4px, -4px); opacity: 0.8; }

/* ───────────────────────────────────────────
   NAV MENU — slides in from top-right
─────────────────────────────────────────── */
#nav-menu {
  position: fixed; top: 0; right: 0; z-index: 205;
  width: 320px; height: 100vh;
  background: var(--paper-dark);
  border-left: 1px solid rgba(30,23,16,0.08);
  padding: 110px 40px 40px;
  transform: translateX(100%);
  opacity: 0;
  pointer-events: none;
  overflow-y: auto;
  /* DESIGNER REVIEW: nav reveal style — currently a right-panel slide */
}

#nav-menu.open {
  pointer-events: all;
}

.nav-section {
  margin-bottom: 36px;
}
.nav-section-label {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 14px;
}
.nav-link {
  display: block;
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 400;
  color: var(--ink-mid);
  text-decoration: none;
  padding: 4px 0;
  border: none; background: none; cursor: pointer;
  text-align: left;
  width: 100%;
  transition: color 0.2s, transform 0.2s;
  transform: translateX(0);
  opacity: 0; /* animated in */
}
.nav-link:hover { color: var(--hover); transform: translateX(4px); }

.nav-external {
  display: flex; gap: 16px; flex-wrap: wrap;
}
.nav-ext-link {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  text-decoration: none;
  border: none; background: none; cursor: pointer;
  padding: 0;
  transition: color 0.2s;
}
.nav-ext-link:hover { color: var(--hover); }

/* ───────────────────────────────────────────
   ARTISTIC SKILLS TRACKER LINK — bottom corner
   Was a barely-visible easter egg; now legible but still quiet.
─────────────────────────────────────────── */
/* Same treatment as the object labels — Argent Pixel CF, no mono/
   uppercase/letter-spacing — so these corner links read as part of
   the same family of links as "Photography", "Product Design", etc. */
.skills-tracker-link,
.about-link {
  position: absolute; bottom: 22px; z-index: 40;
  font-family: var(--font-serif);
  font-size: 16px;
  color: var(--ink-mid);
  opacity: 0.75;
  background: none; border: none; cursor: pointer;
  padding: 8px 4px;
  transition: opacity 0.25s, color 0.25s;
}
.skills-tracker-link { left: 28px; }
.about-link { right: 28px; }
.skills-tracker-link:hover,
.about-link:hover { opacity: 1; color: var(--hover); }

/* ───────────────────────────────────────────
   SURFACE / DESK
   padding-top keeps objects clear of the fixed header (percentage
   positions inside resolve against the padded box, so top:0% already
   starts just below the header — no object can slide behind it).
─────────────────────────────────────────── */
/* Wood-desk texture + vignette, shared by every scrollable surface
   (the desk AND every domain view/gallery/etc — not just home).
   Both layers use background-attachment:local so they scroll WITH
   the content and always cover its full height, never just the first
   viewport — that's what was cutting off before the footer. Putting
   the vignette here as a background layer (rather than a separate
   ::after sized to the element's own fixed box) is what fixes it. */
/* The texture TILES (rather than stretching one copy to cover however
   tall the scrollable content gets) — stretching a single image over
   a very long page blew it up into a blurry mess. A fixed tile size
   repeating keeps it crisp no matter how long the page is. The
   vignette gradient stays a single cover layer since gradients don't
   pixelate when scaled. */
/* Texture always fills the full width (background-size: 100% auto)
   and only repeats VERTICALLY (repeat-y) once the page runs past one
   image-height's worth of content — never tiled sideways, and never
   stretched to blur across the whole scroll height either. */
.wood-surface {
  background-color: var(--paper);
  background-image:
    radial-gradient(ellipse at 50% 50%, transparent 55%, rgba(30,23,16,0.07) 100%),
    url('assets/background-faded.jpg');
  background-size: cover, 100% auto;
  background-position: center, center top;
  background-repeat: no-repeat, repeat-y;
  background-attachment: local, local;
}

#desk {
  position: fixed; inset: 0; z-index: 10;
  overflow-x: hidden;
  overflow-y: auto;
}

/* ───────────────────────────────────────────
   OBJECTS — desk items
   All ten objects live inside #desk directly (position:absolute,
   percentage top/left) but their SIZE is a percentage of the viewport
   clamped between a floor and ceiling — see per-object rules below.
   That combination is what keeps spacing visually consistent at every
   breakpoint while letting objects grow generously on large screens
   and never shrink past a legible floor on small ones.
─────────────────────────────────────────── */
.obj {
  position: absolute;
  cursor: pointer;
  opacity: 0; /* GSAP reveals */
  z-index: 20;
}

.obj-img {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  filter: drop-shadow(0 4px 14px rgba(30,23,16,0.18)) drop-shadow(0 1px 4px rgba(30,23,16,0.1));
}

/* Rotation lives on .obj-inner (and the label, separately) via a
   per-object --rot custom property — NOT on .obj itself — so the
   label's own position/centering logic stays simple while both pieces
   visually sit on the same tilted "plane" as the desk object. */
/* The image itself is never rotated by CSS — it's used exactly as
   supplied. Only the label text underneath carries the --rot tilt. */
.obj-inner {
  position: relative;
  transition: transform 0.4s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.obj-inner .obj-img {
  transition: filter 0.3s ease;
}
.obj:hover .obj-inner {
  transform: translateY(-6px) scale(1.035);
}
.obj:hover .obj-inner .obj-img {
  filter: drop-shadow(0 18px 30px rgba(30,23,16,0.28)) drop-shadow(0 4px 10px rgba(30,23,16,0.16));
}

/* label — visible at rest now (not hover-only); tilted per --rot.
   pointer-events is NOT none here on purpose: hovering/clicking the
   label itself (not just the image) needs to open the same domain,
   via bubbling up to .obj's hover state and onclick handler. */
.obj-label {
  position: absolute;
  bottom: -30px; left: 50%;
  transform: translateX(-50%) rotate(var(--rot, 0deg));
  font-family: var(--font-serif);
  font-size: 16px;
  color: var(--ink-mid);
  opacity: 0.7;
  white-space: nowrap;
  transition: opacity 0.22s ease, color 0.22s, transform 0.22s;
  cursor: pointer;
}
.obj:hover .obj-label { opacity: 1; color: var(--hover); transform: translateX(-50%) translateY(-2px) rotate(var(--rot, 0deg)); }

/* ── individual object placements & sizes ──
   Positions are percentage-based (top/left, relative to #desk) so
   the cluster scales as one unit as the viewport resizes — that's
   what keeps the gaps between objects visually consistent instead of
   stretching apart on wide screens. Width is also percentage-based
   (of the viewport) but clamped so objects grow large on big screens
   without shrinking below a comfortable floor on small ones. Objects
   are packed tighter (less dead space) than earlier passes, and each
   carries a --rot custom property used by both the image and its label.
   DESIGNER REVIEW: positions/rotations loosely matched to the v1 flat-lay. */

/* Positions/sizes read directly off the reference flat-lay at its
   exact 1920×928 breakpoint (top/left as % of viewport, matching the
   image's own proportions pixel-for-pixel; width in vw calibrated so
   it lands on the exact px width at 1920 and scales down gracefully
   below that). top uses max(header-clearance, Y%) — the percentage
   alone matches the reference exactly at normal/tall viewports, and
   the max() only kicks in as a floor on unusually short ones so
   nothing can slide behind the fixed header.
   Label rotation: CSS rotate() is positive-clockwise, but the given
   angles are positive-counterclockwise, so every value is negated
   here (e.g. "6.76° CCW" → rotate(-6.76deg)). */
/* Y% values shifted up ~3.3 points from the raw reference reading so
   the whole cluster sits vertically centered in the viewport (as a
   block) rather than centered only in the space below the header —
   that's what was reading "a little low" before. */
#o-product      { top: max(calc(var(--header-h) + 10px), 15.3%); left: 4.75%;  width: clamp(150px, 15.25vw, 293px); --rot: -6.76deg; }
#o-photography  { top: max(calc(var(--header-h) + 10px), 13.2%); left: 32.75%; width: clamp(150px, 13.75vw, 264px); --rot: -0.63deg; }
#o-writing      { top: max(calc(var(--header-h) + 10px), 7.55%); left: 57.75%; width: clamp(120px, 9.25vw, 178px); --rot: 9.6deg; }
#o-shop         { top: max(calc(var(--header-h) + 10px), 16.8%); left: 85.25%; width: clamp(90px, 6.25vw, 120px);  --rot: 14.33deg; }
#o-games        { top: max(calc(var(--header-h) + 10px), 56.1%); left: 7.5%;   width: clamp(130px, 10.25vw, 197px); --rot: -10.38deg; }
#o-illustration { top: max(calc(var(--header-h) + 10px), 46.3%); left: 24.75%; width: clamp(190px, 18vw, 346px);   --rot: 3.45deg; }
#o-bookshelf    { top: max(calc(var(--header-h) + 10px), 50.4%); left: 46.5%;  width: clamp(105px, 11.25vw, 216px); --rot: 6.62deg; }
#o-film         { top: max(calc(var(--header-h) + 10px), 37.5%); left: 66.5%;  width: clamp(180px, 18vw, 346px);   --rot: -14.5deg; }
#o-music        { top: max(calc(var(--header-h) + 10px), 69.5%); left: 70.25%; width: clamp(90px, 7.75vw, 149px);  --rot: -13.92deg; }
#o-performance  { top: max(calc(var(--header-h) + 10px), 57.7%); left: 85%;    width: clamp(100px, 10.75vw, 206px); --rot: 11.04deg; }

/* Label placement per object, matching the reference: most labels
   sit ABOVE their object, a few stay below or shift to one side —
   not a uniform "always centered below" rule. Rotation (--rot)
   still applies the same way regardless of side. */
#o-product .obj-label,
#o-writing .obj-label,
#o-games .obj-label,
#o-music .obj-label {
  top: -30px; bottom: auto;
}
#o-shop .obj-label {
  top: -22px; bottom: auto;
  left: auto; right: -18px;
  transform: rotate(var(--rot, 0deg));
}
#o-shop:hover .obj-label { transform: translateY(-2px) rotate(var(--rot, 0deg)); }
#o-film .obj-label {
  top: -30px; bottom: auto;
  left: 28%;
}

/* Pixel nudges on top of the placements above — measured against the
   reference flat-lay at the 1920×928 desktop breakpoint. */
#o-product .obj-label      { left: calc(50% - 19px); top: -15px; }
#o-photography .obj-label  { bottom: -15px; }
#o-writing .obj-label      { left: calc(50% + 34px); top: -14px; }
#o-film .obj-label         { left: calc(28% - 40px); top: 64px; }
#o-shop .obj-label         { right: -53px; top: 32px; }
#o-music .obj-label        { left: calc(50% - 25px); top: -13px; }
#o-performance .obj-label  { left: calc(50% - 26px); bottom: -9px; }
#o-bookshelf .obj-label    { left: calc(50% - 13px); bottom: -25px; }
#o-illustration .obj-label { left: calc(50% - 31px); bottom: -20px; }
#o-games .obj-label        { left: calc(50% - 23px); top: -15px; }

/* ───────────────────────────────────────────
   DOMAIN VIEW — full / near-full screen.
   No in-view back button — the persistent header name is the way home.
─────────────────────────────────────────── */
#domain-view {
  position: fixed; inset: 0; z-index: 100;
  opacity: 0;
  pointer-events: none;
  overflow-y: auto;
}
#domain-view.active { pointer-events: all; }

.dv-content {
  min-height: 100vh;
  padding: calc(var(--header-h) + 50px) 60px 100px;
  max-width: 880px;
  margin: 0 auto;
  opacity: 0;
}
/* wide modifier — grids/embeds/image-heavy views get much more room;
   prose-heavy views (Writing, About) stay at the narrower reading width */
.dv-content.wide {
  max-width: min(1700px, 96vw);
}

/* Same fixed size as the header name-mark — page titles shouldn't
   dominate the screen, the content underneath should. */
.dv-title {
  font-family: var(--font-serif);
  font-size: 28px;
  font-weight: 400; line-height: 1.1;
  color: var(--ink);
  margin-bottom: 24px;
}
.cs-detail .dv-title { font-size: 24px; }

/* Photography / Illustration & Collage: tighter top spacing and
   side padding so the images are the centerpiece, not the header —
   the left/right margin matches the gap between tiles themselves. */
.dv-content.gallery-view {
  padding-top: calc(var(--header-h) + 26px);
  padding-left: 18px;
  padding-right: 18px;
}
@media (min-width: 700px) {
  .dv-content.gallery-view { padding-left: 26px; padding-right: 26px; }
}
.gallery-view .dv-title { margin-bottom: 18px; }
.gallery-view .gallery-tabs { margin-bottom: 22px; }

.dv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 24px;
  margin-bottom: 60px;
}
.dv-card {
  background: var(--paper-dark);
  border-radius: 4px;
  aspect-ratio: 4/3;
  display: flex; align-items: flex-end;
  padding: 16px;
  transition: transform 0.3s cubic-bezier(0.34,1.3,0.64,1), box-shadow 0.25s;
  cursor: pointer;
  overflow: hidden;
  position: relative;
}
.dv-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--sh-lift);
}
.dv-card-label {
  font-family: var(--font-serif);
  font-size: 15px; color: var(--ink-mid); opacity: 0.85;
}

.dv-prose {
  font-family: Georgia, serif;
  font-size: 17px; line-height: 1.88;
  color: var(--ink-mid);
  max-width: 640px;
}
.dv-prose p { margin-bottom: 1.3em; }
.dv-prose a { color: var(--amber); }

/* ───────────────────────────────────────────
   GALLERY — Photography, Illustration & Collage
   True CSS columns (not a row-aligned grid): each image keeps its own
   aspect ratio and simply fills its column's width — nothing is
   cropped, and rows don't line up, which gives the staggered/masonry
   look. Fixed at 3 columns from tablet width up; images just get
   wider (not more numerous) on large screens.
─────────────────────────────────────────── */
.gallery-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 32px;
}
.gallery-tab {
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: none;
  border: 1px solid rgba(30,23,16,0.16);
  border-radius: 100px;
  padding: 10px 18px;
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.gallery-tab:hover { color: var(--ink-mid); border-color: rgba(30,23,16,0.32); }
.gallery-tab.active {
  color: var(--paper);
  background: var(--ink);
  border-color: var(--ink);
}

/* True CSS column-masonry (not a row-based grid) — each image keeps
   its own aspect ratio and simply fills its column's width, giving
   the staggered/interlocking look. Order in the data arrays matters:
   a column fills top-to-bottom before moving to the next, so an item
   that should never land at the top of a column (see the Faces
   comment above) needs to sit near the end of its list. */
.gallery-grid {
  column-count: 2;
  column-gap: 18px;
  margin-bottom: 40px;
}
@media (min-width: 700px) {
  .gallery-grid { column-count: 3; column-gap: 26px; }
}
.gallery-tile {
  break-inside: avoid;
  margin-bottom: 18px;
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
  display: block;
  transition: transform 0.3s cubic-bezier(0.34,1.3,0.64,1), box-shadow 0.25s;
}
@media (min-width: 700px) {
  .gallery-tile { margin-bottom: 26px; }
}
.gallery-tile:hover {
  transform: translateY(-3px) scale(1.015);
  box-shadow: var(--sh-lift);
}
.gallery-tile img {
  width: 100%; height: auto;
  display: block;
}

/* lightbox */
#lightbox {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(14,10,6,0.94);
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  pointer-events: none;
}
#lightbox.active { pointer-events: all; }
.lb-stage {
  max-width: 84vw; max-height: 80vh;
  display: flex; align-items: center; justify-content: center;
}
.lb-stage img, .lb-stage video {
  max-width: 84vw; max-height: 80vh;
  width: auto; height: auto;
  border-radius: 2px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.5);
}
.lb-close {
  position: absolute; top: 20px; right: 20px;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  background: none; border: none; cursor: pointer;
  transition: color 0.2s;
}
.lb-close:hover { color: #fff; }
.lb-counter {
  position: absolute; top: 30px; left: 30px;
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.1em;
  color: rgba(255,255,255,0.4);
}
.lb-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 48px; height: 48px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.2);
  background: rgba(255,255,255,0.04);
  color: rgba(255,255,255,0.7);
  font-family: var(--font-mono);
  font-size: 18px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.2s, border-color 0.2s, transform 0.2s;
}
.lb-arrow:hover { background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.4); }
.lb-arrow.prev { left: 24px; }
.lb-arrow.next { right: 24px; }
.lb-arrow:active { transform: translateY(-50%) scale(0.92); }

@media (max-width: 680px) {
  .lb-arrow { width: 40px; height: 40px; }
  .lb-arrow.prev { left: 10px; }
  .lb-arrow.next { right: 10px; }
}

/* ───────────────────────────────────────────
   PRODUCT DESIGN — case study index (image-first, like the galleries)
   + case study detail (centered prose, nothing cropped)
─────────────────────────────────────────── */
.cs-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 56px;
  margin-bottom: 40px;
}
@media (min-width: 700px) {
  .cs-grid { grid-template-columns: 1fr 1fr; gap: 48px 40px; }
}
/* Bounded aspect ratio (not the image's natural size) so both cards —
   image, title, blurb, buttons — fit inside one desktop viewport
   without scrolling. The image itself is never cropped to fill that
   box — it's letterboxed (object-fit:contain, centered) so the whole
   thing stays visible either way. */
/* No fill — just an outline "card", so the PNG's own (often transparent)
   background shows the page behind it rather than a white/paper box. */
/* No outline — the card should look like it's simply resting on the
   wood until hovered, when the lift + shadow makes it read as rising
   off the surface. */
.cs-card-image {
  width: 100%;
  aspect-ratio: 16/10;
  border-radius: 6px;
  overflow: hidden;
  cursor: pointer;
  margin-bottom: 16px;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.3s cubic-bezier(0.34,1.3,0.64,1), box-shadow 0.25s;
}
.cs-card-image:hover { transform: translateY(-3px); box-shadow: var(--sh-lift); }
/* width/height:100% + object-fit:contain (not max-width/max-height
   with auto dimensions) — the auto-dimension approach is the one
   that was cropping the top/bottom of these PNGs on real iOS Safari
   even though it rendered fine in desktop responsive/device-emulation
   mode; this is the more broadly-supported pattern for a flex item
   that must never be cropped. */
.cs-card-image img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* Fragments' source image reads much larger than the app-store cards
   at the same box size — scale it down and let it float centered. */
.cs-card-image.fragments img { width: 66%; height: 66%; }
.cs-card-title {
  display: block;
  font-family: var(--font-serif);
  font-size: 24px;
  color: var(--ink);
  margin-bottom: 10px;
  cursor: pointer;
  text-decoration: none;
}
.cs-card-title:hover { color: var(--hover); }
.cs-card-blurb {
  font-family: Georgia, serif;
  font-size: 15px; line-height: 1.7;
  color: var(--ink-mid);
  margin-bottom: 18px;
}
.cs-card-actions { display: flex; gap: 12px; flex-wrap: wrap; }
.cs-btn {
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 12px 20px;
  border-radius: 100px;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.2s, color 0.2s, border-color 0.2s;
  border: 1px solid var(--ink);
}
.cs-btn-primary { background: var(--ink); color: var(--paper); }
.cs-btn-primary:hover { background: var(--hover); border-color: var(--hover); }
.cs-btn-secondary { background: none; color: var(--ink-mid); }
.cs-btn-secondary:hover { border-color: var(--hover); color: var(--hover); }

/* case study detail — centered prose column, images never cropped */
/* Everything in a case study reads left-aligned — including the
   headers — per feedback; centering was making it feel disconnected
   from the rest of the site (which is left-aligned throughout). */
.cs-detail {
  max-width: 720px;
  margin: 0 auto;
  text-align: left;
}
.cs-detail .dv-title { text-align: left; }
.cs-appicon-row {
  display: flex; flex-direction: column; align-items: flex-start; gap: 18px;
  margin-bottom: 8px;
}
.cs-appicon-row img { width: 84px; height: 84px; border-radius: 20px; box-shadow: var(--sh-base); }

.cs-meta-row {
  display: flex; flex-wrap: wrap; justify-content: flex-start;
  gap: 32px;
  padding: 28px 0;
  margin: 28px 0 48px;
  border-top: 1px solid rgba(30,23,16,0.12);
  border-bottom: 1px solid rgba(30,23,16,0.12);
}
.cs-meta-item .cs-meta-label {
  font-family: var(--font-mono);
  font-size: 9px; letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 8px;
}
.cs-meta-item .cs-meta-value {
  font-family: var(--font-serif);
  font-size: 16px;
  color: var(--ink-mid);
}

/* section headers get real breathing room above them — this is what
   was missing before ("more spacing above them") */
.cs-block { margin: 64px 0 48px; }
.cs-eyebrow {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: 16px;
}
.cs-h2 {
  font-family: var(--font-serif);
  font-size: clamp(22px, 3.4vw, 32px);
  font-weight: 400;
  color: var(--ink);
  margin-bottom: 18px;
}
.cs-p {
  font-family: Georgia, serif;
  font-size: 17px; line-height: 1.9;
  color: var(--ink-mid);
}
.cs-p + .cs-p { margin-top: 1.2em; }
.cs-list {
  text-align: left;
  max-width: 560px;
  margin: 0;
  font-family: Georgia, serif;
  font-size: 17px; line-height: 1.85;
  color: var(--ink-mid);
  padding-left: 22px;
}
.cs-list li { margin-bottom: 0.5em; }
.cs-icon-list {
  display: flex; flex-direction: column; gap: 14px;
  max-width: 520px; margin: 0;
}
.cs-icon-row {
  display: flex; align-items: flex-start; gap: 12px;
  font-family: Georgia, serif;
  font-size: 16px; line-height: 1.6;
  color: var(--ink-mid);
  text-align: left;
}
.cs-icon-row .icon { font-size: 18px; flex-shrink: 0; }

/* full-bleed single image — natural size, never cropped */
.cs-img-full { max-width: 100%; margin: 40px 0; }
.cs-img-full img { width: 100%; height: auto; display: block; border-radius: 4px; }

/* a row of images (e.g. testimonial screenshots) — wraps, each at its
   own natural aspect ratio, nothing forced into a fixed box */
.cs-img-row {
  display: flex; flex-wrap: wrap; justify-content: flex-start; gap: 16px;
  margin: 40px 0;
}
.cs-img-row img {
  height: 260px; width: auto; max-width: 100%;
  border-radius: 4px;
  box-shadow: var(--sh-base);
}

/* horizontal, tap/swipe-through carousel — used for testimonial rows
   instead of stacking every screenshot in one wrapped block */
.cs-carousel-wrap { position: relative; margin: 40px 0; }
.cs-carousel {
  display: flex; gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding: 4px 4px 16px;
  -webkit-overflow-scrolling: touch;
}
.cs-carousel::-webkit-scrollbar { height: 6px; }
.cs-carousel::-webkit-scrollbar-thumb { background: rgba(30,23,16,0.15); border-radius: 3px; }
.cs-carousel img {
  height: 300px; width: auto; flex-shrink: 0;
  scroll-snap-align: center;
  border-radius: 4px;
  box-shadow: var(--sh-base);
}
.cs-carousel-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(30,23,16,0.16);
  background: var(--paper);
  color: var(--ink-mid);
  font-family: var(--font-mono);
  font-size: 16px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: border-color 0.2s, color 0.2s;
  box-shadow: var(--sh-base);
}
.cs-carousel-arrow:hover { border-color: var(--hover); color: var(--hover); }
.cs-carousel-arrow.prev { left: -8px; }
.cs-carousel-arrow.next { right: -8px; }
@media (max-width: 900px) {
  .cs-carousel-arrow { display: none; }
}

.cs-stats {
  display: flex; flex-wrap: wrap; justify-content: flex-start; gap: 40px;
  margin-bottom: 8px;
}
.cs-stat-value {
  font-family: var(--font-serif);
  font-size: clamp(28px, 4vw, 44px);
  color: var(--amber);
  line-height: 1;
}
.cs-stat-label {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 8px;
}

.cs-logo-strip {
  display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; gap: 32px;
  opacity: 0.75;
}
.cs-logo-strip img { height: 22px; width: auto; object-fit: contain; filter: grayscale(1); }

.cs-nav-footer {
  display: flex; justify-content: space-between; align-items: center;
  padding-top: 40px; margin-top: 20px;
  border-top: 1px solid rgba(30,23,16,0.12);
}
.cs-nav-footer button {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-faint);
  background: none; border: none; cursor: pointer;
  transition: color 0.2s;
}
.cs-nav-footer button:hover { color: var(--hover); }

/* same "← All case studies" link, repeated at the top of a case study
   so there's a quick way back without scrolling to the bottom */
.cs-back-top {
  display: block;
  margin-bottom: 32px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-faint);
  background: none; border: none; cursor: pointer;
  transition: color 0.2s;
}
.cs-back-top:hover { color: var(--hover); }

@media (max-width: 680px) {
  .cs-img-row img { height: 200px; }
  .cs-carousel img { height: 220px; }
}

/* ───────────────────────────────────────────
   SHELF — Games & Bookshelf share this pattern:
   a cover image with a short editable note beneath it. Same component,
   different data array per domain (see GAMES / BOOKS in script.js).
─────────────────────────────────────────── */
/* GAMES — own row-major grid: fixed column width, each cover keeps
   its own natural height (no crop, no stretch, no upscaling past
   source resolution). Column count grows with viewport, capped at 6
   per request, with a tight-enough min width to keep the page from
   reading sparse. */
.games-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  align-items: start;
  gap: 16px;
  margin-bottom: 40px;
}
@media (min-width: 480px) { .games-grid { grid-template-columns: repeat(3, 1fr); gap: 18px; } }
@media (min-width: 700px) { .games-grid { grid-template-columns: repeat(4, 1fr); gap: 22px; } }
@media (min-width: 1000px) { .games-grid { grid-template-columns: repeat(5, 1fr); gap: 24px; } }
@media (min-width: 1300px) { .games-grid { grid-template-columns: repeat(6, 1fr); gap: 26px; } }
.games-tile {
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
  display: block;
  transition: transform 0.3s cubic-bezier(0.34,1.3,0.64,1), box-shadow 0.25s;
}
.games-tile:hover { transform: translateY(-3px); box-shadow: var(--sh-lift); }
.games-tile img { width: 100%; height: auto; display: block; }

/* BOOKSHELF — opposite of Games: every cover shares one row height,
   width follows each cover's own aspect ratio (no crop, no stretch).
   That shared height is computed in JS (see sizeBooksRow in
   script.js) so the covers justify to fill the FULL row width edge-
   to-edge, like a justified photo gallery, instead of sitting at an
   arbitrary fixed height that leaves slack on the sides. Below the
   mobile breakpoint the row just wraps at a fallback height instead. */
.books-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 22px;
  margin-bottom: 40px;
  height: 240px;
}
@media (min-width: 700px) { .books-row { height: auto; } }
.books-tile {
  height: 100%;
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
  display: block;
  transition: transform 0.3s cubic-bezier(0.34,1.3,0.64,1), box-shadow 0.25s;
}
.books-tile:hover { transform: translateY(-3px); box-shadow: var(--sh-lift); }
.books-tile img { height: 100%; width: auto; max-width: 100%; display: block; }

/* ───────────────────────────────────────────
   EMBEDS — Film (video), Playlist (Spotify), Writing (Substack)
─────────────────────────────────────────── */
.embed-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 40px;
  margin-bottom: 40px;
}
@media (min-width: 800px) {
  .embed-grid { grid-template-columns: 1fr 1fr; gap: 44px 36px; }
}
.embed-video {
  position: relative;
  width: 100%;
  aspect-ratio: 16/9;
  border-radius: 6px;
  overflow: hidden;
  background: var(--paper-dark);
  box-shadow: var(--sh-base);
}
.embed-video.portrait { aspect-ratio: 9/16; }
.embed-video iframe, .embed-video blockquote {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: none;
}
.embed-caption {
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.05em;
  color: var(--ink-faint);
  margin-top: 10px;
}
/* ───────────────────────────────────────────
   PERFORMANCE — custom layout (not the shared .gallery-grid). The
   video is given full priority: height:100% of the row + width:auto
   renders it at its complete natural size (nothing cropped, nothing
   shrunk to make room). It doesn't flex-shrink, so whatever width is
   left over is split evenly between the two photo columns (flex:1
   each) — one full, uncropped photo per column. Order fixed in the
   markup: photo, photo, video.
─────────────────────────────────────────── */
.performance-row {
  display: flex;
  gap: 24px;
  align-items: stretch;
  justify-content: center;
  height: calc(100vh - var(--header-h) - 140px);
  max-height: 720px;
}
.performance-tile {
  border-radius: 6px;
  overflow: hidden;
  cursor: pointer;
  box-shadow: var(--sh-base);
  transition: transform 0.3s cubic-bezier(0.34,1.3,0.64,1), box-shadow 0.25s;
}
.performance-tile:hover { transform: translateY(-3px); box-shadow: var(--sh-lift); }
/* Photo columns: each takes an equal share of whatever width the
   video leaves behind — a masonry column, not a box the photo has to
   fit inside. The photo always fills the FULL column width at its
   own aspect ratio, top-aligned, with no height limit — it's free to
   run past the row's height (and off the bottom of the viewport) at
   its full width rather than ever being shrunk or cropped to fit. */
.performance-photo-tile {
  flex: 1 1 0;
  min-width: 0;
  align-self: flex-start;
}
.performance-photo-tile img {
  width: 100%; height: auto;
  display: block;
}
/* Video: full row height, natural (auto) width — its true size,
   never cropped. */
.performance-video-wrap {
  height: 100%;
  flex-shrink: 0;
}
.performance-video-wrap video {
  height: 100%; width: auto;
  object-fit: contain;
  display: block;
}
@media (max-width: 900px) {
  .performance-row {
    flex-direction: column;
    height: auto; max-height: none;
  }
  /* Shrink-wrapped to the video itself (not stretched to the full
     row width, and no card box behind it) so the clickable area and
     visible frame are exactly the video, matching how the photo
     tiles behave. width:auto + max-width/max-height (not width:100%)
     avoids the same stretch bug fixed above for the photos. */
  .performance-video-wrap {
    width: auto;
    height: auto;
    align-self: center;
    border-radius: 0;
    box-shadow: none;
  }
  .performance-video-wrap video { width: auto; height: auto; max-width: 100%; max-height: 55vh; }
  /* Reset the desktop flex:1 1 0 (which governs HEIGHT once the row
     switches to a column) — otherwise flex-basis:0 collapses each
     photo tile to zero height and overflow:hidden clips the image
     out entirely. Tile stays shrink-wrapped to the image (not
     stretched to the full row width) so the clickable area matches
     the photo exactly, same as desktop. */
  .performance-photo-tile { flex: 0 0 auto; align-self: center; }
  /* width:100% + height:auto + max-height together would stretch a
     tall photo (the height cap kicks in without shrinking width to
     match, distorting the aspect ratio) — width:auto + both max-
     dimensions lets the browser pick whichever constraint binds and
     scale proportionally instead. */
  .performance-photo-tile img { width: auto; height: auto; max-width: 100%; max-height: 45vh; }
}

/* Video page — one row of 4 on desktop (all visible, no wrapping),
   2×2 on mobile. Each cell sizes to its own content (no forced
   aspect-ratio box for Instagram, which self-sizes via embed.js), so
   there's no leftover blank space around a smaller video. */
.video-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  align-items: start;
  margin-bottom: 40px;
}
@media (max-width: 900px) {
  .video-grid { grid-template-columns: repeat(2, 1fr); gap: 14px; }
}
.video-tile { min-width: 0; }
.video-tile > iframe.instagram-media,
.video-tile > blockquote.instagram-media {
  width: 100% !important;
  min-width: 0 !important;
  margin: 0 !important;
}
.embed-empty {
  font-family: Georgia, serif;
  font-size: 16px; line-height: 1.8;
  color: var(--ink-mid);
  max-width: 560px;
  padding: 40px 0;
}
.embed-empty code {
  font-family: var(--font-mono);
  font-size: 13px;
  background: var(--paper-dark);
  padding: 2px 6px;
  border-radius: 3px;
}

/* full-screen-feeling wrapper for the single-embed views (Playlist, Writing) —
   fills the space below the header rather than reading as a small
   widget lost in a mostly-empty page */
.embed-fullscreen {
  min-height: calc(100vh - var(--header-h));
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
  padding: 40px 20px;
}
.embed-fullscreen .dv-title { text-align: center; }

/* ───────────────────────────────────────────
   PLAYLIST MODAL — the only way to reach the playlist; tapping the
   iPod on the desk opens it over whatever's currently on screen.
─────────────────────────────────────────── */
/* No card, no title bar, no cream background — just the embed itself
   rising up flush with the bottom edge of the screen (like a drawer),
   a faint dimming behind it, and a plain white circular close button.
   GSAP animates translateY (slides up to open, back down to close). */
#playlist-modal {
  position: fixed; inset: 0; z-index: 320;
  background: rgba(14,10,6,0.28);
  display: flex; align-items: flex-end; justify-content: center;
  opacity: 0;
  pointer-events: none;
}
#playlist-modal.active { pointer-events: all; }
.playlist-panel {
  position: relative;
  width: 100%; max-width: 480px;
  padding: 0 16px;
}
.playlist-modal-close {
  position: absolute; top: -52px; right: 16px;
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none; background: #fff;
  color: var(--ink);
  cursor: pointer;
  font-family: var(--font-mono); font-size: 14px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.25);
  transition: transform 0.2s;
}
.playlist-modal-close:hover { transform: scale(1.08); }

.spotify-embed {
  width: 100%;
  max-width: 760px;
  border-radius: 12px 12px 0 0;
  overflow: hidden;
  box-shadow: 0 -20px 50px rgba(0,0,0,0.3);
}
.spotify-embed iframe { display: block; width: 100%; height: min(660px, 72vh); border: none; }
.playlist-panel .spotify-embed { margin: 0; }

.substack-cta {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-mono);
  font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--paper);
  background: var(--ink);
  padding: 16px 28px;
  border-radius: 100px;
  text-decoration: none;
  margin-bottom: 36px;
  transition: background 0.2s;
}
.substack-cta:hover { background: var(--hover); }
.substack-embed {
  width: 100%;
  max-width: 560px;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: var(--sh-lift);
}
.substack-embed iframe { display: block; width: 100%; height: min(480px, 60vh); border: none; }

/* ───────────────────────────────────────────
   ABOUT VIEW
─────────────────────────────────────────── */
/* top block — portrait (+ Get in Touch under it) on the left, header
   and the first two intro paragraphs on the right. Everything below
   this (the two lists, skills preview) runs the full column width. */
.about-top {
  display: flex; flex-direction: column; gap: 28px;
  margin-bottom: 40px;
}
@media (min-width: 760px) {
  .about-top { flex-direction: row; align-items: flex-start; gap: 56px; }
}
.about-portrait-col { display: flex; flex-direction: column; align-items: center; gap: 18px; flex-shrink: 0; width: 100%; }
@media (min-width: 760px) {
  .about-portrait-col { max-width: 320px; }
}
/* Mobile-only "Get in Touch": a second button rendered after "Reach
   out if you're" (see renderAbout) — on mobile it replaces the one
   under the portrait; on desktop it's hidden and the portrait one
   shows instead. */
.about-mobile-cta { display: none; }
@media (max-width: 759px) {
  .about-portrait-col > .cs-btn { display: none; }
  .about-mobile-cta { display: inline-flex; margin-top: 20px; }
}
.about-portrait {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 6px;
  background: var(--paper-dark);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.about-portrait img { width: 100%; height: 100%; object-fit: cover; }
.about-portrait span {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-faint);
}
.about-intro { flex: 1; min-width: 0; text-align: left; }
.about-intro .dv-title { text-align: left; margin-bottom: 24px; }
.about-intro .dv-prose { max-width: 620px; }

.about-full { text-align: left; }
.about-full h3 {
  font-family: var(--font-serif);
  font-size: 19px;
  color: var(--ink);
  margin: 0 0 12px;
}
.about-full section + section { margin-top: 32px; }
.about-list {
  font-family: Georgia, serif;
  font-size: 16px; line-height: 1.8;
  color: var(--ink-mid);
  max-width: 680px;
  padding-left: 20px;
}
.about-list li { margin-bottom: 0.4em; }
.about-list a { color: var(--hover); }
.about-bottom-cta { margin-top: 48px; text-align: left; }

/* shared skills-year rows — used on both /about (a preview) and the
   full Artistic Skills Tracker page */
.skills-year {
  display: flex; gap: 28px; align-items: baseline;
  padding-bottom: 20px;
  border-bottom: 1px solid rgba(30,23,16,0.1);
}
.skills-year-label {
  font-family: var(--font-serif);
  font-size: 22px; color: var(--amber);
  width: 132px; flex-shrink: 0;
  white-space: nowrap;
}
.skills-list {
  font-family: var(--font-mono);
  font-size: 13px; letter-spacing: 0.03em;
  color: var(--ink-mid);
  line-height: 1.8;
}

/* ───────────────────────────────────────────
   ARTISTIC SKILLS TRACKER — full page (content, not the old
   constellation visualization)
─────────────────────────────────────────── */
.skills-years-list {
  display: flex; flex-direction: column; gap: 22px;
  margin-bottom: 56px;
}
.skills-proficiency {
  display: grid;
  grid-template-columns: 1fr;
  gap: 28px;
}
@media (min-width: 640px) {
  .skills-proficiency { grid-template-columns: repeat(3, 1fr); }
}
.skills-proficiency-tier-label {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 10px;
}
.skills-proficiency-list {
  font-family: var(--font-serif);
  font-size: 16px;
  color: var(--ink-mid);
  line-height: 1.7;
}

/* ───────────────────────────────────────────
   TRANSITION FLASH — analog film feel
─────────────────────────────────────────── */
#transition-flash {
  position: fixed; inset: 0; z-index: 600;
  background: var(--paper);
  opacity: 0;
  pointer-events: none;
}

/* ───────────────────────────────────────────
   NAV OVERLAY BACKDROP
─────────────────────────────────────────── */
#nav-backdrop {
  position: fixed; inset: 0; z-index: 195;
  background: rgba(30,23,16,0.22);
  opacity: 0; pointer-events: none;
  transition: opacity 0.35s;
}
#nav-backdrop.open { opacity: 1; pointer-events: all; }

/* ───────────────────────────────────────────
   FOOTER — on every page. Pushed below the first viewport on the
   desk (margin-top:100vh) so it's never visible until the user
   scrolls, per feedback; domain views just get it after their content.
─────────────────────────────────────────── */
.site-footer {
  position: relative; z-index: 5;
  margin-top: 100vh;
  padding: 36px 28px 40px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  display: flex; flex-direction: column; gap: 8px;
}
.site-footer a { color: var(--ink-faint); text-decoration: none; transition: color 0.2s; }
.site-footer a:hover { color: var(--hover); }
#domain-view .site-footer { margin-top: 0; }

/* ───────────────────────────────────────────
   MOBILE — desk becomes a card grid/list.
   Header, name size, social/nav tap targets stay identical to
   desktop per feedback; only the desk surface itself adapts.
─────────────────────────────────────────── */
@media (max-width: 680px) {
  /* Edge-to-edge 2-column grid, every card exactly the same size —
     object image on top, label underneath, nothing rotated. */
  #desk {
    overflow-y: auto; overflow-x: hidden;
    padding-top: var(--header-h);
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  .obj {
    position: relative !important;
    top: auto !important; left: auto !important;
    width: auto !important;
    margin: 0;
    transform: none !important;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 10px;
    aspect-ratio: 1 / 1.05;
    padding: 14px 10px;
    opacity: 0; /* GSAP reveals */
  }
  /* grid order — matches the requested reading order rather than DOM order */
  #o-product      { order: 1; }
  #o-photography  { order: 2; }
  #o-writing      { order: 3; }
  #o-illustration { order: 4; }
  #o-shop         { order: 5; }
  #o-bookshelf    { order: 6; }
  #o-music        { order: 7; }
  #o-games        { order: 8; }
  #o-film         { order: 9; }
  #o-performance  { order: 10; }

  .site-footer { order: 13; grid-column: 1 / -1; margin-top: 20px; }

  /* On mobile these were floating absolutely over the grid (and the
     tracker link was landing below the footer). Instead they're just
     two more grid cells — same footprint as an object card, just
     text-only, sitting after Film/Performance. */
  .about-link,
  .skills-tracker-link {
    position: relative !important;
    top: auto !important; left: auto !important; right: auto !important; bottom: auto !important;
    aspect-ratio: 1 / 1.05;
    display: flex; align-items: center; justify-content: center;
    text-align: center;
    padding: 14px 10px;
    font-size: 19px;
    opacity: 0.9;
  }
  .about-link { order: 11; }
  .skills-tracker-link { order: 12; }

  .obj-inner {
    width: 80%;
    flex: 1;
    min-height: 0;
    display: flex; align-items: center; justify-content: center;
    transform: none !important;
  }
  .obj-img {
    max-width: 100%; max-height: 100%;
    width: auto; height: auto;
    object-fit: contain;
    filter: none;
  }
  .obj:hover .obj-inner .obj-img { filter: none; }
  .obj-label {
    position: static;
    transform: none !important;
    opacity: 0.9 !important;
    color: var(--ink-mid) !important;
    font-size: 15px;
    text-align: center;
    white-space: normal;
  }

  .dv-content { padding: calc(var(--header-h) + 30px) 22px 60px; }
  .cs-image-row { grid-template-columns: 1fr; }
  .cs-index-item { gap: 16px; }
  .cs-index-thumb { width: 92px; height: 68px; }
  #nav-menu { width: 100%; }
}
