/* ==========================================================================
   THE SCRUB WALL
   The Film Keep. Grid card, grid, and scrub-on-hover interaction.

   Brandy (Brand Director), 2026-08-23.
   Every value below comes from the kit, not from taste:
     Clients/TheFilmKeep/Brand/colors.json  v1.0.0  (computed contrast ratios)
     Clients/TheFilmKeep/Brand/fonts.json   v1.0.0  (three faces, one job each)
     Clients/TheFilmKeep/Brand/avatar.md            (Alma 58, Priya 34, Ruben 44)
     RFM-Films/UNA-PLAYER-SPEC.md                   (motion tokens, WCAG floors)

   Pairs with assets/films-wall.js. Load AFTER films.css so it wins on the
   properties it deliberately overrides.

   ---------------------------------------------------------------------------
   THE GOVERNING IDEA, so nobody "fixes" it later.

   The frame does not move. The film moves inside it.

   Every cheap portfolio grid announces hover by moving the CONTAINER: scale,
   lift, shadow bloom, a glass play button popping in. Expensive interfaces
   move the CONTENT and leave the container exactly where it was. That is the
   only reason a scrubbing card and a static card can sit side by side without
   fighting, and it is why the following are all deliberately absent here:

     - transform: scale() on the card or the image
     - a hover shadow, or any shadow at all (colors.json: "Depth comes from
       surface value, never from shadow")
     - the .rff-play glass circle (a hover-only affordance that competes with
       the scrub and lies about what a click does: a click navigates)
     - any change to the card's size, position, z-index or border

   The only things that change on hover are the picture, a 2px hairline, and
   the digits in the timecode chip.
   ========================================================================== */


/* ==========================================================================
   1. THE WALL
   Three in a row, confirmed. Reasoning is in the report; the load-bearing
   part is that scrub precision is a function of card width, so the wall runs
   ONE card size at every breakpoint. No alternating rows, no masonry, no
   feature tiles. Variety comes from the films' own colours, per the
   colors.json usage rule: "the grid is a wall of other people's colors."
   ========================================================================== */

.rff-wall{
  /* Lane tokens. Light is the default; [data-lane="vault"] flips them.       */
  --w-paper:#faf7f2;
  --w-tile:#f3eee5;          /* the ground behind a poster that has not loaded */
  --w-ink:#1a1815;
  --w-ink2:#6b645a;
  --w-ink3:#726b5f;
  --w-hair:#e4ddd0;
  --w-brass:#7d5f38;         /* light-lane accent. 5.10:1 on paper-2.          */
  --w-gold:#c8a469;          /* dark-surface-only. Used ONLY over footage.     */
  --w-radius:3px;            /* a printed plate, not a phone app.              */
  --w-colgap:20px;
  --w-rowgap:44px;

  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  column-gap:var(--w-colgap);
  row-gap:var(--w-rowgap);
  align-items:start;
  max-width:1440px;          /* the wall breaks out past the 1280 text column */
  margin-inline:auto;
  width:100%;
}

/* The asymmetric gap is doing real work and is almost never done.
   The eye scans a grid horizontally, so columns want to sit close enough to
   compare and rows want to sit far enough apart to separate. 20/44 is a 1:2.2
   ratio. Equal gaps are what makes a wall of 40 read as a contact sheet. */

@media (max-width:1179px){ .rff-wall{--w-colgap:16px;--w-rowgap:36px} }
@media (max-width:899px){
  .rff-wall{grid-template-columns:repeat(2,minmax(0,1fr));--w-colgap:16px;--w-rowgap:34px}
}
@media (max-width:699px){
  /* One column on a phone. Two of three visitors arrive here from a text
     message, and a 169px card at 390px viewport deletes every face in it.
     The scroll gets long. An archive is allowed to be long. */
  .rff-wall{grid-template-columns:minmax(0,1fr);--w-rowgap:30px}
}

/* Year headings keep their full-width row. Chunking is what turns 42 films
   into six readable groups. */
.rff-wall > .rff-year{
  grid-column:1/-1;
  margin:clamp(22px,3.4vw,44px) 0 0;
  padding:0 0 12px;
  font:600 12px/1 Inter,system-ui,sans-serif;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--w-ink3);
  border-bottom:1px solid var(--w-hair);
}
.rff-wall > .rff-year:first-child{margin-top:0}


/* ==========================================================================
   2. THE HERO
   Position one gets read no matter what, so spend it. But spend it as a
   FULL-WIDTH row, not a 2-column span.

   A 2-col span inside a 3-col grid leaves the third column holding a 214px
   card inside a 440px row: a hole where a film should be. Full bleed at 21:9
   cannot create a hole, and 21:9 is also the widest scrub travel on the page
   (1344px across 24 frames = 56px per frame, the most precise control here).

   Gate it in PHP, not CSS: hero only when the first film has a real title AND
   a poster. Never on a filtered view, never twice on one page.
   ========================================================================== */

@media (min-width:900px){
  .rff-wall > .rff-card[data-hero]{grid-column:1/-1}
  .rff-wall > .rff-card[data-hero] .rff-frame{aspect-ratio:21/9}
  .rff-wall > .rff-card[data-hero] .rff-meta{padding-top:22px}
  .rff-wall > .rff-card[data-hero] .rff-title{
    font-weight:400;                       /* fonts.json: 400 above 32px      */
    font-size:clamp(30px,3.4vw,46px);
    letter-spacing:-.015em;                /* fonts.json tracking, 32-71px    */
  }
  .rff-wall > .rff-card[data-hero] .rff-sub{font-size:15.5px}
}
/* An untitled film is never the hero. Belt and braces with the PHP gate. */
.rff-wall > .rff-card[data-hero][data-untitled]{grid-column:auto}
.rff-wall > .rff-card[data-hero][data-untitled] .rff-frame{aspect-ratio:16/9}


/* ==========================================================================
   3. THE CARD
   ========================================================================== */

.rff-wall .rff-card{position:relative;min-width:0}
.rff-wall .rff-card[hidden]{display:none}
.rff-wall .rff-card > a{display:block;color:inherit;text-decoration:none}

.rff-wall .rff-frame{
  position:relative;
  aspect-ratio:16/9;                 /* every source asset is 16:9. Any other
                                        ratio crops the filmmaker's frame, and
                                        the brand rule is that the film wins.  */
  overflow:hidden;
  border-radius:var(--w-radius);
  background:var(--w-tile);
  /* colors.json: "Poster frames get a 1px line border, not a shadow, so a
     poster with a bright edge still reads as a bounded object."               */
  box-shadow:inset 0 0 0 1px var(--w-hair);
  /* The card is a link. The cursor stays a pointer, because a click navigates.
     A scrub cursor here would lie about what clicking does, and both Priya and
     Ruben click through. The affordance is the hairline, not the cursor.      */
  cursor:pointer;
  /* Stop the browser turning a horizontal sweep on a card into a page gesture */
  touch-action:manipulation;
}

.rff-wall .rff-poster{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block;
  /* NO transform. NO transition on transform. See the governing idea. */
}
.rff-wall .rff-noposter{
  position:absolute;inset:0;
  background:linear-gradient(135deg,#2a2419,#141821);
}

/* --- the scrub surface ---------------------------------------------------
   One element, one background-image, driven entirely by background-position.
   background-size and the tile grid are set inline by the JS from the card's
   data attributes, so a 6x4 composite strip and a 6x6 Bunny sheet both work
   through the same element.

   The 0.4% vertical stretch from Bunny's 300x168 cell into a 16:9 box is
   below the perceptual floor and needs no correction.                        */

.rff-wall .rff-face{
  position:absolute;inset:0;z-index:1;
  background-repeat:no-repeat;
  background-color:#0b0b0b;
  opacity:0;
  transition:opacity .26s ease;      /* SLOW out: the film settles            */
  pointer-events:none;
}
.rff-wall .rff-card.is-scrub .rff-face{
  opacity:1;
  transition-duration:.12s;          /* FAST in: it is already moving         */
}
.rff-wall .rff-card.is-live .rff-face{will-change:background-position}

/* --- the scrim -----------------------------------------------------------
   It exists ONLY while a card is live. A permanently darkened bottom third
   across 40 tiles is exactly what Chad's Law 2 argues against, and it makes
   the whole wall look dimmed. At rest the wall is pure film. Under the cursor
   the scrim appears to give the hairline and the timecode a legal ground
   (WCAG 1.4.11, 3:1 for a UI component over an arbitrary bright frame, and
   this catalogue is full of white dresses in full sun).                      */

.rff-wall .rff-frame::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(to top,
    rgba(6,6,6,.74) 0%,
    rgba(6,6,6,.30) 20%,
    rgba(6,6,6,0)   42%);
  opacity:0;
  transition:opacity .18s ease;
  border-radius:inherit;
}
.rff-wall .rff-card.is-live .rff-frame::after{opacity:1}

/* --- the hairline --------------------------------------------------------
   The card's own bottom edge becomes the timeline. This is the entire chrome
   budget for the interaction: two pixels.

   It appears the instant the pointer enters, BEFORE the strip has loaded, so
   the card is responsive on a cold cache. It leaves instantly on exit, while
   the picture fades back over 260ms. Two speeds on purpose: chrome leaves
   immediately, content settles. Same principle as the player.                */

.rff-wall .rff-line{
  position:absolute;left:0;right:0;bottom:0;height:2px;z-index:4;
  background:rgba(0,0,0,.55);
  opacity:0;
  transition:opacity .12s ease;
  pointer-events:none;
  overflow:hidden;
  border-bottom-left-radius:inherit;
  border-bottom-right-radius:inherit;
}
.rff-wall .rff-card.is-live .rff-line{opacity:1}
.rff-wall .rff-line i{
  display:block;height:100%;width:100%;
  background:var(--w-gold);
  transform:scaleX(0);
  transform-origin:left center;      /* scaleX only. Compositor, never layout */
}

/* --- the timecode chip ---------------------------------------------------
   Same box at rest and on scrub. At rest it reads the runtime. On scrub the
   position appears before it and the total demotes.

   IBM Plex Mono, tabular, slashed zero (fonts.json). Tabular figures are the
   whole reason the digits do not jitter while sweeping, and jitter is the
   single most recognisable "homemade" tell in a scrub. The chip's width
   changes ONCE, on state entry, with no transition. Animating width during a
   scrub is the bug, not the feature.                                         */

.rff-wall .rff-dur{
  position:absolute;right:10px;bottom:12px;z-index:5;
  display:flex;align-items:baseline;gap:4px;
  padding:4px 8px;border-radius:3px;
  background:rgba(0,0,0,.58);
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
  font-family:'IBM Plex Mono',ui-monospace,'SF Mono',Menlo,monospace;
  font-size:12.5px;line-height:1.1;
  font-variant-numeric:tabular-nums;
  font-feature-settings:'zero' 1;
  color:#fff;
  white-space:nowrap;
  transition:none;
}
.rff-wall .rff-dur .rff-pos,
.rff-wall .rff-dur .rff-sep{display:none}
.rff-wall .rff-card.is-live .rff-dur .rff-pos{display:inline;font-weight:500;color:#fff}
.rff-wall .rff-card.is-live .rff-dur .rff-sep{display:inline;color:rgba(255,255,255,.38)}
.rff-wall .rff-card.is-live .rff-dur .rff-tot{color:rgba(255,255,255,.55)}
.rff-wall .rff-dur .rff-tot{font-weight:400}

@media (max-width:699px){
  .rff-wall .rff-dur{font-size:13.5px;padding:5px 9px;right:9px;bottom:9px}
}


/* ==========================================================================
   4. THE TYPE UNDER THE FILM
   MUBI's tile carries the title, then a bolded second line, then country and
   year. That one editorial choice does most of the work of reading as a
   serious product. The Film Keep equivalent is the names and the place,
   chosen rather than dumped.

   Every value here is a correction of live drift from fonts.json:
     eyebrow was 9.5px  -> 12px    (avatar.md: nothing under 14px for reading,
                                    12px is the floor for a letterspaced label,
                                    and the tracking drops .20em -> .14em)
     title   was w400   -> w600    (fonts.json: 400 loses authority under 32px)
     sub     was w300   -> w400    (300 is not in the three-weight ladder)
     runtime was Inter  -> Plex Mono (fonts.json: mono for machine-true values)
   ========================================================================== */

.rff-wall .rff-meta{padding:16px 2px 0;min-width:0}

.rff-wall .rff-cat{
  margin:0 0 8px;
  font:600 12px/1.1 Inter,system-ui,sans-serif;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--w-brass);              /* 5.10:1 on paper-2. Measured.          */
}

.rff-wall .rff-title{
  margin:0;
  font-family:Fraunces,'Iowan Old Style',Georgia,serif;
  font-weight:600;                   /* fonts.json: 600 from 20px to 32px     */
  font-variation-settings:'SOFT' 0,'WONK' 0;
  font-optical-sizing:auto;
  font-size:20px;
  letter-spacing:0;                  /* fonts.json: tracking 0 at 20-31px     */
  line-height:1.18;
  color:var(--w-ink);
  overflow-wrap:anywhere;
}

.rff-wall .rff-sub{
  margin:6px 0 0;
  font:400 14px/1.45 Inter,system-ui,sans-serif;
  font-feature-settings:'tnum' 1,'cv05' 1,'ss03' 1;
  color:var(--w-ink2);               /* 5.05:1 on paper-2. Measured.          */
}

/* A card with no printable title. The collection and year re-rank INTO the
   title slot rather than leaving a hole where a name should be. Roughly 40%
   of the live Weddings collection is in this state, which is a data problem,
   not a CSS problem, but the CSS must not make it look like a bug. */
.rff-wall .rff-card[data-untitled] .rff-cat{
  font-family:Fraunces,'Iowan Old Style',Georgia,serif;
  font-weight:400;font-size:19px;line-height:1.2;
  text-transform:none;letter-spacing:-.005em;
  color:var(--w-ink2);
  margin:0;
}
.rff-wall .rff-card[data-untitled] .rff-sub{margin-top:5px}

@media (max-width:699px){
  .rff-wall .rff-meta{padding-top:14px}
  .rff-wall .rff-cat{font-size:13px;letter-spacing:.12em}
  .rff-wall .rff-title{font-size:22px}
  .rff-wall .rff-sub{font-size:15px}
}


/* ==========================================================================
   5. FOCUS
   WCAG 2.4.11 / 2.4.13. The ring wraps the whole card, not just the frame,
   because the title is part of the link.
   ========================================================================== */

.rff-wall .rff-card > a:focus-visible{
  outline:2px solid var(--w-brass);
  outline-offset:5px;
  border-radius:5px;
}
.rff-wall[data-lane="vault"] .rff-card > a:focus-visible,
.rff-wall[data-theme="dark"] .rff-card > a:focus-visible{outline-color:var(--w-gold)}


/* ==========================================================================
   6. THE VAULT LANE
   Not a lesser version of the wall. A different room.

   The public archive is a WALL: for comparison at distance, 42 weddings deep.
   The vault is a TABLE: one delivery, close, with your hands on it.

   Four changes, no new components:
     1. dark ground     (colors.json: "the dark is the brand's home")
     2. two columns     (a delivery holds 1 to 6 items; bigger is the gift)
     3. a label line    (Alma does not know what "highlight film" means and
                         will not ask, so the vault always explains itself)
     4. a brass rule    (reassurance, not a PRIVATE badge in red)
   ========================================================================== */

.rff-wall[data-lane="vault"]{
  --w-paper:#0b0b0b;
  --w-tile:#141414;
  --w-ink:#e6e1d8;                   /* 14.15:1 on night-2. Never pure white. */
  --w-ink2:#a49d93;                  /* 6.86:1                                */
  --w-ink3:#8a847b;                  /* 4.97:1, legal for text on dark        */
  --w-hair:#2b2b2b;
  --w-brass:#c8a469;                 /* on dark the brass role resolves to gold */
  --w-colgap:26px;
  --w-rowgap:56px;
  grid-template-columns:repeat(2,minmax(0,1fr));
  max-width:1240px;
}
@media (max-width:899px){
  .rff-wall[data-lane="vault"]{grid-template-columns:minmax(0,1fr);--w-rowgap:36px}
}
/* No hero in the vault. Nothing here outranks anything else here. */
.rff-wall[data-lane="vault"] > .rff-card[data-hero]{grid-column:auto}
.rff-wall[data-lane="vault"] > .rff-card[data-hero] .rff-frame{aspect-ratio:16/9}

/* The label line. Plain language, always present, one sentence.
   "The ceremony, start to finish." Not "CEREMONY_FULL_v3". */
.rff-wall[data-lane="vault"] .rff-note{
  margin:8px 0 0;
  font:400 15px/1.5 Inter,system-ui,sans-serif;
  color:var(--w-ink2);
}
.rff-wall[data-lane="vault"] .rff-title{font-size:23px}

/* The reassurance rule. semantic.private from colors.json: deliberately not
   red and not gold, because a private film is a normal state and colouring it
   red teaches a client that their own film is a problem. */
.rff-vault-rule{
  max-width:1240px;margin:0 auto clamp(28px,4vw,48px);
  padding-top:18px;
  border-top:1px solid #7d5f38;
  font:400 14px/1.6 'IBM Plex Mono',ui-monospace,Menlo,monospace;
  color:#a49d93;
  font-variant-numeric:tabular-nums;
}

/* The Keep line. The only sentence on this site that exists purely to answer
   a fear, and it is the fear that is literally written in avatar.md:
   "I am afraid one day I will tap the link and it will be gone."             */
.rff-keepline{
  max-width:1240px;margin:clamp(56px,8vw,104px) auto 0;
  padding-top:clamp(26px,3vw,38px);
  border-top:1px solid #2b2b2b;
  font-family:Fraunces,'Iowan Old Style',Georgia,serif;
  font-weight:400;font-variation-settings:'SOFT' 0,'WONK' 0;
  font-size:clamp(20px,2.4vw,28px);line-height:1.42;letter-spacing:-.015em;
  color:#e6e1d8;
  max-width:26ch;
}


/* ==========================================================================
   7. DARK PAGE THEME (public wall on a dark page, e.g. the "more from"
   rail at the bottom of a film page)
   ========================================================================== */

.rff-wall[data-theme="dark"],
.rff-film .rff-wall{
  --w-paper:#0b0b0b;--w-tile:#141414;--w-ink:#e6e1d8;--w-ink2:#a49d93;
  --w-ink3:#8a847b;--w-hair:#2b2b2b;--w-brass:#c8a469;
}


/* ==========================================================================
   8. MOTION
   A cursor-driven scrub is DIRECT MANIPULATION, not an animation, and WCAG
   2.3.3 targets non-essential animation. So reduced-motion does NOT disable
   the scrub. It disables the things that move on their own: the riffle, the
   crossfades, the scrim fade.

   The existing films.css blanket rule kills .rff-prev entirely under reduced
   motion. That was correct for an autoplaying webp loop. It is wrong for a
   scrub, and this is the place people get it wrong.
   ========================================================================== */

@media (prefers-reduced-motion:reduce){
  .rff-wall .rff-face,
  .rff-wall .rff-frame::after,
  .rff-wall .rff-line{transition-duration:.01ms !important}
  .rff-wall .rff-card.is-riffle{animation:none !important}
}

/* Data saver and 2g get the poster and nothing else. Handled in JS too; this
   is the belt. */
@media (prefers-reduced-data:reduce){
  .rff-wall .rff-face{display:none}
}


/* ==========================================================================
   9. WHAT films.css MUST STOP DOING
   These are live rules that fight the wall. Wesley: delete them at source in
   films.css rather than leaving these overrides in place. They are here so
   this file is droppable today without a films.css edit.
   ========================================================================== */

.rff-wall .rff-frame img:not(.rff-face){transition:none !important}
.rff-wall .rff-card:hover .rff-frame img{transform:none !important}
.rff-wall .rff-play{display:none !important}   /* the glass circle, retired   */
.rff-wall .rff-prev{display:none !important}   /* the 2.34 MB webp, retired   */
.rff-wall .rff-frame{box-shadow:inset 0 0 0 1px var(--w-hair) !important}
