/* =========================================================================
   GEUSE portfolio
   Modern CSS: custom properties, CSS Grid, Flexbox, dvh, clamp().
   ========================================================================= */

:root {
  --base-size: 14px;

  --font-stack: -apple-system, "system-ui", Inter, "Segoe UI", Roboto, Oxygen,
    Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;

  --ink: #0b0b0d;
  --muted: rgba(11, 11, 13, 0.68);
  --faint: rgba(11, 11, 13, 0.62);
  --line-color: rgba(12, 16, 24, 0.16);
  --hair: 1px solid var(--line-color);
  --browser-chrome: #e8eaef;

  /* Neumorphic surfaces & engraving tokens. A soft paper base lets the paired
     light/shadow strokes read as carved grooves and raised bars. Surfaces are
     kept deliberately thin (liquid-glass): a translucent tint carries the
     color, and --glass-veil adds the specular sheen that makes the material
     read as glass rather than fog. */
  --paper: #e8eaef;                          /* recessed base plane */
  --surface: rgba(246, 247, 250, 0.35);      /* glassy raised bars */
  --surface-strong: rgba(246, 247, 250, 0.22);  /* story panel tint */
  --glass-veil: linear-gradient(
    160deg,
    rgba(255, 255, 255, 0.22),
    rgba(255, 255, 255, 0.05) 45%,
    rgba(255, 255, 255, 0.16)
  );
  /* Punchier variant for glass sitting on the opaque paper plane (index
     slides, CV sheet), where there is no backdrop to refract: the material
     has to come from the sheen itself. --glass-glare is the diagonal specular
     streak layered on top. */
  --glass-veil-strong: linear-gradient(
    160deg,
    rgba(255, 255, 255, 0.38),
    rgba(255, 255, 255, 0.1) 45%,
    rgba(255, 255, 255, 0.28)
  );
  --glass-glare: linear-gradient(
    115deg,
    rgba(255, 255, 255, 0) 42%,
    rgba(255, 255, 255, 0.4) 50%,
    rgba(255, 255, 255, 0) 58%
  );
  --glass-border: rgba(255, 255, 255, 0.62);
  --glass-border-strong: rgba(255, 255, 255, 0.8);
  --nm-light: rgba(255, 255, 255, 0.95);     /* top-left highlight */
  --nm-dark: rgba(12, 16, 24, 0.16);         /* bottom-right shadow line */
  --nm-dark-soft: rgba(12, 16, 24, 0.08);
  --nm-dark-strong: rgba(12, 16, 24, 0.26);
  --nm-blur: 26px;

  /* Engraved type: paired counter-strokes that press text into the surface. */
  --nm-text-light: rgba(255, 255, 255, 0.88);
  --nm-text-dark: rgba(12, 16, 24, 0.22);

  --pad: 0.5rem;       /* page edge / cell padding */
  --gutter: 0.5rem;    /* column-set gutter */
  --font-size: 1.05rem;
  --line-height: 1.12;
}

@view-transition {
  navigation: auto;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  font-size: var(--base-size);
  min-height: 100%;
  background: var(--browser-chrome);
}

body {
  margin: 0;
  font-family: var(--font-stack);
  font-size: var(--font-size);
  line-height: var(--line-height);
  color: var(--ink);
  background:
    linear-gradient(135deg, #f1f3f7 0%, var(--paper) 48%, #dfe4ec 100%),
    var(--browser-chrome);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;

  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

a {
  color: inherit;
  text-decoration: none;
}
@media (hover: hover) and (pointer: fine) {
  a:hover {
    text-decoration: underline;
  }
}
a:focus-visible,
.pane:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}
.skip-link {
  position: fixed;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 100;
  padding: 0.55rem 0.7rem;
  color: #fff;
  background: #0b0b0d;
  transform: translateY(-150%);
}
.skip-link:focus {
  transform: translateY(0);
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
i,
em {
  font-style: italic;
}
/* Engraved hairline: a dark stroke with a light highlight beneath it, so
   dividers read as carved into the paper rather than printed on it. */
hr {
  border: 0;
  height: 0;
  margin: var(--pad) 0;
  border-top: 1px solid var(--nm-dark);
  box-shadow: 0 1px 0 var(--nm-light);
}

/* Engraved type on the paper plane: light stroke below-right, dark above-left,
   so text reads as pressed into the surface. Scoped to text-only containers —
   the on-image .mediameta overlays keep their own legibility shadows and must
   not inherit this. */
.topbar__cell,
.footer,
.entry__label,
.caption,
.cv {
  text-shadow:
    0.5px 0.5px 0 var(--nm-text-light),
    -0.5px -0.5px 0 var(--nm-text-dark);
}

/* ----- Fixed top labels --------------------------------------------------- */
.topbar {
  display: grid;
  grid-template-columns: 1fr 1fr;
  position: relative;
  z-index: 2;
  padding-top: env(safe-area-inset-top);
  background: var(--glass-veil), var(--surface); /* raised glass bar above the paper */
  border-bottom: 1px solid var(--nm-dark);
  box-shadow: 0 1px 0 var(--nm-light), 0 14px 34px -28px var(--nm-dark-strong);
  backdrop-filter: blur(32px) saturate(1.4);
  -webkit-backdrop-filter: blur(32px) saturate(1.4);
}
.topbar__cell {
  padding: var(--pad);
  min-width: 0;
}
.topbar__chat {
  color: inherit;
}
/* Carved vertical seam between the two labels (dark stroke + light highlight). */
.topbar__cell--left {
  border-right: 1px solid var(--nm-dark);
}
.topbar__cell--right {
  border-left: 1px solid var(--nm-light);
}
/* header that carries an inline close control (Information view) */
.topbar__row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--gutter);
}
.topbar__close {
  justify-self: end;
}

/* ----- Two independently scrolling panes ---------------------------------- */
.panes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 0;
  background:
    linear-gradient(90deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.02) 50%, rgba(255, 255, 255, 0.24)),
    var(--paper);          /* recessed content plane */
}
.pane {
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 0.7rem var(--pad) 1.6rem;
  scrollbar-width: none;             /* match the original's hidden scrollbars */
  -ms-overflow-style: none;
}
.pane::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}
.pane:focus-visible {
  scrollbar-width: thin;
}
.pane:focus-visible::-webkit-scrollbar {
  width: 6px;
}
/* Central carved channel: a dark stroke + light highlight, plus soft inner
   shadows on each side so the seam reads as a recessed groove. The inset
   shadows are painted on the border box and stay put while content scrolls. */
.pane--left {
  border-right: 1px solid var(--nm-dark);
  box-shadow: inset -8px 0 10px -10px var(--nm-dark);
}
.pane--right {
  border-left: 1px solid var(--nm-light);
  box-shadow: inset 8px 0 10px -10px var(--nm-dark);
}

/* ----- Fixed bottom navigation -------------------------------------------- */
.footer {
  display: grid;
  grid-template-columns: 6fr 3fr 3fr;   /* original footer span = 6 / 3 / 3 */
  gap: 0.6rem;
  align-items: end;
  padding: var(--pad);
  padding-bottom: max(var(--pad), env(safe-area-inset-bottom));
  position: relative;
  z-index: 2;
  background: var(--glass-veil), var(--surface); /* raised glass bar below the paper */
  border-top: 1px solid var(--nm-dark);
  box-shadow: 0 -1px 0 var(--nm-light), 0 -14px 34px -28px var(--nm-dark-strong);
  backdrop-filter: blur(32px) saturate(1.4);
  -webkit-backdrop-filter: blur(32px) saturate(1.4);
}
.footer__right {
  text-align: right;
}
.footer__nav {
  display: contents;
}
.footer a.is-active {
  /* active item stays plain, like the original */
  text-decoration: none;
}

/* =========================================================================
   Projects view — image + caption entries
   ========================================================================= */
.entry {
  display: block;
}
.entry__media {
  display: block;
}
.entry__media img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
  box-shadow: 9px 9px 20px var(--nm-dark-soft), -8px -8px 18px rgba(255, 255, 255, 0.5);
}
.entry__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gutter);
  margin-top: 1.4rem;
  margin-bottom: 1.4rem;
}
.caption .view {
  text-decoration: none;
}
.entry__title,
.detail__title {
  display: inline;
  margin: 0;
  font: inherit;
}
.caption u {
  text-decoration: underline;
}
/* Blank-line spacer, mirroring the original's stacked <br><br> rhythm. */
.spacer {
  display: block;
  height: 1.1em;
}

/* =========================================================================
   Index view — thumbnail gallery grid
   ========================================================================= */
/* Masonry columns instead of a row grid: mixed portrait/landscape thumbs pack
   seamlessly top-to-bottom with no orientation gaps. (Visual order flows down
   each column — acceptable for a photo wall.) */
.grid {
  columns: 4;
  column-gap: var(--gutter);
}
/* Each thumbnail is mounted like a film slide: a small raised glass card
   carrying the neumorphic lift, with the image set flat inside it. The panes
   are opaque, so the glass reads through the translucent tint alone — no
   backdrop-filter (it would cost GPU per-thumbnail with nothing to blur). */
.grid a {
  display: block;
  break-inside: avoid;
  margin-bottom: var(--gutter);
  padding: 0.45rem;
  background: var(--glass-glare), var(--glass-veil-strong), var(--surface);
  border: 1px solid var(--glass-border-strong);
  border-radius: 8px;
  box-shadow: 10px 10px 22px var(--nm-dark),
    -6px -6px 16px rgba(255, 255, 255, 0.65),
    inset 0 1px 0 var(--nm-light),
    inset 1px 0 0 rgba(255, 255, 255, 0.55);
}
.grid img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
}
@media (hover: hover) and (pointer: fine) {
  .grid a:hover {
    transform: translateY(-2px);
    box-shadow: 12px 14px 28px var(--nm-dark-strong),
      -6px -6px 16px rgba(255, 255, 255, 0.7),
      inset 0 1px 0 var(--nm-light),
      inset 1px 0 0 rgba(255, 255, 255, 0.55);
  }
}

/* Group cards mirror the Index grid (same density, same slide-mount chrome):
   4-up per pane on desktop, 2-up on mobile. Covers are cropped square so the
   wall reads as an even contact sheet regardless of each thumb's aspect. */
.group-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gutter);
  align-items: start;
}
.group-card {
  display: grid;
  gap: 0.35rem;
  min-width: 0;
  padding: 0.45rem;
  background: var(--glass-glare), var(--glass-veil-strong), var(--surface);
  border: 1px solid var(--glass-border-strong);
  border-radius: 8px;
  box-shadow: 10px 10px 22px var(--nm-dark),
    -6px -6px 16px rgba(255, 255, 255, 0.65),
    inset 0 1px 0 var(--nm-light),
    inset 1px 0 0 rgba(255, 255, 255, 0.55);
}
.group-card img {
  display: block;
  width: 100%;
  /* height:auto beats the intrinsic height="…" attribute so aspect-ratio
     can shape the box; object-fit then crops the thumb square. */
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 4px;
}
/* Square card crop wins over the inline aspect-ratio imgBlock writes for
   cover-fit media (that ratio is meant for full project views). */
.group-card .mediaframe,
.group-card .mediaframe--cover {
  width: 100%;
  aspect-ratio: 1 / 1 !important;
  overflow: hidden;
  border-radius: 4px;
}
.group-card__label {
  /* Word-level wrapping only — `anywhere` shattered labels like
     "Photography" into single letters. Two clamped lines with a matching
     min-height keep every card the exact same height. */
  overflow-wrap: break-word;
  hyphens: auto;
  font-size: 12px;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(2 * 1.35em);
}
.group-card__empty {
  display: block;
  aspect-ratio: 1 / 1;
  border-radius: 4px;
  border: var(--hair);
  background: rgba(255, 255, 255, 0.25);
}
.empty-pane {
  color: var(--faint);
  min-height: 30dvh;
  display: grid;
  place-items: center;
  text-align: center;
}

/* =========================================================================
   Information / CV view
   ========================================================================= */
/* The CV rides on a raised glass sheet, matching the story panel's material.
   The pane behind it is opaque paper, so the translucent tint + veil carry
   the glass; no backdrop-filter needed. Engraved text comes from the global
   .cv rule; the hr rules inside already read as carved grooves. */
.cv {
  display: block;
  padding: 1.2rem;
  margin: 0.4rem 0 1rem;
  background: var(--glass-glare), var(--glass-veil-strong), var(--surface-strong);
  border: 1px solid var(--glass-border-strong);
  border-radius: 12px;
  box-shadow: 18px 18px 40px -12px var(--nm-dark-strong),
    -10px -10px 24px rgba(255, 255, 255, 0.65),
    inset 0 1px 0 var(--nm-light),
    inset 1px 0 0 rgba(255, 255, 255, 0.4);
}
.cv__set {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gutter);
  margin-bottom: 1.2rem;
}
.cv__set > * {
  min-width: 0;
  overflow-wrap: anywhere;
}
.cv__updated {
  color: var(--faint);
  margin-top: 3.5rem;
}

/* =========================================================================
   Project detail overlay
   ========================================================================= */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 100vh;
  grid-template-rows: 100dvh;   /* panel fills the viewport even when short */
  /* The overlay must not trap pointer/scroll events: the opposite plane
     behind it stays fully live (scrollable list, openable projects). Only the
     panel re-enables pointer events; the empty half is the click-out region,
     handled by a document click listener in app.js. */
  pointer-events: none;
}
.overlay[hidden] {
  display: none;
}
/* One story panel per plane. Both stay mounted; only the open one is visible
   and interactive. A parked panel is fully transparent and click-through, so
   it never covers the live plane — and a cross-plane switch can slide one out
   while the other slides in. The card is raised: a carved seam on the median
   edge plus a soft shadow cast over the live plane, reinforcing the slide. */
.overlay__panel {
  grid-row: 1;
  /* Liquid glass: a thin tint under a specular veil, heavily blurred so the
     covered plane reads as material behind the panel rather than clutter. */
  background: var(--glass-veil), var(--surface-strong);
  backdrop-filter: blur(40px) saturate(1.35) brightness(1.04);
  -webkit-backdrop-filter: blur(40px) saturate(1.35) brightness(1.04);
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--pad);
  scrollbar-width: none;
  -ms-overflow-style: none;
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
.overlay__panel::-webkit-scrollbar {
  width: 0;
  display: none;
}
.overlay__panel--left {
  grid-column: 1;
  border-right: 1px solid var(--nm-dark);
  box-shadow: 14px 0 32px -10px var(--nm-dark-strong),
    inset -1px 0 0 var(--nm-light);
}
.overlay__panel--right {
  grid-column: 2;
  border-left: 1px solid var(--nm-dark);
  box-shadow: -14px 0 32px -10px var(--nm-dark-strong),
    inset 1px 0 0 var(--nm-light);
}
.overlay__panel.is-open {
  opacity: 1;
  pointer-events: auto;   /* the open story is interactive & scrollable */
}
/* Chromium-only easter egg: real refraction through the story panel. app.js
   adds .has-liquid-lens on <html> only where backdrop-filter: url() renders
   (Blink); #liquid-lens is the SVG filter in index.html. The CSS blur drops
   to 16px because the filter chain displaces and re-blurs the backdrop —
   combined softening stays close to the plain 40px variant, but the blurred
   shapes behind the glass ripple like they're seen through uneven glass. */
html.has-liquid-lens .overlay__panel {
  backdrop-filter: blur(16px) url(#liquid-lens) saturate(1.35) brightness(1.04);
  -webkit-backdrop-filter: blur(16px) url(#liquid-lens) saturate(1.35) brightness(1.04);
}

/* Embossed / carved type on the glass story panel. The panel is far more
   transparent than the paper plane, so the counter-strokes are what keep the
   type anchored to the glass while thumbnails scroll behind it. */
.overlay__panel {
  --nm-text-light: rgba(255, 255, 255, 0.88);
  --nm-text-dark: rgba(12, 16, 24, 0.22);
}
.overlay__panel .detail__head,
.overlay__panel .detail__body,
.overlay__panel .muted,
.overlay__panel .detail__images figcaption {
  text-shadow:
    0.5px 0.5px 0 var(--nm-text-light),
    -0.5px -0.5px 0 var(--nm-text-dark);
}
/* Stronger relief on the headline. */
.overlay__panel .detail__title {
  text-shadow:
    1px 1px 0 var(--nm-text-light),
    -1px -1px 1px var(--nm-text-dark),
    0 2px 8px rgba(12, 16, 24, 0.06);
}
/* Debossed "Close" link: strokes swapped, so it reads pressed-in. */
.overlay__panel .back {
  color: var(--muted);
  text-shadow:
    -0.5px -0.5px 0 var(--nm-text-light),
    0.5px 0.5px 0 var(--nm-text-dark);
}

/* object-fit: cover media (focal-point cropping set inline from data). */
img.is-cover {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* =========================================================================
   Transparent on-image metadata overlay
   A wrapper sized exactly to the rendered image hosts a fully transparent
   layer; the image is never modified. EXIF text runs vertically along one or
   both edges of the image box (left edge reads bottom-to-top, right edge
   top-to-bottom), like the engraved film-rebate markings on a negative.
   ========================================================================= */
.mediaframe {
  position: relative;
  display: block;
  line-height: 0;
  overflow: hidden;           /* bind the overlay to the rendered image box */
  border-radius: 4px;
  box-shadow: 9px 9px 20px var(--nm-dark-soft), -8px -8px 18px rgba(255, 255, 255, 0.5);
}
.mediaframe img {
  display: block;
  width: 100%;
  height: auto;
}
.mediaframe--cover {
  overflow: hidden;
}
.mediaframe--cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Non-destructive 90°/270° display rotation. The image bytes are untouched; the
   wrapper takes the swapped (h/w) display box and the image is sized so that,
   once turned a quarter, it fills that box exactly. --rar is the image's own
   width/height, set inline. (180° is a plain in-place transform on the img.) */
.mediarotate {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: calc(1 / var(--rar, 1)); /* swapped from the image's w/h */
  overflow: hidden;
  line-height: 0;
}
/* Two-class selector so this wins over `.entry__media img` / `.mediaframe img`
   width:100% height:auto rules regardless of where the frame is used. */
.mediarotate .mediarotate__img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(100% * var(--rar, 1));
  height: auto;
  max-width: none;
}
.mediarotate--r90 .mediarotate__img {
  transform: translate(-50%, -50%) rotate(90deg);
}
.mediarotate--r270 .mediarotate__img {
  transform: translate(-50%, -50%) rotate(270deg);
}
.mediameta {
  position: absolute;
  inset: 0;
  pointer-events: none;       /* the layer never intercepts clicks/scroll */
  z-index: 1;
  overflow: hidden;           /* belt-and-suspenders with .mediaframe */
}
/* Structural rules shared by every overlay variant. */
.mediameta__edge {
  position: absolute;
  top: 0;
  bottom: 0;
}
.mediameta__edge--left {
  left: 0;
}
.mediameta__edge--right {
  right: 0;
}

/* Variant 1 — rebate: a single film-rebate caption line per edge. */
.mediameta--rebate .mediameta__edge {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.55rem 0.3rem;
  writing-mode: vertical-rl;
  white-space: nowrap;
  overflow: hidden;
  font-size: 0.95rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1;
  color: rgba(255, 255, 255, 0.94);
  /* Dual shadow keeps the type legible over both light and dark photos while
     the box itself stays transparent. */
  text-shadow: 0 0 1px rgba(0, 0, 0, 0.85), 0 0 4px rgba(0, 0, 0, 0.55),
    0 1px 2px rgba(0, 0, 0, 0.6);
}
.mediameta--rebate .mediameta__edge--left {
  transform: rotate(180deg);  /* read bottom-to-top on the left rebate */
}

/* Variant 2 — bold-edge: N bold vertical columns of manual copy per edge. */
.mediameta--bold-edge .mediameta__edge {
  display: flex;
  flex-direction: row;
  align-items: center;        /* vertical-center the column stack on each edge */
  gap: 0.25rem;
  padding: 0.75rem 0.5rem;    /* inset text from image edges (was 0.5rem 0.35rem) */
  box-sizing: border-box;     /* keep padding inside the edge box */
  font-weight: 700;
  font-size: 0.9rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: none;
  overflow: hidden;           /* per-column containment within the image box */
  max-height: 100%;
}
.mediameta--bold-edge .mediameta__line {
  writing-mode: vertical-rl;
  white-space: nowrap;
  line-height: 1;
  max-height: 100%;
  overflow: hidden;
  min-height: 0;              /* let columns shrink in flex instead of overflowing */
  flex-shrink: 1;
}
.mediameta--bold-edge .mediameta__edge--left {
  transform: rotate(180deg);   /* left reads bottom-to-top */
  justify-content: flex-start;
}
.mediameta--bold-edge .mediameta__edge--right {
  justify-content: flex-end;   /* right reads top-to-bottom */
}
/* Optional legibility shadow — only when the per-image checkbox is on. */
.mediameta--bold-edge.mediameta--shadow .mediameta__edge {
  text-shadow: 0 0 1px rgba(0, 0, 0, 0.85), 0 0 4px rgba(0, 0, 0, 0.55),
    0 1px 2px rgba(0, 0, 0, 0.6);
}

/* Variant 3 — walk-card: centered title + body stack, with optional
   rebate-style side edge(s). */
.mediameta--walk-card .mediameta__center {
  position: absolute;
  left: 12%;
  right: 12%;
  top: 18%;
  bottom: 10%;
  max-height: 72%;
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
  overflow: hidden;             /* keep long stacks clipped inside the frame */
}
.mediameta--walk-card .mediameta__title {
  color: #e60000;
  font-weight: 800;
  font-size: 1.55rem;
  letter-spacing: 0.01em;
  line-height: 1.15;
  text-align: center;
  margin-bottom: 0.9rem;
  text-transform: uppercase;
}
.mediameta--walk-card .mediameta__title-line {
  display: block;
}
.mediameta--walk-card .mediameta__body-line {
  display: block;
  color: #fff;
  font-weight: 500;
  font-size: 0.82rem;
  letter-spacing: 0.08em;
  line-height: 1;
  text-align: center;
  text-transform: uppercase;
  margin-bottom: 1.1em;
}
.mediameta--walk-card .mediameta__body-line:last-child {
  margin-bottom: 0;
}
/* Side edges: reuse the rebate vertical caption idiom. */
.mediameta--walk-card .mediameta__edge {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 0.5rem;
  box-sizing: border-box;
  writing-mode: vertical-rl;
  white-space: nowrap;
  overflow: hidden;             /* per-edge containment within the image box */
  max-height: 100%;
  font-weight: 500;
  font-size: 0.9rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1;
  color: rgba(255, 255, 255, 0.92);
}
.mediameta--walk-card .mediameta__edge--left {
  transform: rotate(180deg);    /* read bottom-to-top on the left edge */
}
/* Optional legibility shadow — only when the per-image checkbox is on. */
.mediameta--walk-card.mediameta--shadow .mediameta__edge,
.mediameta--walk-card.mediameta--shadow .mediameta__center {
  text-shadow: 0 0 1px rgba(0, 0, 0, 0.85), 0 0 4px rgba(0, 0, 0, 0.55),
    0 1px 2px rgba(0, 0, 0, 0.6);
}

.detail__set {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gutter);
}
.detail__head {
  margin-bottom: 1.2rem;
}
.detail__images {
  margin-bottom: 1.2rem;
  align-items: start;
}
.detail__images img {
  display: block;
  width: 100%;
  height: auto;
}
.detail__images figure {
  margin: 0;
}
.detail__images figcaption {
  margin-top: 1.1rem;
  color: var(--muted);
}
.detail__body p {
  margin: 0 0 1.1rem;
}
.muted {
  color: var(--muted);
}
.back {
  text-decoration: none;
}
/* In-pane header row: carries the exit link for views whose topbar label is
   informational only (e.g. an open group). */
.pane-head {
  padding: 0 0 0.75rem;
}
.pane-head .back {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* =========================================================================
   Responsive — narrow screens KEEP the two independently-scrolling planes.
   We only bump the type scale and stack the inner label/content grids, which
   are too tight at half-width.
   ========================================================================= */
@media (max-width: 700px) {
  :root {
    --base-size: 15px;   /* bump scale, two columns remain */
  }

  /* Captions, CV rows and project detail stack label-over-content because
     each plane is only half the (already narrow) viewport. */
  .entry__row,
  .cv__set,
  .detail__set {
    grid-template-columns: 1fr;
  }
  .entry__row {
    gap: 0.4rem;
    margin-top: 1rem;
    margin-bottom: 1rem;
  }

  /* Index thumbnails: 2 masonry columns per half-width plane instead of 4. */
  .grid {
    columns: 2;
  }
  /* Group cards keep Index parity on mobile too. */
  .group-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .footer {
    grid-template-columns: 4fr 3fr 3fr;
    gap: 0.4rem;
  }
}

/* =========================================================================
   Story panel slide — eases in from the plane's outer edge to the median
   line, and retracts the same way on close. Gated behind a motion query so
   reduced-motion users get an instant, offset-free panel (see app.js, which
   hides immediately in that case).
   ========================================================================= */
@media (prefers-reduced-motion: no-preference) {
  @keyframes geuse-view-out {
    from { opacity: 1; filter: blur(0); transform: translateY(0); }
    to { opacity: 0; filter: blur(6px); transform: translateY(0.35rem); }
  }
  @keyframes geuse-view-in {
    from { opacity: 0; filter: blur(6px); transform: translateY(0.35rem); }
    to { opacity: 1; filter: blur(0); transform: translateY(0); }
  }

  ::view-transition-old(root) {
    animation: geuse-view-out 180ms ease both;
  }
  ::view-transition-new(root) {
    animation: geuse-view-in 240ms cubic-bezier(0.23, 1, 0.32, 1) both;
  }

  .view-transition-ghost {
    position: fixed;
    inset: 0;
    z-index: 70;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    pointer-events: none;
    overflow: hidden;
    background: inherit;
    animation: geuse-view-out 180ms ease both;
  }
  .view-transition-ghost .pane {
    overflow: hidden;
  }

  body.is-view-enter > .topbar,
  body.is-view-enter > .panes .pane,
  body.is-view-enter > .footer {
    animation: geuse-view-in 240ms cubic-bezier(0.23, 1, 0.32, 1) both;
  }

  .overlay__panel {
    transition: transform 240ms cubic-bezier(0.23, 1, 0.32, 1),
      opacity 180ms ease;
  }
  .grid a {
    transition: transform 160ms ease, box-shadow 160ms ease;
  }
  /* Resting (closed/closing) position: parked just past the outer edge. */
  .overlay__panel--left {
    transform: translateX(-100%);
  }
  .overlay__panel--right {
    transform: translateX(100%);
  }
  /* Open: slid flush to the median line. */
  .overlay__panel.is-open {
    transform: translateX(0);
  }
}

/* Without backdrop blur, a 0.42-alpha panel over scrolling thumbnails is
   unreadable — fall back to near-opaque surfaces. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topbar,
  .footer {
    background: rgba(246, 247, 250, 0.92);
  }
  .overlay__panel {
    background: rgba(249, 250, 252, 0.96);
  }
}

@media (forced-colors: active) {
  .topbar,
  .footer,
  .pane,
  .overlay__panel,
  .grid a,
  .cv {
    box-shadow: none;
  }
  .topbar__cell,
  .footer,
  .entry__label,
  .caption,
  .cv,
  .overlay__panel .detail__head,
  .overlay__panel .detail__body,
  .overlay__panel .detail__title,
  .overlay__panel .muted,
  .overlay__panel .back,
  .overlay__panel .detail__images figcaption {
    text-shadow: none;
  }
  .splash__pill {
    filter: none;
    border-color: CanvasText;
    background: Canvas;
    color: CanvasText;
  }
  .splash__pill::before,
  .splash__pill::after {
    display: none;
  }
}

/* =========================================================================
   Splash overlay — GEUSE Pixel Matrix (v.2A)
   Full-viewport passthrough layer: the site keeps scrolling underneath.
   Only the aqua pill re-enables pointer events (click to dismiss). The
   blinking character is a hard opacity toggle with no fade.
   ========================================================================= */
.splash {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  pointer-events: none; /* site stays live & scrollable */
  opacity: 0;
  transition: opacity 0.5s cubic-bezier(0.24, 1, 0.29, 1);
}
.splash[hidden] {
  display: none;
}
.splash.is-open {
  opacity: 1;
}
.splash.is-closing {
  opacity: 0;
  transition: opacity 0.5s cubic-bezier(0.24, 1, 0.22, 1);
}

/* Aqua pill — compact glossy capsule tuned for the splash overlay. */
.splash__pill {
  --splash-bg: rgba(27, 113, 174, 0.6);
  --splash-border: rgb(9, 96, 158);
  --splash-shadow: rgba(45, 140, 207, 0.85);
  --splash-text: #ffffff;
  --splash-blink-ms: 150ms;
  --splash-font: 1.2rem;

  position: relative;
  display: inline-block;
  max-width: min(90vw, 100%);
  margin: 0;
  /* Equal vertical padding keeps the label geometrically centered at the same
     total height. */
  padding: 1.1rem 3rem;
  border: 0.4rem solid var(--splash-border);
  border-radius: 999rem;
  background: var(--splash-bg);
  color: var(--splash-text);
  font-family: var(--font-stack);
  font-size: var(--splash-font);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.005em;
  white-space: nowrap;
  text-align: center;
  cursor: pointer;
  pointer-events: auto; /* only interactive piece of the splash */
  filter: drop-shadow(var(--splash-shadow) 0 2rem 1.5rem);
  will-change: filter, transform, opacity;
  -webkit-appearance: none;
  appearance: none;
  transform: translate3d(0, 18px, 0);
  transition: transform 0.5s cubic-bezier(0.24, 1, 0.29, 1);
}
.splash.is-open .splash__pill {
  transform: translate3d(0, 0, 0);
}
.splash.is-closing .splash__pill {
  transform: translate3d(0, 18px, 0);
  transition: transform 0.5s cubic-bezier(0.24, 1, 0.22, 1);
}
.splash__pill:focus-visible {
  outline: 2px solid var(--splash-text);
  outline-offset: 4px;
}

/* Gloss layers — bottom bloom plus top specular highlight. */
.splash__pill::before,
.splash__pill::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}
.splash__pill::before {
  background: linear-gradient(
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0) 60%,
    rgba(255, 255, 255, 0.5) 100%
  );
  filter: blur(15px);
}
.splash__pill::after {
  inset: 0% 2.5% 50%;
  background: linear-gradient(
    rgba(255, 255, 255, 0.5) 0%,
    rgba(255, 255, 255, 0.3) 45%,
    rgba(255, 255, 255, 0) 100%
  );
}

.splash__label {
  position: relative;
  z-index: 1;
  display: inline-block;
}

/* Hard blink with no fade. Toggled via JS so the
   half-period matches blinkMs exactly (CSS step animations are unreliable
   across browsers at sub-200ms intervals). */
.splash__blink {
  display: inline-block;
}
.splash__blink.is-off {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }

  .splash,
  .splash__pill {
    transition: none;
  }
  .splash.is-open .splash__pill,
  .splash.is-closing .splash__pill {
    transform: none;
  }
}
