/* ==========================================================================
   CRIT — profile environments.
   Ten composition families driven from profile data. Same typography, grain,
   lighting and motion quality everywhere — never the same composition twice.
   ========================================================================== */

/* A room is exactly one screen. It never grows, so it never scrolls — every
   layout is a composition made to fit the frame, not a document to be read
   downward. `overflow: hidden` also stops a stray descendant from turning the
   archive page into a scroller. */
.profile {
  position: relative;
  z-index: var(--z-type);
  height: 100svh;
  min-height: 100svh;
  overflow: hidden;
  opacity: 0;
  transition: opacity 620ms var(--ease-out-expo);
}

.profile[data-ready='1'] { opacity: 1; }

/* ------------------------------------------------------- environment */

.pf-env {
  position: fixed;
  inset: 0;
  z-index: var(--z-media);
  /* The room's base colour. Previously written by the console but never read;
     it now sits under the media and tint as the room's true ground. */
  background: var(--pf-base, #050506);
  pointer-events: none;
  overflow: hidden;
}

/* The wrapper only carries opacity and the per-frame parallax transform. The
   grade used to live here too — a filter on the same element that moves every
   frame re-runs the whole chain (blur included) across the full screen on every
   pointer move. Putting the grade on the still instead lets the renderer cache
   the filtered result and merely move it. */
.pf-env__media {
  position: absolute;
  inset: -6%;
  opacity: var(--pf-opacity, 1);
  transform-origin: var(--pf-origin-x, 50%) var(--pf-origin-y, 50%);
  transform: translate3d(var(--pf-shift-x, 0px), var(--pf-shift-y, 0px), 0) scale(var(--pf-scale, 1));
  will-change: transform;
}

.pf-env__media img,
.pf-env__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--pf-position-x, 50%) var(--pf-position-y, 50%);
}

/* A still can be graded once and cached.
   Colour stays colour. This used to close with `grayscale(calc(1 - saturation))`,
   which read the catalog's "no saturation control" default of 0 literally and
   printed every room photograph in black and white - the film behind it was in
   colour while the still in front of it was not. `--pf-saturation` is now the
   colour itself (natural unless an operator moves it), never a monochrome dial. */
.pf-env__media img {
  filter:
    blur(var(--pf-blur, 0px))
    brightness(var(--pf-brightness, 1))
    contrast(var(--pf-contrast, 1))
    saturate(var(--pf-saturation, 1));
}

/* A moving picture cannot. A live filter over a full-screen playing video
   re-filters every frame — measured at roughly a full frame budget on a machine
   rendering in software. The film stays unfiltered and the tint layer carries
   the grade instead (see `.pf-env[data-video='1'] .pf-env__tint`). */
.pf-env__media video {
  filter: none;
}

/* ...unless the operator actually asks for a treatment. Then the film is graded,
   but as cheaply as the same result allows: `--pf-video-grade` is one fused
   matrix for contrast and saturation (plus blur when it is asked for), and
   brightness rides on the flat dim plane below. The JS builds both from the
   operator's own numbers, and maps the archival saturation default (0) to natural
   colour for moving pictures. */
.pf-env[data-video='1'][data-grade='1'] .pf-env__media video {
  filter: var(--pf-video-grade, none);
}

/* `brightness(b)` and this plane are the same multiplication — `v x b` either
   way, in the same colour space — but a constant layer does not re-filter the
   film, so it costs nothing per frame. Only present when a film is being dimmed. */
.pf-env__dim {
  position: absolute;
  inset: 0;
  background: #000;
  opacity: var(--pf-grade-dim, 0);
  pointer-events: none;
}

/* An empty environment has nothing to show. A filtered, full-screen element
   that is never seen is pure compositor cost — most rooms carry no film yet. */
.pf-env__media:empty { display: none; }

.pf-env__tint {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(5, 5, 6, var(--pf-overlay, 0.6)) 0%, rgba(5, 5, 6, calc(var(--pf-overlay, 0.6) * 0.55)) 42%, rgba(5, 5, 6, calc(var(--pf-overlay, 0.6) * 0.85)) 100%),
    radial-gradient(120% 100% at 50% 40%, transparent 30%, rgba(3, 3, 4, 0.9) 100%);
}

/* Video grade. With no live filter the film keeps its own colour, so the tint
   carries the archival cast: a flat, desaturating wash under the usual vignette.
   Static layers only — no per-frame blending, no per-frame filtering. */
.pf-env[data-video='1'] .pf-env__tint {
  background:
    linear-gradient(180deg, rgba(5, 5, 6, var(--pf-overlay, 0.6)) 0%, rgba(5, 5, 6, calc(var(--pf-overlay, 0.6) * 0.55)) 42%, rgba(5, 5, 6, calc(var(--pf-overlay, 0.6) * 0.85)) 100%),
    radial-gradient(120% 100% at 50% 40%, transparent 30%, rgba(3, 3, 4, 0.9) 100%),
    linear-gradient(rgba(19, 20, 24, 0.34), rgba(19, 20, 24, 0.34));
}

.pf-env__accent {
  position: absolute;
  inset: 0;
  background: var(--accent, transparent);
  opacity: 0.1;
  mix-blend-mode: soft-light;
}

.pf-grain,
.pf-vignette {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

.pf-grain {
  z-index: var(--z-micro);
  background-image: var(--grain-image);
  background-repeat: repeat;
  opacity: var(--pf-grain-opacity, 0.05);
  mix-blend-mode: overlay;
  animation: pfGrain 900ms steps(3, end) infinite;
}

/* Film grain is not a decoration here — it is what keeps a large flat
   environment from looking like an empty web page. */
@keyframes pfGrain {
  0% { transform: translate3d(0, 0, 0); }
  33% { transform: translate3d(-1.5%, 1%, 0); }
  66% { transform: translate3d(1%, -1.5%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* Inside a room the shared atmosphere has already stepped back, so the room's
   own grain holds still too. The texture stays exactly as it looks; only the
   per-frame jitter stops, which is what lets the compositor idle on a machine
   that is rendering in software rather than on the GPU. */
.atmosphere[data-dim='1'] ~ .stage .pf-grain { animation-play-state: paused; }

.pf-vignette {
  z-index: var(--z-light);
  opacity: var(--pf-vignette, 0.62);
  background: radial-gradient(126% 106% at 50% 46%, transparent 32%, rgba(0, 0, 0, 0.55) 76%, rgba(0, 0, 0, 0.95) 100%);
}

/* ---------------------------------------------------------------- stage */

.pf-stage {
  position: relative;
  z-index: var(--z-type);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(6, minmax(0, 1fr));
  gap: clamp(0.3rem, 1vh, 0.9rem) clamp(0.4rem, 1.4vw, 1.4rem);
  height: 100svh;
  min-height: 100svh;
  overflow: hidden;
  padding: clamp(3.6rem, 8vh, 6rem) var(--gutter) clamp(2.4rem, 6vh, 4rem);
}

.pf-slot { position: relative; min-width: 0; min-height: 0; }

/* ==========================================================================
   THE MASTER COMPOSITION
   One spatial hierarchy for the whole archive - ARKIN's composition, made
   reusable. Archive top-left, index top-right, name upper-right, the main
   plate holding the left through the middle, the socials and then the second
   plate stepping down the right edge, bio across the lower centre-left, the
   track at the left-bottom, next/prev bottom-right. Every content zone below
   is a filled rectangle, so the declaration resolves instead of collapsing
   to nothing at all.

   Rows are weighted, not uniform. The first is an empty band holding the fixed
   markers clear of the quote; the last carries the bio and the track, and
   neither needs as much air as the plate does. Starving those two and sharing
   the rest three ways is what keeps the main field dominant instead of letting
   it shrink into a card.
   ========================================================================== */

.profile[data-layout='master'] .pf-stage {
  /* Weights, not six equal slices: the plate takes by far the largest share. */
  grid-template-rows:
    minmax(0, 1.35fr)  /* the empty top band, above ARCHIVE and PROFILE xx/19 */
    auto              /* quote and name: only as tall as they need */
    minmax(0, 1.8fr)   /* plate, top */
    minmax(0, 1.8fr)   /* plate, middle, socials on the right */
    minmax(0, 1.8fr)   /* plate, bottom, second plate on the right */
    minmax(0, 1fr);    /* track and bio */
  grid-template-areas:
    '. . . . . . . . . . . .'
    'Q Q . . . . . . . I I I'
    'V V V V V V V V . . . .'
    'V V V V V V V V . L L L'
    'V V V V V V V V . X X X'
    'S S B B B B B B . . . .';
}

/* The primary field: the dominant plate. It sits toward the middle of the room
   rather than pressed against the left edge, keeps its top edge tucked just under
   the quote, and gives ground at the bottom so its centre of mass lands near the
   frame's centre line rather than sitting low. */
.profile[data-layout='master'] .pf-slot--visual {
  grid-area: V;
  display: flex;
  align-items: flex-start;
  /* Near the left edge, with about an inch and a half of air in front of the
     picture. The plate stays in the left of the room rather than the middle of it,
     standing close enough to the edge to feel like part of the frame but never
     flush against it. The gap is a clamp rather than a fixed offset so it stays
     the same physical distance on a phone as on a wide desktop. The zone ends
     before the socials and the second plate, so this can never push the picture
     into either of them. */
  justify-content: flex-start;
  padding-inline: clamp(1.5rem, 4vw, 5rem) 0;
}

.profile[data-layout='master'] .pf-visual {
  height: 100%;
  /* The plate no longer runs the full height of its zone. Capping it pulls the
     bottom edge up under a top edge that stays pinned below the quote, which is
     what lifts the whole block toward the middle of the frame and takes the
     dominance out of sheer scale. */
  max-height: 80%;
  width: auto;
  aspect-ratio: 3 / 4;
  max-width: 100%;
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.08),
    0 0 0 8px rgba(255, 255, 255, 0.012);
}

/* The name: ARKIN's upper-right editorial treatment. */
.profile[data-layout='master'] .pf-slot--identity {
  grid-area: I;
  justify-items: end;
  text-align: right;
  z-index: 2;
}

.profile[data-layout='master'] .pf-slot--bio { grid-area: B; }
.profile[data-layout='master'] .pf-slot--quote { grid-area: Q; }

.profile[data-layout='master'] .pf-slot--song {
  grid-area: S;
  align-content: end;
}

.profile[data-layout='master'] .pf-slot--socials {
  grid-area: L;
  justify-self: end;
  align-self: start;
}

/* The second plate lives in the lower-right, in a zone of its own that sits above
   the reserved bottom-right corner, so it can never land on the socials above it, the
   name, the bio or the fixed navigation. It is also allowed to give ground: the slot
   takes the full height of its zone and the plate shrinks when the zone is shorter
   than the plate, so the block can never bleed out of the room. */
.profile[data-layout='master'] .pf-slot--extra {
  grid-area: X;
  justify-self: end;
  align-self: stretch;
  min-height: 0;
}

.profile[data-layout='master'] .pf-extra {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.7rem;
  height: 100%;
  min-height: 0;
}

.profile[data-layout='master'] .pf-extra img {
  height: 100%;
  width: auto;
  aspect-ratio: 3 / 4;
  max-height: 100%;
  max-width: clamp(84px, 10vw, 168px);
  object-fit: cover;
  flex: 0 1 auto;
  min-height: 0;
}

.profile[data-layout='master'] .pf-extra__info {
  max-width: 22ch;
  flex: 0 0 auto;
}

/* ------------------------------------------------------------- identity */

.pf-identity {
  display: grid;
  align-content: center;
  gap: clamp(0.5rem, 1.2vh, 1rem);
}

/* The archive marker. Anchored to the interface, not the portrait: it holds the
   top-right safe area on every profile and can never collide with the name. */
.pf-index {
  position: fixed;
  top: var(--edge);
  right: var(--gutter);
  z-index: var(--z-ui);
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-meta);
  font-size: 0.74rem;
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--fg-faint);
  opacity: 0;
  transform: translate3d(8px, 0, 0);
  transition: opacity 700ms var(--ease-out-expo) 300ms, transform 700ms var(--ease-out-expo) 300ms;
  pointer-events: none;
}

.profile[data-ready='1'] .pf-index { opacity: 1; transform: none; }

.pf-index::before {
  content: '';
  width: clamp(24px, 3.4vw, 58px);
  height: 1px;
  background: linear-gradient(270deg, var(--line), transparent);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 1200ms var(--ease-out-expo) 420ms;
}

.is-revealed .pf-index::before { transform: scaleX(1); }

.pf-name {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 70, 'wght' 640;
  font-size: var(--pf-name-size, var(--type-xxl));
  line-height: 0.82;
  letter-spacing: -0.05em;
  text-transform: uppercase;
  color: var(--white);
  overflow: hidden;
}

.pf-name > span {
  display: block;
  transform: translate3d(0, 108%, 0);
  transition: transform 1150ms var(--ease-out-expo) var(--pf-name-delay, 0ms);
}

.is-revealed .pf-name > span { transform: translate3d(0, 0, 0); }

.pf-nick {
  font-family: var(--font-body);
  font-size: clamp(0.78rem, 0.98vw, 0.94rem);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--fg-mute);
  opacity: 0;
  transform: translate3d(0, 8px, 0);
  transition: opacity 900ms var(--ease-out-expo) 320ms, transform 900ms var(--ease-out-expo) 320ms;
}

.is-revealed .pf-nick { opacity: 1; transform: none; }

/* ----------------------------------------------------------------- bio */

.pf-bio {
  align-self: end;
  display: grid;
  gap: 0.7rem;
}

.pf-bio p {
  font-family: var(--font-body);
  font-size: clamp(0.86rem, 0.96vw, 1rem);
  line-height: 1.7;
  color: var(--fg-soft);
  max-width: 44ch;
}

.pf-label {
  font-family: var(--font-meta);
  font-size: 0.72rem;
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--fg-faint);
}

/* ---------------------------------------------------------------- quote */

.pf-quote {
  display: grid;
  gap: 0.7rem;
  align-content: center;
}

.pf-quote p {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 94, 'wght' 400;
  font-size: clamp(0.98rem, 1.42vw, 1.6rem);
  line-height: 1.34;
  letter-spacing: -0.016em;
  color: var(--bone);
  text-wrap: balance;
  opacity: 0;
  transform: translate3d(0, 12px, 0);
  transition: opacity 1000ms var(--ease-out-expo) 420ms, transform 1100ms var(--ease-out-expo) 420ms;
}

.is-revealed .pf-quote p { opacity: 1; transform: none; }

.pf-quote__rule {
  width: clamp(28px, 4vw, 62px);
  height: 1px;
  background: var(--line-strong);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 900ms var(--ease-out-expo) 380ms;
}

.is-revealed .pf-quote__rule { transform: scaleX(1); }

/* ----------------------------------------------------------------- song */

/* There is no player. No artwork, no title, no artist, no bar, no volume
   control: the zone says one word and stops there. The audio still plays -
   it just is not dressed up as a widget. */
.pf-song {
  align-content: end;
  opacity: 0;
  transform: translate3d(0, 10px, 0);
  transition: opacity 1100ms var(--ease-out-expo) 620ms, transform 1100ms var(--ease-out-expo) 620ms;
}

.is-revealed .pf-song { opacity: 1; transform: none; }

.pf-song__label {
  font-family: var(--font-meta);
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--fg-faint);
  white-space: nowrap;
}

/* --------------------------------------------------------------- socials */

.pf-socials {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(0.7rem, 1.6vw, 1.5rem);
  opacity: 0;
  transform: translate3d(0, 8px, 0);
  transition: opacity 1000ms var(--ease-out-expo) 760ms, transform 1000ms var(--ease-out-expo) 760ms;
}

.is-revealed .pf-socials { opacity: 1; transform: none; }

.pf-social {
  position: relative;
  font-family: var(--font-meta);
  font-size: 0.74rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-mute);
  padding-bottom: 0.3rem;
  transition: color 200ms var(--ease-out-expo);
}

.pf-social::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 320ms var(--ease-out-expo);
}

.pf-social:hover,
.pf-social:focus-visible { color: var(--white); }
.pf-social:hover::after,
.pf-social:focus-visible::after { transform: scaleX(1); }

/* A social that has no real address yet: the label stays, the door does not.
   It is type in the composition, not a control, so nothing about it may invite a
   click - no colour change, no rule sweeping in under the letters. It carries no
   `data-cursor`, so the custom pointer stays its resting shape over it. */
.pf-social--plain,
.pf-social--plain:hover { color: var(--fg-mute); }
.pf-social--plain::after { display: none; }

/* ----------------------------------------------------------- extra room */

.pf-extra {
  display: grid;
  gap: 0.8rem;
  align-content: end;
}

/* The second picture in the room. Same rule as the portrait: colour is the
   picture's own, the dim is the room's. */
.pf-extra img {
  width: clamp(90px, 12vw, 180px);
  aspect-ratio: 1;
  object-fit: cover;
  filter: brightness(0.86) contrast(1.06);
  box-shadow: 0 0 0 1px var(--line);
}

.pf-extra__info {
  font-family: var(--font-body);
  font-size: clamp(0.76rem, 0.86vw, 0.86rem);
  line-height: 1.7;
  color: var(--fg-mute);
  max-width: 34ch;
  white-space: pre-line;
}

/* ---------------------------------------------------------------- visual */

.pf-visual {
  position: relative;
  overflow: hidden;
  background: transparent;
  /* Never taller than the cell it was given. A portrait's aspect-ratio can ask
     for more height than a short row offers (most visibly on phones); the crop
     is handled by object-fit, so capping the frame keeps the room one screen. */
  max-height: 100%;
}

.pf-visual__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--pf-focus, 50% 50%);
  /* The portrait is photographed, not printed. It keeps the editorial dim and
     contrast it always had and loses the hard-coded `grayscale(1)` that made it
     monochrome whatever the operator uploaded. */
  filter: brightness(0.92) contrast(1.05);
  transform: translate3d(var(--pf-image-shift-x, 0px), var(--pf-image-shift-y, 0px), 0) scale(var(--pf-image-scale, 1));
  will-change: transform;
}

.pf-visual--none { display: none; }

/* When a person has no uploaded portrait the composition does not collapse.
   The slot simply keeps holding space, and the typography carries the room. */
.pf-visual[data-empty='1'] { background: none; }

/* ------------------------------------------------------- return control */

.pf-return {
  position: fixed;
  top: var(--edge);
  left: var(--gutter);
  z-index: var(--z-ui);
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.2rem;
  opacity: 0;
  transform: translate3d(-8px, 0, 0);
  transition: opacity 700ms var(--ease-out-expo) 240ms, transform 700ms var(--ease-out-expo) 240ms;
}

.profile[data-ready='1'] .pf-return { opacity: 1; transform: none; }

.pf-return__label {
  font-family: var(--font-meta);
  font-size: 0.74rem;
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--fg-mute);
  transition: color 200ms var(--ease-out-expo);
}

.pf-return__label b { color: var(--fg-soft); font-weight: 400; }

.pf-return:hover .pf-return__label,
.pf-return:focus-visible .pf-return__label { color: var(--white); }

.pf-return__arrow {
  width: 22px;
  height: 1px;
  background: currentColor;
  position: relative;
  transition: width 320ms var(--ease-out-expo);
}

.pf-return__arrow::before {
  content: '';
  position: absolute;
  left: 0;
  top: -2.5px;
  width: 6px;
  height: 6px;
  border-left: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg);
}

.pf-return:hover .pf-return__arrow,
.pf-return:focus-visible .pf-return__arrow { width: 32px; }

/* Profile index marker, bottom edge — the archival caption. */
.pf-caption {
  position: fixed;
  right: var(--gutter);
  bottom: var(--edge);
  z-index: var(--z-ui);
  display: grid;
  justify-items: end;
  gap: 0.28rem;
  text-align: right;
  opacity: 0;
  transition: opacity 900ms var(--ease-out-expo) 600ms;
  pointer-events: none;
}

.profile[data-ready='1'] .pf-caption { opacity: 1; }

.pf-caption__count b {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 82, 'wght' 520;
  font-size: 0.8rem;
  color: var(--fg-soft);
  font-weight: 500;
}

/* Moving on without going back to the archive first. */
.pf-nav {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: 0.9rem;
  font-family: var(--font-meta);
  font-size: 0.72rem;
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--fg-faint);
  transition: color 200ms var(--ease-out-expo);
}

.pf-nav::after {
  content: '';
  width: 10px;
  height: 1px;
  background: currentColor;
  transition: width 260ms var(--ease-out-expo);
}

.pf-nav:hover,
.pf-nav:focus-visible { color: var(--white); }
.pf-nav:hover::after { width: 18px; }
.pf-nav:last-child::after { display: none; }

/* ==========================================================================
   COMPOSITION FAMILIES
   Each family is a different spatial idea for the same person. Grid areas only
   — the type, grain, light and motion stay identical so every room still
   feels like CRIT.
   ========================================================================== */

/* 01 — FLOAT: the image drifts free of the grid; type holds the left edge. */
.profile[data-layout='float'] .pf-stage {
  grid-template-areas:
    '. . . V V V . . . . . .'
    '. . . V V V . . Q . . .'
    '. I . V V V . . Q . . .'
    '. I . . . . . . Q . . .'
    '. B . . . . . . Q . S .'
    '. . . . . . . . L . . .';
}
.profile[data-layout='float'] .pf-slot--visual { grid-area: V; width: 62%; justify-self: end; }
.profile[data-layout='float'] .pf-slot--identity { grid-area: I; }
.profile[data-layout='float'] .pf-slot--bio { grid-area: B; }
.profile[data-layout='float'] .pf-slot--quote { grid-area: Q; }
.profile[data-layout='float'] .pf-slot--song { grid-area: S; justify-self: end; text-align: right; }
.profile[data-layout='float'] .pf-slot--socials { grid-area: L; justify-self: end; }
.profile[data-layout='float'] .pf-slot--extra { grid-area: B; justify-self: start; align-self: start; }
.profile[data-layout='float'] .pf-visual {
  aspect-ratio: 3 / 4;
  -webkit-mask-image: radial-gradient(72% 62% at 52% 44%, #000 42%, transparent 96%);
  mask-image: radial-gradient(72% 62% at 52% 44%, #000 42%, transparent 96%);
}
.profile[data-layout='float'] .pf-quote { writing-mode: vertical-rl; justify-self: center; height: 100%; }

/* 02 — PORTAL: the image becomes a window the name passes behind. */
.profile[data-layout='portal'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . . .'
    '. . . . . V V V V V . .'
    '. . . . . V V V V V . .'
    '. I I I I I I I I I I .'
    '. . . . . . . . . . S .'
    '. . . . . B . . . L . .';
}
.profile[data-layout='portal'] .pf-slot--visual { grid-area: V; }
.profile[data-layout='portal'] .pf-slot--identity { grid-area: I; }
.profile[data-layout='portal'] .pf-slot--bio { grid-area: B; }
.profile[data-layout='portal'] .pf-slot--song { grid-area: S; justify-self: end; }
.profile[data-layout='portal'] .pf-slot--socials { grid-area: L; }
.profile[data-layout='portal'] .pf-slot--quote { grid-area: I; justify-self: start; align-self: start; }
.profile[data-layout='portal'] .pf-visual {
  aspect-ratio: 4 / 5;
  -webkit-mask-image: radial-gradient(78% 74% at 50% 44%, #000 52%, transparent 94%);
  mask-image: radial-gradient(78% 74% at 50% 44%, #000 52%, transparent 94%);
}
.profile[data-layout='portal'] .pf-identity { z-index: 2; }
.profile[data-layout='portal'] .pf-slot--identity { mix-blend-mode: normal; }

/* 03 — EDGE: the image is cropped by the frame; type is pushed to the margin. */
.profile[data-layout='edge'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . . .'
    'I . . . . . . . . . V V'
    'I . . . . . . . . . V V'
    'B . . . . . . . . . V V'
    'Q . . . . . . . . . V V'
    'L . . . . . . . . S . .';
}
.profile[data-layout='edge'] .pf-slot--identity { grid-area: I; align-content: center; }
.profile[data-layout='edge'] .pf-slot--bio { grid-area: B; }
.profile[data-layout='edge'] .pf-slot--quote { grid-area: Q; }
.profile[data-layout='edge'] .pf-slot--socials { grid-area: L; }
.profile[data-layout='edge'] .pf-slot--song { grid-area: S; justify-self: end; }
.profile[data-layout='edge'] .pf-slot--visual {
  grid-area: V;
  translate: 14% 0;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 26%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 26%);
}
/* The portrait's proportion lives on the frame that holds the image, not on the
   grid item: a ratio on the item would outgrow its tracks and force the room to
   scroll. `max-height` on `.pf-visual` then caps it to the cell. */
.profile[data-layout='edge'] .pf-visual { aspect-ratio: 1 / 2.1; }
.profile[data-layout='edge'] .pf-slot--extra { grid-area: B; align-self: start; }

/* 04 — CENTER: one axis, everything stacked on it. */
.profile[data-layout='center'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . . .'
    '. . . . . V V V . . . .'
    '. . . . . V V V . . . .'
    '. . . Q Q Q Q Q . . . .'
    '. . . . . I I I . . . .'
    '. . . . . B B . . . . .';
}
.profile[data-layout='center'] .pf-slot--visual { grid-area: V; width: 100%; }
.profile[data-layout='center'] .pf-slot--quote { grid-area: Q; justify-self: center; text-align: center; }
.profile[data-layout='center'] .pf-slot--identity { grid-area: I; justify-items: center; text-align: center; }
.profile[data-layout='center'] .pf-slot--bio { grid-area: B; justify-self: center; text-align: center; }
.profile[data-layout='center'] .pf-slot--socials { position: fixed; left: var(--gutter); right: var(--gutter); bottom: var(--edge); justify-content: center; }
.profile[data-layout='center'] .pf-slot--song { position: fixed; left: var(--gutter); bottom: var(--edge); }
.profile[data-layout='center'] .pf-slot--extra { position: fixed; right: var(--gutter); bottom: var(--edge); align-content: end; }
.profile[data-layout='center'] .pf-visual {
  aspect-ratio: 4 / 5;
  /* The frame shrinks to keep its ratio once `max-height` caps it; centre it. */
  margin-inline: auto;
  -webkit-mask-image: radial-gradient(76% 76% at 50% 42%, #000 48%, transparent 95%);
  mask-image: radial-gradient(76% 76% at 50% 42%, #000 48%, transparent 95%);
}

/* 05 — OVERLAP: giant type crossing the frame of the image. */
.profile[data-layout='overlap'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . S .'
    '. . . . V V V V V V . .'
    '. . . . V V V V V V . .'
    'I I I I I I I I I I I I'
    '. Q . . . . . . . . . .'
    'B B . . . . . . . L L .';
}
.profile[data-layout='overlap'] .pf-slot--visual { grid-area: V; }
.profile[data-layout='overlap'] .pf-slot--identity { grid-area: I; align-content: end; }
.profile[data-layout='overlap'] .pf-slot--quote { grid-area: Q; }
.profile[data-layout='overlap'] .pf-slot--bio { grid-area: B; }
.profile[data-layout='overlap'] .pf-slot--song { grid-area: S; justify-self: end; text-align: right; }
.profile[data-layout='overlap'] .pf-slot--socials { grid-area: L; justify-self: end; }
.profile[data-layout='overlap'] .pf-slot--extra { grid-area: B; justify-self: start; align-self: end; }
.profile[data-layout='overlap'] .pf-visual {
  aspect-ratio: 1 / 1;
  mix-blend-mode: luminosity;
  opacity: 0.88;
}

/* 06 — VOID: almost nothing. Type is the entire room. */
.profile[data-layout='void'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . . .'
    '. . . . . . . . . . . .'
    '. . . . I I I I . . . .'
    '. . . . . . . . . . . .'
    '. . . . . . . . . . Q .'
    '. . . B B . . . L L . .';
}
.profile[data-layout='void'] .pf-slot--identity { grid-area: I; justify-items: center; text-align: center; }
.profile[data-layout='void'] .pf-slot--bio { grid-area: B; justify-self: center; text-align: center; }
.profile[data-layout='void'] .pf-slot--quote { grid-area: Q; justify-self: end; text-align: right; }
.profile[data-layout='void'] .pf-slot--socials { grid-area: L; justify-self: center; }
.profile[data-layout='void'] .pf-slot--song { position: fixed; left: var(--gutter); bottom: var(--edge); }
.profile[data-layout='void'] .pf-slot--extra { position: fixed; right: var(--gutter); bottom: var(--edge); }
.profile[data-layout='void'] .pf-slot--visual {
  position: fixed;
  inset: 12vh 0;
  opacity: 0.16;
  -webkit-mask-image: radial-gradient(64% 58% at 50% 50%, transparent 30%, #000 100%);
  mask-image: radial-gradient(64% 58% at 50% 50%, transparent 30%, #000 100%);
}
.profile[data-layout='void'] .pf-visual { aspect-ratio: 1 / 1; }

/* 07 — ARCHIVE: an archival plate with registration marks. */
.profile[data-layout='archive'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . . .'
    '. . . . V V V V V . . .'
    '. . . . V V V V V . . .'
    'I . . . . . . . . . Q .'
    'I . . . . . . . . . Q .'
    'B . . . . . . . . . S .';
}
.profile[data-layout='archive'] .pf-slot--visual { grid-area: V; }
.profile[data-layout='archive'] .pf-slot--identity { grid-area: I; align-content: center; }
.profile[data-layout='archive'] .pf-slot--quote { grid-area: Q; justify-self: end; text-align: right; }
.profile[data-layout='archive'] .pf-slot--bio { grid-area: B; }
.profile[data-layout='archive'] .pf-slot--song { grid-area: S; justify-self: end; text-align: right; }
.profile[data-layout='archive'] .pf-slot--socials { position: fixed; left: var(--gutter); bottom: var(--edge); }
.profile[data-layout='archive'] .pf-slot--extra { position: fixed; right: var(--gutter); bottom: var(--edge); align-content: end; }
.profile[data-layout='archive'] .pf-visual {
  aspect-ratio: 4 / 5;
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.1),
    0 0 0 7px rgba(255, 255, 255, 0.018);
}
.profile[data-layout='archive'] .pf-visual::before,
.profile[data-layout='archive'] .pf-visual::after {
  content: '';
  position: absolute;
  width: 12px;
  height: 12px;
  border: 1px solid rgba(255, 255, 255, 0.24);
  pointer-events: none;
}
.profile[data-layout='archive'] .pf-visual::before { top: 8px; left: 8px; border-right: 0; border-bottom: 0; }
.profile[data-layout='archive'] .pf-visual::after { bottom: 8px; right: 8px; border-left: 0; border-top: 0; }

/* 08 — TYPOGRAPHIC: the name is the room and it crops off the frame. */
.profile[data-layout='typographic'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . . .'
    'I I I I I I I I I I I I'
    'I I I I I I I I I I I I'
    'I I I I I I I I I I I I'
    '. . . Q . . . . . . . .'
    '. . . B . . . . L L . .';
}
.profile[data-layout='typographic'] .pf-slot--identity { grid-area: I; align-content: center; }
.profile[data-layout='typographic'] .pf-slot--quote { grid-area: Q; }
.profile[data-layout='typographic'] .pf-slot--bio { grid-area: B; }
.profile[data-layout='typographic'] .pf-slot--socials { grid-area: L; justify-self: end; }
.profile[data-layout='typographic'] .pf-slot--song { position: fixed; right: var(--gutter); bottom: var(--edge); text-align: right; }
.profile[data-layout='typographic'] .pf-slot--extra { position: fixed; left: var(--gutter); bottom: var(--edge); }
.profile[data-layout='typographic'] .pf-slot--visual {
  position: fixed;
  inset: 0;
  opacity: 0.13;
}
.profile[data-layout='typographic'] .pf-visual { aspect-ratio: auto; height: 100%; }
.profile[data-layout='typographic'] .pf-name { --pf-name-size: min(19vw, 15rem); }

/* 09 — FRAGMENT: the image is cut by the frame, hard edges, no softening. */
.profile[data-layout='fragment'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . . .'
    'V V V . . . . . . . I I'
    'V V V . . . . . . . I I'
    'V V V . . . . . . . I I'
    '. . . . . . . . . . . .'
    '. . . B B . . . . L L .';
}
.profile[data-layout='fragment'] .pf-slot--visual { grid-area: V; translate: -22% 0; }
.profile[data-layout='fragment'] .pf-slot--identity { grid-area: I; justify-items: end; text-align: right; }
.profile[data-layout='fragment'] .pf-slot--bio { grid-area: B; }
.profile[data-layout='fragment'] .pf-slot--socials { grid-area: L; justify-self: end; }
.profile[data-layout='fragment'] .pf-slot--song { position: fixed; left: var(--gutter); bottom: var(--edge); }
.profile[data-layout='fragment'] .pf-slot--extra { position: fixed; right: var(--gutter); bottom: var(--edge); }
.profile[data-layout='fragment'] .pf-visual { aspect-ratio: 1 / 1.4; }
.profile[data-layout='fragment'] .pf-slot--visual::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.12);
  pointer-events: none;
}

/* 10 — IMMERSIVE: full-frame photographic environment, type cut into it. */
.profile[data-layout='immersive'] .pf-stage {
  grid-template-areas:
    '. . . . . . . . . . . .'
    '. . . . . . . . . . . .'
    '. . . Q Q Q Q Q . . . .'
    '. . . . I I I I . . . .'
    '. . . . . B B . . . . .'
    '. . . . . B B . L L . .';
}
.profile[data-layout='immersive'] .pf-slot--visual { position: fixed; inset: 0; z-index: -1; }
.profile[data-layout='immersive'] .pf-visual { aspect-ratio: auto; height: 100%; opacity: 0.92; }
.profile[data-layout='immersive'] .pf-slot--quote { grid-area: Q; justify-self: center; text-align: center; }
.profile[data-layout='immersive'] .pf-slot--identity { grid-area: I; justify-items: center; text-align: center; }
.profile[data-layout='immersive'] .pf-slot--bio { grid-area: B; justify-self: center; text-align: center; }
.profile[data-layout='immersive'] .pf-slot--socials { grid-area: L; justify-self: end; }
.profile[data-layout='immersive'] .pf-slot--song { position: fixed; left: var(--gutter); bottom: var(--edge); }
.profile[data-layout='immersive'] .pf-slot--extra { position: fixed; right: var(--gutter); bottom: var(--edge); }
.profile[data-layout='immersive'] .pf-name { mix-blend-mode: difference; }

/* ------------------------------------------------- composition fallback */

/* Applied by the composition guard when `grid-template-areas` failed to resolve
   in the engine. The named areas are simply absent, so rather than leaving seven
   slots stacked in two auto rows, the room falls back to one plain reading
   order: image, identity, quote, bio, links. Deliberately not another grid
   composition — the fallback exists for engines that cannot be trusted with one. */
.profile[data-composition='stacked'] .pf-stage {
  display: flex;
  flex-direction: column;
  align-content: flex-start;
  justify-content: flex-start;
  gap: clamp(1rem, 3vh, 2rem);
  overflow-y: auto;
  min-height: 100svh;
  max-height: 100svh;
  padding-block: clamp(4.4rem, 12vh, 7rem) clamp(2.4rem, 7vh, 4rem);
}

.profile[data-composition='stacked'] .pf-slot {
  width: 100%;
  max-width: 62ch;
  margin-inline: auto;
  flex: 0 0 auto;
}

.profile[data-composition='stacked'] .pf-slot--visual {
  max-width: min(100%, 34rem);
  margin-inline: auto;
}

.profile[data-composition='stacked'] .pf-slot--song,
.profile[data-composition='stacked'] .pf-slot--extra {
  position: static;
  display: none;
}

.profile[data-composition='stacked'] .pf-name {
  font-size: clamp(2.4rem, 11vw, 5.4rem);
}

.profile[data-composition='stacked'] .pf-identity {
  justify-items: center;
  text-align: center;
}

/* ------------------------------------------------------- name treatments */

.profile[data-name-orient='v'] .pf-name {
  writing-mode: vertical-rl;
  font-size: clamp(1.6rem, 4.4vw, 4.4rem);
  letter-spacing: 0.02em;
  line-height: 1;
}

/* The master is one spatial hierarchy for all nineteen profiles, and a stacked
   name is as tall as its letter count: SPHINX at six letters would push the
   plate under the mid-line while MIJI at four sat correctly. The master
   therefore always sets the name horizontally. The stored orientation stays in
   the record untouched, ready for a future per-profile treatment; it simply
   cannot resize this room. */
.profile[data-layout='master'][data-name-orient] .pf-name {
  writing-mode: horizontal-tb;
}

.profile[data-name-align='end'] .pf-identity { justify-items: end; text-align: right; }
.profile[data-name-align='center'] .pf-identity { justify-items: center; text-align: center; }

/* ---------------------------------------------------------------- 404 */

.nowhere {
  position: relative;
  z-index: var(--z-type);
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  place-items: center;
  padding: var(--edge) var(--gutter);
  text-align: center;
  gap: var(--gap-lg);
}

.nowhere__code {
  font-family: var(--font-meta);
  font-size: 0.74rem;
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--fg-faint);
}

.nowhere__title {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 72, 'wght' 600;
  font-size: clamp(1.5rem, 5.2vw, 5rem);
  line-height: 0.9;
  letter-spacing: -0.045em;
  text-transform: uppercase;
  color: var(--white);
  overflow: hidden;
}

.nowhere__title > span { display: block; }

.nowhere__line {
  font-family: var(--font-meta);
  font-size: clamp(0.72rem, 0.86vw, 0.92rem);
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--fg-mute);
  max-width: 34ch;
  margin-inline: auto;
  line-height: 2;
}

.nowhere__action {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  margin-top: var(--gap);
  padding: 0.7rem 1.4rem;
  font-family: var(--font-meta);
  font-size: 0.74rem;
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--fg-soft);
  border: 1px solid var(--line);
  transition: color 220ms var(--ease-out-expo), border-color 220ms var(--ease-out-expo);
}

.nowhere__action:hover { color: var(--white); border-color: var(--line-strong); }
