/* Chroma Noir — the photographs index
   ---------------------------------------------------------------------------
   A POSTER WALL OF SETS, then one set, then one photograph. The same three
   surfaces the films page has, in the same language and at the same
   measurements — this file MIRRORS films.css rather than importing it,
   because the site has no build step and an @import is a second blocking
   request on every page load for a file whose other half this page does not
   use. The tokens, the measure, the grid, the chips, the dialogs and the
   Chrome 79 workarounds below are all the films page's; where a rule differs
   it says so and says why.

   THREE THINGS ARE NEW, and all three are in the approved mock:

   1. THE STACK. Two hairlines offset behind a set's cover, so a tile reads as
      a set of photographs rather than as one photograph, with no type doing
      the saying. It survives on a locked tile because it is drawn on the
      WRAPPER, not on the picture.
   2. THE COUNT WHERE THE CLOCK GOES. A set has no running time; it has a
      number of photographs, in the same .len slot, in tabular figures.
   3. COLOUR INSIDE A SET. The wall is grayscale like everything else on this
      subdomain. The contact sheet is not, and neither is the lightbox — it is
      the one deliberate break with the house language, so that entering a set
      is a change of state you can feel.

   Chrome 79 rules, as everywhere public here: longhand offsets rather than
   `inset`, margins rather than flex `gap` (grid `gap` is Chrome 66 and fine),
   `aspect-ratio` only behind @supports, and never `:focus-visible` in a
   selector list with anything that has to survive — an unparseable selector
   takes the whole list down with it.

   AND TWO PAIR RULES, which are the same rule twice: `svh` and `env()` are
   both newer than the television, and a declaration a browser cannot parse is
   dropped WHOLE rather than partly. So every one of them is written as a pair
   — the plain `vh` or the plain length on the line before, the new spelling
   after it. A dropped ceiling is a picture with no limit at all and a dropped
   padding is a layout touching the frame.

   THE DEVICE PASS ADDED, on top of the three above: two columns on the wall
   and three on the contact sheet below 520px, a press payoff where a pointer
   would have hovered, a 44px target under every rail control, the 16px input
   floor, the safe-area pairs, and the LIGHTBOX ZOOM — a double tap, a pinch
   and a pan, as a designed state rather than the page zooming by accident.
*/

:root{
  --ink:#f4f3f1; --dim:#9a9894; --faint:#6a6764; --gold:#c9a14a;
  --rule:rgba(244,243,241,.14);
  --rule2:rgba(244,243,241,.34);
  --sans:'Inter Tight',-apple-system,'Segoe UI',sans-serif;
  --serif:'Fraunces',Georgia,serif;
  /* 16px at 390px, which is the gutter the wall was drawn to. */
  --pad:clamp(16px,4vw,56px);
  /* 1.760px leaves a third of a 2560px monitor black, so it is raised to
     2.040 above 1900 — see the bottom of this file. The films wall does the
     same, and the two walls have to agree. */
  --wide:1760px;
  --ease:cubic-bezier(.22,.61,.36,1);

  /* The zoomed picture's two ends, as tokens because the CSS and photos.js
     both have to know them and a number written twice is a number that drifts.
     2,5x and not 2x on a double tap: these are high-resolution stills and 2x
     rarely reveals anything new. 4x is the hard ceiling — past about 3x you
     are looking at JPEG artefacts rather than at the photograph. */
  --zoom-tap:2.5;
  --zoom-max:4;

  /* A real spacing scale, FOR NEW RULES ONLY — the same eight steps films.css
     carries. The literals already in this file each have their own written
     reasoning; replacing them with tokens would be churn with a regression
     risk and no visible gain. They cannot be used in a media-query condition:
     a custom property is illegal there and there is no preprocessor. */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px;
  --s5:24px; --s6:32px; --s7:48px; --s8:64px;
  color-scheme:dark;
}
*{box-sizing:border-box}
body{margin:0;background:#000;color:var(--ink);font-family:var(--sans);
     font-synthesis:none;-webkit-font-smoothing:antialiased}
[hidden]{display:none !important}

/* ---- the ground ----------------------------------------------------------- */
.scrim--flat{position:fixed;top:0;right:0;bottom:0;left:0;z-index:0;pointer-events:none;
  background:linear-gradient(rgba(244,243,241,.035) 0%,rgba(0,0,0,0) 38%)}

/* THE SAFE AREA, written as a pair every time: a plain value on the line
   before and the env() version after it. env() is Chrome 69 for this spelling
   and the television is 79, but an unsupported env() drops the WHOLE
   declaration — and a dropped padding is a layout touching the frame. The
   pair costs one line and cannot fail. Same discipline as films.css. */
.page{position:relative;z-index:2;max-width:var(--wide);margin:0 auto;
      padding:0 var(--pad) clamp(44px,9vh,96px);
      padding-left:calc(var(--pad) + env(safe-area-inset-left));
      padding-right:calc(var(--pad) + env(safe-area-inset-right));
      padding-bottom:calc(clamp(44px,9vh,96px) + env(safe-area-inset-bottom))}

/* ---- chrome --------------------------------------------------------------- */
.bar{display:flex;flex-wrap:wrap;align-items:center;padding:17px 0;
     padding-top:calc(17px + env(safe-area-inset-top))}
.bar > *{margin:5px 0}
.bar > * + *{margin-left:18px}
/* The set page's own bar sits under the site bar, so it does not pay for the
   top padding a second time. */
.bar--sub{padding-top:0;padding-bottom:clamp(10px,2vh,18px)}
.link{display:inline-flex;align-items:center;border:0;padding:0;background:none;
      font:inherit;font-size:14px;font-weight:500;color:var(--dim);text-decoration:none;
      cursor:pointer;transition:color .25s}
/* The arrow's distance from the word, and the nudge it takes on approach. A
   margin, not a flex gap, which the television does not have. */
.link > span{margin-right:10px;transition:margin-right .25s}
.link:hover{color:var(--ink)}
.link:hover > span{margin-right:15px}
.link:focus-visible{outline:2px solid var(--gold);outline-offset:4px}
.bar__gap{flex:1 1 auto}

/* ---- the rail --------------------------------------------------------------
   A GRID RATHER THAN A WRAPPING FLEX ROW, because grid gap works on the wall
   TV and flex gap does not. Below 860px the filters take one line and the
   sort takes the next, deterministically. */
.railwrap{display:grid;grid-template-columns:minmax(0,1fr);row-gap:10px;
  padding-bottom:clamp(20px,3.2vh,38px)}
@media (min-width:860px){
  .railwrap{grid-template-columns:minmax(0,1fr) auto;column-gap:24px;align-items:center}
}
.rail{display:flex;flex-wrap:nowrap;align-items:center;min-width:0;
  overflow-x:auto;padding-bottom:2px;
  scrollbar-width:thin;scrollbar-color:rgba(244,243,241,.16) transparent}
.rail > * + *{margin-left:6px}
.rail::-webkit-scrollbar{height:4px}
.rail::-webkit-scrollbar-thumb{background:rgba(244,243,241,.16)}
/* --dim at rest. These are 9,5-10,5px controls and --faint is about 3,7:1 on
   black — a size and a contrast agreeing with each other in the wrong
   direction. Quiet is done with the hairline box and the tracking. */
.chip{display:inline-flex;align-items:baseline;padding:5px 10px;white-space:nowrap;
  border:1px solid var(--rule);border-radius:2px;background:none;
  font:inherit;font-size:10.5px;font-weight:500;letter-spacing:.11em;text-transform:uppercase;
  color:var(--dim);cursor:pointer;
  transition:color .22s,border-color .22s,background-color .22s}
.chip:hover{color:var(--ink);border-color:var(--rule2)}
.chip[aria-pressed="true"]{color:var(--ink);border-color:var(--rule2);
  background:rgba(244,243,241,.05)}
.chip:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.chip__n{font-size:9.5px;letter-spacing:.04em;color:var(--dim);margin-left:7px;
  font-variant-numeric:tabular-nums}
.chip[aria-pressed="true"] .chip__n{color:var(--ink)}
.rail__div{flex:0 0 1px;align-self:stretch;background:var(--rule);margin:0 8px}
.rail__k{font-size:9.5px;font-weight:500;letter-spacing:.24em;text-transform:uppercase;
  color:var(--dim);white-space:nowrap;margin-right:3px}
.railright{display:flex;flex-wrap:wrap;align-items:center}
.railright > * + *{margin-left:16px}
.sortbox{display:inline-flex;align-items:baseline}
.sortbox > * + *{margin-left:9px}
.tbtn{border:0;padding:0;background:none;font:inherit;cursor:pointer;
  font-size:10.5px;font-weight:500;letter-spacing:.11em;text-transform:uppercase;
  color:var(--dim);transition:color .22s;white-space:nowrap}
.tbtn:hover{color:var(--ink)}
.tbtn[aria-pressed="true"]{color:var(--ink)}
.tbtn:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.tsep{color:var(--rule2);font-size:11px}

.lede{margin:0 0 clamp(14px,2.4vh,30px);font-family:var(--serif);font-style:italic;
      font-weight:300;font-size:clamp(15px,1.6vw,19px);color:var(--faint);
      min-height:1.3em}

/* ---- a collection ---------------------------------------------------------- */
.sec{padding-top:clamp(22px,3.4vh,42px)}
.sec:first-child{padding-top:0}
.sec__h{display:flex;align-items:center;margin-bottom:12px}
.sec__h > * + *{margin-left:14px}
/* A collection name is the owner's text and can be any length. nowrap keeps it
   on the heading's one line; min-width:0 with the ellipsis is what stops a
   long one pushing the whole page sideways on a 390px phone. */
.sec__n{margin:0;font-size:9.5px;font-weight:500;letter-spacing:.26em;
  text-transform:uppercase;color:var(--dim);white-space:nowrap;
  min-width:0;overflow:hidden;text-overflow:ellipsis}
.sec__r{flex:1 1 auto;height:1px;background:var(--rule)}
.sec__c{font-size:9.5px;color:var(--dim);font-variant-numeric:tabular-nums;flex:0 0 auto}

/* ---- the wall ---------------------------------------------------------------
   min(215px,100%) rather than a bare 215px: auto-fill cannot make a track
   narrower than its minimum, so on a window under about 250px the grid would
   be wider than the page and the whole document would scroll sideways. */
.wall{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(215px,100%),1fr));
  gap:clamp(20px,2.6vw,34px) clamp(14px,1.8vw,24px)}

/* TWO ACROSS ON A PHONE, the same ruling and the same breakpoint as the film
   wall: measured at 390px the track resolved to ONE 358px column, which for a
   page whose name is "the wall" is a column of sets and a very long scroll.
   Explicit columns rather than a lower minmax, for the same reason .railwrap
   is a grid: deterministic beats whatever the tracks happen to add up to.
   Above 483px auto-fill already resolves to two, so this changes nothing in
   483-520 — it fixes 320-483.

   THE STACK STILL FITS. The column gap at 2-up is 14px and the two hairlines
   push 8px at rest; on touch there is no approach, so the 12px fan never
   happens. The outermost column pushes 8px into a gutter that is never
   narrower than 16px, so nothing scrolls sideways — measured. */
@media (max-width:520px){
  .wall{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* The tile caption's phone size is NOT here. A media query adds no
     specificity and .tile__t's own rule is further down this file, so written
     here it would lose to it. It has its own block directly after that rule.
     (films.css records the same trap, measured at 16,5px on a 390px phone.) */
}

.tile__a{display:block;width:100%;text-align:left;text-decoration:none;color:inherit}
/* .is-on IS NOT DECORATION, it is the television's focus ring. :focus-visible
   does not exist before Chrome 86 and the wall TV runs Chrome 79, where a
   selector containing it is thrown away whole. The class is set by the page
   in focusCell() and in the focus handler, and every rule below that answers
   focus answers it too. */
/* THE RING NEEDS A COMPANION OVER A PICTURE. Gold is 8,72:1 on black, but
   these rings sit over a STILL, and gold on a blown-out frame can fall under
   3:1. The dark halo costs nothing on black and saves the ring on white.
   Two rules, never one list — Chrome 79 drops a list containing
   :focus-visible, which would take the .is-on fallback down with it. */
.tile__a:focus-visible{outline:2px solid var(--gold);outline-offset:3px;
  box-shadow:0 0 0 4px rgba(0,0,0,.65)}
.tile__a.is-on{outline:2px solid var(--gold);outline-offset:3px;
  box-shadow:0 0 0 4px rgba(0,0,0,.65)}

/* ---- THE STACK --------------------------------------------------------------
   Two hairlines offset up and to the right, behind the cover. A set reads as
   a set with no type at all.

   DRAWN ON THE WRAPPER, NOT ON THE PICTURE, and that is the whole trick: a
   locked 18+ set is sent no cover, so its still is a flat dark rectangle —
   and the hairlines are still behind it, because they belong to the tile
   rather than to the photograph it does not have.

   .tile__still carries z-index:1 so the two pseudo-elements (z-index:0) stay
   behind it. Without the pair, ::after comes after the children in tree order
   and would paint straight over the cover.

   THE OFFSETS STAY INSIDE THE GRID GAP. The column gap bottoms out at 14px
   and the row gap at 20px, so 8px at rest and 12px on approach can never
   reach the neighbouring tile; the outermost column pushes 12px into a gutter
   that is never narrower than 16px, so nothing makes the page scroll
   sideways. */
.tile__stack{position:relative;display:block}
.tile__stack::before,.tile__stack::after{content:"";position:absolute;
  top:0;right:0;bottom:0;left:0;z-index:0;pointer-events:none;
  border:1px solid var(--rule);border-radius:2px;background:#050505;
  transition:transform .45s var(--ease),border-color .3s}
.tile__stack::before{transform:translate(4px,-4px)}
.tile__stack::after{transform:translate(8px,-8px)}
/* The fan opens a little on approach. Two rules, not one list: Chrome 79
   drops a list containing :focus-visible and would take .is-on with it. */
.tile__a:hover .tile__stack::before,.tile__a:focus-visible .tile__stack::before{
  transform:translate(6px,-6px);border-color:var(--rule2)}
.tile__a:hover .tile__stack::after,.tile__a:focus-visible .tile__stack::after{
  transform:translate(12px,-12px);border-color:var(--rule2)}
.tile__a.is-on .tile__stack::before{transform:translate(6px,-6px);border-color:var(--rule2)}
.tile__a.is-on .tile__stack::after{transform:translate(12px,-12px);border-color:var(--rule2)}

/* The cover carries everything: the ratio, the hairline, the clipping. The
   image is never given a size of its own, so a frame of any dimension fills
   the box without distortion.

   THE RATIO IS A PADDING BOX FIRST. aspect-ratio arrived in Chrome 88 and the
   wall TV is Chrome 79, where the box would have no height at all and the
   whole grid would collapse into a column of captions. */
.tile__still{display:block;position:relative;z-index:1;overflow:hidden;
  border:1px solid var(--rule);border-radius:2px;background:#050505;
  transition:border-color .3s}
.tile__still::before{content:"";display:block;padding-top:56.25%}
@supports (aspect-ratio:16/9){
  .tile__still{aspect-ratio:16/9}
  .tile__still::before{display:none}
}
.tile__a:hover .tile__still{border-color:var(--rule2)}
/* Longhand rather than inset, for the same Chrome 79 reason. */
.tile__img{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;
  object-fit:cover;display:block;
  filter:grayscale(1) contrast(1.06) brightness(.78);
  transition:filter .45s ease,transform .45s var(--ease);transform:scale(1.005)}
.tile__a:hover .tile__img,.tile__a:focus-visible .tile__img{
  filter:grayscale(0) contrast(1) brightness(1);transform:scale(1.03)}
.tile__a.is-on .tile__img{
  filter:grayscale(0) contrast(1) brightness(1);transform:scale(1.03)}

/* ---- THE FLIP ---------------------------------------------------------------
   A set has no motion of its own, so the hover is a turned page: on the same
   450ms rest a film tile waits before it previews, the cover cross-fades to
   the set's SECOND photograph.

   It costs no request. The second thumbnail only exists on this page once the
   set itself has been opened, and a tile with nothing cached simply does not
   flip — see wantFlip() in photos.js, which never fires a fetch on hover.

   z-index 1: over the cover, under the gold edge at 2. */
.tile__flip{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;
  object-fit:cover;display:block;z-index:1;opacity:0;
  filter:grayscale(1) contrast(1.06) brightness(.78);
  transition:opacity .5s ease,filter .45s ease,transform .45s var(--ease);
  transform:scale(1.005)}
.tile__a.is-flip .tile__flip{opacity:1}
.tile__a:hover .tile__flip,.tile__a:focus-visible .tile__flip{
  filter:grayscale(0) contrast(1) brightness(1);transform:scale(1.03)}
.tile__a.is-on .tile__flip{
  filter:grayscale(0) contrast(1) brightness(1);transform:scale(1.03)}

/* THE ONE GOLD MARK ON A TILE. It sweeps rather than appears.
   z-index 2 because the flip is appended after it and, among positioned
   siblings with no z-index, the last one in the markup wins. */
.tile__edge{position:absolute;left:0;right:0;bottom:0;height:1px;z-index:2;
  background:var(--gold);
  transform:scaleX(0);transform-origin:left;transition:transform .5s var(--ease)}
.tile__a:hover .tile__edge,.tile__a:focus-visible .tile__edge{transform:scaleX(1)}
.tile__a.is-on .tile__edge{transform:scaleX(1)}

/* A TILE WITH NO PICTURE, and there are three honest reasons for one: the set
   is 18+ and this browser was sent no cover, it is 16+ and nobody has said
   they are old enough yet, or it has no cover on file. Each says which, and
   none of them is a broken image.

   MARGIN, NOT GAP. Flexbox gap is Chrome 84 and the wall TV is Chrome 79,
   where every flex gap on this page silently becomes zero — the padlock would
   sit on top of "18+". */
.tile__dark{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;
  flex-direction:column;align-items:center;justify-content:center;
  background:#070707}
.tile__dark > * + *{margin-top:9px}
/* text-indent cancels the trailing letter-space, which otherwise leaves a
   centred word sitting half a letter-space to the left of centre. */
.tile__18{font-size:11px;font-weight:500;letter-spacing:.26em;text-indent:.26em;
  color:var(--dim);font-variant-numeric:tabular-nums}
.tile__none{font-size:9.5px;font-weight:500;letter-spacing:.22em;text-indent:.22em;
  text-transform:uppercase;color:var(--dim)}

.tile__t{display:block;font-family:var(--serif);font-weight:300;font-size:16.5px;
  line-height:1.18;letter-spacing:-.01em;color:var(--dim);margin-top:11px;
  overflow-wrap:break-word;transition:color .3s}
.tile__a:hover .tile__t,.tile__a:focus-visible .tile__t{color:var(--ink)}
.tile__a.is-on .tile__t{color:var(--ink)}
/* AT TWO ACROSS ON A PHONE the measure is 172px at 390. 16,5px Fraunces is
   about sixteen characters a line there; 15px keeps a two-word title on one
   line more often and the rows stay closer to level. It has to sit AFTER the
   rule above: a media query adds no specificity, so the same selector later in
   the file wins wherever it is written. */
@media (max-width:520px){
  .tile__t{font-size:15px;line-height:1.2;margin-top:9px}
}
.tile__m{display:flex;align-items:baseline;margin-top:7px}
.tile__m > * + *{margin-left:9px}
.tile__no{font-size:9.5px;letter-spacing:.2em;color:var(--dim);
  font-variant-numeric:tabular-nums;transition:color .35s}
.tile__a:hover .tile__no,.tile__a:focus-visible .tile__no{color:var(--gold)}
.tile__a.is-on .tile__no{color:var(--gold)}
/* A locked tile's gold is already spent on the padlock, and a second mark
   would promise something the press does not deliver. */
.tile__a.is-locked:hover .tile__no,.tile__a.is-locked:focus-visible .tile__no{color:var(--dim)}
.tile__a.is-locked.is-on .tile__no{color:var(--dim)}
.tile__sp{flex:1 1 auto}

/* ---- a set that has not arrived yet -----------------------------------------
   A DASHED FRAME, in the set's own place in the running order. Not a link and
   not focusable: there is nothing behind it to press. */
.soon{display:block;width:100%;text-align:left}
.soon .tile__still{border-style:dashed;border-color:rgba(244,243,241,.2)}
/* The stack goes dashed with it, or the tile reads as a finished set behind a
   provisional cover. */
.soon .tile__stack::before,.soon .tile__stack::after{
  border-style:dashed;border-color:rgba(244,243,241,.14)}
.soon__k{font-size:9.5px;font-weight:500;letter-spacing:.24em;text-transform:uppercase;
  color:var(--dim)}
.soon__d{font-size:12.5px;color:var(--ink);font-variant-numeric:tabular-nums}
/* The only moving gold on the wall, and it has earned it: a number that is
   visibly running down is the difference between an announcement and a date. */
.soon__c{font-size:15px;color:var(--gold);font-variant-numeric:tabular-nums;
  letter-spacing:.06em}
.soon .tile__t{color:var(--dim)}
.soon .tile__no,.soon .len{color:var(--faint)}

/* ---- the rating badge and the count ----------------------------------------
   --dim at rest rather than --faint. At ten pixels this is the smallest type
   on the page and it is the one label somebody may need before they press
   anything. */
.badge{font-size:9px;font-weight:500;letter-spacing:.1em;padding:2px 5px;
  display:inline-flex;align-items:center;white-space:nowrap;
  border:1px solid var(--rule);border-radius:2px;color:var(--dim);
  font-variant-numeric:tabular-nums;transition:color .3s,border-color .3s}
.badge .lock{margin-right:5px}
.tile__a:hover .badge,.tile__a:focus-visible .badge{color:var(--ink);border-color:var(--rule2)}
.tile__a.is-on .badge{color:var(--ink);border-color:var(--rule2)}
/* WHERE THE RUNNING TIME GOES. A set has none; it has a number of
   photographs, and it is written out in words rather than left as a bare
   figure, because "24" on its own beside a title is a number about nothing. */
.len{font-size:12px;color:var(--dim);font-variant-numeric:tabular-nums}
/* The padlock, at two sizes: beside the rating on the tile's own line, and
   large in the middle of the dark cover. */
.lock{display:inline-flex;color:var(--gold);flex:0 0 auto}
.lock--lg{color:var(--gold);opacity:.92}

.note{margin:0 0 18px;font-size:13px;line-height:1.6;color:var(--gold)}
.note a{color:var(--ink);text-decoration:underline;text-underline-offset:.2em;
  text-decoration-thickness:1px}
.note a:focus-visible{outline:2px solid var(--gold);outline-offset:3px}

.none{margin:22px 0 0;font-family:var(--serif);font-style:italic;font-weight:300;
      font-size:clamp(16px,1.8vw,20px);color:var(--faint)}

/* ---- one set: the head ------------------------------------------------------
   The measure is capped in characters rather than pixels: this is the only
   prose on the page and a description running the full 1760px would be a
   line nobody can track back to the start of. */
.head{margin:0 0 clamp(20px,3.4vh,38px);max-width:66ch}
.head__k{margin:0;font-size:9.5px;font-weight:500;letter-spacing:.26em;
  text-transform:uppercase;color:var(--dim)}
.head__t{margin:10px 0 0;font-family:var(--serif);font-weight:300;
  font-size:clamp(28px,4.4vw,56px);line-height:1.02;letter-spacing:-.025em;color:var(--ink)}
.head__d{margin:13px 0 0;font-family:var(--serif);font-style:italic;font-weight:300;
  font-size:clamp(15px,1.6vw,19px);line-height:1.45;color:var(--dim)}
.head__m{display:flex;flex-wrap:wrap;align-items:center;margin:clamp(14px,2vh,20px) 0 0}
.head__m > *{margin:0 12px 0 0}

/* ---- one set: the contact sheet ---------------------------------------------
   UNIFORM CELLS, which is what makes it a sheet rather than a second wall: a
   set mixes portrait and landscape and a grid that honoured every frame's own
   ratio would be a ragged column of different-sized rectangles. Square, cover
   cropped, the whole picture one press away.

   160px is two across a 390px phone and ten across the 1760px measure. */
.sheet{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(160px,100%),1fr));
  gap:clamp(10px,1.4vw,18px)}

/* THREE ACROSS ON A PHONE, and the reason is in the name. Measured at 390px
   the sheet resolved to TWO 174px cells, which is a gallery; a contact sheet
   is MEANT to be dense. Three at 115px is 345 physical pixels at 3x, which is
   plenty to recognise a frame by, and the whole picture is one press away.
   The gap drops 10px -> 6px with it, because at this size the gap is a larger
   share of the row than the design ever intended. */
@media (max-width:520px){
  .sheet{grid-template-columns:repeat(3,minmax(0,1fr));gap:6px}
}
.shot__a{display:block;width:100%;text-decoration:none;color:inherit}
/* The same dark companion as the wall's ring, and here it matters more: a
   contact sheet cell is IN COLOUR at rest, so a bright frame under a gold
   hairline is the ordinary case rather than the unlucky one. Two rules. */
.shot__a:focus-visible{outline:2px solid var(--gold);outline-offset:3px;
  box-shadow:0 0 0 4px rgba(0,0,0,.65)}
.shot__a.is-on{outline:2px solid var(--gold);outline-offset:3px;
  box-shadow:0 0 0 4px rgba(0,0,0,.65)}
.shot__box{display:block;position:relative;overflow:hidden;
  border:1px solid var(--rule);border-radius:2px;background:#050505;
  transition:border-color .3s}
.shot__box::before{content:"";display:block;padding-top:100%}
@supports (aspect-ratio:1/1){
  .shot__box{aspect-ratio:1/1}
  .shot__box::before{display:none}
}
.shot__a:hover .shot__box{border-color:var(--rule2)}
/* IN COLOUR AT REST. The one deliberate break with the house language, and it
   is approved: the wall is grayscale, and a set opening into colour is a
   change of state you can feel without being told about. The rest state is
   held a little under full brightness so there is still somewhere to go on
   approach. */
.shot__img{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;
  object-fit:cover;display:block;filter:brightness(.86);
  transition:filter .4s ease,transform .45s var(--ease);transform:scale(1.005)}
.shot__a:hover .shot__img,.shot__a:focus-visible .shot__img{
  filter:brightness(1);transform:scale(1.045)}
.shot__a.is-on .shot__img{filter:brightness(1);transform:scale(1.045)}
.shot__edge{position:absolute;left:0;right:0;bottom:0;height:1px;z-index:2;
  background:var(--gold);
  transform:scaleX(0);transform-origin:left;transition:transform .5s var(--ease)}
.shot__a:hover .shot__edge,.shot__a:focus-visible .shot__edge{transform:scaleX(1)}
.shot__a.is-on .shot__edge{transform:scaleX(1)}

/* ---- the lightbox -----------------------------------------------------------
   THE CINEMA'S CONSTRUCTION, with a still where the film goes. The class
   names are the films page's on purpose: the two files can be read side by
   side and a fix to one is findable in the other.

   Longhand offsets, not the `inset` shorthand: that is Chrome 87 and the wall
   TV is Chrome 79, where a fixed element with no offsets at all sits at its
   static position and the lightbox stops being an overlay. The grid `gap`
   below is fine — grid gap is Chrome 66; it is FLEX gap that is Chrome 84. */
/* ONE COLUMN, EXPLICITLY minmax(0,1fr), and this is the film side's measured
   bug fix carried over rather than tidying. The implicit column is `auto`,
   which is max-content sized, so any child whose preferred width exceeds the
   window widens the track and takes the stage, the caption row and the buttons
   out past the frame with it. max-width:100% cannot save that: the percentage
   resolves against the very column the content has already widened. On the
   films page it put Close at x 442..504 in a 390px window.

   MEASURED HERE, AND IT DOES NOT HAPPEN TODAY: at 390x844 and at 430x932 the
   stage, the picture, the caption row and Close are identical before and after
   this declaration, for all five test shapes — 3:2, 2:3, 1:1, 8:3 and a bright
   3:2 — with the bytes loaded, with the bytes refused, and with aspect-ratio
   forced onto the <img>. The difference is the element: the film's <video> had
   a CSS ratio and no intrinsic size, so its preferred width was 56svh x 16/9 =
   839px; a loaded <img> is a replaced box whose contribution Chrome clamps to
   the available width. So this is written for the latent track, not for a live
   symptom — the next bare `aspect-ratio` or wide inline child in the stage
   would wake it, and the two stylesheets have to say the same thing. */
.cinema{position:fixed;top:0;right:0;bottom:0;left:0;z-index:60;
        display:grid;grid-template-rows:1fr auto;grid-template-columns:minmax(0,1fr);
        gap:clamp(10px,1.8vh,24px);
        background:rgba(0,0,0,.94);
        -webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);
        padding:clamp(16px,3.2vh,44px) clamp(14px,3vw,40px);
        /* position:fixed over the whole viewport is exactly where the insets
           bite. Plain value first, env() after — see .page. */
        padding:calc(clamp(16px,3.2vh,44px) + env(safe-area-inset-top))
                calc(clamp(14px,3vw,40px) + env(safe-area-inset-right))
                calc(clamp(16px,3.2vh,44px) + env(safe-area-inset-bottom))
                calc(clamp(14px,3vw,40px) + env(safe-area-inset-left))}

/* The picture is POSITIONED against this box rather than laid out inside it.
   Laid out as an ordinary centred item, max-height:100% resolves to nothing
   at all — a percentage needs a containing block with a definite height, and
   the 1fr track does not give the stage one, so a 6000px-tall photograph
   would paint straight over its own caption. Absolute positioning gives those
   percentages a definite box: four zero offsets with auto margins and auto
   dimensions centre the picture and, whatever its shape, it cannot escape.

   tabindex="-1" is on this element, so there is somewhere to put focus when
   the lightbox opens; the ring is suppressed because the whole screen is
   plainly the thing that just happened. */
.cinema__stage{position:relative;min-height:0;overflow:visible}
.cinema__stage:focus{outline:none}
.cinema__stage img{position:absolute;top:0;right:0;bottom:0;left:0;
  margin:auto;width:auto;height:auto;
  max-width:100%;max-height:100%;
  /* On a tall screen, stop short of the edges: a picture sized to the last
     pixel reads as a fullscreen that failed rather than as a framed print. A
     ceiling only — where room is short the stage still decides. The plain
     100% above is the fallback for anything without svh or min(), which
     includes the wall TV. */
  max-height:min(100%,80svh);
  display:block;background:#050505;z-index:1;
  /* THE ZOOM'S HOME POSITION. transform-origin is written out rather than
     left to the default so the arithmetic in photos.js has something to point
     at: it translates and scales around the picture's centre, which is what
     lets a double tap be anchored on the point that was tapped.

     touch-action:none ON THE PICTURE, at all times and not only while zoomed,
     and that is a deliberate substitution rather than a removal: every gesture
     that lands on the photograph is now the page's own, so a double tap is our
     1x/2,5x toggle instead of the browser's page zoom, and a pinch scales the
     photograph instead of the document. It is also what makes all four touch
     handlers in photos.js passive — nothing has to preventDefault.
     WHAT IS NOT TAKEN AWAY: the viewport carries no maximum-scale and no
     user-scalable=no, so pinching the caption row, the wall or the contact
     sheet still zooms the page exactly as before. Only the picture answers
     differently, and it answers with more of itself. Chrome 36, so the
     television has it — not that it has a finger. */
  transform-origin:center center;
  touch-action:none;
  transition:transform 260ms var(--ease)}

/* ---- the zoomed picture ----------------------------------------------------
   A DESIGNED STATE, not a browser accident. Before this a pinch inside the
   lightbox zoomed the PAGE — a position:fixed overlay panned unreliably
   across engines and the caption row went with it.

   Double tap toggles 1x and --zoom-tap, anchored on the tap. A pinch scales
   freely to --zoom-max. One finger pans while zoomed, and the horizontal
   swipe to the next photograph is suspended for as long as it does — see
   photos.js, where panning right would otherwise skip the frame.
   -------------------------------------------------------------------------- */
/* Only while a gesture is live, so the browser does not keep a composited
   layer for a picture nobody is touching, and so a pan follows the finger
   instead of easing 260ms behind it. */
.cinema__stage img.is-grab{transition:none;will-change:transform}
/* THE STAGE BECOMES THE WINDOW the photograph is panned behind. The stage is
   `overflow:visible` at rest and has to be: at 2,5x the picture is larger than
   its own frame, and without this it would paint over the caption row and out
   past the edge of the screen. Only while zoomed, so nothing about the resting
   lightbox changes. touch-action goes with it, for the gesture that continues
   onto the black beside the picture. */
.cinema.is-zoom .cinema__stage{overflow:hidden;touch-action:none}
/* Reachable on a screen that has both a finger and a cursor — a touch laptop,
   an iPad with a trackpad — which is the only place a zoomed picture and a
   pointer are on the same screen. */
.cinema.is-zoom .cinema__stage img{cursor:grab}
/* The zoom factor takes the dimensions' slot while it is on — tabular figures
   already, and photos.js writes it Danish ("2,5x"). Nothing new is drawn. */

/* Margins rather than flex gap, for the television. The vertical half is on
   every child so a wrapped foot keeps its rhythm. */
.cinema__foot{display:flex;flex-wrap:wrap;align-items:baseline;
              padding-top:clamp(7px,2vw,17px)}
.cinema__foot > *{margin:5px 0}
.cinema__foot > * + *{margin-left:22px}
/* THE IDENTITY, in one item. It exists so the foot can become a grid above
   860px without the three spans each needing a cell. The geometry below
   reproduces exactly what they had as direct children: the 5px vertical
   margins stay on the SPANS and the wrapper takes none, so the row is not a
   pixel taller than it was. Shorthand first, then the gap — the other way
   round `margin:5px 0` silently zeroes margin-left and the number, the title
   and the dimensions run into each other. */
.cinema__foot > .cinema__id{margin-top:0;margin-bottom:0}
.cinema__id{display:flex;flex-wrap:wrap;align-items:baseline;min-width:0}
.cinema__id > *{margin:5px 0}
.cinema__id > * + *{margin-left:22px}
/* --dim, not --faint. --faint is about 3,7:1 on black, which fails AA for
   small text, and these are 11px and 12px. This file already moves --faint to
   --dim wherever a line has to be read; these two were missed, as they were on
   the films page. */
.cinema__no{font-size:11px;letter-spacing:.2em;color:var(--dim);
  font-variant-numeric:tabular-nums}
.cinema__title{font-family:var(--serif);font-weight:300;font-size:clamp(19px,2.3vw,28px);
               letter-spacing:-.01em;color:var(--ink)}
.cinema__len{font-size:12px;color:var(--dim);font-variant-numeric:tabular-nums}
/* An empty full-width item ends the flex line before it, which is what keeps
   BOTH the break and the 66ch measure — flex-basis:100% on the caption alone
   never broke the row, because a flex line is broken on the hypothetical main
   size and 66ch fits beside three buttons. */
.cinema__break{flex-basis:100%;height:0;margin:0}
.cinema__desc{flex-basis:100%;margin:0;max-width:66ch;font-size:13px;line-height:1.6;
              color:var(--dim)}
.cinema__foot > .cinema__break{margin-left:0}
.cinema__foot > .cinema__desc{margin-left:0}
/* Written as a child selector so it OUTWEIGHS the 22px above. */
.cinema__foot > .cinema__acts{margin-left:auto}
.cinema__acts{display:flex}
.cinema__acts > * + *{margin-left:6px}
.cbtn{border:1px solid var(--rule);border-radius:999px;background:none;color:var(--dim);
      font:inherit;font-size:12px;font-weight:500;padding:7px 15px;cursor:pointer;
      transition:color .2s,border-color .2s}
.cbtn:hover:not(:disabled){color:var(--gold);border-color:var(--gold)}
.cbtn:disabled{opacity:.3;cursor:default}
.cbtn:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.cbtn--solid{border-color:var(--ink);background:var(--ink);color:#000}
.cbtn--solid:hover:not(:disabled){background:var(--gold);border-color:var(--gold);color:#000}
/* --dim, not --faint. This is the DISMISS control of a modal and --faint
   measures about 3,7:1 on black at 12px. Quiet is done by dropping the
   border, not by going grey enough to be hard to find. */
.cbtn--quiet{border-color:transparent;color:var(--dim)}
.cbtn--quiet:hover:not(:disabled){border-color:transparent;color:var(--ink)}

/* ---- the two age dialogs ----------------------------------------------------
   Same construction as the lightbox: a fixed overlay div toggled with
   `hidden`, the element itself as the backdrop and the box as its child.

   z-index 70, ABOVE the lightbox's 60, because a pass that runs out
   mid-viewing has to be able to raise the form over a picture that is already
   on screen. In practice the picture is put away first — two things that both
   answer Escape must never be on screen together — but the stacking has to be
   right for the frame in between.

   flex with margin:auto rather than place-items:center. A grid-centred box
   taller than the window is cut off at the TOP, where the heading is. */
.veil{position:fixed;top:0;right:0;bottom:0;left:0;z-index:70;
      display:flex;align-items:center;
      justify-content:center;padding:20px;overflow-y:auto;overscroll-behavior:contain;
      /* The plain 20px above is the fallback; see .page. */
      padding:calc(20px + env(safe-area-inset-top)) calc(20px + env(safe-area-inset-right))
              calc(20px + env(safe-area-inset-bottom)) calc(20px + env(safe-area-inset-left));
      background:rgba(0,0,0,.92);
      -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
      animation:veilIn .2s ease-out}
@keyframes veilIn{from{opacity:0}to{opacity:1}}

.veil__box{width:min(420px,100%);margin:auto;padding:clamp(22px,3vw,30px);
           border:1px solid var(--rule);border-left:2px solid var(--gold);
           background:#0a0a0a;text-align:left}
.veil__box--tight{width:min(380px,100%)}
.veil__title{margin:0 0 10px;font-family:var(--serif);font-weight:300;
             font-size:clamp(22px,2.6vw,29px);letter-spacing:-.015em;color:var(--ink)}
.veil__say{margin:0 0 20px;font-size:13px;line-height:1.6;color:var(--dim)}
.veil__box--tight .veil__say{margin-bottom:24px}
/* Gold, like every other message on this subdomain that is trying to tell you
   something went wrong. Red would be a fourth colour on a page with three. */
.veil__bad{margin:0 0 16px;font-size:13px;line-height:1.55;color:var(--gold)}

.veil__field{margin:0 0 14px}
.veil__label{display:block;margin:0 0 5px;font-size:11px;font-weight:500;
             letter-spacing:.2em;text-transform:uppercase;color:var(--dim)}
.veil__in{width:100%;padding:9px 12px;border:1px solid var(--rule2);border-radius:2px;
          background:#000;color:var(--ink);font:inherit;font-size:14px}
.veil__in:focus-visible{outline:2px solid var(--gold);outline-offset:1px}
.veil__in[aria-invalid="true"]{border-color:var(--gold)}
.veil__err{margin:5px 0 0;font-size:12px;line-height:1.5;color:var(--gold)}

.veil__tick{display:flex;align-items:flex-start;margin:18px 0 22px;
            font-size:12px;line-height:1.55;color:var(--dim);cursor:pointer}
/* margin-right rather than the container's gap: flex gap is Chrome 84 and the
   television would have put the tick box hard against the sentence. */
.veil__tick input{flex:none;width:16px;height:16px;margin:1px 10px 0 0;accent-color:var(--gold)}
.veil__tick input:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* The server's own sentence, of unknown length and possibly carrying a long
   unbroken address in it. */
.veil__consent{min-width:0;overflow-wrap:anywhere}

.veil__read{margin:-12px 0 20px}
.veil__acts{display:flex;flex-wrap:wrap;align-items:center;margin-bottom:-10px}
.veil__acts > *{margin:0 10px 10px 0}

/* Chrome's autofill paints its own near-white background over a dark field.
   The inset shadow is the only way to override it and -webkit-text-fill-color
   the only way to set the text colour. */
.veil__in:-webkit-autofill,
.veil__in:-webkit-autofill:focus{
  -webkit-box-shadow:0 0 0 40px #000 inset;
  -webkit-text-fill-color:var(--ink)}

.sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ---- the device classes -----------------------------------------------------
   One layout, bent where a device actually differs. Nothing below is keyed to
   a device NAME: the questions are can the pointer hover, is the screen
   narrow and upright, and is the type being read from across a room.

   THE WHOLE REGISTER, because a breakpoint cannot be a custom property — a
   media-query condition will not take one and there is no preprocessor — so
   the only way to keep the numbers honest is to list them in one place. These
   are the films page's numbers, deliberately: the two walls are the same wall
   twice and a breakpoint that differed between them would be a bug waiting
   for a window to be dragged.

     (max-width:520px)                   phone portrait: wall 2-up, sheet 3-up,
                                         phone type scale
     (max-width:720px)                   the italic lede goes --dim
     (max-width:720px) and (orientation:portrait)
                                         the lightbox: picture at the top,
                                         words under it
     (min-width:860px)                   the rail splits; the caption becomes
                                         a grid
     (min-width:1900px)                  --wide:2040px
     (pointer:coarse)                    targets, hover undo, type floor, the
                                         16px input floor
     (hover:hover) and (pointer:fine)    for NEW rules only: hover payoffs
     (hover:none)                        the press payoff
     (prefers-reduced-motion:reduce)     motion off
     .is-tv                              from the UA. SIZE ONLY.

   NOT HERE, and that is a scope line rather than an omission: the films page's
   (max-height:560px) and (orientation:landscape) class, which turns the player
   into a bare picture with a floating band. A photograph has no running time,
   no queue and no ambient glow, so there is nothing for such a band to carry,
   and the lightbox in a 844x390 window measures a 771px picture over a 59px
   caption row — cramped, not broken. Left for a pass of its own.

   WHAT IS KEYED TO WHAT. Capability — (pointer:coarse) / (hover:none) —
   whenever the question is about INPUT: target sizes, sticky hover payoffs,
   the 16px input floor (an iPad at 1024px wide is still iOS Safari and still
   zooms), whether a long press is a gesture. Width whenever the question is
   about ROOM: column counts, type scale, whether the caption sits under the
   picture. The user agent for nothing but .is-tv, and there for nothing but
   size.
   -------------------------------------------------------------------------- */

/* ---- a touch screen ------------------------------------------------------ */
@media (pointer:coarse){
  /* 32px high is a mouse target. A finger needs 44, which is the figure both
     Apple and Google publish. */
  .cbtn{padding:12px 18px;font-size:13px;min-height:44px}
  .chip,.tbtn{min-height:34px}
  .chip{padding-top:0;padding-bottom:0;align-items:center}
  /* HOVER STYLING STICKS AFTER A TAP on a touch screen, leaving a tile looking
     chosen long after the finger has gone — full colour, gold edge drawn, the
     stack fanned open, which reads as "this one is open". */
  .tile__a:hover .tile__img{filter:grayscale(1) contrast(1.06) brightness(.78);transform:scale(1.005)}
  .tile__a:hover .tile__flip{filter:grayscale(1) contrast(1.06) brightness(.78);transform:scale(1.005)}
  .tile__a:hover .tile__edge{transform:scaleX(0)}
  .tile__a:hover .tile__no{color:var(--dim)}
  .tile__a:hover .tile__t{color:var(--dim)}
  .tile__a:hover .badge{color:var(--dim);border-color:var(--rule)}
  .tile__a:hover .tile__stack::before{transform:translate(4px,-4px);border-color:var(--rule)}
  .tile__a:hover .tile__stack::after{transform:translate(8px,-8px);border-color:var(--rule)}
  .shot__a:hover .shot__img{filter:brightness(.86);transform:scale(1.005)}
  .shot__a:hover .shot__edge{transform:scaleX(0)}
  /* ...but focus is set by a key or by the page, never by a finger, so the
     focus half of each pair above survives untouched. */

  /* THE 16px INPUT FLOOR. iOS Safari zooms the page whenever a focused input's
     computed size is under 16px, and it does not zoom back out — so
     registering for an 18+ set zoomed the page three times and left it zoomed
     over a modal. Keyed to the pointer and not to a width, because an iPad at
     1024px wide is still iOS Safari.
     THERE IS NO .find HERE. The films page's search box is the other half of
     §8.1 and it is fixed in films.css; this page has no search field at all
     (photos.js says why: there are not twelve sets), so the 18+ form's three
     fields are the whole of the problem on this page. */
  .veil__in{font-size:16px;padding:11px 12px}

  /* A BIGGER TARGET, NOT A BIGGER CHIP. 34px visual plus 5px top and bottom is
     44px of target with no pixel of the design moved. The rail's own
     separation goes 6px -> 10px first, so two neighbouring extenders can never
     overlap. */
  .rail > * + *{margin-left:10px}
  .chip,.tbtn{position:relative}
  .chip::after,.tbtn::after{content:"";position:absolute;
    top:-5px;right:0;bottom:-5px;left:0}
  .sortbox > * + *{margin-left:14px}

  /* A TYPE FLOOR, keyed to coarse rather than to width: the reason is
     sub-pixel rendering plus the finger, and a 390px desktop window needs
     neither. 9px and 9,5px Inter Tight uppercase at .1-.26em tracking,
     antialiased and unhinted, is legible-but-fuzzy on a phone. 11px for
     anything read BEFORE pressing (the rating), 10px for counters. */
  .badge{font-size:11px;padding:3px 6px}
  .chip{font-size:11px}
  .chip__n{font-size:10px}
  .tbtn{font-size:11px}
  .rail__k,.sec__n,.soon__k{font-size:10px}
  .tile__no{font-size:10px}
  .cinema__no{font-size:11px}
  /* The set page's own labels, which the films page has no equivalent of. */
  .head__k{font-size:10px}
  .tile__none{font-size:10px}
}

/* ---- a finger, where a pointer would have hovered -------------------------
   THE FLIP STAYS A DESKTOP ENRICHMENT, and that is the decision rather than an
   omission. It needs the set's SECOND thumbnail, whose URL only exists on this
   page once the set itself has been opened — wantFlip() in photos.js
   deliberately fires no request on approach — and on a touch screen there is
   no rest gesture to hang it on: `focus` arrives on the way to a tap, so a
   cross-fade there reads as the tile glitching.

   What carries the meaning instead is already drawn: THE STACK. Two offset
   hairlines behind the cover say "a set of photographs" with no type and no
   motion, and they survive a locked tile because they are on the wrapper.

   What was genuinely missing is an answer to the press. Until now a finger got
   nothing at all between touching a tile and the set opening, on either
   surface. So the hover payoff arrives on :active instead — the same two
   gestures, colour returning and the gold hairline drawing, on the press
   rather than on the approach. */
@media (hover:none){
  /* .tile__flip is in the list although a finger never earns one: a keyboard
     or a remote can, and (hover:none) matches both an iPad with a keyboard and
     the wall TV. With the flip showing on top at opacity 1, returning colour
     only to the cover underneath would look like a press that did nothing. */
  .tile__a:active .tile__img,.tile__a:active .tile__flip{
    filter:grayscale(0) contrast(1) brightness(1);transform:scale(1.015);
    transition:filter 160ms var(--ease),transform 160ms var(--ease)}
  .tile__a:active .tile__edge{transform:scaleX(1);
    transition:transform 180ms var(--ease)}
  .tile__a:active .tile__t{color:var(--ink)}
  /* A locked tile's gold is already spent on the padlock. */
  .tile__a.is-locked:active .tile__edge{transform:scaleX(0)}
  /* The stack fans on the press too — it is the one thing on a set tile that
     says "set", so it is the one thing that should answer. */
  .tile__a:active .tile__stack::before{transform:translate(6px,-6px);
    border-color:var(--rule2);transition:transform 180ms var(--ease)}
  .tile__a:active .tile__stack::after{transform:translate(10px,-10px);
    border-color:var(--rule2);transition:transform 180ms var(--ease)}

  /* The contact sheet answers the same way. It is already in colour, so what
     is left to give is the last of the brightness and the gold edge. */
  .shot__a:active .shot__img{filter:brightness(1);transform:scale(1.02);
    transition:filter 160ms var(--ease),transform 160ms var(--ease)}
  .shot__a:active .shot__edge{transform:scaleX(1);
    transition:transform 180ms var(--ease)}

  /* A long press on a picture offers "Download linked file" / "Save image" on
     both iOS and Android. Friction rather than protection — mark.js says the
     same about itself — but it is the one place the site invites a gesture and
     then looks careless, and here it is a display copy of a photograph rather
     than a frame of a film. The lightbox picture is in the list for exactly
     that reason. */
  .tile__a,.tile__img,.tile__flip,.shot__a,.shot__img,.cinema__stage img{
    -webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
}

/* The lede is 15px on a phone, where --faint (about 3,7:1) no longer clears
   even the 3:1 LARGE-text floor — large text starts at 18,66px. At 19px on a
   desktop this italic line qualifies and --faint is right; here it is not. */
@media (max-width:720px){
  .lede{color:var(--dim)}
}

/* ---- a phone held upright ------------------------------------------------ */
/* A landscape photograph in a portrait window is limited by WIDTH, so centred
   in a full-height stage it sits as a stamp in a sea of black with the caption
   pinned half a screen away from the picture it describes. */
@media (max-width:720px) and (orientation:portrait){
  .cinema{grid-template-rows:auto auto;align-content:start;
          padding:clamp(12px,2vh,20px) clamp(12px,3vw,20px)}
  /* Back into the flow. The absolute positioning upstairs exists to solve a
     percentage that cannot resolve against a 1fr track; here the picture is
     capped in vh, which is a real length and needs no containing block.
     POSITION:RELATIVE, NOT STATIC — the watermark box is an absolutely
     positioned child measured against this element. */
  .cinema__stage{position:relative}
  .cinema__stage img{position:static;margin:0 auto;display:block;
                     width:auto;height:auto;max-width:100%;
                     max-height:62vh;max-height:62svh}
  /* THE ROW RHYTHM FIRST, THE GAP SECOND, and the order is load-bearing.
     `.cinema__foot > *` and `.cinema__foot > * + *` have the SAME specificity,
     so whichever is written last wins — and with the shorthand last, its
     implied margin-left:0 silently undoes the 14px and the number, the title
     and the dimensions jam into one another. Measured at 390x844. (films.css
     has these two the other way round in its own portrait block and the same
     thing happens there.) */
  .cinema__foot > *{margin:4px 0}
  .cinema__foot > * + *{margin-left:14px}
  /* The identity row keeps the same rhythm now that it is one item. Shorthand
     first, then the gap — the paragraph above is the whole reason the order
     matters. */
  .cinema__foot > .cinema__id{margin-top:0;margin-bottom:0}
  .cinema__id > *{margin:4px 0}
  .cinema__id > * + *{margin-left:14px}
  .cinema__foot > .cinema__acts{margin-left:0;width:100%}
  .cbtn{flex:1 1 auto;text-align:center}
  /* CLOSE TAKES ITS NATURAL WIDTH, at the far right with clear space before
     it. flex:1 1 auto next to Next, 6px apart, means a mis-hit on a 106px pill
     puts away the photograph somebody is looking at. Measured at 390x844:
     Close was at x 272..378 hard against Next. */
  .cinema__acts > * + *{margin-left:10px}
  .cinema__acts #sClose{flex:0 0 auto;margin-left:auto}
}

/* ---- a window with room: the caption becomes a grid ------------------------
   .cinema__foot was a flex row doing a grid's job, held together by
   margin-left:22px plus two documented hacks — .cinema__break, and a
   child-combinator rewrite of .cinema__acts to win a specificity fight. A grid
   row break is not a hack, so above 860 both go. Grid `gap` is Chrome 66, so
   the television is safe; it is FLEX gap that is Chrome 84. Below 860 the flex
   layout is untouched. The films page carries the same block. */
@media (min-width:860px){
  .cinema__foot{display:grid;
    grid-template-columns:minmax(0,1fr) auto;
    column-gap:var(--s6);row-gap:10px;align-items:baseline}
  .cinema__id{grid-column:1;grid-row:1}
  .cinema__acts{grid-column:2;grid-row:1;margin-left:0}
  .cinema__desc{grid-column:1;grid-row:2;margin-left:0;max-width:66ch}
  /* Kept in the DOM and switched off, so there is no HTML change and the flex
     layout below 860 is intact. */
  .cinema__break{display:none}
}

/* 1.760px leaves a third of a 2560px monitor black — 400px of gutter either
   side plus the pad. Eight 215px tiles and seven 24px gaps is 1.888px, which
   fits 2.040 with the pad: 8-up instead of 6-up. Nothing below 1900 moves.
   Here as well as on the films page because the two walls are seen one after
   the other on the same monitor, and one of them being a strip would read as
   a bug in whichever was second. */
@media (min-width:1900px){
  :root{--wide:2040px}
}

/* ---- a television -------------------------------------------------------- */
/* Set from the user agent, which is a guess, and therefore used for NOTHING
   BUT SIZE — see IS_TV in photos.js. At three metres, 11px is not small type,
   it is an absence of type.

   The padding is for overscan. A television may crop several per cent of every
   edge, and anything flush to the frame is simply not on the screen. */
.is-tv body{font-size:18px}
.is-tv .page{padding-left:clamp(40px,4vw,72px);padding-right:clamp(40px,4vw,72px);
             padding-bottom:clamp(40px,5vh,64px)}
.is-tv .bar{padding-top:clamp(28px,3.5vh,44px);padding-bottom:clamp(10px,1.5vh,18px)}
.is-tv .bar--sub{padding-top:0}
.is-tv .link{font-size:20px}
.is-tv .lede{font-size:24px}
.is-tv .cinema__no{font-size:16px}
.is-tv .len,.is-tv .cinema__len{font-size:17px}
.is-tv .cinema__desc{font-size:19px;max-width:none}
.is-tv .cbtn{font-size:17px;padding:10px 22px}
/* FEWER, BIGGER TILES. At three metres a 215px tile is a postage stamp and its
   16,5px caption is not type at all. Four across the 1760px measure instead of
   six, with the caption scaled to match. */
.is-tv .wall{grid-template-columns:repeat(auto-fill,minmax(min(360px,100%),1fr))}
/* And the contact sheet with it: five across rather than ten. */
.is-tv .sheet{grid-template-columns:repeat(auto-fill,minmax(min(300px,100%),1fr))}
/* A 4px hairline is not a hairline at three metres, it is nothing. The fan is
   scaled with the tile; 18px still clears the 24px column gap. */
.is-tv .tile__stack::before{transform:translate(6px,-6px)}
.is-tv .tile__stack::after{transform:translate(12px,-12px)}
.is-tv .tile__a:hover .tile__stack::before,
.is-tv .tile__a:focus-visible .tile__stack::before{transform:translate(9px,-9px)}
.is-tv .tile__a:hover .tile__stack::after,
.is-tv .tile__a:focus-visible .tile__stack::after{transform:translate(18px,-18px)}
.is-tv .tile__a.is-on .tile__stack::before{transform:translate(9px,-9px)}
.is-tv .tile__a.is-on .tile__stack::after{transform:translate(18px,-18px)}
.is-tv .tile__t{font-size:26px;margin-top:14px}
.is-tv .tile__no{font-size:15px}
.is-tv .tile__18{font-size:19px}
.is-tv .tile__none{font-size:15px}
.is-tv .sec__n{font-size:15px}
.is-tv .sec__c{font-size:14px}
.is-tv .chip,.is-tv .tbtn,.is-tv .rail__k{font-size:15px}
.is-tv .chip__n{font-size:14px}
.is-tv .head__k{font-size:14px}
.is-tv .head__d{font-size:22px}
.is-tv .soon__k{font-size:14px}
.is-tv .soon__d{font-size:17px}
.is-tv .soon__c{font-size:22px}
.is-tv .note{font-size:19px}
/* A rating nobody can read from the sofa is a rating that is not there. The
   dialogs get the same treatment: a television can reach the 18+ form with
   its remote, and a form is the worst possible place for 11px type. */
.is-tv .badge{font-size:14px;padding:3px 8px}
.is-tv .veil__box{width:min(620px,100%)}
.is-tv .veil__say,.is-tv .veil__tick,.is-tv .veil__bad{font-size:18px}
.is-tv .veil__label{font-size:14px}
.is-tv .veil__in{font-size:19px;padding:12px 14px}
.is-tv .veil__err{font-size:16px}
/* A remote has no pointer: the focus ring is the only thing telling anyone
   where they are, so it is drawn to be seen from the sofa. */
.is-tv .tile__a:focus-visible,.is-tv .shot__a:focus-visible,.is-tv .cbtn:focus-visible,
.is-tv .link:focus-visible,.is-tv .chip:focus-visible,.is-tv .tbtn:focus-visible{
  outline:3px solid var(--gold);outline-offset:5px}
/* The one that actually fires on the television, whose browser predates
   :focus-visible. Its own rule, because a selector list is dropped whole when
   one selector in it cannot be parsed. */
.is-tv .tile__a.is-on{outline:3px solid var(--gold);outline-offset:5px}
.is-tv .shot__a.is-on{outline:3px solid var(--gold);outline-offset:5px}
/* AND THE CONTROLS, which have no .is-on twin because the script only ever
   marks tiles and sheet cells. Without this the rule above them is the only
   thing offering them a ring, it is a :focus-visible list, and Chrome 79
   drops it whole — so on the television the buttons, the rail chips and the
   sort would fall back to the user agent's own hairline outline and the
   remote would be hard to follow across them. Plain :focus, which every
   browser has had for twenty years: on a television there is no pointer, so
   focus and focus-visible mean the same thing anyway. */
.is-tv .cbtn:focus,.is-tv .link:focus,.is-tv .chip:focus,.is-tv .tbtn:focus{
  outline:3px solid var(--gold);outline-offset:5px}

@media (prefers-reduced-motion:reduce){
  /* The flip never starts at all under this (see STILL_ONLY in photos.js);
     these only cover the pictures and the marks around them. .link > span is
     the arrow that slides away from the word on approach. */
  .tile__img,.tile__flip,.tile__edge,.tile__t,.tile__no,.badge,.chip,.tbtn,
  .link,.link > span,.shot__img,.shot__edge,.tile__still,.shot__box{transition:none}
  /* The stack still fans — it is a static offset, not an animation — but it
     stops MOVING on approach, which is the part that is motion. */
  .tile__stack::before,.tile__stack::after{transition:none}
  .tile__a:hover .tile__stack::before,.tile__a:focus-visible .tile__stack::before{
    transform:translate(4px,-4px)}
  .tile__a:hover .tile__stack::after,.tile__a:focus-visible .tile__stack::after{
    transform:translate(8px,-8px)}
  .tile__a.is-on .tile__stack::before{transform:translate(4px,-4px)}
  .tile__a.is-on .tile__stack::after{transform:translate(8px,-8px)}
  /* Two rules, not one list: Chrome 79 drops a list containing
     :focus-visible, and that would take the .is-on line with it. */
  .tile__a:hover .tile__img,.tile__a:focus-visible .tile__img{transform:scale(1.005)}
  .tile__a.is-on .tile__img{transform:scale(1.005)}
  .shot__a:hover .shot__img,.shot__a:focus-visible .shot__img{transform:scale(1.005)}
  .shot__a.is-on .shot__img{transform:scale(1.005)}
  .link:hover > span{margin-right:10px}
  .veil{animation:none}

  /* THE PRESS PAYOFF, under the same rule as the approach payoff: the colour
     still returns and the gold edge still draws, because neither is motion —
     what goes is the travel. The scale is capped at the rest value and the
     stack keeps its rest offsets, exactly as the hover half above does. */
  .tile__a:active .tile__img,.tile__a:active .tile__flip,
  .tile__a:active .tile__edge,
  .shot__a:active .shot__img,.shot__a:active .shot__edge{transition:none}
  .tile__a:active .tile__img,.tile__a:active .tile__flip{transform:scale(1.005)}
  .shot__a:active .shot__img{transform:scale(1.005)}
  .tile__a:active .tile__stack::before{transform:translate(4px,-4px);transition:none}
  .tile__a:active .tile__stack::after{transform:translate(8px,-8px);transition:none}

  /* THE ZOOM STILL ZOOMS, it just arrives rather than travels. Taking the
     state away under this setting would make the preference a way to switch
     the lightbox's pinch off, which is not what it asks for — the same
     reasoning terms.css gives for leaving the watermark present. */
  .cinema__stage img{transition:none}
}
