/* ============================================================================
   room-curators-rail.css — the interior of the Portraits Room.

   recomposing-room-pilots S3, Pilot 2. Presentation only.

   ── WHY THIS FILE EXISTS RATHER THAN A BLOCK IN room-windows.css ────────────
   The same reason room-intake-manifest.css exists, and the reason is rollback.
   room-windows.css is the CHASSIS engine — shell, well, silhouette linework and
   the per-room palette tokens, shared by every vessel and asserted byte-wise by
   room-windows.smoke.cjs + themed-vessels.smoke.cjs. What follows is ONE Room's
   interior. Reverting this pilot is `git checkout` on this one file plus its one
   <link>, with no chassis diff to read and no other Room disturbed.

   ── THE SEAM ───────────────────────────────────────────────────────────────
   buildWindow() stamps `.ffrw-shell.ffrw-vessel.ffrw-portraits`, and the
   view-room factory REPARENTS the live App-Panel view element into `.ffrw-body`
   with `.ffrw-hosted`. So

       .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted <anything>

   reaches every node of the hosted gallery with zero JavaScript, is scoped to
   this Room by construction, and leaves the flat App-Panel view untouched when
   the Room closes and the element goes home.

   S0 finding 5, verified live at S1 (F4): `portraits.js` emits fully-classed
   markup with ZERO inline `style` attributes in the tile path. So unlike Pilot 1
   this Room needed no renderer edit at all — the one inline style in scope was
   the `<p>` in index.html, excised into § 1 below.

   ── WHAT THIS FILE MAY NOT DO ──────────────────────────────────────────────
   No handler, no onclick, no label, no route, no authorization, no truthful
   string. No `!important`. No second scroll owner — `#view-portraits` is the
   one, and it stays the one. And, specifically for this Room:

     ⛔ NOTHING BELOW TOUCHES `object-fit`, `aspect-ratio`, `width` or `height`
        ON `.portrait-tile-img`. The source rendition is 320x320 square and the
        box is square, so today's `object-fit: cover` performs no crop and no
        distortion. Any "improvement" to the fitting rule is the exact move that
        made 17 of 49 crops visibly worse on `cropping-solo-portraits` while
        every metric improved. The frame is restyled AROUND the image; the image
        itself is left literal.

     ⛔ `.portrait-tile` is NOT a control (DOM_OWNERSHIP_PORTRAITS § 2). Nothing
        below gives it a cursor, a hover-as-affordance, or any treatment that
        would invite a click the markup cannot answer.

   Two sections, and the split is load-bearing:

     § 1  BASE — the verbatim replacement for the one inline style excised from
          index.html. Not room-scoped. It exists so the flat App-Panel view and
          the mobile bundle render EXACTLY as they did before the excision. Read
          it as a transcript, not as design.
     § 2  ROOM — the Curator's Rail recomposition, room-scoped, layered on top.
          Delete § 2 and the Room reverts to today's presentation with every
          control still in place.
   ============================================================================ */


/* ══════════════════════════════════════════════════════════════════════════
   § 1  BASE — the excision transcript (flat-view parity, NOT room-scoped)

   ONE node, enumerated in docs/rooms-blueprint/pilots/PILOT_PORTRAITS.md § 3.
   An inline style outranks any room-scoped rule at any specificity, which is
   the whole reason § 2 could not restyle this header until it moved.
   ══════════════════════════════════════════════════════════════════════════ */

/* index.html — #view-portraits .view-header <p> */
#view-portraits .view-header p {
  margin: 8px 0 0 0;
  font-size: 0.95rem;
  opacity: 0.9;
}


/* ══════════════════════════════════════════════════════════════════════════
   § 2  ROOM — Portraits as a curated family gallery

   Palette is the manifest entry `portraits`, verbatim and unextended:
     surface #071F23 · raised #17393A · accent #3FB8B2 · accentAlt #D49A38
     text #F0FCFB · muted #A5C1BD · border #2E6E6D · innerLip #D49A38
   Type families are the manifest's shared.typeFamilies:
     display "Playfair Display", Georgia, serif · operational "Source Sans 3"

   The vessel's authored identity (room-window-themes.js THEMES['curators-rail'])
   is a HANGING RAIL with turned finials, picture wires descending to a plate
   that SAGS under the weight of what is hung on it, and era gems graduated down
   both flanks. The interior below is that same idea one scale smaller: pictures
   in mounted frames, each hung on its own wire, each with a plate beneath it.

   Nothing below invents a fact. No era band, no coverage figure, no media gem,
   no featured label, no provenance badge, no export control. Every string on
   screen is the one portraits.js already emitted, and every tile keeps every one
   of its actions.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The well ─────────────────────────────────────────────────────────────
   #view-portraits is the scroll owner. It had padding 0 on three sides, which
   is why the header subtitle's right edge WAS the vessel wall at all eight
   measured geometries (GEOMETRY_CONTRACT C1, S1 F6). The gutters below are the
   fix; S4 owns the contract that makes them checkable.

   `padding-bottom: 40px` from styles.css is KEPT — it is what keeps the last
   row of plates clear of the sag in the vessel's floor — and joined by a
   scroll-padding so the last row is reachable rather than merely painted. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted {
  --cr-gutter: 20px;
  --cr-ink: #F0FCFB;
  --cr-ink-muted: #A5C1BD;
  --cr-rule: rgba(46, 110, 109, 0.46);
  --cr-accent: #3FB8B2;
  --cr-brass: #D49A38;
  --cr-mat: rgba(23, 57, 58, 0.40);

  /* ── S4 · GEOMETRY CONTRACT §2.5 — the exclusion zones, named and derived ──
     The contract asks S4 to give both pilots "an explicit, equal reservation
     derived from the measured ornament height." The reservation implemented is
     an equal FLOOR, not an equal value, and the divergence is deliberate:

       · the floor is max(well border-radius 10px, §2.3 separation 16px) = 16px,
         identical in both Rooms and asserted as such;
       · Portraits already reserves 40px (styles.css `#view-portraits`), which
         exceeds the floor. Cutting it to 16px to make the NUMBERS equal would
         tighten the last row of plates against the sag in the vessel's floor —
         regressing a composition S3 arrived at by looking at it. Equalising a
         guarantee is the point; equalising a number would cost a picture.

     Nothing floats over this scroll owner — Portraits has no sticky control —
     so unlike Import Media there is no separate floating-control zone, and
     `scroll-padding-block-end` needs only to clear the reserved floor. The 20px
     already set does that; it is restated here against the named token so the
     two Rooms read from the same vocabulary.

     `--cr-reserve-start` is the top zone: nothing floats at the top either, so
     it is §2.3's separation alone — enough that a keyboard-scrolled plate does
     not land flush in the well's top radius. */
  --cr-reserve-start: 16px;
  --cr-reserve-floor: 16px;

  scroll-padding-block-start: var(--cr-reserve-start);
  scroll-padding-block-end: 20px;
  color: var(--cr-ink);
}

/* ── Room identity: one visible title, and it is the chassis's ─────────────
   The chassis header already says `Portraits / CURATOR'S RAIL` in the vessel's
   own display type. The hosted view repeats it in a second typeface eight
   pixels below, which is the duplicate the commission names.

   VISUALLY RETIRED, NOT REMOVED. `display: none` would take the H2 out of the
   accessibility tree, and it is the ROOT of this view's heading outline — an
   H2 over eighteen per-person H3s (DOM_OWNERSHIP_PORTRAITS § 4). The clip
   technique keeps it named, in document order, and reachable by AT; only the
   pixels go, and the H2 -> H3 nesting is preserved exactly. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .view-header h2 {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── The placard: what this gallery is, in the gallery's own register ──────
   The band itself is painted by the launcher-visual chrome layer
   (.ffvl-chromed-header sets background + bottom rule + text colour, and only
   those). Nothing here touches those three properties — this is spacing and
   type, so removing the chrome class still leaves a coherent placard. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .view-header {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0;
  padding: 11px var(--cr-gutter) 10px;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .view-header p {
  margin: 0;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.62;
  /* C1: the placard stops short of the wall instead of running into it. */
  padding-inline-end: 4px;
  min-width: 0;
}

/* ── The hanging wall ─────────────────────────────────────────────────────
   `#portraitsContent` is the renderer's host div and carries no styling of its
   own, which makes it the right place for the container context: it is the
   grid's PARENT, so a container query on it can restyle the grid itself — which
   a query on the grid could not, since an element is never matched by its own
   container.

   ⚠️ A CONTAINER QUERY, NOT A VIEWPORT QUERY. S2's craft note (a): a Room is a
   window the family DRAGS TO SIZE. Portraits' declared minimum is 640x460 on a
   screen that may be 1920 wide, so `@media (max-width: …)` asks the wrong
   question — it would never fire for the case that most needs it. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted #portraitsContent {
  container-type: inline-size;
  container-name: cr-gallery;
}

/* Columns a little narrower than the 230px base, and a taller row gap than
   column gap: pictures on a wall are spaced more generously vertically than
   horizontally, and the extra vertical air is what the wires hang in. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portraits-grid {
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 30px 18px;
  padding: 26px var(--cr-gutter) 10px;
}

/* ── The frame ────────────────────────────────────────────────────────────
   A rounded 14px card with a lifting hover is a web widget. A hung picture is
   square-cornered, sits in a MAT (the padding below is the mount board), carries
   a fine gilt lip at the picture's edge, and throws its shadow DOWNWARD onto the
   wall behind it.

   `overflow: visible` replaces the base `overflow: hidden` so the picture wire
   can rise out of the top of the frame. The photo keeps its own clipping — it
   always had `overflow: hidden` of its own — so nothing that was clipped before
   is unclipped now. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile {
  position: relative;
  overflow: visible;
  padding: 7px 7px 0;
  border: 1px solid var(--cr-rule);
  border-radius: 3px;
  background: linear-gradient(178deg, rgba(23, 57, 58, 0.46), rgba(7, 31, 35, 0.62));
  box-shadow: 0 7px 15px -6px rgba(3, 13, 14, 0.62);
  transition: border-color 160ms cubic-bezier(.2,.8,.2,1),
              background 160ms cubic-bezier(.2,.8,.2,1),
              box-shadow 160ms cubic-bezier(.2,.8,.2,1);
}

/* THE RAIL, AND THE WIRE EACH PICTURE HANGS ON — the vessel's own `ffvc-rail`
   motif at tile scale. Pure ornament, both halves `pointer-events: none`, drawn
   entirely inside the air the row-gap already reserved, so neither can cover
   text, reduce a contrast, or take a click. Both are CONSTANT across all
   eighteen tiles: they say "these are hung", never anything about a particular
   person. Nothing here is a badge, a band, or a status.

   ⚠️ THE RAIL IS DRAWN AS PER-TILE SEGMENTS THAT MEET. `::after` spans the
   tile's own column PLUS half the column gap at each end (9px of the 18px gap),
   so adjacent tiles' segments touch and the eye reads ONE continuous rod
   crossing the whole row — which is what makes the wire legible as a wire
   rather than as a stray tick above a card. The first draft drew the wire and a
   hanging gem with no rod; rendered, it read as an artifact, and looking at the
   picture is what said so. At the row's outer edges each segment overhangs 9px
   into the grid's own 20px gutter, so nothing reaches the vessel wall. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile::after {
  content: "";
  position: absolute;
  left: -9px;
  right: -9px;
  top: -17px;
  height: 1px;
  pointer-events: none;
  background: rgba(212, 154, 56, 0.26);
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -17px;
  width: 1px;
  height: 17px;
  margin-left: -0.5px;
  pointer-events: none;
  /* Brighter for the first few pixels: the hook over the rod, then the drop. */
  background: linear-gradient(180deg,
      rgba(212, 154, 56, 0.66) 0 4px,
      rgba(212, 154, 56, 0.30) 4px 100%);
}

/* The frame comes to attention: the gilt lip warms and the shadow deepens.
   ⛔ NOT a lift. The base rule translates the card -2px on hover, which is a
   widget gesture and, on a wall of eighteen, a wall of twitching pictures. And
   the tile is not a control — the hover says "this frame", not "click me";
   every action on this surface is an explicit button inside it. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile:hover {
  transform: none;
  border-color: rgba(212, 154, 56, 0.52);
  background: linear-gradient(178deg, rgba(28, 68, 69, 0.54), rgba(9, 37, 41, 0.66));
  box-shadow: 0 10px 20px -6px rgba(3, 13, 14, 0.72);
}

/* ── The picture ──────────────────────────────────────────────────────────
   The gilt lip. `aspect-ratio` resolves against the border box, so a 1px border
   takes one pixel off BOTH axes and the box stays square — the image's fitting
   is arithmetically unchanged.

   ⛔ `.portrait-tile-img` itself is not addressed anywhere in this file. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-photo {
  border: 1px solid rgba(212, 154, 56, 0.30);
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px rgba(3, 13, 14, 0.55);
}

/* The monogram that shows when a face has no crop and no designed fallback —
   in the gallery's ink rather than the base periwinkle.

   S4 · §2.4: this node is `aria-hidden="true"` (portraits.js:139) and
   `position: absolute; inset: 0`, so it is declared ornament that covers the
   entire picture box — and it was the ONE aria-hidden node in either pilot
   still taking pointer events. The contract's rule is that declared ornament is
   pointer-transparent, and here it costs nothing to honour: `.portrait-tile` is
   not a control (DOM_OWNERSHIP_PORTRAITS § 2) and there is no handler on the
   photo box, so no click changes destination — only the pointer stops landing
   on a node that the accessibility tree has been told is not there. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-initial {
  color: rgba(165, 193, 189, 0.42);
  font-family: "Playfair Display", Georgia, serif;
  pointer-events: none;
}

/* ── The plate ────────────────────────────────────────────────────────────
   A museum label: name, then dates, then the work's title in italic. The base
   put name and dates on ONE baseline with `justify-content: space-between`, so
   at 210px a two-word surname wrapped and the dates floated away from it. On a
   plate they stack, which is also how a label is actually set. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-body {
  gap: 4px;
  margin-top: 9px;
  padding: 0 3px 10px;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-namerow {
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 1px;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-name {
  font-family: "Playfair Display", Georgia, serif;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.16;
  letter-spacing: 0.005em;
  color: var(--cr-ink);
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-years {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: rgba(212, 154, 56, 0.86);
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-sub {
  margin-top: 2px;
  font-size: 11.5px;
  font-style: italic;
  line-height: 1.34;
  color: var(--cr-ink-muted);
}

/* ── Two registers of action, not five equal chips ────────────────────────
   The base wrapped five same-weight chips into FOUR rows of chrome under a
   235px face — more visual weight on the buttons than on the person. The
   commission asks that primary VIEWING actions be separated from the quieter
   biography / Living Portrait / sources actions "according to the controls
   actually present".

   That last clause is why this is done with `order` and a zero-height break
   rather than with `:nth-child`. `Read` is gated on (authorized-bio OR admin)
   and the Dressing Room is withheld from guests, so the count varies per tile
   and per tier — and it changed twice in one day on 2026-08-12, when the
   desktop gate came off `Biography` (S2 / D6) and the mirror arrived (D8). A
   positional rule would put the break in the wrong place the moment one is
   absent. Keyed on each action's own class, the two registers
   stay correct however many controls the renderer emitted, and the break
   simply collapses when a register is empty.

   ⛔ NOTHING IS HIDDEN. All controls remain visible, focusable, in DOM
   order, and on their own handlers. Only their weight changes. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-actions {
  /* ⚠️ THE ROOM STAYS FLEX ON PURPOSE (fitting-rail-and-cards S2 / D10).
     The base row became a grid so the plain Portraits view breaks at the same
     place on every card. This vessel's two-register layout is built from flex
     mechanics the grid does not have: the ::before below is a zero-height
     `flex-basis: 100%` line-break sitting at order 3, which is what puts the
     viewing actions on one line and the quiet ones on the next. Under a grid
     that pseudo-element silently becomes a CELL — an empty gap in the middle
     of the row — and the two registers collapse into one. So the Room restates
     the flex model it depends on rather than inheriting a layout written for a
     different surface. */
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 8px;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-actions::before {
  content: "";
  order: 3;
  flex-basis: 100%;
  height: 0;
  pointer-events: none;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-watch { order: 1; }
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-read  { order: 2; }
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-desk  { order: 4; }
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-lp    { order: 5; }
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-src   { order: 6; }
/* The mirror joins the quiet register, last: it is the one control that
   CHANGES the person's record rather than opening it (S2 / D8). */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-dress { order: 7; }

/* Shared chip shape. The icons are scaled down from the renderer's 15px
   attribute so a tile of controls does not read as a toolbar; the SVGs
   themselves are untouched. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act {
  gap: 5px;
  min-height: 26px;
  padding: 4px 9px;
  border: 1px solid;
  border-radius: 4px;
  background: transparent;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.015em;
  transition: background 160ms cubic-bezier(.2,.8,.2,1),
              border-color 160ms cubic-bezier(.2,.8,.2,1),
              color 160ms cubic-bezier(.2,.8,.2,1);
}

/* On a finger (FergiPAD — the desktop shell on touch): 26 px controls are the iPad's front
   door to every biography, and Biography / Living Portrait / Sources / Dressing Room
   measured 24 px tall (Greg 2026-10-17, FergiPAD probe). The phone already carries 44. */
@media (pointer: coarse) {
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act { min-height: 44px; }
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act svg {
  width: 13px;
  height: 13px;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act:focus-visible {
  outline: 2px solid #FFFFFF;
  outline-offset: 2px;
}

/* PRIMARY — watch the bio-Story. The base painted this `#4f46e5` indigo, a
   colour from no part of this vessel; it becomes the Room's own teal. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-watch {
  color: #04191A;
  background: var(--cr-accent);
  border-color: var(--cr-accent);
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-watch:hover {
  color: #04191A;
  background: #66D0CA;
  border-color: #66D0CA;
}

/* PRIMARY, second — read the document. Same family, outlined. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-read {
  color: var(--cr-accent);
  border-color: rgba(63, 184, 178, 0.52);
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-read:hover {
  color: #A6E7E3;
  background: rgba(63, 184, 178, 0.14);
  border-color: var(--cr-accent);
}

/* QUIET — the biography desk, the Living Portrait, the sources. Still plainly
   controls (a border, a hit box above the 24px floor, a hover, a focus ring),
   just no longer competing with the two viewing actions or with the face. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-desk,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-lp,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-dress,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-src {
  min-height: 24px;
  padding: 3px 7px;
  font-size: 10.5px;
  font-weight: 500;
  color: rgba(165, 193, 189, 0.86);
  border-color: rgba(165, 193, 189, 0.22);
}
/* …and on a finger the quiet register keeps its look but not its 24 px height (Greg 2026-10-17). */
@media (pointer: coarse) {
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-desk,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-lp,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-dress,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-src { min-height: 44px; }
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-desk:hover,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-lp:hover,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-dress:hover,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-src:hover {
  color: var(--cr-ink);
  background: rgba(165, 193, 189, 0.12);
  border-color: rgba(165, 193, 189, 0.44);
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-desk svg,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-lp svg,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-dress svg {
  width: 12px;
  height: 12px;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-src-icon {
  font-size: 11px;
}

/* ── "Also appears in" ────────────────────────────────────────────────────
   ⚠️ ONE tile of eighteen carries this (S1 F9), so any assumption that the
   plates are the same height is wrong about exactly that one. `margin-top:auto`
   pushes the chips to the foot of the plate, which means the OTHER tiles in
   that grid row absorb the extra height as air under their labels instead of
   stretching their type away from their frames — the row still reads level. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-assoc {
  margin-top: auto;
  padding-top: 9px;
  border-top: 1px solid var(--cr-rule);
  border-top-style: solid;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-assoc-label {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.13em;
  color: rgba(165, 193, 189, 0.62);
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-assoc-chip {
  padding: 3px 8px;
  border-radius: 3px;
  border-color: rgba(165, 193, 189, 0.24);
  background: transparent;
  color: rgba(165, 193, 189, 0.88);
  font-size: 10.5px;
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-assoc-chip:hover {
  background: rgba(165, 193, 189, 0.12);
  border-color: rgba(165, 193, 189, 0.44);
  color: var(--cr-ink);
}

.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-assoc-chip:focus-visible {
  outline: 2px solid #FFFFFF;
  outline-offset: 2px;
}

/* ── The other surfaces of the same Room ──────────────────────────────────
   The loader and the resting-empty state are preserved exactly as behaviours;
   they are given the gallery's gutters so they do not sit against the wall. */
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portraits-loading,
.ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portraits-empty {
  padding: 44px var(--cr-gutter);
  color: var(--cr-ink-muted);
  font-size: 13px;
  font-style: italic;
}

/* ── Responsive retirement, before anything collides ──────────────────────
   ⚠️ THE ONE CONTENT-SAFETY FAILURE THIS PILOT INHERITED. At the Room's own
   declared minimum (640x460 — 576px of body, 322px of it visible) the gallery
   showed TWO FACES AND NOTHING ELSE: no name, no dates, no control, because a
   square picture plus a plate is taller than the whole visible well
   (GEOMETRY_CONTRACT C3, S1 F6). The first essential elements of the scroll
   owner were not reachable without scrolling past a picture.

   ⚠️ THE THRESHOLD IS SET FROM THE COLUMN ARITHMETIC, NOT FROM A ROUND NUMBER,
   and the first cut got it wrong by guessing. 520px never fired: at the
   declared minimum the gallery host is 576px wide, so the grid was still
   laying out TWO 259px columns of square picture and the failure survived the
   fix. Three columns need 3x210 + 2x18 gap + 40 gutter = 706px, so anything
   under 700px is a gallery that cannot hold three pictures across — which is
   exactly the width at which a square picture plus its plate stops fitting the
   well. Below it, the wall becomes a RAIL: one entry per row,
   the picture reduced to a hung miniature at the left — which is precisely the
   `ffvc-hung-gallery` motif the chassis already draws in the vessel's sag — and
   the plate beside it. Name, dates, title and the primary action are all above
   the fold at the minimum, and three entries fit where two faces used to.

   Where @container is unsupported the gallery simply stays a grid, which is
   today's behaviour — so the degradation is no worse than the baseline. */
@container cr-gallery (max-width: 700px) {
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portraits-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 9px;
    padding: 13px var(--cr-gutter) 10px;
  }

  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile {
    display: grid;
    /* 104px is measured, not chosen: it is what makes the hung miniature about
       as tall as the plate beside it (name + dates + title + two action rows),
       so a rail entry reads as one object rather than as a small picture with a
       tall block of text stranded next to it. */
    grid-template-columns: 104px minmax(0, 1fr);
    align-items: start;
    gap: 0 12px;
    padding: 8px;
  }

  /* A rail entry is not hung on its own wire — it is ON the rail. */
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile::before,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile::after {
    display: none;
  }

  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-body {
    margin-top: 0;
    padding: 1px 0 2px;
  }

  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-name {
    font-size: 14.5px;
  }

  /* ⛔ NO LINE CLAMP ON THE TITLE. The first draft clamped `.portrait-tile-sub`
     to two lines here, on the theory that a rail entry could afford to lose it.
     Rendered at the declared minimum, the plate is 420px wide and the
     renderer's own 120-character cap wraps to two lines anyway — so the clamp
     would have hidden nothing today while standing ready to silently truncate a
     truthful string the first time the numbers moved. Removed rather than kept
     as insurance: this Room preserves every subtitle it is given. */

  /* The chips stay inside the plate — the plate is still a flex column here,
     so `margin-top: auto` above is what holds them at its foot. */
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile-assoc {
    margin-top: 8px;
    padding-top: 7px;
  }
}

/* ── Reduced motion ──────────────────────────────────────────────────────
   Every transition above is a colour or shadow transition on hover, which
   prefers-reduced-motion does not require removing — but the vault's practice
   is to still them anyway, and a wall of pictures has no reason to animate. */
/* S4: scoped to the well rather than to a list of three classes. Portraits
   happened to measure clean at every run — but it measured clean the same way
   Import Media's list did before the renderer showed a state that added nodes,
   so the two Rooms are given the same guarantee rather than the same luck.
   Rationale and the `animation` carve-out: room-intake-manifest.css. */
/* The floor plus the list, for the reason room-intake-manifest.css sets out at
   length: `*` is (0,3,1) and loses to every single-class rule in this file, so
   the floor catches unenumerated markup and the list catches the rest. Dropping
   the list in favour of the floor alone woke `.portrait-tile` and
   `.portrait-act` back up — measured, not predicted. */
@media (prefers-reduced-motion: reduce) {
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted * {
    transition: none;
  }

  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-assoc-chip {
    transition: none;
  }
}

/* ── Forced colours ──────────────────────────────────────────────────────
   Under a forced-colours palette every background and border above is
   discarded by the UA, which would leave the two action registers
   indistinguishable — and the register difference is the whole point of the
   separation. Restoring a system border keeps the shapes; the primary pair is
   additionally given the system's own selected-text pairing so the hierarchy
   survives without this file choosing a colour. */
@media (forced-colors: active) {
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-assoc-chip {
    border: 1px solid ButtonBorder;
  }

  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-watch,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-act-read {
    border: 2px solid Highlight;
  }

  /* Ornament has no business surviving into a forced palette. */
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile::before,
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .portrait-tile::after {
    display: none;
  }

  /* ⚠️ The same opacity trap S4 found on the Intake Manifest's masthead: the UA
     guarantees the contrast of the colour PAIR it substitutes, then our
     `opacity: 0.62` is applied to the composited result and washes it out. The
     placard is essential text (§2.9). Fixed on both pilots together — the two
     placards are the same construction, so one of them failing meant both did,
     whatever the capture happened to show. */
  .ffrw-portraits .ffrw-body > #view-portraits.ffrw-hosted .view-header p {
    opacity: 1;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PORTRAITS v14 — the ported Curator's Rail.  (porting-perimeter-souls S2)

   Everything below this line is reached ONLY through `html.ffv-modal-fleet-v14`,
   the reflection FFAFlags writes for `ffa_modal_fleet_v14`. With the flag off
   not one selector here matches, and the room renders exactly as the 671 lines
   above render it.

   WHY THE REFLECTION AND NOT A JS BRANCH. The LINEWORK half gates itself inside
   layers(): flag-off never emits a `.ffvc-v14-*` element, so those rules could
   not match anyway and are belt-and-braces. The SHELL half cannot work that
   way. This room's v14 chassis needs its own weights on `.ffvc-rim-glow`,
   `.ffvc-body` and `.ffvc-lip` — three classes room-windows.css draws in BOTH
   flag states. An unconditioned override would reach the flag-off room and
   break the byte-identity that makes this flag a rollback path. CSS cannot call
   isOn(); a class on <html> is the narrowest honest form of the condition.

   ── THE APERTURE IS UNCHANGED HORIZONTALLY, AND THAT IS THE POINT ─────────
   Nothing here touches `.ffrw-header`'s or `.ffrw-body`'s insets. The port's
   only geometry is vertical and it moves through the theme's own DERIVED
   keep-outs (143 / 102, from 110 / 67), and `#view-portraits` remains the one
   scroll owner. Every unit of that raise is the SHELL: the gallery lip asks
   143 / 102 before any furniture is placed, and after all four aperture
   directives the derived numbers are still 143 / 102. The decoration costs the
   workspace nothing.

   ── STROKE WIDTHS ARE THE ONE JUDGEMENT ──────────────────────────────────
   Colours and opacities carry over from the lab unchanged — they are unitless
   and a coordinate change cannot touch them. Widths cannot: the lab draws in
   1600x1000 and this fleet in 1000x700, both stretched to their container, so
   a horizontal stroke wants 0.625 and a vertical one 0.7, and one number
   cannot be both. Every width below is the lab's times the GEOMETRIC MEAN,
   sqrt(0.625 * 0.7) = 0.6614, which preserves stroke area and splits the
   residual evenly. Each is written as `lab N x 0.6614` so the arithmetic is
   checkable rather than trusted.

   `.ffvc-v14-*` names appear nowhere else in the vault, so these rules cannot
   reach another room's chassis even if a selector were mis-scoped. They are
   scoped to `.ffrw-portraits` regardless.
   ═══════════════════════════════════════════════════════════════════════════ */

html.ffv-modal-fleet-v14 .ffrw-portraits {
  --v14-rose:  #ef6f9f;   /* the lab's --room-a     */
  --v14-amber: #e8b759;   /* the lab's --room-b     */
  --v14-blue:  #55b8ff;   /* the lab's --room-c     */
  --v14-metal: #40e2d2;   /* the lab's --room-metal */
}

/* The shell: the lab's heavy aura, its solid rim and its pale inner lip. These
   three override the fleet-wide widths in room-windows.css, which is why they
   carry the room class as well as the flag class. */
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-rim-glow { stroke-width: 6.61; opacity: .24; }   /* lab 10  x 0.6614 */
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-body     { stroke-width: 2.65; }                 /* lab 4   x 0.6614 */
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-lip      { stroke: rgba(220, 246, 255, .45); stroke-width: .99; opacity: 1; }  /* lab 1.5 x 0.6614 */

/* the room's furniture, all of it in the margins the working panel leaves */
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-gallery path,
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-labelwork path {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* the head: the museum picture rail, and the wires hanging from it. The wires
   stop at y=136 and the inner lip sits at 136.5, so each one runs behind the
   room's own edge rather than ending in mid-air — which is why they are drawn
   BEFORE the lip in source order but read as passing beneath it. */
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-picture-rail {
  stroke: var(--v14-metal);
  stroke-width: 3.31;                                                                           /* lab 5   x 0.6614 */
}
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-wire {
  stroke: color-mix(in srgb, var(--v14-metal) 70%, transparent);
  stroke-width: .99;                                                                            /* lab 1.5 x 0.6614 */
}

/* the gallery crest, seated on the crown plateau (sunk 20.5 — see the theme) */
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-hero-ring { fill: rgba(4, 15, 26, .96); stroke-width: 1.46; }  /* lab 2.2 x 0.6614 */
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-hero-line {
  stroke: color-mix(in srgb, var(--v14-rose) 78%, #ffffff 8%);
  stroke-width: 1.32;                                                                           /* lab 2   x 0.6614 */
}

/* the foot: the two label rails and the pair of blank wall labels */
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-label-rail {
  stroke: var(--v14-rose);
  stroke-width: 1.32;                                                                           /* lab 2   x 0.6614 */
}
html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-gallery-label {
  fill: #17171a;
  stroke: var(--v14-metal);
  stroke-width: .93;                                                                            /* lab 1.4 x 0.6614 */
}

/* ── motion ────────────────────────────────────────────────────────────────
   DIVERGENCE_MATRIX.md gives this room "static gallery dignity; no machine
   pulse", and the lab honours that by giving `gallery` no motion recipe at
   all. So there is none here: no breath on the crest, no travelling signal on
   the rail. The only movement in the room is the drifting gallery dust the
   theme's particle recipe seeds into the two flank reveals, which the chassis
   already stands down under reduced motion. Declared rather than omitted,
   because "this port adds no animation" is a decision and the next reader
   should not have to infer it from an absence. */

/* ── forced colours ────────────────────────────────────────────────────────
   The UA discards the fills and strokes above. The chassis is decoration and
   is allowed to disappear — what may NOT happen is the rail, the wires and the
   wall labels surviving as unstyled black-on-black shapes across the room's
   head and foot. Hand them the system's own pairing so they either read or
   vanish cleanly. */
@media (forced-colors: active) {
  html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-gallery-label {
    fill: Canvas;
    stroke: CanvasText;
  }
  html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-picture-rail,
  html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-wire,
  html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-label-rail,
  html.ffv-modal-fleet-v14 .ffrw-portraits .ffvc-v14-hero-line {
    stroke: CanvasText;
  }
}

/* You and your family first (Greg, 2026-10-02; see views/portraits.js _render). */
.portraits-section-title {
    margin: 6px 0 10px;
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #fbbf24;
}
.portraits-grid--mine { margin-bottom: 22px; }
.portrait-tile-role {
    display: inline-block;
    margin-bottom: 4px;
    padding: 2px 8px;
    border-radius: 999px;
    background: rgba(251, 191, 36, 0.18);
    color: #fde68a;
    font-size: 0.72rem;
    font-weight: 700;
}
