/* ============================================================
   auroo.art — /graphics: the black room.

   A black-cube corridor in CSS 3D. Nineteen works hung down the
   left and right walls; page scroll pushes the room past you.

   Rules this file keeps to, because breaking them is what made the
   old corridor unreliable:
     · the shell — floor, ceiling, two walls, back wall — never moves.
       Only the works translate in Z. A static shell cannot run out,
       cannot cross the camera plane, and cannot tear;
     · one transform, on one element, driven by one number;
     · every frame is the same height with the same mat, so the hang
       is one line down the room whatever shape the art is;
     · every image carries a real src and a reserved box, so nothing
       can fail to appear and nothing reflows once it loads;
     · no video plays in the room. Motion pieces show their frame and
       play in the lightbox, on purpose.

   Loaded only by /graphics. It adds no colour tokens of its own —
   the page is the site's black, and every surface below reads from
   site.css. The only local colours are the shades of black the shell
   is built out of, which have to be literal to sit next to each other.
   ============================================================ */

/* ---------- 1. Local colour ----------
   Named here only because the room needs a vocabulary of near-blacks
   that the site's six tokens do not carry. Nothing else overrides a
   token — --fg, --fg-meta, --line and --acid all still mean what they
   mean everywhere else on the site. */
.g-page {
  --room-lit:   #17171a;   /* the wall a work is hung on, at the near end */
  --room-dim:   #0a0a0c;   /* the same wall as it runs away from you */
  --room-floor: #0e0e10;
  --room-ceil:  #050506;
  --mat:        #f4f3f1;   /* what sits behind a poster while it decodes */

  background: var(--bg);
  color: var(--fg);
}

/* ---------- 2. The room ---------- */
.room-sec {
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}

/* The track is the scroll budget. JS writes its real height from the
   measured depth of the room; this is only the pre-JS value. */
.room-track {
  position: relative;
  height: 100vh;

  /* JS overwrites the first five in px on every layout pass. They are
     declared here so the room is still geometrically valid for the one
     frame before JS runs. */
  --room-w: 1030px;   /* wall to wall */
  --room-h: 880px;    /* floor to ceiling */
  --art-h:  480px;    /* the hang height every work shares */

  /* The corridor is narrower than the screen, which is what gives it its
     angle — a room the shape of the viewport puts its corners on the
     screen's corners and the walls barely lean at all. A narrow room
     would show its own mouth, so the mouth is pushed this far in FRONT
     of z=0, where perspective magnifies it back out past the edges of
     the screen. Everything else about the shell is unchanged. */
  --mouth: 360px;

  /* deeper than the works ever reach, so the back wall is always behind
     the last frame and never has to be sorted against one */
  --shell-d: 9000px;
  --mw: clamp(9px, 0.9vw, 16px);   /* the mat, uniform on all four sides */
}

.room {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background: var(--bg);
}

/* perspective + overflow live together here on purpose: putting
   preserve-3d on this element instead would flatten the whole scene. */
.room__stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  perspective: 1150px;          /* JS overwrites, from the stage width */
  perspective-origin: 50% 50%;
}

.room__scene,
.room__shell,
.room__world {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}
.room__world { will-change: transform; }

/* ---------- 3. The shell ----------
   Five planes, all anchored on the centre of the stage and pushed out
   to their faces. Nothing here is animated: the camera stays at the
   mouth of the corridor and the art comes to it. */
.room__plane {
  position: absolute;
  left: 50%;
  top: 50%;
}

.room__plane--left,
.room__plane--right {
  width: var(--shell-d);
  height: var(--room-h);
  margin-left: calc(var(--shell-d) / -2);
  margin-top: calc(var(--room-h) / -2);
}
/* after rotateY(90deg) the plane's own left edge is the near end of the
   room and its right edge the far end, so the gradient runs `to right`.
   On the opposite wall the mapping flips, hence `to left`.

   Both walls run from lit at the near end to flat black at the far, so
   depth is carried by the fall-off itself. The right wall is a shade
   under the left, which is the only thing telling the two apart once
   they are the same black. */
.room__plane--left {
  transform: translateX(calc(var(--room-w) / -2)) translateZ(calc(var(--mouth) - var(--shell-d) / 2)) rotateY(90deg);
  background: linear-gradient(to right, var(--room-lit) 0%, #111114 24%, var(--room-dim) 54%, #000 100%);
}
.room__plane--right {
  transform: translateX(calc(var(--room-w) / 2)) translateZ(calc(var(--mouth) - var(--shell-d) / 2)) rotateY(-90deg);
  background: linear-gradient(to left, #131316 0%, #0d0d10 24%, #08080a 54%, #000 100%);
}

.room__plane--floor,
.room__plane--ceil {
  width: var(--room-w);
  height: var(--shell-d);
  margin-left: calc(var(--room-w) / -2);
  margin-top: calc(var(--shell-d) / -2);
}
/* the hairlines are the floor's own side edges, which land exactly on
   the two wall junctions — the only drawn lines in the whole room */
.room__plane--floor {
  transform: translateY(calc(var(--room-h) / 2)) translateZ(calc(var(--mouth) - var(--shell-d) / 2)) rotateX(90deg);
  background: linear-gradient(to top, var(--room-floor) 0%, #0a0a0c 26%, #000 62%);
  border-left: 1px solid rgba(255, 255, 255, 0.06);
  border-right: 1px solid rgba(255, 255, 255, 0.06);
}
.room__plane--ceil {
  transform: translateY(calc(var(--room-h) / -2)) translateZ(calc(var(--mouth) - var(--shell-d) / 2)) rotateX(-90deg);
  background: linear-gradient(to bottom, var(--room-ceil) 0%, #030304 30%, #000 64%);
}

.room__plane--end {
  width: var(--room-w);
  height: var(--room-h);
  margin-left: calc(var(--room-w) / -2);
  margin-top: calc(var(--room-h) / -2);
  transform: translateZ(calc(var(--mouth) - var(--shell-d)));
  background: #000;
}

/* distance fog. Everything far away converges on the middle of the
   stage, so a soft blob of the page's own black there is the whole
   effect — and it means the far end of the corridor never has to be
   resolved. On black it doubles as the thing that stops the last
   works in the room piling into a smear at the vanishing point. */
.room__haze {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(26% 30% at 50% 50%,
    #000000 0%,
    rgba(0, 0, 0, 0.94) 28%,
    rgba(0, 0, 0, 0.55) 56%,
    rgba(0, 0, 0, 0) 82%);
}

/* ---------- 4. A hung work ----------
   --ar is the artwork's true aspect ratio and --art-h is shared by
   every work, so the frame is the exact shape of the file at a common
   height: nothing is cropped, letterboxed, or resized once the image
   arrives, and the mat is the same width on all four sides of all
   nineteen. --side is -1 on the left wall and 1 on the right; --z is
   how far down the room the work hangs. JS writes both. */
.piece {
  --ar: 0.75;
  --side: -1;
  --z: 0px;
  --pw: calc(var(--art-h) * var(--ar) + var(--mw) * 2);
  --ph: calc(var(--art-h) + var(--mw) * 2);

  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--pw);
  height: var(--ph);
  margin-left: calc(var(--pw) / -2);
  margin-top: calc(var(--ph) / -2);

  /* 3px shy of the wall, so the frame and the plaster are never
     coplanar and can never fight for the same pixels */
  transform:
    translateX(calc(var(--side) * (var(--room-w) / 2 - 3px)))
    translateZ(var(--z))
    rotateY(calc(var(--side) * -90deg));
  transform-origin: 50% 50%;
  backface-visibility: hidden;

  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
}

.piece__frame {
  position: absolute;
  inset: 0;
  display: block;
  padding: var(--mw);
  /* The mat stays white. On a black wall it is the whole reason the hang
     reads as framed work rather than as images floating in the dark, and
     it needs no keyline of its own — the wall supplies the contrast. The
     shadow is only there to lift a frame off the lit near end of the
     wall, where the plaster is not yet black. */
  background: #fff;
  box-shadow: 0 26px 44px -24px rgba(0, 0, 0, 0.9);
  transition: box-shadow var(--d-mid) var(--e-out);
}
.piece:hover .piece__frame,
.piece:focus-visible .piece__frame {
  box-shadow:
    0 0 0 3px var(--acid),
    0 34px 56px -24px rgba(0, 0, 0, 0.9);
}

.piece__art {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  background: var(--mat);
}
.piece__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;   /* --ar matches the file, so this crops nothing */
  display: block;
}
.piece__art::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 1px rgba(14, 14, 14, 0.16);
  pointer-events: none;
}

/* the plaque, on the wall under the frame */
.piece__label {
  position: absolute;
  left: 0;
  top: calc(100% + 0.9rem);
  width: 100%;
  display: block;
}
.piece__no,
.piece__title,
.piece__medium { display: block; }

.piece__no {
  font-family: var(--f-ui);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  color: var(--fg-meta);
  font-variant-numeric: tabular-nums;
}
.piece__title {
  font-family: var(--f-case);
  font-size: 1.375rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin-top: 0.4rem;
}
.piece:hover .piece__title,
.piece:focus-visible .piece__title {
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
}
.piece__medium {
  font-family: var(--f-ui);
  font-size: 0.8125rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-meta);
  margin-top: 0.35rem;
}

/* ---------- 5. The bar along the bottom ---------- */
.room__bar {
  position: absolute;
  z-index: 3;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  padding-inline: clamp(1.25rem, 7vw, 9rem);
  padding-bottom: clamp(1.5rem, 3vw, 2.5rem);
  /* a work passing close by brings its plaque down to about here, so the
     bar sits on its own patch of floor rather than on top of a title */
  padding-top: clamp(2rem, 5vw, 4rem);
  background: linear-gradient(to top, #000000 38%, rgba(0, 0, 0, 0));
  font-family: var(--f-ui);
  font-size: var(--t-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-meta);
}
.room__count {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--fg);
}
.room__count i { font-style: normal; color: var(--fg-meta); padding-inline: 0.35em; }

.room__line {
  position: relative;
  flex: 1 1 auto;
  height: 1px;
  background: var(--line);
  overflow: hidden;
}
.room__line > span {
  position: absolute;
  inset: 0;
  background: var(--acid);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.room__skip {
  white-space: nowrap;
  color: var(--fg-meta);
  transition: color var(--d-fast) var(--e-out);
}
.room__skip:hover { color: var(--fg); }

/* ---------- 6. Flat mode ----------
   Phones, reduced motion, and no JS. The room collapses to what it
   actually is underneath — one line of framed works — swiped with a
   finger instead of walked through. Every 3D declaration above is
   unwound here rather than guarded above, so the 3D path stays a
   single unconditional block. */
.room-track.is-flat,
html:not(.js-ready) .room-track {
  height: auto !important;
  --art-h: clamp(260px, 56vh, 480px);
  --mw: clamp(10px, 2.4vw, 18px);
}

.room-track.is-flat .room,
html:not(.js-ready) .room-track .room {
  position: static;
  height: auto;
  overflow: visible;
  padding-block: clamp(2.5rem, 6vw, 4.5rem) clamp(2rem, 5vw, 3.5rem);
}

.room-track.is-flat .room__shell,
.room-track.is-flat .room__haze,
.room-track.is-flat .room__bar,
html:not(.js-ready) .room-track .room__shell,
html:not(.js-ready) .room-track .room__haze,
html:not(.js-ready) .room-track .room__bar { display: none; }

.room-track.is-flat .room__stage,
html:not(.js-ready) .room-track .room__stage {
  position: static;
  overflow: visible;
  perspective: none;
}
.room-track.is-flat .room__scene,
html:not(.js-ready) .room-track .room__scene {
  position: static;
  transform-style: flat;
}

.room-track.is-flat .room__world,
html:not(.js-ready) .room-track .room__world {
  position: static;
  transform: none !important;
  transform-style: flat;
  display: flex;
  align-items: flex-end;
  gap: clamp(1.5rem, 6vw, 3rem);
  padding-inline: clamp(1.25rem, 7vw, 9rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: clamp(1.25rem, 7vw, 9rem);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.room-track.is-flat .room__world::-webkit-scrollbar,
html:not(.js-ready) .room-track .room__world::-webkit-scrollbar { display: none; }

/* one work at a time, sized from the screen: a 3:4 poster held to a
   shared height ends up barely wider than a phone's margins */
.room-track.is-flat .piece,
html:not(.js-ready) .room-track .piece {
  position: static;
  transform: none !important;
  visibility: visible !important;
  opacity: 1 !important;
  flex: 0 0 auto;
  width: min(74vw, var(--pw));
  height: auto;
  margin: 0;
  scroll-snap-align: center;
}
.room-track.is-flat .piece__frame,
html:not(.js-ready) .room-track .piece__frame { position: static; }

.room-track.is-flat .piece__art,
html:not(.js-ready) .room-track .piece__art {
  height: auto;
  aspect-ratio: var(--ar);
}

/* fixed height so a two-line title cannot push its neighbour's frame up */
.room-track.is-flat .piece__label,
html:not(.js-ready) .room-track .piece__label {
  position: static;
  min-height: 5.25rem;
  margin-top: 1rem;
}

/* ---------- 7. Lightbox ---------- */
.glb {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: grid;
  place-items: center;
  padding: clamp(0.75rem, 3vw, 2.5rem);
}
.glb[hidden] { display: none; }
.glb__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.glb__panel {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
  gap: clamp(1rem, 3vw, 3rem);
  align-items: center;
  max-width: 1180px;
  width: 100%;
  animation: glbIn var(--d-mid) var(--e-out) both;
}
@keyframes glbIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.glb__stage {
  --stage-h: min(80vh, 860px);
  justify-self: end;
  width: min(100%, calc(var(--stage-h) * var(--ar, 0.75)));
  aspect-ratio: var(--ar, 0.75);
  max-height: var(--stage-h);
  background: #0b0b0d;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.glb__stage > img,
.glb__stage > video { width: 100%; height: 100%; object-fit: contain; }

.glb__no {
  font-family: var(--f-ui);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--fg-meta);
  font-variant-numeric: tabular-nums;
}
.glb__title {
  font-family: var(--f-case);
  font-size: clamp(1.5rem, 3vw, 2.25rem);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin-top: 0.7rem;
}
.glb__medium {
  font-family: var(--f-ui);
  font-size: 0.75rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--fg-meta);
  margin-top: 0.7rem;
}
.glb__copy {
  font-family: var(--f-case);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--fg-muted);
  margin-top: 1.1rem;
}
.glb__actions { display: flex; gap: 0.5rem; margin-top: 1.5rem; flex-wrap: wrap; }
.glb__close { position: absolute; top: -0.25rem; right: 0; z-index: 2; }
.glb__count {
  font-family: var(--f-ui);
  font-size: var(--t-xs);
  letter-spacing: 0.12em;
  color: var(--fg-meta);
  margin-top: 1.4rem;
  font-variant-numeric: tabular-nums;
}

.gchip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  padding: 0.5rem 0.9rem;
  font-family: var(--f-ui);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  transition: color var(--d-fast) var(--e-out), border-color var(--d-fast) var(--e-out);
}
.gchip:hover { border-color: var(--fg); }
.gchip--icon { width: 38px; height: 38px; padding: 0; font-size: 15px; }
/* a class selector outranks the UA rule for [hidden], so the sound toggle
   would still show on stills without this */
.gchip[hidden] { display: none; }

/* ---------- 8. The flat index ---------- */
.index__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: var(--s-4);
}
.index-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: clamp(1.5rem, 3vw, 2.75rem);
}
/* Bottom-aligned, the way work is actually hung: every piece in a row sits
   on one line and every caption starts on one line, whatever shape the
   piece is. Aligning tops instead leaves the captions stepped down the row,
   which is what made the old grid read as untidy. A stretched <button>
   would centre its own contents and undo this, hence align-self. */
.index-item {
  display: block;
  align-self: end;
  text-align: left;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
/* these are spans so they can sit inside a <button>; aspect-ratio is
   ignored on an inline box, so they have to be blockified explicitly */
.index-item__frame {
  display: block;
  position: relative;
  background: #0b0b0d;
  aspect-ratio: var(--ar, 3 / 4);
}
.index-item__frame > img { width: 100%; height: 100%; object-fit: cover; }
.index-item__frame::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
  transition: box-shadow var(--d-mid) var(--e-out);
  pointer-events: none;
}
.index-item:hover .index-item__frame::after {
  box-shadow: inset 0 0 0 2px var(--acid);
}
.index-item__play {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 1;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(14, 14, 14, 0.84);
}
.index-item__play::before {
  content: '';
  width: 0;
  height: 0;
  margin-left: 2px;
  border-left: 9px solid #fff;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
}

/* number and title on one line, medium beneath — a single flex row squeezed
   longer titles into an ellipsis at grid widths */
/* fixed height so bottom-aligning the items lines the artwork up too —
   a two-line title would otherwise push its poster higher than its
   neighbours' */
.index-item__cap {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-content: start;
  column-gap: 0.6rem;
  min-height: 4.25rem;
  margin-top: 0.85rem;
  font-family: var(--f-ui);
  font-size: var(--t-xs);
}
.index-item__no {
  grid-column: 1;
  color: var(--fg-meta);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.index-item__title { grid-column: 2; color: var(--fg); font-weight: 500; line-height: 1.35; }
.index-item:hover .index-item__title {
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.index-item__medium {
  grid-column: 2;
  margin-top: 0.3rem;
  color: var(--fg-meta);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ---------- 9. Responsive ---------- */
@media (max-width: 900px) {
  .glb__panel { grid-template-columns: 1fr; align-content: start; max-height: 100%; overflow-y: auto; }
  .glb__stage { --stage-h: min(54vh, 460px); justify-self: center; }
  .glb__close { top: 0.25rem; right: 0.25rem; }
}
@media (max-width: 640px) {
  .index-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  .glb__panel { animation: none; }
}
