/*
 * The Portfolio (/portfolio/) — justified rows.
 *
 * WHY THIS REPLACES THE 5-COLUMN GRID
 * The shared sheet lays this out as `grid-template-columns: repeat(5, 1fr)`
 * with `aspect-ratio: 2/3` forced on every tile. That was right when the page
 * was key art only. It is now a showcase of everything — OOH, billboards,
 * banners, stills — and forcing a 6:1 billboard into a 2:3 box crops it to a
 * sliver of itself.
 *
 * THE MATHS
 * Each tile gets `--ar` (its true width/height, set inline by the template from
 * the file's own dimensions). Then:
 *
 *     flex-basis: calc(var(--ar) * var(--row-h))   ideal width at the row height
 *     flex-grow:  var(--ar)                        share of the leftover space
 *
 * Setting grow to the SAME ratio as basis is the whole trick. Flexbox hands out
 * leftover space in proportion to grow, so every tile on a row is scaled by an
 * identical factor — widths stay in exact proportion, the row fills the
 * container to the pixel, and each tile keeps its real aspect ratio. Heights are
 * uniform because every tile is `height: var(--row-h)`.
 *
 * A constant grow (`flex-grow: 1`) would add the same NUMBER OF PIXELS to every
 * tile instead, which fattens narrow tiles disproportionately and quietly
 * distorts every ratio on the row. That is the usual way this gets built wrong.
 *
 * THE LAST ROW
 * A partly-filled final row would stretch its few tiles across the whole width
 * — three posters at triple size under a tidy grid. The `::after` soaks up that
 * space instead: zero height, no visual footprint, and a grow so large the real
 * tiles keep essentially their natural width. It only ever lands on the last
 * line, because it is the last item.
 */

.keyart-grid.is-justified {
  --row-h: clamp(190px, 22vw, 330px);

  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  grid-template-columns: none; /* unset the shared grid */
}

.keyart-grid.is-justified .keyart-item {
  /*
   * ⚠️ THE HEIGHT MUST COME FROM THE RATIO, NOT BE FIXED. This read
   * `height: var(--row-h)` until 2026-08-14 and it cropped the art.
   *
   * Widths on a row end up as `ar × k`, where k is a constant for that row
   * (k = --row-h + leftover / Σar). With the height pinned to --row-h, the BOX
   * ratio becomes ar × k / --row-h — wider than the image whenever there is any
   * leftover space, which is almost always. `object-fit: cover` then quietly
   * trimmed the top and bottom. The Netflix logo disappeared off the base of
   * The RIP that way.
   *
   * `aspect-ratio: var(--ar)` makes height = width / ar = k instead. Since k is
   * the same for every tile on the row, heights still match exactly — and the
   * box now has the image's true ratio, so there is nothing to crop.
   *
   * Row heights differ slightly BETWEEN rows as a result. That is what a
   * justified gallery is, and it is the price of never cropping the art.
   */
  aspect-ratio: var(--ar, 0.667);
  height: auto;

  /*
   * ⚠️ ×100, AND THAT FACTOR IS LOAD-BEARING — do not "simplify" it away.
   *
   * Flexbox has a rule that only bites at the bottom of the range: when the
   * flex-grow factors on a LINE sum to less than 1, each item gets
   * `grow × free space` and THE REMAINDER IS LEFT AS A GAP. Raw `--ar` is
   * 0.667 for a portrait poster, so a line holding a single portrait tile
   * summed to 0.667, absorbed two thirds of the leftover and left the last
   * third as dead black to the right of the tile.
   *
   * Invisible on desktop, where a line always holds several tiles and the sum
   * clears 1 easily. On a phone one portrait tile plus one landscape tile
   * overflows, the landscape wraps, and the portrait is alone on its line —
   * which is exactly the gap Kay photographed on 2026-08-21.
   *
   * Grow factors are relative, so multiplying every one by the same constant
   * changes no proportion; it only lifts any single-item line clear of 1.
   */
  flex-grow: calc(var(--ar, 0.667) * 100);
  flex-shrink: 1;
  flex-basis: calc(var(--ar, 0.667) * var(--row-h));

  /* Without this a wide tile refuses to shrink below its content and pushes the
     row past 100%, which shows up as a horizontal scrollbar on the whole page. */
  min-width: 0;
}

/*
 * LAZY-RENDER (2026-08-18). The portfolio is unpaginated on purpose (the
 * filters need the whole set in the DOM) and passed 297 tiles the day this
 * landed, still growing. content-visibility lets the browser skip layout,
 * paint and image decode for every off-screen tile — the page renders what the
 * viewport shows and the rest materialises on scroll.
 *
 * WHY THIS IS SAFE IN A JUSTIFIED FLEX ROW: the tile's geometry never comes
 * from its content — flex-basis, flex-grow and aspect-ratio all derive from
 * --ar above — so size containment changes nothing about row maths.
 * contain-intrinsic-size is only the placeholder estimate for scrollbar
 * stability while a row is skipped; --row-h is the honest guess.
 *
 * Pairs with loading="lazy" on the images (network) — this is the rendering
 * half of the same economy. Browsers without support ignore both lines and
 * render everything, which is exactly the pre-2026-08-18 behaviour.
 */
.keyart-grid.is-justified .keyart-item {
  content-visibility: auto;
  contain-intrinsic-size: auto var(--row-h);
}

/* Still ~5,000x the largest real line total after the ×100 above, so the
   last line keeps behaving. */
.keyart-grid.is-justified::after {
  content: "";
  flex-grow: 1000000;
  flex-basis: 0;
  height: 0;
}

/*
 * `contain`, not `cover`. The tile's ratio now matches the image's, so in the
 * normal case the two are identical and neither crops. They diverge only when
 * --ar is wrong or missing — a mis-measured attachment, or a file WordPress
 * could not read dimensions for. `cover` would silently crop those; `contain`
 * letterboxes them instead.
 *
 * On a portfolio the rule is: never trim the artwork, even when the metadata is
 * wrong. A letterboxed tile looks like a mistake and gets fixed. A cropped
 * poster looks intentional and ships.
 */
.keyart-grid.is-justified .keyart-item img,
.keyart-grid.is-justified .keyart-item video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/*
 * Letterbox bars must not read as a grey box.
 *
 * The shared sheet gives every .keyart-item `background: var(--iron-grey-dark)`,
 * which is right for a fixed 2:3 grid where the image always fills its tile. On
 * a justified row the box matches the image, so the background is invisible —
 * EXCEPT for the pieces whose --ar was clamped, where `contain` letterboxes and
 * the grey suddenly frames the art. Transparent lets those bars fall through to
 * the page, so an extreme ratio costs empty space and nothing more.
 */
.keyart-grid.is-justified .keyart-item {
  background: transparent;
}

/*
 * The chartreuse "Case Study" strip.
 *
 * ⚠️ These rules are a SECOND COPY. The original is in entertainment.css, which
 * is a deliberately verbatim port of the static build's <style> block and is
 * kept diffable against it — so it cannot be the shared home for a component,
 * and it is not loaded on this page anyway. Values are identical to that copy;
 * change one and change the other.
 *
 * Until 2026-08-17 this page emitted no strip at all, because it hand-built its
 * tiles instead of calling refinery_gallery_tile(). A piece with a case study
 * behind it was indistinguishable from one without, on the one page that shows
 * every piece.
 */
.keyart-grid.is-justified .keyart-case-strip {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  padding: 0.42rem 0.5rem;
  background: var(--chartreuse);
  color: var(--dark);
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
}

.keyart-grid.is-justified .keyart-item--case .keyart-info {
  bottom: 1.7rem; /* clear the strip */
}

/*
 * The strip is the one part of a tile that is always visible — the rest of the
 * overlay waits for hover. That is the point: it advertises that there is more
 * to read, to someone who has not touched the tile yet.
 */
.keyart-grid.is-justified .keyart-case-strip:hover,
.keyart-grid.is-justified .keyart-case-strip:focus-visible {
  background: var(--white);
}

/* Filtering hides tiles with display:none, so rows re-justify around whatever
   is left — no gaps, no half-empty rows. Nothing extra needed for that; this is
   just a note that it was checked. */

@media (max-width: 640px) {
  .keyart-grid.is-justified {
    /* Shorter rows on a phone: at 190px a landscape billboard is ~2 tiles wide
       and everything else becomes a stamp. */
    --row-h: clamp(150px, 42vw, 240px);
  }

  /*
   * THE FILTERS LOSE THEIR PILLS ON A PHONE (Kay, 2026-08-21).
   *
   * Five bordered pills at 0.8125rem wrapped onto two lines and took ~130px of
   * height above the work — a control strip taller than the first row of the
   * thing it filters. The buttons are the same five words either way, so the
   * pill is the part that can go: they become a single scrollable line of
   * plain labels, with the active one carrying the chartreuse.
   *
   * Horizontal scroll is deliberate and contained: `overflow-x: auto` on the
   * strip itself with `flex-wrap: nowrap`, so the row scrolls and the PAGE
   * never does. The negative margins let it bleed to the screen edges, which
   * is what makes it read as scrollable rather than clipped.
   */
  .keyart-filters {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    gap: 1.15rem;
    /* 1.25rem is `.container`'s padding below 768px in the shared sheet —
       hard-coded because it is a literal there, not a token. If that value
       ever moves, this bleed goes with it. */
    margin-inline: -1.25rem;
    padding-inline: 1.25rem;
    margin-bottom: 1.5rem;
    -webkit-overflow-scrolling: touch;
  }

  .keyart-filters::-webkit-scrollbar { display: none; }

  .keyart-filters .filter-btn {
    flex: 0 0 auto;
    padding: 0.25rem 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    font-size: 0.8125rem;
    color: rgba(255, 255, 255, 0.55);
    white-space: nowrap;
  }

  /* `.filter-btn.active` in the shared sheet paints a dark pill — dark on dark
     here, and there is no pill any more. Chartreuse text instead. */
  .keyart-filters .filter-btn.active {
    background: transparent;
    border: 0;
    color: var(--chartreuse);
    font-weight: 600;
  }
}
