/* Project page — Figma `Desktop-Project-iteration-x` (140:17213) and
   `Mobile-Project-iteration-x` (140:19624).

   The white sheet's gutter is HALF the nav's: 12 against the nav's 24, which is
   what the Figma frame has (Body+Footer at x=12, nav padding 24). The sheet is
   not flush with the nav's buttons — it sits 12px outside them on each side. */

.project-page {
  padding: 0 12px;
  /* Exactly the bar's height, nothing added. That is what puts 24 above the
     cards and 24 below them: the bar is 24 + the cards' 80 + 24, and the sheet
     begins where the bar ends. I added a second 24 here at one point, which
     was wrong — the symmetry comes from the bar's own padding, not from a gap
     underneath it. */
  padding-top: var(--project-nav-h);
}

/* Body+Footer (174:27432): a white sheet on the page grey. 1576 is the nav's
   1600 container less two 12px gutters, so the half-of-the-nav relationship
   holds at every width rather than only at 1280. */
.project-body {
  max-width: 1576px;
  margin: 0 auto;
  /* primary/50, not white. The sheet and the page it sits on now share a
     ground: what separates the project's content from the site is no longer a
     lighter rectangle but the space around it. */
  background: var(--primary-50);
  /* All four corners, not just the top two: the footer sits outside the sheet,
     so its bottom edge shows. */
  border-radius: 12px;
  /* The sheet closes 128 after the last section — the same gap it uses between
     sections, so the end of the white reads as one more step of the rhythm. */
  padding-bottom: 128px;
}

/* ── Header-introduction (174:27201) ─────────────────────────────────────── */

.project-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 64px;
}

/* Project-Cover (151:22382): a 1248/749 box with the thumbnail inside, on a
   gradient that shows through wherever the thumbnail does not reach */
.project-cover {
  width: 100%;
  aspect-ratio: 1248 / 749;
  border-radius: 12px 12px 0 0;
  overflow: hidden;
  /* No ground of its own. It carried Iteration X's own purple-to-pink, which
     is part of that project's photograph and not of the frame — on any other
     cover it simply showed through as pink. The frame holds one thing, the
     photograph, and the photograph fills it. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* The picture takes the whole frame: same box, no radius of its own. A 16 on
   the image inside a 12 on the frame left four slivers of whatever was behind
   it showing at the corners, which is where the pink was coming from. The
   frame's own overflow does the rounding. */
.project-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
}

/* Project-introduction (174:27203) */
.project-intro {
  width: 640px;
  max-width: 100%;
  padding: var(--space-32);
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
}

.project-intro-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  padding: 0 var(--space-16);
  width: 100%;
}

/* Project/Date-location (163:22624) */
.project-dateline p {
  margin: 0;
  font-family: var(--font-body);
  font-weight: var(--weight-book);
  font-size: var(--body-16-size);
  line-height: 24px;
  letter-spacing: -0.16px;
  /* No white halo. The Figma puts a 34 blur behind this text — a 17px CSS
     radius — and it is deliberately not carried over: removed on the headlines
     first, then everywhere, because it reads as a smudge on a screen where in
     Figma it reads as separation. Whatever a text needs to sit on, it gets
     from what is behind it, not from a glow around each letter. */
}

.project-date { color: var(--text-project-date); }
.project-location { color: var(--text-project-location); }

/* Project/Title (174:27214): the name in grey over the sentence in dark */
.project-title {
  width: 542px;
  max-width: 100%;
  margin: 0;
  font-family: var(--font-headings);
  font-weight: var(--weight-book);
  font-size: var(--h1-size);
  line-height: var(--h1-line);
}

.project-title span { display: block; }
.project-title .is-name { color: var(--text-project-title-grey); }
.project-title .is-line { color: var(--text-project-title-dark); }

.project-metrics {
  display: flex;
  flex-direction: column;
  gap: 48px;
  padding: 0 var(--space-16);
  width: 100%;
}

/* Project/Introduction (174:27218) */
.project-lede {
  margin: 0;
  width: 542px;
  max-width: 100%;
  font-family: var(--font-body);
  font-weight: var(--weight-book);
  font-size: var(--h3-size);
  line-height: var(--h3-line);
  color: var(--text-project-introduction);
}

/* Meta Row list (174:27220) */
.meta-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  width: 100%;
}

.meta-row {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  width: 542px;
  max-width: 100%;
}

.meta-row--centred { align-items: center; }

.meta-label {
  width: 140px;
  flex: none;
  margin: 0;
  font-family: var(--font-body);
  font-weight: var(--weight-regular);
  font-size: var(--body-16-size);
  line-height: var(--body-16-line);
  color: var(--text-project-meta-label);
}

.meta-values {
  flex: 1 0 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  justify-content: center;
}

.meta-value {
  display: flex;
  gap: var(--space-8);
  align-items: center;
  margin: 0;
  font-family: var(--font-body);
  font-weight: var(--weight-regular);
  font-size: var(--body-16-size);
  line-height: var(--body-16-line);
  color: var(--text-project-key-label);
}

/* A 20x20 box, the icon contained inside it at its own proportions. Three of
   the six are not square — landing is 20x14, mobile 11.7x18, illustration
   9x18.6 — and forcing them all to 20x20 stretched them. Figma also exports
   them with preserveAspectRatio="none", which grants permission to distort;
   that attribute is stripped from the files. */
.meta-value img {
  width: 20px;
  height: 20px;
  object-fit: contain;
  flex: none;
}

/* Project/Team (163:22746): the first avatar keeps the action ring, the rest
   do not — that is the component's own distinction, not an oversight */
.project-team {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  width: 394px;
  max-width: 100%;
}

.project-team .testimonial { border-color: transparent; }
.project-team .testimonial:first-child { border-color: var(--border-action); }

/* ── Mobile — Figma 174:28050 ────────────────────────────────────────────── */

@media (max-width: 700px) {
  /* Mobile does not follow the desktop rule: here the sheet is flush with the
     nav, 16 against the nav's 16, where desktop is half of it. */
  .project-page { padding: 0 var(--space-16); padding-top: var(--project-nav-h); }
  .project-header { gap: var(--space-16); }
  /* 174:28051: the cover is square on a phone — 361 by 361 — where a wide
     screen gives it 5:3. Its image stays 602 wide inside that square and is
     cropped to the middle, which is what `cover` on a square frame does. */
  .project-cover { aspect-ratio: 1 / 1; }
  .project-cover img { height: 100%; aspect-ratio: auto; }
  .project-intro { width: 100%; padding: 0; }
  /* 174:28053 et 174:28066 : x=0, 361 de large, comme le reste. */
  .project-intro-header,
  .project-metrics { padding: 0; }
  .project-title,
  .project-lede,
  .meta-row { width: 100%; }
  /* the label stops being a column and sits above its values */
  /* 174:28070 : la ligne empile son libellé et ses valeurs, 8 entre les deux. */
  .meta-row { flex-direction: column; gap: var(--space-8); }
  .meta-label { width: 100%; }
  .project-team { width: 100%; }
}

/* ── Success-cards (168:23710) ───────────────────────────────────────────── */

.success-cards {
  display: flex;
  gap: var(--space-project-gap);
  align-items: center;
  width: 100%;
}

.success-card {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow: hidden;
  border-radius: 12px;
  background: var(--primary-400);
}

/* the third card is the dark variant (168:23694) */
.success-card--dark { background: var(--primary-800); }

.success-card-headlines {
  width: 100%;
  padding: var(--space-24);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  font-family: var(--font-body);
  font-weight: var(--weight-regular);
  font-size: var(--body-16-size);
  line-height: var(--body-16-line);
}

.success-card-headlines p { margin: 0; width: 100%; }
.success-card-headlines .is-head { color: var(--text-primary-light); }
.success-card-headlines .is-item { color: var(--text-secondary-dark); }
.success-card--dark .success-card-headlines .is-item { color: var(--text-secondary-light); }
/* the last two items of the dark card carry their own weights in the Figma */
.success-card-headlines .is-book { font-weight: var(--weight-book); }
.success-card-headlines .is-italic { font-style: italic; }

.success-card img {
  width: 100%;
  aspect-ratio: 403 / 369;
  object-fit: cover;
  margin-top: auto;
}

@media (max-width: 700px) {
  /* 174:28081: the three stack, 12 apart, and stop being a fixed height */
  .success-cards { flex-direction: column; gap: var(--space-project-gap); }
  /* `flex: none` aussi : empilées, les trois cartes gardaient la base de 0 de
     la rangée desktop et s'écrasaient à rien — elles ne formaient plus qu'un
     seul cadre vide sur téléphone. */
  .success-card { flex: none; width: 100%; height: auto; }
}

/* ── Project body blocks ──────────────────────────────────────────────────

   Every `# Anchor-*` section is a stack of blocks with 128px between them,
   measured off the Figma: each block's bottom to the next block's top is 128
   throughout Overview (1288.83→1416.83, 1672.83→1800.83, 3328.83→3456.83).
   Inside a block, related pieces sit 12 apart (`Spaces/project/gaps`). */

.project-section {
  display: flex;
  flex-direction: column;
  gap: 128px;
  /* 12, the same inset as the page's outer gutter and the same as the gap
     between a pair. It was 11.5, taken off the Overview frame's x; every other
     anchor frame sits at 12 and Flora wants the three to agree. */
  padding: 0 12px;
}

/* Sections sit 128 apart, and the first sits 128 below the header. The Figma
   anchor frames have no padding of their own: the 128 belongs between them. */
.project-sections {
  display: flex;
  flex-direction: column;
  gap: 128px;
  padding-top: 128px;
}

/* Images-block: pieces 12 apart */
.images-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-project-gap);
  width: 100%;
}

/* Image-Full-width (168:24247): a bordered, rounded frame, image cover inside */
.image-full {
  position: relative;
  width: 100%;
  aspect-ratio: 1215 / 729.03;
  border: 1px solid var(--primary-100);
  border-radius: var(--radius-12);
  overflow: hidden;
}

.image-full img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A label sitting inside the frame, e.g. "Our Roadmap" (I172:25002;172:24844) */
.image-full-label {
  position: absolute;
  left: 23px;
  top: 23px;
  width: 584px;
  margin: 0;
  font-family: var(--font-headings);
  font-weight: var(--weight-book);
  font-size: 24px;
  line-height: 32px;
  letter-spacing: -0.48px;
  color: var(--text-secondary);
}

/* Project-big-text (168:24393): a 640 column centred in the sheet, 32 of
   horizontal padding, and the text itself padded 16 inside that. */
.project-big-text {
  width: 640px;
  margin: 0 auto;
  padding: 0 var(--space-32);
}

.project-text {
  padding: var(--space-16);
  margin: 0;
  font-family: var(--font-headings);
  font-weight: var(--weight-book);
  font-size: 24px;
  line-height: 32px;
  letter-spacing: -0.48px;
  color: var(--text-secondary);
}

/* The opening clause of a big text runs in grey before the dark sentence */
.project-text .is-lead { color: var(--greyscale-500); }
.project-text p { margin: 0; }

/* Project-Problem-Solution (172:25000): two 610 cards, the Solution one on a
   lilac gradient with its label in the accent violet. */
.problem-solution {
  display: flex;
  gap: var(--space-project-gap);
  /* Stretch, not centre. Two cards shown side by side to be compared have to be
     the same height, always — otherwise the one with a longer line sits lower
     and the comparison reads as an accident. Centring made them differ by
     exactly the height of one line of title. */
  align-items: stretch;
  width: 100%;
}

.ps-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  align-items: center;
  flex: 1 1 0;
  min-width: 0;
  background: #e7e5ea;
  border-radius: var(--radius-12);
  overflow: hidden;
}

.ps-card--solution { background: linear-gradient(to bottom, #e3ddf8, #eceaef); }

.ps-titles {
  display: flex;
  flex-direction: column;
  gap: 1px;
  align-items: center;
  justify-content: center;
  padding: var(--space-24);
  width: 100%;
}

.ps-type {
  margin: 0;
  width: 100%;
  font-family: var(--font-headings);
  font-weight: var(--weight-book);
  font-size: 24px;
  line-height: 32px;
  letter-spacing: -0.48px;
  color: var(--text-tertiary);
  text-align: center;
}

.ps-card--solution .ps-type { color: #5344ff; }

.ps-description {
  display: flex;
  /* A column, so that whatever this holds stacks. The real fix was upstream:
     each description is ONE paragraph and always was. Splitting it into two —
     to reproduce the line break the frame draws — turned it into two flex
     items standing side by side. A sentence breaks where the column runs out,
     not where the markup says. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 64px;
  width: 100%;
}

.ps-description p {
  margin: 0;
  /* Measured: the full sentence is 475px wide and the space it sits in is 482,
     so it clings to one line and reads as too long. "High cost of context
     switching" is 311 and adding "and" makes 357, so a cap between the two
     forces the break where Flora wants it. 330 sits in that window with room
     for the real face — these numbers come from a metrically close stand-in,
     since Suisse Intl cannot be loaded here to measure directly.

     On the whole card family, not just this one sentence. */
  max-width: 330px;
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--font-headings);
  font-weight: var(--weight-book);
  font-size: 24px;
  line-height: 32px;
  letter-spacing: -0.48px;
  color: var(--text-secondary);
  text-align: center;
}

/* No forced ratio and no crop. The Figma's Image node is square, but the
   export is 1220x1196 — close, not equal — and a square box with object-fit:
   cover trims the 24px difference off the picture. The image sets its own
   height instead, so the card grows by those 24px and nothing is lost.

   Third time today this pairing has cut something: a ratio plus cover always
   crops whatever does not match it to the pixel. */
.ps-image {
  width: 100%;
  height: auto;
  display: block;
  /* The card stretches to its neighbour's height; the picture stays at the
     bottom of it and the room that stretching creates opens under the title,
     where it reads as breathing rather than as a gap. */
  margin-top: auto;
}

/* Textsection-main (106:2741): title over body, both H3, 16 of padding */
.textsection {
  width: 576px;
  margin: 0 auto;
  padding: var(--space-16);
  display: flex;
  flex-direction: column;
  font-family: var(--font-headings);
  font-weight: var(--weight-book);
  font-size: var(--h3-size);
  line-height: var(--h3-line);
}

.textsection-title { margin: 0; color: var(--text-about-title); }
.textsection-body { margin: 0; color: var(--text-about-body); }

/* 172:25022 carries a taller image than the standard full-width frame */
.image-full--tall { aspect-ratio: 2432 / 1634; }

/* Double images (172:25711): a pair sharing the row, 12 apart */
.double-images {
  display: flex;
  gap: var(--space-project-gap);
  align-items: flex-start;
  width: 100%;
}

.double-images > * { flex: 1 1 0; min-width: 0; }

.image-full--square { aspect-ratio: 1 / 1; }
.image-full--43 { aspect-ratio: var(--ratio-project-card); }
/* 316:55029 — 1256 by 739, the sheet's full width. Neither the block default
   nor 4:3: this frame has its own. */
.image-full--wide { aspect-ratio: 1256 / 739; }

/* ── Dashboard blocks ─────────────────────────────────────────────────────

   Several frames here are coloured panels holding a video. The videos cannot
   be exported from Figma (a video fill returns no source), so the panels are
   built to their exact size and carry `data-video` naming the Figma layer.
   Dropping the file in later is a one-line change per panel. */

.video-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  border-radius: var(--radius-12);
  overflow: hidden;
}

.video-panel > .video-slot,
.video-panel > video,
.video-panel > img { width: 100%; border-radius: var(--radius-12); display: block; }

.video-slot { background: rgba(0, 0, 0, 0.04); }

/* Video-extension (172:25717) */
.video-extension { background: #f1edff; padding: var(--space-project-images) 54px; }
.video-extension .video-slot { width: 949px; aspect-ratio: 949 / 595; }

/* Video-issues-list (172:25730): a white sheet, radius 20, video edge to edge */
.video-issues-list { background: #ffffff; border-radius: 20px; padding: 0; }
.video-issues-list .video-slot { aspect-ratio: 1232 / 762; border-radius: 0; }

/* Video-issue-list (172:25773) */
.video-issue-list { background: #ebd8ff; border-radius: 16px; padding: var(--space-project-images); }
.video-issue-list .video-slot { aspect-ratio: 2140 / 1218; }

/* Extension-video-features (172:25733): a 610 text column beside a 610 media
   panel. On desktop they alternate sides; the order follows the markup. */
.feature-row {
  display: flex;
  gap: var(--space-project-gap);
  align-items: center;
  width: 100%;
}

/* Exactly half the row each, less half the 12 gap — the same width as every
   other half-row pair. `flex: 1 1 0` should already give that and did not, and
   I could not find what was overriding it, so this states the width outright
   rather than leaving it to be computed. */
.feature-row > * { flex: 0 0 calc(50% - (var(--space-project-gap) / 2)); min-width: 0; }

.feature-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
  align-items: center;
  justify-content: center;
  padding: 0 64px;
}

.feature-text .textsection { width: 100%; margin: 0; }

/* The dark media panel that holds a role or search still — 610x813, radius 20 */
.feature-media {
  position: relative;
  background: #0e0b11;
  border-radius: 20px;
  overflow: hidden;
  aspect-ratio: 610 / 813;
}

.feature-media img,
.feature-media .video-slot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
}

/* Issue-small (172:25764) and Issue-big (172:25766): a gradient panel with the
   screenshot inset 64, the gradient running the opposite way on the second.
   The panel is a fixed 738 in the Figma and the screenshot is centred and
   clipped inside it, so the height is set rather than derived. */
.issue-panel {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* The panel is 1232 x 738 (172:25764), so its ratio is pinned rather than a
     pixel height — it holds at every width. The 54/64 insets leave exactly the
     1104 x 629.6 the mockup occupies, so it is neither cut nor floating: it was
     cut before because the padding was 64 all round, which left 610 for 630. */
  /* The shared container ratio. It is safe to hold it again now that the
     padding is 54 rather than 64: 738 less 108 leaves exactly the 630 the
     mockup needs, so the shape is kept AND nothing is cut. The earlier cut
     came from forcing a ratio on the image as well, which is gone. */
  aspect-ratio: var(--ratio-project-image);
  padding: 54px var(--space-project-images);
  border-radius: var(--radius-12);
  background: linear-gradient(to bottom, #e1e0e4, #e3ddf8);
}

.issue-panel--reverse {
  background: linear-gradient(to bottom, #e3ddf8, #e1e0e4);
}

.issue-panel img {
  width: 100%;
  /* height: auto, so the file's own proportions decide. No object-fit: there is
     nothing to fit into any more. */
  height: auto;
  border-radius: var(--radius-12);
  display: block;
}

/* Issue-small holding the phone mockups (172:26124) */
.mockup-panel {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* The frame holds what it holds. Iteration X's five phones fit inside theirs,
     so this never showed there; PropertyGRID's four are wider apiece and stand
     124px taller than the room between the panel's 128 of vertical padding.
     Without this they simply hang out of the coloured rectangle. */
  overflow: hidden;
  /* The shared ratio (172:26124). The phone mockups sit well short of the
     edges here, so this one never clipped — but without the ratio the panel
     collapsed onto them and lost its shape, which is the custom size Flora
     spotted. */
  aspect-ratio: var(--ratio-project-image);
  /* 12 all round, on request. It started at 54 vertical and 64 horizontal,
     which I had taken from where the mockups happen to sit inside the frame
     rather than from a padding the frame declares. The panel keeps its ratio,
     so the mockups take the room instead and read larger. */
  padding: var(--space-project-gap);
  border-radius: var(--radius-12);
  background: linear-gradient(to bottom, #eceaf2, #f7def9);
}

.mockup-row { display: flex; gap: var(--space-project-gap); align-items: center; width: 100%; }

/* The five phones share the row: each takes an equal fifth of whatever is left
   after the gaps, so they grow with the panel instead of sitting at the 222px
   the Figma happens to draw them at on a 1232 frame. That fixed width is why
   they still read small after the padding came down — the room was there, they
   just weren't taking it.

   All five files are 444x966, so height: auto gives each its exact proportion.
   No ratio to force here and nothing to crop. */
.mockup-row img {
  flex: 1 1 0;
  min-width: 0;
  width: auto;
  height: auto;
  border-radius: var(--radius-16);
  display: block;
}

/* Video-archives (174:27152) */
.video-archives { background: #e4def8; padding: var(--space-project-images); }
.video-archives .video-slot { aspect-ratio: 1920 / 1190; }

/* Archives-illustrations (174:27199): a pair of dark panels and a caption */
.archives-illustrations {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  align-items: center;
  /* full width, like every other pair on the page. It was capped at the Figma
     frame's 1216 while the sections around it run the whole width, so the two
     frames sat narrower than everything else. */
  width: 100%;
}

/* 12 between them, the same gap every other half-width pair uses */
.archives-pair { display: flex; gap: var(--space-project-gap); align-items: stretch; width: 100%; }

.archives-pair > div {
  flex: 1 1 0;
  min-width: 0;
  aspect-ratio: 612 / 816;
  background: #0e0b11;
  border-radius: 20px;
  overflow: hidden;
  position: relative;
}

/* img OR video: the slots became <video> once Flora exported them, and this
   rule only named img, so they sat at their natural size instead of filling. */
.archives-pair img,
.archives-pair video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

/* Caption (174:27196): centred, italic, 482 wide */
.project-caption {
  width: 482px;
  max-width: 100%;
  margin: 0;
  font-family: var(--font-headings);
  font-weight: var(--weight-book);
  font-style: italic;
  font-size: var(--caption-size);
  line-height: var(--caption-line);
  color: var(--text-secondary-light);
  text-align: center;
}

/* # Anchor-AI blocks */

/* Video-itx-AI (174:26462): a dark 610x813 panel holding a video */
.ai-video-panel {
  position: relative;
  width: 100%;
  aspect-ratio: 610 / 813;
  background: #1a1a1a;
  border-radius: 20px;
  overflow: hidden;
}

.ai-video-panel .video-slot { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 0; }

/* Video (174:26466): a near-black panel, the clip inset 64 with an 8 radius */
.ai-video-wide { background: #0e0b11; padding: var(--space-project-images); border-radius: var(--radius-12); }
.ai-video-wide .video-slot { aspect-ratio: 2136 / 1216; border-radius: 8px; }

/* Image-Caption (174:27140): a 610 portrait with its caption 24 below.
   The caption is not centred — in the Figma it sits at x=64 with a width of
   482 inside the 610 frame, so it is inset 64 on both sides. Centring it made
   these two read differently from every other block. */
.image-caption {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  align-items: stretch;
  /* A <figure> carries 40px of margin on each side by default, which was
     pulling these two in and making them narrower than every other half-width
     pair on the page. They fill their half now, like the square ones above. */
  margin: 0;
}

.image-caption .project-caption {
  width: auto;
  padding: 0 64px;
}

/* The default shape inside a captioned figure, and only the default: a
   modifier on the frame itself has one class where this has two, so it lost
   every time. `--square` on a captioned image was being silently overruled and
   drawn at 610x813. Each modifier now says so at the same weight. */
.image-caption .image-full { aspect-ratio: 610 / 813; }
.image-caption .image-full--square { aspect-ratio: 1 / 1; }
.image-caption .image-full--tall   { aspect-ratio: 2432 / 1634; }
.image-caption .image-full--whole  { aspect-ratio: auto; }
.image-caption .image-full--43     { aspect-ratio: var(--ratio-project-card); }
.image-caption .image-full--wide   { aspect-ratio: 1256 / 739; }

/* Top, not centre — but only inside a pair. A square beside a portrait has to
   start on the same edge or they read as two loose objects rather than two
   views of one thing. Said on the figure itself it also stopped a full-width
   figure from being full width: in a column, `align-self: flex-start` shrinks
   an item to its content. That is what took the conference photograph off the
   sheet's width. */
.double-images > .image-caption { align-self: flex-start; }

.image-full--portrait { aspect-ratio: 610 / 813; }

/* ── Project-Crosslink-footer (174:27414) ────────────────────────────────── */

.crosslink-footer {
  width: 640px;
  max-width: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-32);
  /* No margin. The sheet already carries 128 below its last section, so the
     space between the last thing you read and this footer is 128 — the same
     as on the home, About and Approach. Adding another 128 here made it 256
     and this was the only page out of step. */
  padding: 128px var(--space-32);
}

.crosslink-title {
  margin: 0;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  font-family: var(--font-headings);
  font-weight: var(--weight-book);
  font-size: var(--h4-size);
  line-height: var(--h4-line);
  color: var(--text-tertiary);
  text-align: center;
}

/* Projects (174:27334) */
.crosslink-projects {
  display: flex;
  gap: 40px;
  align-items: center;
  justify-content: center;
  padding: 0 var(--space-16);
  width: 100%;
}

/* Held at the resting height, so a card that grows grows around the row's
   middle line rather than pushing the row itself taller. */
.crosslink-group { display: flex; gap: var(--space-8); align-items: center; height: 125px; }

.crosslink-card {
  display: block;
  border-radius: 32px;
  background: var(--primary-black);
  overflow: hidden;
  flex: none;
}

/* One height per card drives both dimensions, so growing it on hover is one
   number and the shape cannot drift. 4/3 for the big one, square for the small,
   the same pair as everywhere else. */
.crosslink-card--big   { --card-h: 123px; height: var(--card-h); width: calc(var(--card-h) * 4 / 3); }
.crosslink-card--small { --card-h: 125px; height: var(--card-h); width: var(--card-h); }
.crosslink-card img { width: 100%; height: 100%; object-fit: cover; }

.crosslink-sep {
  width: 1px;
  height: 95px;
  flex: none;
  background: var(--glassy-dark-400);
}

@media (max-width: 700px) {
  /* 174:28205 is 305 tall against the desktop's 433, so the padding comes in */
  /* 174:28205 : 32 sur les côtés, 64 en haut et en bas, et 40 entre les deux
     groupes — le même écart que sur desktop. */
  .crosslink-footer { padding: 64px var(--space-32); }
  .crosslink-projects { padding: 0; }

  /* Les cartes font 123 de haut, et le groupe fait exactement la leur. Pas de
     grossissement au survol : c'est lui qui, dans la barre du haut, faisait
     glisser la carte hors du pointeur et mangeait le clic. Sur un écran
     tactile il ne se produit de toute façon jamais. */
  .crosslink-card--big   { --card-h: 123px; }
  .crosslink-card--small { --card-h: 123px; }
  .crosslink-group { height: 123px; }
  .crosslink-sep { height: 95px; }
}

/* ── Arrival ──────────────────────────────────────────────────────────────

   The sheet rises into place. On the finished site the visitor comes from the
   home page, where this project is a small card, and the sheet is far wider —
   so it should read as arriving over that content rather than fading in.

   Deliberately restrained: 28px of travel, no scale, no bounce. The curve does
   the work — expo-out spends most of its time decelerating, which is what makes
   a short movement feel like it settles rather than stops. */

/* It travels 80 rather than 28, so the sheet reads as arriving from below and
   settling under the bar rather than just fading up.

   It cannot overlap the navbar, by construction: the movement only ever goes
   from BELOW its resting place towards it, never past it. 80 is positive, the
   end state is 0, and nothing in between is negative. That is the guarantee,
   not a value chosen to be safe. */
@keyframes project-body-rise {
  from { opacity: 0; transform: translateY(80px); }
  to   { opacity: 1; transform: translateY(0); }
}

.project-body {
  /* `backwards` for the same reason as the nav centre, and because the end
     state is simply the element's natural position — nothing to hold.
     No `will-change`: promoting a sheet this tall to its own layer was more
     likely to cost a repaint than save one. */
  animation: project-body-rise 900ms cubic-bezier(0.16, 1, 0.3, 1) 80ms backwards;
}

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

/* A filled slot is a <video> and must fill its frame exactly as the still it
   replaces did; an empty one keeps its faint placeholder so the page still
   reads while Flora finds the last clips. */
video.video-slot {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: none;
  /* 12 like every other piece of media on the page. The panels that need
     something else — the white sheet at 20, the AI clip at 8, the ones that
     run edge to edge at 0 — say so themselves and still win, because they are
     more specific than this. */
  border-radius: var(--radius-12);
}

/* Images inside a rounded frame take the frame's own value. NOT .mockup-panel
   img: those phone mockups carry their own 16 (172:26130), and listing them
   here overrode it with the panel's 12 — same specificity, written later. */
.feature-media img,
.crosslink-card img,
.nav-project img { border-radius: inherit; }

/* The same growth the project nav's thumbnails make — these are the same kind
   of object, a row of small covers, so they answer the same way. A fifth
   larger in both dimensions, pushing its neighbours apart rather than covering
   them, so every gap in the row stays 8. The image zooms inside the frame on
   top of that. */
.crosslink-card {
  transition: width var(--hover-travel) var(--hover-ease),
              height var(--hover-travel) var(--hover-ease);
}

.crosslink-card img {
  transition: transform var(--hover-travel) var(--hover-ease);
  backface-visibility: hidden;
}

.crosslink-card--big:hover,
.crosslink-card--big:focus-visible     { --card-h: 148px; }
.crosslink-card--small:hover,
.crosslink-card--small:focus-visible   { --card-h: 150px; }

.crosslink-card:hover img,
.crosslink-card:focus-visible img { transform: scale(1.075); }

@media (prefers-reduced-motion: reduce) {
  .crosslink-card,
  .crosslink-card img { transition: none; }
  .crosslink-card--big:hover   { --card-h: 123px; }
  .crosslink-card--small:hover { --card-h: 125px; }
  .crosslink-card:hover img { transform: none; }
}

/* ── Rendering cost ───────────────────────────────────────────────────────

   The page is 26,000px tall. Without this the browser lays out and paints all
   of it on every frame, which is what makes a long page feel heavy however
   light its files are.

   `content-visibility: auto` lets it skip the work for anything off screen,
   and `contain-intrinsic-size` gives it a height to assume in the meantime so
   the scrollbar stays honest and nothing jumps as you go.

   Not applied to the header: it is the first thing painted and skipping work
   there would delay it rather than save anything. */

.project-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 2000px;
}

/* ── The body on a phone ──────────────────────────────────────────────────

   Read off Mobile-Project-iteration-x (140:19624). Its shape in one line: the
   sheet is 361 wide inside a 393 screen, and everything in it is 361 too — une
   seule marge, celle de la page, et aucun retrait à l'intérieur. Elle en avait
   un de 16 en plus, qui ramenait tout à 329 : c'est ce que Flora a corrigé dans
   la maquette en passant chaque bloc à `px: 0`.

   The other number that changes everywhere is the distance between blocks:
   64 here against desktop's 128, measured across Overview at every join
   (1684→1748, 2164→2228, 3236→3300, 3844→3908) and between the anchors
   themselves (4129.9→4193.9).

   Everything that sits in a row on a desktop stacks, and each pairing keeps
   the gap the frame gives it rather than one value applied to all of them. */

@media (max-width: 700px) {

  .project-body { padding-bottom: 64px; }

  /* 174:28063: the title block takes H2 here, 24 on 32, where a wide screen
     gives it 28 on 36. Both its lines share it — the name and the sentence
     under it are one text in the component, not a heading and a subheading. */
  .project-title {
    font-size: var(--h2-size);
    line-height: var(--h2-line);
    letter-spacing: -0.48px;
  }

  /* Aucun retrait ici. Dans la maquette (174:28049 et tous ses enfants) chaque
     bloc est à x=0 sur 361 de large, du haut de la page au bas : la seule marge
     est celle de la page, portée par `Body+Footer` à x=16.

     Le retrait de 16 écrit ici s'ajoutait à celui-là et ramenait le contenu à
     329. C'est ce décalage de 32 qui rendait toutes les proportions écrites
     plus bas fausses : elles avaient été relevées sur une maquette à 329. */
  .project-section { gap: 64px; padding: 0; }

  /* Full width, and its own 16 rather than the desktop column's 576. */
  .textsection { width: 100%; margin: 0; padding: var(--space-16) 0; }

  /* The eight blocks of the narration. 640 wide with no max-width, inside a
     329 column: they were running 311px off the side of the screen, which is
     what "the texts are enormous and cut" was. The frame gives this block the
     full sheet and lets `Project-text` inside it carry the 16 (174:28086). */
  .project-big-text { width: 100%; padding: 0; }

  /* And the text inside it keeps only its vertical 16. In the frame the block
     is the full sheet and `Project-text` carries the inset (174:28086); here
     the section already carries it, so the block's own horizontal padding was
     landing on top of it. The column was 297 rather than 329 — a text set at
     24 breaks a word earlier in it and reads bigger for it, which is what was
     wrong with these blocks and not their size. */
  .project-text { padding: var(--space-16) 0; }

  /* 584 wide, pinned 23 from a corner that is 329 wide here. */
  .image-full-label { position: static; width: 100%; padding: var(--space-16); }

  /* One clip carried a hard 949 from its desktop frame. */
  .video-extension .video-slot { width: 100%; height: 100%; }

  /* 174:28070: rows 329 wide, 32 apart, where desktop leaves 24. */
  .meta-rows { gap: var(--space-32); }

  /* 174:28090: the two cards stack, 16 apart. */
  .problem-solution { flex-direction: column; gap: var(--space-16); }
  .ps-card { width: 100%; }
  /* The desktop limit exists to break the title after "switching" in a 610
     column; at 329 the line breaks where it must anyway. */
  .ps-description p { max-width: none; }

  /* 174:28101: 12 apart, the project's own gap. */
  .double-images { flex-direction: column; gap: var(--space-project-gap); }
  /* `flex: none` et pas seulement la largeur. Sur desktop les deux blocs se
     partagent la rangée avec `flex: 1 1 0` ; empilés en colonne, cette base de
     0 devient une hauteur de 0 — et comme elle rend la hauteur définie, elle
     l'emporte sur la proportion du bloc. Les deux disparaissaient. */
  .double-images > * { flex: none; width: 100%; }

  /* 174:28112: the text sits above its media, 16 apart, and each takes the
     full width instead of the half desktop gives them. */
  .feature-row { flex-direction: column; gap: var(--space-16); align-items: stretch; }
  .feature-row > * { flex: none; width: 100%; }

  /* 174:28121: the screenshot sits 12 from the panel's edge, not the 54 and 64
     desktop needs to keep a 1232 frame from swallowing it. The panel's shape
     does not change — 329 x 196.8 is the same 5:3. */
  .issue-panel { padding: var(--space-project-gap); }

  /* The clips are not all treated the same way in the mobile frame, and that is
     the point of them: three sit inside their panel with 12 around them, four
     are scaled up and cropped by it. Reading each one's own numbers:

       Video-extension     (174:28105) 329x361    clip 305x191 at 12, 84.9  → inside
       Video-issue-list    (174:28126) 329x301.6  clip 305x174 at 12, 64    → inside
       Video-archives      (174:28169) 329x329    clip 305x189 at 12, 70    → inside
       Video-issues-list   (174:28109) 329x329    clip 584x361 at -127, -16 → cropped
       Video-extension-fts (174:28115) 329x438.7  clip 414x552 at -43, -40  → cropped
       Video-itx-AI        (174:28153) 329x548.3  clip 450x600 at -60, -22  → cropped
       Video (ai-full)     (174:28156) 329x438.7  clip 713x406 at -397, 16  → cropped

     A panel that crops has no padding: the clip fills it and the panel's own
     shape decides what is kept. A panel that contains keeps its 12. */

  .video-panel { padding: 0; }

  .video-extension,
  .video-issue-list,
  .video-archives { padding: var(--space-project-gap); }

  /* Toutes relevées à nouveau sur la maquette à 361 : les précédentes avaient
     été prises quand le corps faisait 329, et une proportion ne se transpose
     pas d'une largeur à l'autre. Nœuds : 178:28555, 174:28126, 174:28169,
     174:28109, 174:28156, 174:28153, 174:28115. */
  .video-extension  { aspect-ratio: 1 / 1; }
  .video-issue-list { aspect-ratio: 361 / 319.81; }
  .video-archives   { aspect-ratio: 1 / 1; }

  .video-issues-list { aspect-ratio: 1 / 1; }
  .ai-video-wide     { aspect-ratio: 3 / 4; }
  .ai-video-panel    { aspect-ratio: 361 / 601.67; }
  .feature-media     { aspect-ratio: 3 / 4; }

  /* Cropped means cropped from the middle: the frame draws each clip larger
     than its panel and centred on what matters, which is what `cover` does. */
  .video-issues-list .video-slot,
  .ai-video-wide .video-slot,
  .ai-video-panel .video-slot,
  .feature-media .video-slot,
  .feature-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
  }

  /* 174:28198: the two illustrations stack, 16 apart. */
  /* 174:28203 : la légende est à 24 des illustrations, comme sur desktop.
     Ce sont les deux illustrations entre elles qui sont à 16. */
  .archives-illustrations { gap: var(--space-24); }
  .archives-pair { flex-direction: column; gap: var(--space-16); }
  /* Même raison que .double-images : la base de 0 du desktop devient une
     hauteur de 0 une fois empilé. Ces deux-là ne portent que des vidéos en
     position absolue, donc rien à l'intérieur ne les retient — elles
     disparaissaient entièrement. */
  /* `> div` et pas `> *` : la règle desktop vise `.archives-pair > div`, soit
     une classe et un type, ce qu'un `*` ne rattrape pas — elle gagnait quel que
     soit l'ordre des fichiers. C'est le même piège que les légendes d'images. */
  .archives-pair > div { flex: none; width: 100%; }

  /* 174:28140: the panel turns portrait — 329 x 514.66 — and the phones keep
     their own size inside it rather than sharing the width five ways, which at
     this scale would leave them 57px across. The row runs off the edge and
     scrolls, exactly as the 1163-wide row in the frame does inside its 329. */
  /* 309:43546 : 361 x 613.27, avec 32 sur les côtés et 64 en haut et en bas.
     Les téléphones gardent leur taille — 223.2 de large — et la rangée déborde
     du cadre plutôt que de partager la largeur à cinq, ce qui les laisserait à
     57. */
  .mockup-panel {
    aspect-ratio: 361 / 613.27;
    padding: var(--space-64) var(--space-32);
    justify-content: flex-start;
  }

  .mockup-row {
    height: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    touch-action: pan-x;
    scrollbar-width: none;
  }

  .mockup-row::-webkit-scrollbar { display: none; }

  .mockup-row img {
    flex: none;
    height: 100%;
    width: auto;
  }
}

/* ── PropertyGRID ─────────────────────────────────────────────────────────

   Two panels this project has and Iteration X does not. Everything else it
   uses is already here — the sheet, the cover, the introduction column, the
   full-width frames, the pairs, the Problem/Solution cards — which is the
   point of building the second case study on the first one's bones. */

/* 309:43458: a 610 x 813 panel on near-black, the clip 477 x 638 in the
   middle of it. The inset is the panel's, not the clip's: the recording is
   left at its own size and the frame gives it room. */
.pg-video-map {
  aspect-ratio: 610 / 813;
  background: #001310;
  padding: 0;
}

.pg-video-map .video-slot {
  width: 78.2%;          /* 477 of 610 */
  height: auto;
  border-radius: var(--radius-12);
}

/* 309:43560: four phones on #d5eee9, 128 above and below and 34 at the sides,
   16 between them. Same behaviour as Iteration X's row — each phone takes an
   equal share and keeps its own proportion — only the colour and the count
   differ. */
/* 309:43560 — and this is a standing rule, not a one-off:

   A MOCKUP PANEL HOLDING FOUR PHONES IS INSET BY 64, NOT 32.

   Five phones take 12 (Iteration X); four take 64. Fewer phones means each is
   wider, and the frame answers by giving them more room to stand in rather
   than letting them run to the edges.

   The rest falls out of it. At 64 a side the row has 1104, and four phones at
   264 with 16 between them come to exactly 1104 — so they fill the width, and
   their own proportion gives the height. Nothing here needs to be measured
   twice. */
.pg-mockup-panel {
  background: #d5eee9;
  padding: var(--space-64);
  aspect-ratio: var(--ratio-project-image);
}

/* The phones no longer share the row: their width is capped and the group
   centres in what is left. 220.8 each, 16 apart, four of them come to 931.2
   inside a 1104 row — which leaves the 86.4 the frame puts on either side.

   `flex: 0 1` rather than `0 0`: capped, but still able to give ground if the
   panel is ever narrower than the four of them need. */
.pg-mockup-panel .mockup-row {
  gap: var(--space-16);
  justify-content: center;
}

.pg-mockup-panel .mockup-row img {
  flex: 0 1 220.8px;
  max-width: 220.8px;
}

/* 309:50738: the property page shown twice at once — the desktop one stacked
   down a 892.83 column and the mobile beside it in 307. Both are proportions of
   the 1232 they sit in rather than fixed widths, so the pair holds together at
   any sheet width: 72.47%, a 2.61% gutter, 24.92%.

   Each well is the same #e5e5e1 and clips what it holds; the screenshots run
   edge to edge inside their inset and butt against each other, which is how the
   frame stacks them. */
.pg-property-page {
  display: flex;
  /* Stretch, not start: the right column takes the left one's height (309:50743
     is exactly as tall as 309:50739) and the mobile well below the clip fills
     whatever is left of it. */
  align-items: stretch;
  /* 12, the project's own gap, everywhere in this block — between the columns
     and between the two wells inside the right one. It was 32 and 24, measured
     off an earlier version of the frame. */
  gap: var(--space-project-gap);
  width: 100%;
}

.pg-pp-desktop,
.pg-pp-mobile,
.pg-pp-video {
  background: #e5e5e1;
  border-radius: var(--radius-12);
  overflow: hidden;
}

.pg-pp-desktop {
  /* Takes whatever the fixed right column leaves: 912.83 of 1232 at the width
     the frame is drawn at. */
  flex: 1 1 0;
  min-width: 0;
  padding: 6.35% 4.93% 0;   /* 58 and 45 of 912.83 */
}

.pg-pp-side {
  flex: 0 0 24.92%;
  display: flex;
  flex-direction: column;
  gap: var(--space-project-gap);
}

.pg-pp-video {
  aspect-ratio: 307 / 569;
  padding: 13%;           /* 40 of 307 */
}

.pg-pp-video .video-slot {
  width: 100%;
  height: auto;
  border-radius: var(--radius-16);
}

/* 309:50746: flex-1 inside the column, clipping what does not fit. The two
   screenshots run to 2842 together against the 2447 the column leaves them —
   the well is meant to cut them, and to end level with the desktop block
   beside it. */
.pg-pp-mobile {
  flex: 1 1 0;
  min-height: 0;
  padding: 20.8% 13.36% 0;   /* 64 and 41 of 307 */
}

.pg-pp-desktop img,
.pg-pp-mobile img {
  width: 100%;
  height: auto;
  display: block;
}

@media (max-width: 700px) {
  /* The two columns stop being columns: at 329 the desktop screenshots would be
     unreadable beside anything. */
  .pg-property-page { flex-direction: column; gap: var(--space-project-gap); }
  .pg-pp-desktop,
  .pg-pp-side { flex: none; width: 100%; }
}

/* 316:50797: the same near-black well as the map clip, but the recording fills
   it edge to edge rather than sitting inside it. Two of them side by side. */
.pg-video-well {
  aspect-ratio: 610 / 813;
  background: #001310;
  padding: 0;
}

.pg-video-well .video-slot {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-12);
}

/* 316:54108 and 316:54109: this pair carries its own grounds rather than the
   dark and light of Iteration X's — the problem on a cool grey, the solution on
   the same mint the phone row uses. */
.pg-ps-problem  { background: #e7e5ea; }
.pg-ps-solution { background: #d5eee9; }
/* The two labels are not the same weight in the frame, and that is the point:
   the problem is stated in grey and the solution in near-black (316:54108 uses
   text/main-pages/tertiary, 316:54109 uses text/project-pages/primary). One is
   the situation, the other is the answer. I had given them the same colour. */
.pg-ps-problem .ps-type  { color: var(--text-tertiary); }
.pg-ps-solution .ps-type { color: var(--text-project-primary); }
.pg-ps-problem .ps-description p,
.pg-ps-solution .ps-description p { color: var(--text-secondary-dark); }

/* Shown whole: no ratio, no crop, the image sets its own height.

   For anything that is read rather than glanced at — a flow diagram, a card
   system laid out in full — a frame is the wrong tool. A forced ratio plus
   `object-fit: cover` always trims whatever does not match it exactly, and
   what it trims here is the argument.

   The card system landed in `--tall`, which carries a LANDSCAPE 2432/1634
   inherited from Iteration X, while this image is portrait at 0.75. The frame
   was cutting away most of its height. */
.image-full--whole { aspect-ratio: auto; }
.image-full--whole img { position: static; width: 100%; height: auto; }

/* Square frame, whole image. `cover` fills the frame by scaling the picture up
   until the shorter side reaches the edge and cropping the rest — which on a
   1194x1208 file in a 610 square means enlarging it and shaving the sides.
   Small in numbers, plainly visible on a map: the zoom level is the subject.

   `contain` keeps the frame square and fits the picture inside it whole. */
.image-full--fit img { object-fit: contain; }

/* 316:50824: the same two-column row Iteration X uses, with its own proportions.

   The media is square here, not the 610x813 portrait that row carries by
   default — and it has no dark ground behind it, because the image fills it.

   The text is narrower than it looks: the column is 610 and the frame insets it
   by 64 on each side, leaving 482 for the text block and 450 for the words. The
   textsection was taking the full half and reading as a paragraph rather than a
   caption beside a picture. */
.pg-feature .feature-media {
  aspect-ratio: 1 / 1;
  background: none;
  border-radius: var(--radius-12);
}

/* The clip fills the square the same way a still would — the rule that does
   that is written for images and for `.video-slot` alike, so this needs no
   second version of it. */

.pg-feature .textsection {
  width: auto;
  /* Padding, not margin. A flex item's basis is its box; a margin sits OUTSIDE
     that box and adds to it, so 64 a side turned a 610 column into 738 and
     pushed the text off the screen. Padding eats into the 610 instead.

     80 = the frame's own 64 of inset plus the 16 this block already carries,
     which lands the words on the 450 the Figma gives them. */
  padding-left: calc(var(--space-64) + var(--space-16));
  padding-right: calc(var(--space-64) + var(--space-16));
}


/* PropertyGRID's descriptions are written as the frame writes them, one
   paragraph per line, so they need no width to break at. Iteration X's cap
   exists to force a break after "switching" in its own copy and would push
   these onto a third line. */
.pg-ps-problem .ps-description p,
.pg-ps-solution .ps-description p { max-width: none; }


/* 316:54107: the image slot in this pair is square — aspect-[610/610] — where
   Iteration X lets its own picture set the height. With the card's titles
   above it that is what makes the 777 the frame draws. */
.pg-ps-problem .ps-image,
.pg-ps-solution .ps-image {
  aspect-ratio: 1 / 1;
  object-fit: cover;
}


/* 294:34534: this pair puts the picture BEHIND the words rather than under
   them. The image fills the card and the titles sit on top of it, which is why
   its text is light — it is reading against a photograph, not against a panel.

   The other pair on this page (316:54107) does the opposite: titles, then a
   square picture beneath. Same component, two arrangements, and telling them
   apart is the whole difference between the two sections. */
.pg-ps-bg {
  position: relative;
  aspect-ratio: 610 / 777;
  justify-content: flex-start;
}

.pg-ps-bg .ps-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  margin: 0;
  aspect-ratio: auto;
}

.pg-ps-bg .ps-titles { position: relative; z-index: 1; }

/* Light on a photograph. The old positioning names itself in white over its
   own image; the new one steps back to 60% white and lets its description
   carry the weight instead. */
.pg-ps-bg--old .ps-type          { color: var(--primary-white); }
.pg-ps-bg--old .ps-description p { color: var(--text-secondary-dark); }
.pg-ps-bg--new .ps-type          { color: var(--glassy-white-300, rgba(255, 255, 255, 0.6)); }
.pg-ps-bg--new .ps-description p { color: var(--primary-white); }

/* 300:37438: the second square of the Brand pair holds a clip, not a still —
   its slot carries no image at all in the frame, which is what gave it away.
   Square like its neighbour, and the clip fills it. */
.pg-video-square {
  aspect-ratio: 1 / 1;
  padding: 0;
  background: none;
}

.pg-video-square .video-slot {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-12);
}
