/* =====================================================================
   PLAYGROUND — "All" panel

   A bento of one representative piece per category. Own namespace
   (.pg-all) for the same reason the Product Stack has one: the existing
   .pg-panel--* rules carry a long tail of !important overrides, and this
   panel needs none of them.
   ===================================================================== */

#perspective .pg-gallery__stage[data-active="all"] { height: clamp(500px, 44vw, 690px); }

#perspective .pg-panel--all {
  display: grid;
  /* Columns from the comp: a narrow portrait, a wide film, then a column
     that splits into one image over a pair of tracks. */
  grid-template-columns: 0.75fr 1.27fr 1.2fr;
  grid-template-rows: 1.03fr 1fr;
}

#perspective .pg-all--photo  { grid-area: photo; }
#perspective .pg-all--film   { grid-area: film; }
#perspective .pg-all--motion { grid-area: motion; }

/* The two tracks share the bottom-right cell as their own row. */
#perspective .pg-panel--all::after { content: none; }

/* tokens.css deliberately strips `.pg-panel > button` of background and
   radius — media surfaces are not controls, and these tiles ARE that
   rule's target. Everything the tiles genuinely need back is restated
   here at matching weight rather than by loosening the global rule,
   which still protects every other panel button. */
:root body #perspective .pg-all[class][class][class] {
  border-radius: var(--radius-media, 16px) !important;
  background: var(--surface-sunken, #e4e4e4) !important;
}
:root body #perspective :is(.pg-all__chip, .pg-all__play)[class][class][class] {
  border-radius: 999px;
}
:root body #perspective .pg-all__chip[class][class][class] {
  background: rgba(14, 16, 18, .52);
  -webkit-backdrop-filter: blur(14px) saturate(1.2) !important;
  backdrop-filter: blur(14px) saturate(1.2) !important;
  color: #fff;
}
:root body #perspective .pg-all__play[class][class][class] {
  background: rgba(18, 20, 22, .44);
  -webkit-backdrop-filter: blur(16px) saturate(1.25) !important;
  backdrop-filter: blur(16px) saturate(1.25) !important;
  color: #fff;
}

#perspective .pg-all {
  position: relative;
  overflow: hidden;
  min-width: 0;
  text-align: left;
  /* The panel's own entrance animation writes opacity/transform, so the
     hover transition is scoped to the properties it does not touch. */
  transition: filter var(--dur-3) var(--ease-out);
}

#perspective .pg-all > img,
#perspective .pg-all > video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-3) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  #perspective .pg-all:hover > img,
  #perspective .pg-all:hover > video { transform: scale(1.035); }
  #perspective .pg-all:hover .pg-all__chip { opacity: 1; transform: none; }
  
}

#perspective .pg-all:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 3px;
}

/* ---- Category chip: the tile's destination, revealed on approach ---- */
#perspective .pg-all__chip {
  position: absolute;
  z-index: 3;
  top: clamp(12px, 1vw, 16px);
  left: clamp(12px, 1vw, 16px);
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(14, 16, 18, .52);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  color: #fff;
  font-weight: 500;
  letter-spacing: -.01em;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--dur-2) ease, transform var(--dur-2) var(--ease-out);
}
/* Touch has no hover to reveal it, so it just stays visible there. */
@media (hover: none) {
  #perspective .pg-all__chip { opacity: 1; transform: none; }
}

/* ---- Play affordance on the film tile ------------------------------ */
#perspective .pg-all__play {
  position: absolute;
  z-index: 3;
  top: 50%;
  left: 50%;
  display: grid;
  place-items: center;
  width: clamp(52px, 4.6vw, 68px);
  height: clamp(52px, 4.6vw, 68px);
  background: rgba(18, 20, 22, .42);
  -webkit-backdrop-filter: blur(16px) saturate(1.25);
  backdrop-filter: blur(16px) saturate(1.25);
  color: #fff;
  transform: translate(-50%, -50%);
  transition: transform var(--dur-3) var(--ease-out), background var(--dur-2) ease;
}
#perspective .pg-all__play i,
#perspective .pg-all__play svg { width: 42%; height: 42%; }
@media (hover: hover) and (pointer: fine) {
  #perspective .pg-all--film:hover .pg-all__play {
    transform: translate(-50%, -50%) scale(1.08);
    background: rgba(18, 20, 22, .58);
  }
}

/* ---- Track cards ----------------------------------------------------
   These are real .pg-track buttons, identical to the Music panel's, so
   the gradient, the ripple canvas, the play/pause knob and the playing
   animation all come from the existing component. Only the things that
   are wrong at a THIRD of that panel's card height are restated here:
   the giant index numeral and the 28px insets were drawn for a
   full-height card. */
#perspective .pg-panel--all .pg-track__number {
  top: clamp(10px, 1vw, 16px);
  right: clamp(12px, 1.1vw, 18px);
  font-size: clamp(2.4rem, 3.6vw, 3.6rem);
}
#perspective .pg-panel--all .pg-track__meta {
  right: clamp(12px, 1.1vw, 18px);
  bottom: clamp(12px, 1.1vw, 18px);
  left: clamp(12px, 1.1vw, 18px);
}
#perspective .pg-panel--all .pg-track__meta small { margin-top: 4px; font-size: .75rem; }
#perspective .pg-panel--all .pg-track__play { width: clamp(44px, 3.8vw, 58px); height: clamp(44px, 3.8vw, 58px); }

/* ---- Tablet: the tracks move under the pair above them -------------- */
@media (max-width: 1099px) {
  #perspective .pg-gallery__stage[data-active="all"] { height: clamp(520px, 62vw, 640px); }
  #perspective .pg-panel--all {
    grid-template-columns: 0.86fr 1.14fr;
    grid-template-rows: 1.25fr 1fr;
  }
    }

/* ---- Phone: one column, natural height ------------------------------
   The stage is absolutely positioned and height-locked on desktop; on
   phones the existing rules already release it to flow, so the panel
   just becomes a stack with real aspect ratios per tile. */
@media (max-width: 900px) {
  #perspective .pg-gallery__stage[data-active="all"] {
    height: auto !important;
    min-height: 0 !important;
  }
  #perspective .pg-panel--all {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    grid-template-areas:
      "film"
      "photo"
      "motion"
      "tracks";
  }
  #perspective .pg-all--film   { aspect-ratio: 16 / 10; }
  #perspective .pg-all--photo  { aspect-ratio: 4 / 5; }
  #perspective .pg-all--motion { aspect-ratio: 16 / 9; }
  /* Out of the shared cell and back into a plain two-up row. */
  #perspective .pg-all--track-one,
  #perspective .pg-all--track-two {
    margin: 0;
    aspect-ratio: 1 / 1;
  }
    #perspective .pg-panel--all { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    #perspective .pg-all__chip { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  #perspective .pg-all,
#perspective .pg-all > img,
#perspective .pg-all > video,
#perspective .pg-all__chip,
#perspective .pg-all__play { transition: none; }
}

/* =====================================================================
   Mobile filter strip

   The buttons were stretching to the container's full width, so each
   category landed on its own line and the filter row read as a menu
   rather than a set of tabs — a fifth entry only made it longer. They
   size to their label here and share one scrollable row; the existing
   centre-in-strip behaviour in playground-media.js was already written
   for exactly this.
   ===================================================================== */
/* Retired: the phone treatment for this row is now the shared filter
   component in css/editorial-polish.css, which puts Selected Work,
   Services and Playground on one full-width line with the buttons
   splitting the width equally.

   What stood here was the older scrolling strip — nowrap with a gap, an
   overflow-x row bleeding to the page edges, and a min-width/padding
   floor on each chip. It had to go rather than be out-specified: the
   group ended `#perspective #pgTabAll, #perspective #pgTabAerial`, and a
   two-ID selector outranks any sane container-scoped rule, so those two
   tabs alone kept a 64px floor and 16px side padding while the other
   three took the shared sizing. That is exactly the uneven row it was
   originally written to fix. */
@media (max-width: 900px) {
  #perspective .pg-gallery__tabs::-webkit-scrollbar { display: none; }
}

/* =====================================================================
   Expanded state

   expandMedia lifts the tile out to <body> so it can be FLIPped against
   the viewport. Every rule above is scoped to #perspective and therefore
   stops matching the moment that happens — which left the video at its
   tile size in the top-left of a full-screen box. These restate only
   what the expanded tile needs, at body level.
   ===================================================================== */
:root body .pg-all.pg-media-expanded {
  display: block;
  overflow: hidden;
  background: #000 !important;
}
:root body .pg-all.pg-media-expanded > img,
:root body .pg-all.pg-media-expanded > video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: none;
}
/* The grid affordances are meaningless full screen: the video has native
   controls there, and the category is no longer one tile among five. */
:root body .pg-all.pg-media-expanded .pg-all__chip,
:root body .pg-all.pg-media-expanded .pg-all__play {
  display: none;
}

/* =====================================================================
   Travel grid — neutralise leftover FLIP styles

   The lightbox FLIPs the photo itself between its tile and full screen,
   and the animation library commits the final transform to the node as
   an inline style when it stops. That commit lands on an unpredictable
   frame — clearing it in JS after the close lost the race even with
   three passes and a 200ms timeout, leaving one thumbnail parked off its
   cell at 0.64 scale, which reads as an empty grey slot.

   Declaring it instead removes the race entirely. The rule only applies
   while the lightbox is CLOSED, and during a flip the photo lives inside
   .pg-preview rather than its figure, so this can never fight a running
   animation.
   ===================================================================== */
html:not(.pg-travel-preview-open) #perspective .pg-panel--travel > figure > img {
  transform: none !important;
  /* filter and opacity join the list now that paging between photos
     dissolves rather than slides: the outgoing frame is left at
     opacity 0, and that used to ride back into the grid as a blank
     cell. Every property the preview animates is reset here, so no
     future tweak to the transition can leak into the thumbnails. */
  filter: none !important;
  opacity: 1 !important;
}

/* =====================================================================
   Playground chrome — recede on open, reform on close

   With the lightbox filling the screen, the section's own furniture
   (title, filter row, summary) has no business still sitting there. It
   used to simply be covered; now it steps back as the photo grows and
   reassembles as the photo returns, so the whole section reads as one
   gesture rather than an image scaling over a static page.

   Only the chrome moves. The tiles are left alone on purpose — the panel
   swap animates them with inline styles, and a competing transition here
   would fight it.
   ===================================================================== */
#perspective #perspectiveTitle,
#perspective .pg-gallery__navrow {
  transition: opacity var(--dur-3) var(--ease-out) var(--chrome-delay, 0s),
    transform var(--dur-3) var(--ease-out) var(--chrome-delay, 0s),
    filter var(--dur-3) var(--ease-out) var(--chrome-delay, 0s);
}
/* !important on the transform only: the reveal script leaves an inline
   `transform: none` on these after the section's entrance, which would
   otherwise pin them in place and let only the opacity move. Scoped to
   .pg-chrome-out so it applies solely while the lightbox is up and can
   never fight that entrance. */
#perspective.pg-chrome-out #perspectiveTitle {
  opacity: 0 !important;
  transform: translate3d(0, -14px, 0) !important;
  filter: blur(6px);
}
#perspective.pg-chrome-out .pg-gallery__navrow {
  opacity: 0 !important;
  transform: translate3d(0, -10px, 0) !important;
  filter: blur(5px);
}
/* Reforming, the title leads and the filters follow a beat later — the
   same order they read in. Coming apart, they leave together. */
#perspective:not(.pg-chrome-out) .pg-gallery__navrow { --chrome-delay: .07s; }

@media (prefers-reduced-motion: reduce) {
  #perspective #perspectiveTitle,
  #perspective .pg-gallery__navrow { transition: none; }
  #perspective.pg-chrome-out #perspectiveTitle,
  #perspective.pg-chrome-out .pg-gallery__navrow { filter: none; transform: none; }
}

/* =====================================================================
   Section lead-in — match the rest of the page

   #perspective is a tall grid (it has to be, to give the pinned stage
   something to scroll through) and it was centring its single child
   vertically. That put the Playground title 313px below the section's
   top edge where every neighbouring section sits at 122-164px, which
   read as a large empty band above the heading.

   Starting the content at the top and giving the section a real
   padding-top puts the title on the same line as its neighbours. The
   sticky child keeps its own 88px, which is the clearance it needs from
   the nav once it pins — so the two are additive here and independent
   once pinned.
   ===================================================================== */
#perspective {
  /* align-items only. align-content: start collapses the grid ROW to its
     content height, which shrinks the sticky child's containing block
     from the section's full height down to ~871px — the stage then stops
     pinning and scrolls away with the page. The row must stay stretched;
     only the item inside it starts at the top. */
  align-items: start !important;
}

@media (max-width: 900px) {
  }

/* The lightbox rail's thumbnails are round buttons, but the <img> inside
   each one kept square corners and overhung the circle. Clipping on the
   button covers whatever it holds, now and later. */
#perspective .pg-preview__rail button {
  overflow: hidden;
}

/* =====================================================================
   Round affordances that are NOT the control element

   The pill rules in tokens.css key off the control itself — a <button>,
   .nav__talk, and a named list — so a round badge rendered as a <span>
   INSIDE a button is never matched, and the global square sweep leaves
   it a box. The FAQ's plus/minus was the visible case: a 38px square
   outline in a row of otherwise pill-shaped controls.

   Stated here rather than in tokens.css because the square sweep's top
   tier is id-scoped, so beating it needs both an id and a later sheet.
   ===================================================================== */
:root body #insights .accordion__icon[class][class][class] {
  border-radius: 999px;
}

/* =====================================================================
   Lightbox stage — let the inset define the box, not vw/dvh

   .pg-preview is position:fixed with inset:0 AND width:100vw /
   height:100dvh. Because top and height are both specified, the height
   wins over bottom:0 — so the moment 100dvh disagrees with the real
   viewport the overlay stops reaching the bottom and the page shows
   through as a white band under the photo. dvh is resolved lazily, which
   is why it only misses on the first open, before the browser has
   settled it.

   Dropping the explicit size makes the four insets define the box, so it
   is the viewport by construction — on any device, at any moment, with
   or without a scrollbar. It also removes the 100vw-vs-innerWidth
   scrollbar discrepancy the flip has to measure around.
   ===================================================================== */

/* =====================================================================
   Travel grid — its own composition

   The old arrangement (two full-height tiles on the left, a 2x2 block on
   the right) echoed the All tab's bento closely enough that the two tabs
   read as the same layout with different pictures.

   This is deliberately a different rhythm: a wide landscape opens the
   row, the one portrait asset in the set anchors the middle at full
   height, and the remaining frames sit around them in an uneven,
   editorial beat rather than a tidy quadrant. It also puts each photo in
   a slot that suits its own aspect — the portrait had been stuck in a
   small landscape cell, cropped to almost nothing.
   ===================================================================== */
/* An editorial mosaic, not an accordion. Nine photos sit in nine fixed
   cells of deliberately mixed size — one tall hero, near-square pairs, a
   wide strip for the panorama — laid on a 12-column grid over a row unit
   that scales with the viewport, so the whole mosaic is fluid without a
   single fixed pixel height. Cell proportions were picked to suit the
   photo each one holds: the library is mostly phone portraits, so tall
   and square cells outnumber landscape ones and nothing is cropped to a
   letterbox. Tiles never move, so a photo's cell IS its identity and the
   lightbox always morphs back into the tile it came from. */
#perspective .pg-gallery__stage[data-active="travel"] { height: clamp(540px, 66vh, 740px); }
#perspective .pg-panel--travel {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  /* Fractional rows, not a fixed row unit: the panel is absolutely
     positioned to fill a stage whose height is fixed by the pinned
     section, so the mosaic has to resolve INTO that box rather than grow
     past it. Twelve of each means one ratio controls the whole thing and
     every cell rescales together at any viewport. */
  grid-template-rows: repeat(12, minmax(0, 1fr)) !important;
  grid-auto-rows: 0;
  height: 100%;
}
#perspective .pg-panel--travel > figure {
  min-width: 0;
  min-height: 0;
}
#perspective .pg-panel--travel > figure > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* rows 1-4   t1 | t2 | t3
   rows 5-7   t1 | t4 | t5
   rows 8-10  t7 | t4 | t6
   rows 11-12 t8 t9 | t4 | t6      — tiles every cell, no holes, no overlap */
#perspective .pg-travel__t1 { grid-column: 1 / 5;   grid-row: 1 / 8; }
#perspective .pg-travel__t2 { grid-column: 5 / 9;    }
#perspective .pg-travel__t3 { grid-column: 9 / 13;   }
#perspective .pg-travel__t4 { grid-column: 5 / 9;    }
#perspective .pg-travel__t5 { grid-column: 9 / 13;   }
#perspective .pg-travel__t7 { grid-column: 1 / 5;   grid-row: 8 / 11; }
#perspective .pg-travel__t8 { grid-column: 1 / 3;   grid-row: 11 / 13; }
#perspective .pg-travel__t9 { grid-column: 3 / 5;   grid-row: 11 / 13; }

/* Framing belongs to the photo, not the cell. */

/* The title arrives on hover/focus only — the mosaic reads as photographs
   first, captions second. Touch has no hover, so it never shows there. */
#perspective .pg-panel--travel > figure::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 45%;
  background: linear-gradient(to top, rgba(0, 0, 0, .58), rgba(0, 0, 0, 0));
  opacity: 0;
  transition: opacity var(--dur-3) ease;
  pointer-events: none;
  z-index: 2;
}
#perspective .pg-travel__caption {
  position: absolute;
  left: clamp(10px, 1.1vw, 16px);
  right: clamp(10px, 1.1vw, 16px);
  bottom: clamp(9px, 1vw, 14px);
  z-index: 3;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transform: translateY(7px);
  transition: opacity var(--dur-3) ease, transform var(--dur-3) ease;
  pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
  #perspective .pg-panel--travel > figure:hover::after,
  #perspective .pg-panel--travel > figure:focus-visible::after { opacity: 1; }
  #perspective .pg-panel--travel > figure:hover .pg-travel__caption,
  #perspective .pg-panel--travel > figure:focus-visible .pg-travel__caption {
    opacity: 1;
    transform: none;
  }
  /* No scale on the photo: the rule that resets stale inline transforms on
     these thumbnails (see the top of this file) sets transform:none
     !important while the lightbox is closed, and a hover transform would
     have to fight it. The caption reveal carries the hover on its own. */
}

/* ---- Tablet (<=1100px): eight columns over the same twelve rows. The
   hero keeps the top-left, the panorama takes a wide strip on the bottom
   band, and the rest pair off. Every cell of the grid is filled. -------- */
@media (max-width: 1100px) {
  #perspective .pg-panel--travel {
    grid-template-columns: repeat(8, minmax(0, 1fr));
  }
  #perspective .pg-travel__t1 { grid-column: 1 / 5; grid-row: 1 / 7; }
  #perspective .pg-travel__t2 { grid-column: 5 / 9;  }
  #perspective .pg-travel__t3 { grid-column: 5 / 9;  }
  #perspective .pg-travel__t4 { grid-column: 1 / 3;  }
  #perspective .pg-travel__t5 { grid-column: 3 / 5;  }
        }

/* Below 900px the stage stops being a fixed-height pinned box and flows to
   the active panel's own height, so `height: 100%` has nothing to resolve
   against and the fractional rows would each grow to fit their content —
   the mosaic ballooned to ~2900px. An aspect-ratio gives the panel a
   definite height derived from its own fluid width instead, and the rows
   divide that. */
@media (max-width: 900px) {
  #perspective .pg-panel--travel {
    height: auto;
  }
}

/* ---- Phone (<=640px): four columns and a portrait canvas. All nine
   photos stay on screen — the stage scrolls here, so there is room to give
   the hero and the panorama full-width bands rather than dropping any. -- */
@media (max-width: 640px) {
  #perspective .pg-panel--travel {
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
    grid-template-rows: repeat(19, minmax(0, 1fr)) !important;
    aspect-ratio: 2 / 7;
  }
  #perspective .pg-travel__t1 { grid-column: 1 / 5; grid-row: 1 / 5 !important; }
  #perspective .pg-travel__t2 { grid-column: 1 / 3 !important; grid-row: 5 / 8 !important; }
  #perspective .pg-travel__t3 { grid-column: 3 / 5 !important; grid-row: 5 / 8 !important; }
  #perspective .pg-travel__t4 { grid-column: 1 / 3; grid-row: 8 / 12 !important; }
  #perspective .pg-travel__t5 { grid-column: 3 / 5; grid-row: 8 / 12 !important; }
  #perspective .pg-travel__t7 {  grid-row: 12 / 14 !important; }
  #perspective .pg-travel__t6 { grid-column: 1 / 3 !important; grid-row: 14 / 17 !important; }
  #perspective .pg-travel__t8 { grid-column: 3 / 5 !important; grid-row: 14 / 17 !important; }
  #perspective .pg-travel__t9 { grid-column: 1 / 5 !important; grid-row: 17 / 20 !important; }
}

@media (prefers-reduced-motion: reduce) {
  #perspective .pg-travel__caption,
  #perspective .pg-panel--travel > figure::after { transition: none !important; }
}

/* ---- One gutter across every Playground tab ---------------------------
   One value for all panels (and the aerial/reel pair), and it is the
   site's shared card gap: All and Travel had already picked up
   --grid-gap from the grid-rhythm pass while Photography and Music kept
   a 4px mosaic gutter, so switching tabs changed the spacing. Declared
   last with the same !important weight the bundle's per-breakpoint gaps
   use, so it wins everywhere. */
#perspective { --pg-gutter: var(--grid-gap, clamp(10px, .9vw, 16px)); }
#perspective .pg-gallery__stage .pg-panel,
#perspective .pg-gallery__stage .pg-panel--travel {
  gap: var(--pg-gutter) !important;
}
/* The All tab's two music cards share one grid area and split it with
   margins; half the gutter each keeps their seam at the same gutter. */
@media (min-width: 901px) {
    }

/* ---------------------------------------------------------------------
   Travel mosaic, refined

   Nine cells at stage height meant several photos were reduced to a
   sliver — a subject cropped down to a corner of itself. Six larger
   cells instead, each holding its subject centred, and the same gap the
   rest of the site's card grids use.
   ------------------------------------------------------------------ */
:root body #perspective .pg-panel--travel > figure > img {
  object-position: center;
}
@media (min-width: 1100px) {
  :root body #perspective .pg-travel__t1[class] { grid-column: 1 / 5;  grid-row: 1 / 8; }
  :root body #perspective .pg-travel__t2[class] { grid-column: 5 / 9;  grid-row: 1 / 6 !important; }
  :root body #perspective .pg-travel__t3[class] { grid-column: 9 / 13;  }
  :root body #perspective .pg-travel__t4[class] { grid-column: 5 / 9;  grid-row: 6 / 13 !important; }
  :root body #perspective .pg-travel__t5[class] { grid-column: 9 / 13;  }
  :root body #perspective .pg-travel__t6[class] { grid-column: 1 / 5 !important;  grid-row: 8 / 13 !important; }
  /* The last three stay in the lightbox's paging order, just not on the
     wall — six photographs at this size read better than nine slivers. */
  :root body #perspective .pg-travel__t7[class],
  :root body #perspective .pg-travel__t8[class],
  :root body #perspective .pg-travel__t9[class] { display: none; }
}
@media (min-width: 901px) and (max-width: 1099px) {
  :root body #perspective .pg-travel__t1[class] { grid-column: 1 / 5 !important; grid-row: 1 / 7 !important; }
  :root body #perspective .pg-travel__t2[class] { grid-column: 5 / 9 !important; grid-row: 1 / 4 !important; }
  :root body #perspective .pg-travel__t3[class] { grid-column: 5 / 9 !important; grid-row: 4 / 7 !important; }
  :root body #perspective .pg-travel__t4[class] { grid-column: 1 / 5 !important; grid-row: 7 / 13 !important; }
  :root body #perspective .pg-travel__t5[class] { grid-column: 5 / 9 !important; grid-row: 7 / 10 !important; }
  :root body #perspective .pg-travel__t6[class] { grid-column: 5 / 9 !important; grid-row: 10 / 13 !important; }
  :root body #perspective .pg-travel__t7[class],
  :root body #perspective .pg-travel__t8[class],
  :root body #perspective .pg-travel__t9[class] { display: none !important; }
}

/* ---------------------------------------------------------------------
   Travel hover: a readable caption and a plain fullscreen cue

   The caption was set at caption weight and size; it is the only label a
   photograph gets, so it carries a little more of both. The expand glyph
   is the affordance the tiles were missing — the cursor already said
   "zoom-in", but nothing on screen did.
   ------------------------------------------------------------------ */
:root body #perspective .pg-travel__caption[class] {
  font-weight: 600;
  letter-spacing: -.01em;
  text-shadow: 0 1px 12px rgba(0, 0, 0, .45);
}
:root body #perspective .pg-panel--travel > figure {
  /* Anchors the cue; the figure was already relative but state it so the
     glyph cannot escape a future layout change. */
  position: relative;
}
:root body #perspective .pg-panel--travel > figure .pg-travel__zoom {
  position: absolute;
  top: clamp(9px, 1vw, 14px);
  right: clamp(9px, 1vw, 14px);
  z-index: 3;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: rgba(20, 20, 20, .52);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
  color: #fff;
  opacity: 0;
  transform: scale(.88);
  transition: opacity var(--dur-2) ease, transform var(--dur-2) var(--ease-out);
  pointer-events: none;
}
:root body #perspective .pg-panel--travel > figure .pg-travel__zoom svg {
  width: 15px;
  height: 15px;
  stroke: currentColor;
  stroke-width: 1.9;
  fill: none;
}
@media (hover: hover) and (pointer: fine) {
  :root body #perspective .pg-panel--travel > figure:hover .pg-travel__zoom,
  :root body #perspective .pg-panel--travel > figure:focus-visible .pg-travel__zoom {
    opacity: 1;
    transform: none;
  }
}
/* Touch has no hover to reveal it, so the cue is simply always on. */
@media (hover: none) {
  :root body #perspective .pg-panel--travel > figure .pg-travel__zoom {
    opacity: .9;
    transform: none;
  }
}

/* Caption stack: the title, and the camera line under it. */
:root body #perspective .pg-travel__caption[class] {
  white-space: normal;
  line-height: 1.25;
}
:root body #perspective .pg-travel__caption .pg-travel__device {
  display: block;
  margin-top: 3px;
  font-weight: 500;
  letter-spacing: .01em;
  color: rgba(255, 255, 255, .78);
  text-shadow: 0 1px 10px rgba(0, 0, 0, .4);
}
/* A taller cell for the portrait at top right — at 5 rows the subject was
   cropped to a band. */
@media (min-width: 1100px) {
  :root body #perspective .pg-travel__t3[class] { grid-row: 1 / 7 !important; }
  :root body #perspective .pg-travel__t5[class] { grid-row: 7 / 13 !important; }
}

/* Travel preview close. Opening fades the stage in; closing fades the
   dark stage, the blurred backdrop and the controls out while the photo
   scales back to its tile, so the photo lands alone instead of wrapped in
   grey bands. js/playground-media.js adds .is-closing for the length of
   the close and removes it as the preview hides. */
:root body .pg-preview.is-open.is-closing {
  background-color: rgba(16, 18, 16, 0) !important;
  transition: background-color var(--dur-3) var(--ease-out) !important;
}
:root body .pg-preview.is-open.is-closing .pg-preview__backdrop {
  opacity: 0 !important;
  transition: opacity var(--dur-2) var(--ease-out) !important;
}
:root body .pg-preview.is-open.is-closing :is(.pg-preview__caption, .pg-preview__modes,
  .pg-preview__close, .pg-preview__nav, .pg-preview__rail) {
  opacity: 0 !important;
  transition: opacity var(--dur-1) ease-out !important;
}
@media (prefers-reduced-motion: reduce) {
  :root body .pg-preview.is-open.is-closing,
  :root body .pg-preview.is-open.is-closing * { transition: none !important; }
}

/* ---------------------------------------------------------------------
   Travel mosaic — four-column masonry (desktop)

   Four equal columns, two photos each. Every column splits at a different
   height (7 / 5 / 8 / 6 of twelve rows), so the seams step across the wall
   like a masonry collage instead of lining up into a table. The people
   shots — the helm portrait, the summit, the drone launch — take the tall
   cells, where a phone portrait loses the least to the crop. The Konkan
   panorama is the one landscape frame and cannot sit in a portrait column
   without being cut to a strip, so it stays in the lightbox only.

     col 1        col 2        col 3        col 4
     helm         chicago      drone        bali
     helm         summit       drone        catamaran
     golden       summit       lagoon       catamaran
   ------------------------------------------------------------------ */
@media (min-width: 1100px) {
  :root body #perspective .pg-panel--travel[class] {
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
    grid-template-rows: repeat(12, minmax(0, 1fr)) !important;
  }
  :root body #perspective .pg-travel__t3[class] { display: block; grid-column: 1 !important; grid-row: 1 / 8 !important; }
  :root body #perspective .pg-travel__t6[class] { display: block; grid-column: 1 !important; grid-row: 8 / 13 !important; }
  :root body #perspective .pg-travel__t1[class] { display: block; grid-column: 2 !important; grid-row: 1 / 6 !important; }
  :root body #perspective .pg-travel__t9[class] { display: block; grid-column: 2 !important; grid-row: 6 / 13 !important; }
  :root body #perspective .pg-travel__t5[class] { display: block; grid-column: 3 !important; grid-row: 1 / 9 !important; }
  :root body #perspective .pg-travel__t2[class] { display: block; grid-column: 3 !important; grid-row: 9 / 13 !important; }
  :root body #perspective .pg-travel__t4[class] { display: block; grid-column: 4 !important; grid-row: 1 / 7 !important; }
  :root body #perspective .pg-travel__t8[class] { display: block; grid-column: 4 !important; grid-row: 7 / 13 !important; }
  :root body #perspective .pg-travel__t7[class] { display: none !important; }
}

/* Focal points, all breakpoints. Each photo is cropped around its
   subject rather than its centre: faces sit high in the frame, figures on
   the shoreline and the moored boats sit low. */
:root body #perspective .pg-travel__t3[class] > img { object-position: 62% 36%; }  /* helm: face */
:root body #perspective .pg-travel__t9[class] > img { object-position: 55% 40%; }  /* summit: seated figure */
:root body #perspective .pg-travel__t5[class] > img { object-position: 60% 50%; }  /* drone: figure and drone */
:root body #perspective .pg-travel__t6[class] > img { object-position: 50% 58%; }  /* shore: runners and sun */
:root body #perspective .pg-travel__t1[class] > img { object-position: 50% 52%; }  /* chicago: skyline */
:root body #perspective .pg-travel__t2[class] > img { object-position: 55% 58%; }  /* lagoon: boats */
:root body #perspective .pg-travel__t4[class] > img { object-position: 50% 52%; }  /* bali: horizon */
:root body #perspective .pg-travel__t8[class] > img { object-position: 38% 58%; }  /* catamaran: hull */

/* ---------------------------------------------------------------------
   All tab — three-column masonry (desktop)

   Six tiles in three columns, each column split at a different height
   (7 / 5 / 6 of twelve rows) so the seams step across the wall like the
   Travel mosaic. One music card only; the rest is photographs and the
   two published videos (the aerial film and the Instagram reel). Motion
   stays unpublished (see #pgHideMotion in index.html). The helm portrait,
   the drone launch and the reel exist only for this layout
   (.pg-all--extra) and stay hidden below 1100px, where the tablet and
   phone stacks are unchanged.

     col 1        col 2        col 3
     helm         film         drone
     helm         chicago      reel
     track 01     chicago      reel

   Photos and videos alternate: the two videos never share a column or a
   row band (film tops column 2, the reel closes column 3).
   ------------------------------------------------------------------ */
#perspective .pg-all--extra { display: none !important; }

@media (min-width: 1100px) {
  #perspective .pg-all--extra { display: block !important; }

  :root body #perspective #pgPanelAll[class] {
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    grid-template-rows: repeat(12, minmax(0, 1fr)) !important;
    grid-template-areas: none !important;
  }
  :root body #perspective #pgPanelAll > * { margin: 0 !important; min-height: 0 !important; }

  :root body #perspective #pgPanelAll > .pg-all--helm      { grid-column: 1 !important; grid-row: 1 / 8 !important; }
  :root body #perspective #pgPanelAll > .pg-all--track-one { grid-column: 1 !important; grid-row: 8 / 13 !important; }
  :root body #perspective #pgPanelAll > .pg-all--film:not(.pg-all--reel) { grid-column: 2 !important; grid-row: 1 / 6 !important; }
  :root body #perspective #pgPanelAll > .pg-all--reel      { grid-column: 3 !important; grid-row: 7 / 13 !important; }
  :root body #perspective #pgPanelAll > .pg-all--drone     { grid-column: 3 !important; grid-row: 1 / 7 !important; }
  :root body #perspective #pgPanelAll > .pg-all--photo:not(.pg-all--extra) { grid-column: 2 !important; grid-row: 6 / 13 !important; }
  /* One sound card on the wall; the second track lives in the Music tab. */
  :root body #perspective #pgPanelAll > .pg-all--track-two { display: none !important; }

  /* In this short, wide cell the centred title ran straight into the
     visualizer rings. The title moves to the top-left corner, opposite the
     index numeral; genre and duration stay centred at the foot, below the
     rings. The meta box spans the card so both can be placed inside it. */
  :root body #perspective #pgPanelAll .pg-track__meta[class] {
    inset: clamp(16px, 1.6vw, 26px) !important;
    display: block !important;
    text-align: left !important;
    pointer-events: none;
  }
  :root body #perspective #pgPanelAll .pg-track__meta[class] > strong {
    max-width: 60%;
  }
  :root body #perspective #pgPanelAll .pg-track__meta[class] > small {
    position: absolute !important;
    bottom: 0;
    left: 50%;
    margin: 0 !important;
    transform: translateX(-50%);
    align-items: center !important;
    text-align: center !important;
    white-space: nowrap;
  }
}

/* Same subject-first focal points as the Travel wall. */
:root body #perspective .pg-all--helm > img   { object-position: 62% 36%; }
:root body #perspective .pg-all--drone > img  { object-position: 60% 50%; }
:root body #perspective .pg-all--reel > video { object-position: 50% 42%; }

/* Travel preview as a shared-element lightbox. Only the photo morphs out
   of its tile (js/playground-media.js springs it); behind it the page is
   frosted rather than replaced by a stage. Everything that is not the
   photo — veil, bottom gradient, controls — follows --pg-veil, which the
   same spring writes, so the open, the close and a drag-to-dismiss all
   fade the surround in lockstep with the photo. */
:root body .pg-preview[data-pg-preview] {
  background: transparent !important;
  touch-action: none;
}
:root body .pg-preview[data-pg-preview]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(8, 10, 9, 0.8);
  -webkit-backdrop-filter: blur(40px) saturate(1.1);
  backdrop-filter: blur(40px) saturate(1.1);
  opacity: var(--pg-veil, 1);
  pointer-events: none;
}
:root body .pg-preview[data-pg-preview] .pg-preview__backdrop { display: none; }
/* No blurred caption strip over the photo: the progressive blur belongs
   to the thumbnails only, and the frosted veil already carries the
   caption, which sits beside the photo rather than on it. */
:root body .pg-preview[data-pg-preview]::after { display: none; }
:root body .pg-preview[data-pg-preview] :is(.pg-preview__close, .pg-preview__nav, .pg-preview__rail, .pg-preview__modes) {
  opacity: var(--pg-veil, 1);
}
:root body .pg-preview__image--shared {
  border-radius: 14px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
  -webkit-user-drag: none;
  user-select: none;
}
:root body .pg-preview[data-pg-preview]:not(.is-zoomed) .pg-preview__image--shared { cursor: grab; }
:root body .pg-preview[data-pg-preview].is-dragging,
:root body .pg-preview[data-pg-preview].is-dragging .pg-preview__image--shared { cursor: grabbing; }

/* Fullscreen film expander, matched to the photo lightbox: the same
   frosted veil behind it (its opacity is written by the spring in
   js/playground-media.js), and a grab cursor for drag-to-dismiss. */
:root body .pg-media-backdrop {
  background: rgba(8, 10, 9, 0.8);
  -webkit-backdrop-filter: blur(40px) saturate(1.1);
  backdrop-filter: blur(40px) saturate(1.1);
}
:root body .pg-media-expanded:not(.pg-media-expanded--rotate) { touch-action: none; cursor: grab; }
:root body .pg-media-expanded.is-dragging { cursor: grabbing; }

/* Travel tile hover: a progressive blur over the bottom third in place of
   the black gradient. js/progressive-blur.js builds the layer stack and
   sets each layer's mask and strength. The reveal fades each LAYER, not
   the stack: an ancestor with opacity below 1 becomes the backdrop root,
   so its children would blur nothing until the fade had finished. */
#perspective .pg-panel--travel > figure::after { display: none; }
#perspective .pg-pblur {
  position: absolute;
  inset: auto 0 0 0;
  height: 34%;
  z-index: 2;
  border-radius: inherit;
  pointer-events: none;
}
#perspective .pg-pblur__layer {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  opacity: 0;
  transition: opacity var(--dur-3) ease;
}
@media (hover: hover) and (pointer: fine) {
  #perspective .pg-panel--travel > figure:hover .pg-pblur__layer,
  #perspective .pg-panel--travel > figure:focus-visible .pg-pblur__layer { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  #perspective .pg-pblur__layer { transition: none; }
}

/* Viewer, Fill mode: the photo runs under the caption and the thumbnail
   rail, so the bottom of the screen gets the same progressive blur as
   the tiles. Fit leaves the photo clear of the caption, so it has none.
   The layers also follow --pg-veil, fading with the open, the close and
   a drag-to-dismiss. #perspective is in each selector because the tile
   rules above are ID-scoped and would otherwise win. */
:root body #perspective .pg-preview[data-pg-preview] > .pg-pblur {
  position: absolute;
  inset: auto 0 0 0;
  height: 32%;
  z-index: 3;
  pointer-events: none;
}
:root body #perspective .pg-preview[data-pg-preview] > .pg-pblur .pg-pblur__layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  /* Tracks --pg-veil frame by frame; a transition would lag the spring. */
  transition: none;
}
:root body #perspective .pg-preview[data-pg-preview][data-fit="fill"] > .pg-pblur .pg-pblur__layer {
  /* --pg-fill is set only while a mode switch involving Fill animates. */
  opacity: calc(var(--pg-veil, 1) * var(--pg-fill, 1));
}
