/* Project page, the dossier. Zone `pp-`, instrument layer, loaded after style.css.
   Not imported by index.html: the home reuses .pp-section. D-069, D-070. */

/* 1. Shell */
/* Composed from --header-h rather than a hardcoded 60px. */
.project-page-wrapper { padding-block-start: calc(var(--header-h) + var(--rhythm-inter)); }

/* Legacy container neutralised, not removed: it still anchors the markup. */
.project-page {
  max-width: none;
  margin-inline: 0;
  padding: 0;
}

/* Back link: system control, not a button. Surface lives in project-legacy.css. D-209. */
.page-back {
  margin: 0 0 var(--rhythm-intra);
}

/* 2. Dossier header */

/* Tag line is metadata, so metadata ink. */
.pp-tag {
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  text-transform: uppercase;
  letter-spacing: var(--meta-tracking);
  color: var(--text-meta);
  margin-block-end: var(--space-4);
  line-height: 1.6;
}

/* Section scale, not hero: wdth 115 / weight 700 against 100 / 500 for chapters. */
.pp-title {
  font-family: var(--type-family-display);
  font-variation-settings: "wdth" 115, "wght" 700;
  font-size: var(--type-section);
  line-height: var(--display-leading);
  letter-spacing: var(--display-tracking);
  text-transform: uppercase;
  color: var(--text-primary);
  margin-block-end: var(--rhythm-intra);
}

/* Identity block. An instrument dial, not a card. */
.pp-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-5);
  padding-block: var(--space-5);
  border-block: 1px solid var(--border-default);
  margin-block-end: var(--rhythm-inter);
}
/* Family rule removed with the colour coding. D-132. */

.pp-meta-item { min-width: 0; }
.pp-meta-label {
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  text-transform: uppercase;
  letter-spacing: var(--meta-tracking);
  color: var(--text-meta);
  margin-block-end: var(--space-1);
}
.pp-meta-value {
  font-family: var(--type-family-body);
  font-size: var(--type-small);
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1.45;
}

/* Stats band. One key figure carries the accent. */
/* 140 not 110: at 1024, five 124px columns left 82px usable. D-148. */
.pp-stats-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-5);
  margin-block-end: var(--rhythm-inter);
}
/* Legacy count variants no longer drive the grid; auto-fit does. */
.pp-stats-4,
.pp-stats-6 { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }

.pp-stat { min-width: 0; }
.pp-stat-val {
  font-family: var(--type-family-display);
  font-variation-settings: "wdth" 115, "wght" 700;
  font-size: 2.5rem;
  line-height: 1;
  color: var(--text-primary);
}
.pp-stat--key .pp-stat-val { color: var(--state-active); }
/* The unit belongs to the figure, so it takes its colour. */
.pp-stat-val span { color: inherit; }
.pp-stat-label {
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  text-transform: uppercase;
  letter-spacing: var(--meta-tracking);
  color: var(--text-meta);
  margin-block-start: var(--space-2);
  line-height: 1.4;
}

/* 3. Body */
.pp-body {
  font-family: var(--type-family-body);
  font-size: var(--type-body);
  line-height: 1.65;
  color: var(--text-secondary);
}

/* Prose is bound to the measure; media and grids are not. */
.pp-body > p,
/* Project page measure. */
.project-page {
  --pp-measure: none;
}

.pp-section-body > p {
  max-width: var(--pp-measure);
  margin-block-end: var(--space-4);
}

/* --text-secondary: the legacy grey fell below reading level. D-066. */
.pp-body strong { color: var(--text-primary); font-weight: 600; }
.pp-body em { font-style: italic; }

/* 4. Chapters. The gutter carries their navigation, the control their opening. */
.pp-section {
  border-top: 1px solid var(--border-default);
  padding-block-start: var(--rhythm-intra);
}

/* Whole frame opens the chapter: collapsed it is 80px tall and the handle only
   occupied 31. Zone from a pseudo-element, so summary keeps its role. D-170. */
.pp-section { position: relative; }

.pp-section:not([open]) .pp-section-head::before {
  content: "";
  position: absolute;
  /* -1px top: inset measures on the padding box, or the rule is a dead line. */
  inset: -1px 0 0 0;
}

/* The following gap belongs to the row. */
.pp-section:not([open]):has(+ .pp-section) .pp-section-head::before {
  bottom: calc(var(--rhythm-inter) * -1);
}
/* The gap belongs to the relation between two chapters, never to one alone. */
.pp-section + .pp-section { margin-block-start: var(--rhythm-inter); }

/* Collapse. Each chapter carries `open` in the HTML: the page opens whole. */
/* Qualified by the chapter: the legacy rule is a class AND an element. */
.pp-section .pp-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-5);
  /* Legacy padding on the summary added 54px of dead space. */
  padding: 0;
  /* cursor.js already lists summary in SEL_INTERACTIVE. */
  list-style: none;
  transition: color var(--motion-state);
}
.pp-section-head::-webkit-details-marker,
.pp-section-head::marker { display: none; }

/* Chevron goes: HIDE/SHOW says it in words. */
.pp-section .pp-section-head::before { content: none; }

.pp-section-head:focus-visible {
  outline: var(--focus-outline);
  outline-offset: var(--focus-offset);
}

/* Rhythm comes from the chapter, or the gap lands inside the click zone. */
.pp-section[open] .pp-section-head { margin-block-end: var(--rhythm-intra); }

/* Departure Mono: the one place the instrument layer shows in the dossier. */
/* One pill template for the row. D-230. */
.pp-section-toggle,
.pp-section-state {
  flex-shrink: 0;
  font-family: var(--type-family-system);
  font-size: var(--type-chrome);
  line-height: 1;
  /* SHOW/HIDE come from pseudo-element content; the state comes from the HTML. */
  text-transform: uppercase;
  color: var(--text-meta);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-os);
  padding: var(--space-2) var(--space-3);
}
.pp-section-toggle {
  transition:
    color var(--motion-state),
    border-color var(--motion-state),
    background-color var(--motion-state);
}
.pp-section[open] .pp-section-toggle::after { content: "HIDE"; }
.pp-section:not([open]) .pp-section-toggle::after { content: "SHOW"; }

.pp-section-head:hover .pp-section-toggle,
.pp-section-head:focus-visible .pp-section-toggle {
  color: var(--text-primary);
  border-color: var(--border-accent);
}
.pp-section-head:active .pp-section-toggle {
  background: var(--action-ghost);
  transition-duration: var(--dur-press);
}

.pp-section-head:hover .pp-section-title { color: var(--text-accent); }

/* WIP chapter: not a <details>. A summary that opens nothing is a lying control. D-229. */
/* Badge drops under the title: the SHOW handle was 55px, this needs 130. */
.pp-section--wip .pp-section-head { flex-wrap: wrap; }
.pp-section--wip .pp-section-head::before { content: none; }
.pp-section--wip .pp-section-head:hover .pp-section-title { color: inherit; }

/* One line per row. D-230. */
.pp-section:last-of-type {
  border-bottom: 0;
  margin-bottom: 0;
}
.pp-section:not(:has(~ .pp-section)) {
  border-bottom: 1px solid var(--border-default);
  margin-block-end: var(--rhythm-inter);
}

/* Collapsed chapters keep rule and title: the page reads as a summary. */
.pp-section:not([open]) { padding-block-end: var(--rhythm-intra); }

/* Chapter titles in mono, not display. */
.pp-section-title {
  display: block;
  font-family: var(--type-family-mono);
  font-size: var(--type-card);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: var(--meta-tracking);
  text-transform: uppercase;
  color: var(--text-primary);
  margin-block-end: 0;
  transition: color var(--motion-state);
}

/* Legacy indented the body 32px to sit under the accordion chevron. */
.pp-section-body {
  display: block;
  padding: 0;
}

/* 5. Proof blocks */

/* Dash, never a round bullet: an OS listing artefact. */
.pp-bullet-list {
  max-width: var(--pp-measure);
  list-style: none;
  margin-block: var(--space-4);
  padding: 0;
}
.pp-bullet-list li {
  position: relative;
  padding-left: var(--space-5);
  line-height: 1.65;
  color: var(--text-secondary);
}
.pp-bullet-list li + li { margin-block-start: var(--space-3); }
.pp-bullet-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: var(--space-3);
  height: 1px;
  background: var(--border-strong);
}
.pp-bullet-list li strong { color: var(--text-primary); font-weight: 600; }

/* Decision block. The number carries the accent: sequence numbers allow it. */
/* Bound on the block, number included, or the edge sits 40px inside. */
.pp-decision {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-5);
  max-width: var(--pp-measure);
  border: 0;
  padding: 0;
}
.pp-decision + .pp-decision { margin-block-start: var(--rhythm-inter); }
.pp-decision-num {
  font-family: var(--type-family-mono);
  font-size: var(--type-card);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--meta-tracking);
  color: var(--state-active);
}
/* One right edge for everything that reads. */
.pp-decision-content { min-width: 0; }
.pp-decision-title {
  font-family: var(--type-family-body);
  font-size: var(--type-lead);
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.35;
  margin-block-end: var(--space-3);
}
.pp-decision-content p { max-width: var(--pp-measure); margin-block-end: var(--space-3); }

/* Quote: left rule, no decorative marks, no italics. */
.pp-quote {
  margin-block-start: var(--space-4);
  padding-left: var(--space-4);
  border-left: 1px solid var(--border-strong);
  font-family: var(--type-family-body);
  font-size: var(--type-lead);
  line-height: 1.5;
  color: var(--text-primary);
  background: none;
}

/* Cells: one treatment for all three. Never three nested card levels. */
.pp-challenges,
.pp-presence-grid,
.pp-two-col {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  margin-block: var(--space-4);
}

.pp-challenge,
.pp-presence-item {
  border: 1px solid var(--border-default);
  border-radius: var(--radius-os);
  padding: var(--space-4);
  font-size: var(--type-small);
  line-height: 1.55;
  color: var(--text-secondary);
  background: var(--surface-raised);
}
.pp-challenge p { font-size: var(--type-small); margin: 0; max-width: none; }
.pp-challenge strong,
.pp-presence-item strong { color: var(--text-primary); font-weight: 600; }

.pp-challenge-label,
.pp-col-label,
.pp-cast-lang,
.pp-level-name,
.pp-doc-label {
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  text-transform: uppercase;
  letter-spacing: var(--meta-tracking);
  color: var(--text-meta);
  margin-block-end: var(--space-2);
}

/* Level block: no frame, it lives in the chapter flow. */
/* Bound on the container, same reason as above. */
.pp-level {
  max-width: var(--pp-measure);
  border-left: 1px solid var(--border-default);
  padding-left: var(--space-5);
  margin-block: var(--rhythm-intra);
}
.pp-level p { max-width: var(--pp-measure); margin-block-end: var(--space-3); }
.pp-level p:last-child { margin-block-end: 0; }
.pp-level-name {
  font-family: var(--type-family-body);
  font-size: var(--type-lead);
  font-weight: 600;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text-primary);
  margin-block-end: var(--space-3);
}

/* Chapter takeaway. */
.pp-highlight {
  max-width: var(--pp-measure);
  /* At 14px, 68ch was 548px and the edge sat 118px inside the paragraphs. */
  font-size: var(--type-body);
  margin-block-start: var(--rhythm-intra);
  padding: var(--space-5);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-os);
  box-shadow: var(--elevation-rest);
  background: var(--surface-raised);
  color: var(--text-primary);
  line-height: 1.6;
}
.pp-highlight strong { color: var(--text-primary); font-weight: 600; }

/* Caveat: metadata about a document, not prose. Left rule, no accent. D-243. */
/* The gap belongs to the relation, posted once, on the relation. D-243. */
.pp-external-link + p { margin-block-start: var(--rhythm-intra); }

.pp-caveat {
  max-width: var(--pp-measure);
  margin-block-start: var(--rhythm-intra);
  padding-inline-start: var(--space-4);
  border-inline-start: 2px solid var(--border-default);
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  letter-spacing: var(--meta-tracking);
  line-height: 1.7;
  color: var(--text-meta);
}

.pp-col-label { display: block; }

/* 6. Media. They bleed out of the reading column. */
.pp-figure {
  margin: 0;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-os);
  box-shadow: var(--elevation-rest);
  overflow: hidden;
  background: var(--surface-raised);
}
/* Figure takes a video at the same rank as an image. D-228. */
.pp-figure img,
.pp-figure video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--pp-figure-ratio);
  object-fit: cover;
}
.pp-figure--contain img { object-fit: contain; }

/* No zoom-in cursor on a video: the lightbox only handles images. */
.pp-figure:has(video) { cursor: inherit; }

/* A lone figure carries the rhythm; a gallery figure does not. */
.pp-body > .pp-figure,
.pp-section-body > .pp-figure { margin-block: var(--rhythm-inter); }

.pp-figure-caption {
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-default);
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  text-transform: uppercase;
  letter-spacing: var(--meta-tracking);
  color: var(--text-meta);
  line-height: 1.5;
}

.pp-gallery,
.pp-figure-row {
  display: grid;
  gap: var(--space-4);
  margin-block: var(--rhythm-inter);
}
.pp-gallery--2col,
.pp-figure-row { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.pp-gallery--3col { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.pp-gallery--portrait .pp-figure img { aspect-ratio: 3 / 4; object-fit: cover; }

.pp-trailer {
  margin-block: var(--rhythm-inter);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-os);
  box-shadow: var(--elevation-rest);
  overflow: hidden;
  background: var(--surface-raised);
}
.pp-trailer video,
.pp-trailer iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
}

.pp-cast-cols {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  margin-block: var(--rhythm-intra);
}
.pp-cast-video {
  margin-block-end: var(--space-4);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-os);
  box-shadow: var(--elevation-rest);
  overflow: hidden;
}
.pp-cast-video video { display: block; width: 100%; height: auto; }

.pp-pdf-preview {
  margin-block: var(--rhythm-inter);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-os);
  box-shadow: var(--elevation-rest);
  overflow: hidden;
}
.pp-pdf-preview iframe { display: block; width: 100%; border: 0; }

.pp-placeholder {
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-os);
  padding: var(--space-7) var(--space-5);
  text-align: center;
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  text-transform: uppercase;
  letter-spacing: var(--meta-tracking);
  color: var(--text-meta);
}

/* 6 bis. Play zone. The retro OS frame around a playable build. Nothing else on
   this page carries system chrome: the two registers never share a block. */

/* Chrome comes from .win. This sets the rhythm, and the note under the frame
   belongs to the frame, so it takes the intra gap and carries the inter one. */
.pp-play {
  margin-block-start: var(--rhythm-inter);
  margin-block-end: var(--space-4);
}

/* Two children, so the title bar's space-between lands the label left and the
   controls right, the way every other window on the site reads. */
.pp-play-name {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.pp-play-controls {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* No control exists until it has something to act on: at rest the frame is a
   title bar and nothing more. Both buttons act on a running game. D-065. */
.pp-play-full,
.pp-play-stop { display: none; }

.pp-play.is-playing .pp-play-full,
.pp-play.is-playing .pp-play-stop { display: inline-grid; }

.pp-play-icon {
  width: var(--type-meta);
  height: var(--type-meta);
  fill: currentColor;
}

/* Cover and frame share one box, so swapping one for the other moves nothing. */
.pp-play-stage {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--surface-page);
}

/* Button sits low, not dead centre: the still has a centred subject and the two
   were landing on each other. Below two thirds of the height the frame is empty
   black, so that is where the action goes. */
.pp-play-cover {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: end center;
  padding-block-end: var(--space-6);
  opacity: 1;
  transition: opacity var(--motion-exit);
}

/* Delay equals the fade, so the button leaves the focus order once it is gone. */
.pp-play.is-playing .pp-play-cover {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--motion-exit), visibility 0s var(--dur-state);
}

.pp-play-shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Sits above the still, which is absolute and earlier in the flow. */
.pp-play-launch { position: relative; }

.pp-play-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* A full panel, not a veil over the still: text never has to hold contrast
   against a photograph, so no scrim is needed to make it readable. */
.pp-play-notice {
  display: none;
  position: absolute;
  inset: 0;
  place-content: center;
  justify-items: center;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--surface-page);
  text-align: center;
  z-index: 1;
}

.pp-play-text {
  max-width: var(--measure);
  font-family: var(--type-family-body);
  font-size: var(--type-small);
  line-height: 1.6;
  color: var(--text-secondary);
}

/* A cross-origin frame cannot be inspected, so a load that never arrives is the
   only failure this page can read. The direct address takes over. */
.pp-play.has-error .pp-play-notice--error { display: grid; }

/* The mention and the address sit on one line under the frame, and the address
   never leaves: a frame refused by X-Frame-Options still fires `load`, so that
   failure is invisible to script. A link that needs no detection covers it. */
.pp-play-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-block-end: var(--rhythm-inter);
}

.pp-play-foot .pp-external-link { margin-block-start: 0; }

/* Caption rank: metadata about the frame above it, not prose. */
.pp-play-note {
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  text-transform: uppercase;
  letter-spacing: var(--meta-tracking);
  color: var(--text-meta);
  line-height: 1.5;
}

/* Full screen keeps the title bar, so the way out stays where it was put. */
.pp-play:fullscreen {
  display: grid;
  grid-template-rows: auto 1fr;
  margin: 0;
  background: var(--surface-page);
}
.pp-play:fullscreen .pp-play-stage { aspect-ratio: auto; height: 100%; }

/* Kept apart: one unknown selector in a list drops the whole rule. */
.pp-play:-webkit-full-screen {
  display: grid;
  grid-template-rows: auto 1fr;
  margin: 0;
  background: var(--surface-page);
}
.pp-play:-webkit-full-screen .pp-play-stage { aspect-ratio: auto; height: 100%; }

/* No fine pointer, no launch button: the game reads a mouse and nothing else.
   The address stays reachable, so the visitor can return to it from a desk. */
@media not all and (pointer: fine) {
  .pp-play-launch { display: none; }

  /* No game will run here, so the box stops holding a 16:9 frame and lets the
     message set its own height. Kept as an overlay it was cropped by the ratio,
     and a message cut in half is worse than no message. */
  .pp-play-stage { aspect-ratio: auto; }
  .pp-play-cover { display: none; }

  .pp-play-notice--touch {
    display: grid;
    position: static;
    inset: auto;
  }

  /* The panel above already carries the address. Twice in fifty pixels is once
     too many, and the second one reads as a different destination. */
  .pp-play-foot .pp-external-link { display: none; }
}

/* 7. External links. System control, four states. */
.pp-links-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block: var(--rhythm-intra);
}
/* This sheet owns the external link. It was declared in three. D-210. */
.pp-external-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* Alone in a chapter it carries its own top gap; in a row, the row does. */
  margin-block-start: var(--rhythm-intra);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-os);
  background: var(--surface-raised);
  font-family: var(--type-family-mono);
  font-size: var(--type-meta);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--meta-tracking);
  color: var(--text-primary);
  text-decoration: none;
  box-shadow: var(--elevation-rest);
  transform: translate(0, 0);
  transition:
    background-color var(--motion-state),
    box-shadow var(--motion-state),
    transform var(--motion-state),
    border-color var(--motion-state);
}
.pp-links-row .pp-external-link { margin-block-start: 0; }

.pp-external-link:hover {
  background: var(--ctrl-bg-hv);
  border-color: var(--border-accent);
  box-shadow: var(--elevation-hover);
  transform: translate(-1px, -1px);
}
.pp-external-link:active {
  background: var(--action-pressed);
  color: var(--text-on-action);
  box-shadow: var(--elevation-pressed);
  transform: translate(3px, 3px);
  transition-duration: var(--dur-press);
}
.pp-external-link:focus-visible {
  outline: var(--focus-outline);
  outline-offset: var(--focus-offset);
}
/* --type-chrome, 13px: it is a chrome glyph. */
.pp-external-arrow {
  font-size: var(--type-chrome);
  transition: transform var(--motion-state);
}
.pp-external-link:hover .pp-external-arrow { transform: translate(2px, -2px); }

/* 8. Mobile. wdth to 100. */
@media (max-width: 767px) {
  /* Chapter titles went mono, so this axis no longer concerns them. */
  .pp-title,
  .pp-stat-val { font-variation-settings: "wdth" 100, "wght" 700; }

  /* Not redundant with layout.css: .pg and .project-page-wrapper have the same
     specificity and this sheet loads last, so the 124px base rule wins. D-243, D-245. */
  .project-page-wrapper {
    padding-block-start: calc(var(--header-h) + var(--rhythm-intra));
  }

  /* Collapsed, a chapter is a row, not a block: intra rhythm, not inter. Open,
     it is a block again and gets its 64px back. D-243. */
  .pp-section + .pp-section:not([open]) {
    margin-block-start: var(--rhythm-intra);
  }

  /* Narrow desktop window: the stage falls to about 240px and the still becomes
     unreadable, while the button lands back on the grid whatever padding it is
     given. Below this width the cover is the action alone, on plain ink. A
     touch pointer never reaches this rule, its cover is already gone. */
  .pp-play-shot { display: none; }
  .pp-play-cover {
    place-items: center;
    padding-block-end: 0;
  }

  .pp-decision { grid-template-columns: minmax(0, 1fr); gap: var(--space-3); }
  .pp-level { padding-left: var(--space-4); }
  .pp-stat-val { font-size: 2rem; }

  /* Opening gallery stays one row: minmax(240px) stacked it below ~340px. */
  .pp-body > .pp-gallery--3col {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-2);
  }
  .pp-body > .pp-gallery--3col .pp-figure img {
    aspect-ratio: 1 / 1;
    object-fit: cover;
  }
}

/* 9. Reduced motion. Rest transforms restated: a residual translate is a bug. */
@media (prefers-reduced-motion: reduce) {
  .pp-external-link:hover,
  .pp-external-link:active { transform: translate(0, 0); }
  .pp-external-link:hover .pp-external-arrow { transform: none; }
}
