/* ============================================================
   Eszter Hajdú — documentary filmmaker portfolio
   Editorial / image-first design. Captions live *below* stills
   (Borenstein-style), sections are numbered like film reels.
   ============================================================ */

:root{
  --ink:        #15130f;   /* near-black, warm */
  --ink-soft:   #4a463f;
  --ink-faint:  #6d685d;   /* AA on paper (4.5:1+) for captions */
  --paper:      #f5f2ec;   /* warm off-white */
  --paper-pure: #fbf9f5;   /* a step lighter — Recognition */
  /* A step DARKER than paper: a warm light grey used to set a section apart
     without going to the near-black of Contact. Deliberately stops here —
     any darker and --ink-faint (.head-meta) drops below 4.5:1 on it. */
  --paper-shade: #eeeae1;
  --line:       #ddd7cc;
  --accent:     #9a2a2a;   /* restrained crimson */
  --accent-dk:  #761f1f;
  --black:      #0b0a08;

  --maxw: 1280px;
  --gut: clamp(1.25rem, 4vw, 4rem);

  --serif: "Fraunces", Georgia, "Times New Roman", serif;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* Third voice: the factual layer only — years, runtimes, festival names, award
     lines, section meta. Keeps metadata from reading in the same register as the
     navigation, and gives the site a slate / archive quality that's specific to
     film rather than generic to portfolios. Never use it for running copy. */
  --mono: "DM Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  --ease: cubic-bezier(.22,.61,.36,1);
  --ease-out: cubic-bezier(.16,1,.3,1);

  /* Pace of the HOME hero entrance (the staggered rise of the
     headline / tagline / button / stats, and the stat count-up in main.js,
     which reads this value so CSS and JS can never drift apart).

     Every duration and delay in that sequence is written as
     `calc(<base> * var(--hero-pace))`, so this one number scales the whole
     composition while preserving the order and the exact ratios between the
     steps. 1 = the original timing; 1.1 = 10% slower, as it ships. */
  --hero-pace: 1.1;
}

/* Same-document navigation fade (index ↔ film). Chrome/Edge honour it; every
   other browser ignores the at-rule entirely and navigates as before. No JS,
   so it can never strand a faded-out page in the back/forward cache. */
@view-transition{ navigation:auto; }
@media(prefers-reduced-motion:reduce){ @view-transition{ navigation:none; } }

*,*::before,*::after{ box-sizing:border-box; }

html{ scroll-behavior:smooth; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; }

/* Accessibility: visible keyboard focus on every interactive element.
   Mouse users don't see it (:focus-visible); keyboard users always do. */
:focus-visible{
  outline:2.5px solid var(--accent);
  outline-offset:3px;
  border-radius:2px;
}
/* White ring only where the background is actually dark. The hero and film hero
   are paper-backed since the editorial pass, so they keep the crimson ring — a
   white one there is invisible. */
.contact :focus-visible, .site-footer :focus-visible, .site-header.nav-is-open :focus-visible{
  outline-color:#fff;
}
/* Reduce 300ms tap delay and stop double-tap zoom on controls */
a, button, input, textarea, .film-card{ touch-action:manipulation; }

/* Keyboard users land here first; invisible until focused */
.skip-link{
  position:absolute; left:-9999px; top:0; z-index:100;
  background:var(--ink); color:var(--paper); padding:.9rem 1.4rem;
  font-size:.78rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
}
.skip-link:focus{ left:0; }

/* Fixed header is ~64–80px tall — keep anchored sections clear of it */
/* #recognition / #about / #contact are their own pages now and are reached as
   documents, not anchors — but #teaching IS an in-page anchor on about.html, and
   #films still is on the home page. Keep the whole set: a stale entry costs
   nothing, a missing one drops the target under the fixed header. */
:where(#films, #recognition, #about, #teaching, #contact){ scroll-margin-top:90px; }

body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--sans);
  font-size:17px;
  line-height:1.65;
  font-weight:400;
}

/* Film-grain wash over everything — kills the flat "digital template" look.
   Pointer-events off so it never intercepts input. */
body::after{
  content:""; position:fixed; inset:0; z-index:90; pointer-events:none;
  opacity:.05; mix-blend-mode:multiply;
  background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" stitchTiles="stitch"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/></filter><rect width="160" height="160" filter="url(%23n)"/></svg>');
}

img{ display:block; max-width:100%; }
a{ color:inherit; text-decoration:none; }
/* `hidden` is a UA rule (`display:none`) with the lowest possible weight, so ANY
   author `display:` on the same element silently beats it — the element stays on
   screen while the DOM says it is hidden. That has now bitten twice here
   (.teach-figure's flex, .aw-year's grid), so it is settled once, globally. */
[hidden]{ display:none!important; }
/* Regions copied into the HTML by docs/prerender.js, for crawlers that don't run
   JS (they read the markup, not the CSS). main.js re-renders each one in the
   visitor's language and init() drops the class, so a visitor only ever sees the
   live render. display:none, NOT visibility:hidden: until JS runs - or with JS
   off - the host must take no space, exactly like the empty host it replaced;
   visibility:hidden reserved the space and left blank gaps on the page. */
.prerendered{ display:none!important; }

.wrap{ max-width:var(--maxw); margin-inline:auto; padding-inline:var(--gut); }
/* Images earn the extra width on large displays; running text never follows
   (see the measure caps on .about-body p / .teach-copy). */
.wrap-wide{ max-width:clamp(var(--maxw), 92vw, 1560px); }

h1,h2,h3{ font-family:var(--serif); font-weight:500; letter-spacing:-.01em; line-height:1.05; margin:0; }

.eyebrow{
  font-family:var(--sans);
  font-size:.72rem;
  font-weight:600;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var(--accent);
}

/* ---------------------------------------------------------- HEADER / NAV */
.site-header{
  position:fixed; inset:0 0 auto 0; z-index:50;
  display:flex; align-items:center; justify-content:space-between;
  padding:1.1rem var(--gut);
  transition:background .4s var(--ease), padding .4s var(--ease), box-shadow .4s var(--ease), color .4s var(--ease);
  color:#fff;
}
.site-header.solid{
  background:rgba(245,242,236,.92);
  backdrop-filter:saturate(140%) blur(12px);
  color:var(--ink);
  box-shadow:0 1px 0 var(--line);
  padding-block:.85rem;
}
.brand{
  font-family:var(--serif); font-size:1.18rem; font-weight:500; letter-spacing:.02em;
  white-space:nowrap; line-height:1.15;
  display:inline-flex; flex-direction:column; justify-content:center; min-height:44px;
}
.brand-name{ display:block; line-height:1; }
.brand-role{ display:block; font-family:var(--sans); font-size:.68rem; letter-spacing:.32em; text-transform:uppercase; opacity:.75; font-weight:600; margin-top:.28rem; }

/* Home hero is light (paper), so the header reads in dark ink from the top.
   When the scroll-morph is active (JS adds .has-morph) the brand starts hidden
   and the big hero title condenses into its place — see initTitleMorph(). */
.home .site-header, .page .site-header{ color:var(--ink); }
/* subtle paper backing so the dark-ink nav stays legible over the poster while
   the header is still transparent (before it solidifies on scroll) */
.home .site-header:not(.solid){ background:linear-gradient(180deg, rgba(245,242,236,.5) 0%, rgba(245,242,236,0) 100%); }
.has-morph .site-header .brand{ opacity:0; }

.nav{ display:flex; align-items:center; gap:clamp(.9rem,2vw,2.2rem); }
.nav a{
  font-size:.78rem; letter-spacing:.16em; text-transform:uppercase; font-weight:600;
  position:relative; opacity:.92;
  /* 44px minimum touch target (was 39px) */
  display:inline-flex; align-items:center; min-height:44px;
}
.nav a::after{
  content:""; position:absolute; left:0; bottom:11px; height:1px; width:0; background:currentColor; transition:width .35s var(--ease);
}
.nav a:hover{ opacity:1; } .nav a:hover::after{ width:100%; }
.nav a.active{ opacity:1; } .nav a.active::after{ width:100%; } /* scroll-spy current section */

/* Language switcher — EN · HU · PT.
   Three languages, so this is a segmented group of toggle buttons rather than
   the old "flip to the other one" button: with more than two you can no longer
   name the destination in a single label, and a user landing in a language they
   don't read needs to SEE their own listed, not guess what the button does.
   One shared 1px box, hairline dividers between the segments. */
.lang-switch{
  display:inline-flex; align-items:stretch;
  border:1px solid currentColor; border-radius:2px; overflow:hidden;
}
.lang-opt{
  font-family:var(--sans); font-size:.7rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase;
  background:transparent; color:inherit; border:0;
  border-left:1px solid currentColor;
  padding:.45rem .62rem; min-height:44px; display:inline-flex; align-items:center; justify-content:center;
  cursor:pointer; opacity:.62;
  transition:background .25s var(--ease), opacity .25s var(--ease);
}
.lang-opt:first-child{ border-left:0; }
.lang-opt:hover{ opacity:1; background:rgba(128,120,105,.16); }
/* Current language: filled, so the active segment is legible without relying on
   opacity alone (which fails for anyone who can't compare two greys). */
.lang-opt.on{ opacity:1; background:currentColor; }
.lang-opt.on span{ color:var(--paper); }
.contact .lang-opt.on span, .nav.open .lang-opt.on span{ color:var(--ink); }

.nav-toggle{ display:none; background:none; border:0; color:inherit; cursor:pointer; padding:.45rem; min-width:44px; min-height:44px; align-items:center; justify-content:center; }
.nav-toggle svg{ width:26px; height:26px; }

/* ---------------------------------------------------------- MASTHEAD
   The opening block, and the head of the films section - they are one section
   now (September 2026, third pass, at the client's request and after the
   reference site). There is no full-height opening screen: the name sits at the
   top of the page, the quote and the CTA under it, the credibility column to
   its right, and the film grid starts immediately below the rule.

   The name is set SMALLER than it was as a hero title. At masthead size it has
   to sit comfortably above a grid rather than fill a screen on its own, and it
   is only a short hop from the navbar brand it morphs into on scroll.

   Class names are still .hero*: initTitleMorph(), initHeader() and
   initStatCounters() key off them, and the composition is the same one. Only
   the height and the neighbours changed. */
.hero{
  position:relative; display:block;
  color:var(--ink); background:var(--paper);
}

/* Name and role stacked, the quote and the CTA under them, and the credibility
   figures boxed on the right at the quote's own level. Then the hairline, then
   the films — the grid still starts high up the page, which is the point of
   merging the two sections in the first place. */
.hero-inner{
  position:relative; z-index:2; width:100%; text-align:left;
  display:grid; grid-template-columns:minmax(0,1fr);
  grid-template-areas:"head" "line" "stats";
  row-gap:0;
}
.hero-headline{ grid-area:head; }
.hero-line{ grid-area:line; }
.hero-stats{ grid-area:stats; }

/* Two columns from 860px: the ledger sits beside the quote rather than beside
   the NAME, so the masthead reads top-down (name, then what she says, with the
   numbers as a note against it) instead of as two competing headlines. */
@media(min-width:860px){
  .hero-inner{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:
      "head  ."
      "line  stats";
    column-gap:clamp(2rem,5vw,4rem);
  }
  .hero-stats{ justify-self:end; align-self:start; }
}

/* name over role — the shape of the navbar brand, which is what each line
   morphs into on scroll.

   `width:fit-content` is load-bearing for that morph: the box has to shrink to
   the glyphs, or the morph reads the container's left edge instead of the
   text's and the landing is off by half the column. Not `inline-block` either,
   which would put the role on the name's baseline off to its right.

   The accent rule is the ::after, so it lives inside the block that fades during
   the hand-off to the navbar brand instead of hanging around after it. */
.hero-headline{ will-change:opacity; }
.hero-headline::after{
  content:""; display:block; width:clamp(48px,5vw,72px); height:2px;
  background:var(--accent); margin-top:clamp(.9rem,1.8vh,1.2rem);
}
.hero-title{
  width:fit-content;
  font-size:clamp(2.1rem,5.2vw,3.7rem);
  font-weight:400; margin-block:0; letter-spacing:.02em; line-height:1;
  transform-origin:left top; will-change:transform; backface-visibility:hidden;
}
.hero-role{
  width:fit-content;
  margin:.55rem 0 0; font-family:var(--sans);
  font-size:clamp(.68rem,1vw,.78rem); font-weight:600;
  letter-spacing:.32em; text-transform:uppercase; color:var(--ink-soft);
  transform-origin:left top; will-change:transform; backface-visibility:hidden;
}

/* Quote, then the button under it. */
.hero-line{ margin-top:clamp(1rem,2.2vh,1.4rem); }
.hero .tagline{
  margin:0; max-width:40ch;
  font-family:var(--serif); font-style:italic; font-weight:300;
  font-size:clamp(1.05rem,1.8vw,1.4rem); line-height:1.35;
  color:var(--ink-soft);
}
.hero .tagline .dash{ color:var(--accent); font-style:normal; margin-right:.15em; }
.hero-actions{ margin:clamp(1.1rem,2.4vh,1.5rem) 0 0; }

/* Call to action - solid ink block (2px radius, like every other control here);
   inverts to the crimson accent on hover. Points at contact.html now that
   Contact is its own page. */
.btn-hero{
  display:inline-flex; align-items:center; gap:.7rem;
  padding:.85rem 1.4rem; min-height:48px; border-radius:2px;
  background:var(--ink); color:var(--paper); border:1px solid var(--ink);
  font-size:.74rem; font-weight:600; letter-spacing:.16em; text-transform:uppercase;
  transition:background .35s var(--ease), border-color .35s var(--ease), color .35s var(--ease);
}
.btn-hero:hover{ background:var(--accent); border-color:var(--accent); color:var(--paper); }
.btn-hero svg{ width:15px; height:15px; transition:transform .35s var(--ease); }
.btn-hero:hover svg{ transform:translateX(4px); }

/* Credibility ledger - a bordered box, one figure per row, numbers right-aligned
   in a fixed column so the labels start on a common left edge. It used to be
   three big serif numbers standing across the top of the page; at this size and
   behind a hairline it reads as a note against the quote, which is what it is.
   The tint is a step lighter than the paper so the box separates without a
   heavier border. */
/* Sized to stand as the right-hand column of the masthead: the figures were
   small enough that the ledger ran out well above the quote beside it, leaving
   the column short and the block lopsided. One step up in figure size and a
   wider row gap give it the height to hold that side of the grid (client's call,
   September 2026). recognition.html carried the same numbers on a matching
   band of its own for a while, but that band is gone (September 2026, see the
   REMOVED note near .recognition below) — this is the only stat row left, so
   there is nothing left to keep it in step with. */
.hero-stats{
  list-style:none; margin:0; padding:1.15rem 0;
  /* Rules top and bottom rather than a full box: a bordered, tinted card reads
     as a UI widget dropped into an editorial page, and this site sets everything
     else - the films rule, the section heads, the award ledger - on hairlines.
     Bracketed this way it reads as what it is, a ledger, and it rhymes with the
     films rule directly beneath it. */
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  min-width:min(275px, 36vw);
  display:flex; flex-direction:column; gap:1.15rem;
}
.hero-stats li{ display:flex; align-items:baseline; gap:.85rem; min-width:0; }
.hero-stats .n{
  position:relative;
  flex:0 0 auto; min-width:2.6ch; text-align:right;
  font-family:var(--serif); font-weight:400; font-size:1.5rem; line-height:1;
  letter-spacing:-.02em;
  /* Tabular figures so the count-up entrance (initStatCounters) doesn't reflow
     the row on every frame - Fraunces' proportional digits are different widths,
     so 0 to 19 would visibly jitter the label beside it. */
  font-variant-numeric:tabular-nums;
}
/* The "+" hangs OUTSIDE the number's box. In flow it is part of the right-aligned
   content, so "40+" and "19" line up on the plus rather than on the digits —
   which left 19 sitting a character to the right of the other two. Taken out of
   flow, all three numbers right-align on their last digit and all three labels
   start on a common left edge; the plus rides in the gap between them (hence the
   slightly wider gap above). */
.hero-stats .n sup{
  position:absolute; left:100%; top:.04em;
  font-size:.5em; color:var(--accent); margin-left:.06em;
  vertical-align:baseline;
}
.hero-stats .l{
  font-size:.7rem; font-weight:600; letter-spacing:.12em; text-transform:uppercase;
  color:var(--ink-faint);
}

/* Below the two-column breakpoint the box goes full width under the button and
   lies down into a wrapped row - three stacked rows of a full-width box is a lot
   of vertical space to spend before the first film. */
@media(max-width:859px){
  .hero-stats{
    /* A clear blank line between the button and the ledger. It was ~22px, which
       on a phone reads as the two being one block - the button looked like it
       belonged to the figures rather than to the quote above it. */
    margin-top:clamp(2.6rem,6vh,3.4rem); min-width:0;
    flex-direction:row; flex-wrap:wrap; align-items:baseline;
    /* The desktop row gap is vertical breathing room for a tall column; lying
       down into a wrapped row it would just hold the lines apart, so it reverts
       to the tight value here. */
    gap:.6rem clamp(1.1rem,4vw,2rem);
  }
  .hero-stats .n{ min-width:0; }
}

/* ---- the films rule ----
   Just the hairline now (client's call): the "FILMS" label was a heading for a
   grid that is plainly a list of films, standing between the masthead and the
   work. The <h2> is still in the markup as .vh so the section keeps its name in
   the document outline. Deliberately NOT part of the group the scroll morph
   dissolves - it belongs to the films, not the masthead. */
.films-bar{
  margin-top:clamp(1.6rem,3.4vh,2.4rem);
  border-top:1px solid var(--line);
  margin-bottom:clamp(1.6rem,3.2vw,2.4rem);
}

/* The section opens the document, so it clears the fixed header itself. */
.films-section{ padding-top:clamp(6rem,12vh,8rem); padding-bottom:clamp(4.5rem,11vh,9rem); }

/* Staggered entrance - reads like titles coming up on screen.
   Every duration/delay is scaled by --hero-pace; see the note at :root and
   initStatCounters(), which reads the same property for the count-up. */
.hero-headline, .hero .tagline, .hero-actions, .hero-stats{
  animation:heroRise calc(.9s * var(--hero-pace)) var(--ease-out) both;
}
.hero-headline{ animation-delay:calc(.12s * var(--hero-pace)); }
.hero .tagline{ animation-delay:calc(.28s * var(--hero-pace)); }
.hero-actions{ animation-delay:calc(.38s * var(--hero-pace)); }
.hero-stats{ animation-delay:calc(.48s * var(--hero-pace)); }
@keyframes heroRise{
  from{ opacity:0; transform:translateY(28px); }
  to{ opacity:1; transform:none; }
}

/* ---------------------------------------------------------- SECTIONS */
.section{ padding-block:clamp(4.5rem,11vh,9rem); }
.section-head{ margin-bottom:clamp(2.6rem,5vw,4.5rem); }
/* Recognition's heading carries its figures, so the ledger can start close
   under it - the old band left a deliberate gap that is now just empty paper. */
.rec-title-block{
  margin-bottom:clamp(1.8rem,3.5vw,2.8rem);
  padding-bottom:clamp(1.4rem,2.8vw,2rem);
  border-bottom:1px solid var(--line);
}
.head-top{
  position:relative;
  display:flex; align-items:baseline; justify-content:space-between; gap:2rem;
  padding-bottom:1rem; margin-bottom:1.2rem;
}
/* The hairline wipes in from the left as the section head reveals. Default is
   the DRAWN state, so a browser without IntersectionObserver (or with JS off)
   can never leave the rule invisible — only the pre-reveal state hides it. */
.head-top::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:var(--line); transform-origin:left;
  transition:transform .6s var(--ease-out) .08s;
}
[data-reveal]:not(.in) .head-top::after{ transform:scaleX(0); }
/* One line wherever there is room for one. The 42ch cap used to break
   "Awards for Judgment in Hungary, 2013-2016" (41 characters, and a mono `ch` is
   wide) onto a second line that carried only the closing year. Narrow screens
   still wrap it rather than overflow the viewport - see the <=600px rule, which
   also left-aligns it. */
.head-meta{
  margin:0 0 0 auto; font-family:var(--mono); font-size:.75rem; letter-spacing:.04em;
  color:var(--ink-faint); text-align:right; max-width:none;
}
@media(min-width:620px){ .head-meta{ white-space:nowrap; } }
/* h1 AND h2: on a sub-page this block holds the document's only top-level
   heading (an <h1>), on the home page it is a section heading. Same type
   either way — the tag carries the outline, not the size. */
.section-head h1, .section-head h2{ font-size:clamp(2.4rem,6.5vw,4.8rem); font-weight:400; margin:0; }
.section-head .lead-note{ color:var(--ink-soft); font-size:1.05rem; max-width:46ch; margin:1rem 0 0; }
.divider{ border:0; border-top:1px solid var(--line); margin:0; }

/* ---------------------------------------------------------- FILM GRID
   Every card is the same width AND the same POSTER-RATIO frame — no feature
   tile, no 7/5 rhythm. The old rhythm rendered the same still at three different sizes
   depending on its slot, which is what read as "the stills are different
   sizes". Uniformity also lets the stills be cropped to 16:9 at build time,
   so nothing is cropped again unpredictably at render.

   Subgrid is what makes the row actually line up. Card titles run from one line
   ("Destino") to three ("O judeu do Fidesz, a mãe sem sentimento nacional, e a
   mediação"), and with the title block ABOVE the still a plain grid would start
   each still at a different height — same size, ragged row. Each card spans the
   same three parent rows (title / still / tagline) and adopts them via subgrid,
   so every still in a row starts and ends on the same line.

   Without subgrid support the cards simply lay out on their own auto rows: the
   stills stay identically sized, they just aren't cross-aligned. */
.film-grid{
  display:grid; grid-template-columns:1fr;
  gap:clamp(2.4rem,4.5vw,3.6rem) clamp(1.2rem,2.6vw,2.2rem);
}
.film-card{ display:block; min-width:0; }

@media(min-width:640px){ .film-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media(min-width:1080px){ .film-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); } }

@supports(grid-template-rows:subgrid){
  .film-card{
    grid-row:span 3;
    display:grid; grid-template-rows:subgrid;
    align-content:start;
    /* A subgrid inherits the parent's gap, which here is the BETWEEN-CARDS
       gutter (~3.6rem) — far too much between a title and its own still. Set
       the card's own row-gap: the parent gutter still separates one card row
       from the next, this only governs the three tracks inside a card. */
    row-gap:.85rem;
  }
  /* gap owns the internal spacing now */
  .film-card .meta-head{ padding-bottom:0; }
  .film-card .tl{ margin-top:0; }
}

/* Title block sits ABOVE the still, so the grid scans as a list of film names
   rather than a wall of images. Ordered head → still → tagline. */
.film-card .meta-head{ padding:0 0 .85rem; }

/* Poster ratio, not 16:9 (September 2026). The cards now lead with each film's
   own one-sheet, and a 16:9 crop of a portrait poster is a thin band through the
   middle that cuts the title off — all three sheets the client supplied carry
   type at the top AND the bottom. 1000:1414 is the ISO ratio those posters are
   already drawn at, so they fill the frame with nothing cropped at all.

   The dark matte is load-bearing: `.fit-contain` art (set from the image's own
   ratio in fitArt()) is letterboxed against it rather than cropped. */
.film-card .thumb{
  position:relative; overflow:hidden; background:var(--black); border-radius:2px;
  aspect-ratio:1000/1414;
}
/* Letterboxed art gets a blurred copy of itself as the matte, so a landscape
   still in a portrait frame fills the card instead of floating in a black slab.
   `scale(1.15)` hides the blur's soft edge; the darkening keeps it firmly behind
   the real image. Purely decorative — it is the same file, already downloaded. */
.film-card .thumb.fit-contain img{ object-fit:contain; position:relative; z-index:1; }
.film-card .thumb.fit-contain::before{
  content:""; position:absolute; inset:0; z-index:0;
  background-image:var(--art); background-size:cover; background-position:center;
  filter:blur(26px) brightness(.42) saturate(.7); transform:scale(1.15);
}
/* Stills enter desaturated and dissolve to colour — a print coming up in the
   developer. Filter values are written literally (not via custom properties):
   unregistered CSS variables animate discretely, which would snap instead of
   dissolve. Hover is a separate, more specific rule so the two never fight. */
.film-card .thumb img{
  width:100%; height:100%; object-fit:cover;
  filter:grayscale(1) saturate(.9);
  transition:transform .5s var(--ease), filter .65s var(--ease);
}
.film-card.in .thumb img{ filter:grayscale(0) saturate(.9); }
.film-card.in:hover .thumb img{ transform:scale(1.04); filter:grayscale(0) saturate(1.05); }

/* Year sits as a small mono line ABOVE the title (column-reverse puts the .yr,
   which follows the h3 in the DOM, on top). It used to be a baseline flex row
   with justify-content:space-between — which collided badly on narrow screens:
   a two-line title wrapped underneath the right-aligned year and the two ran
   into each other. Stacking removes the failure mode at every width. */
.meta-top{ display:flex; flex-direction:column-reverse; align-items:flex-start; gap:.4rem; }
.film-card h3{
  font-size:clamp(1.2rem,1.9vw,1.6rem); font-weight:500; line-height:1.18;
  transition:color .3s var(--ease);
  text-wrap:balance;
}
.film-card:hover h3{ color:var(--accent); }
/* Underline draws left→right on hover. Applied to an inner span so it tracks the
   text width, not the flex item's full width. */
.film-card h3 .t{
  background-image:linear-gradient(currentColor,currentColor);
  background-repeat:no-repeat; background-position:0 100%; background-size:0 1px;
  padding-bottom:.08em;
  transition:background-size .35s var(--ease);
}
.film-card:hover h3 .t, .film-card:focus-visible h3 .t{ background-size:100% 1px; }
.film-card .yr{
  font-family:var(--mono); font-size:.75rem; font-weight:400; letter-spacing:.06em;
  color:var(--ink-faint); white-space:nowrap; font-variant-numeric:tabular-nums;
}
/* Tagline now follows the still (title moved above it) */
.film-card .tl{
  font-family:var(--serif); font-style:italic; font-size:.98rem; color:var(--ink-soft);
  margin:.85rem 0 0; max-width:52ch;
}
.badge-hu{
  position:absolute; top:1rem; left:1rem; z-index:3;
  font-size:.7rem; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  background:rgba(245,242,236,.94); color:var(--ink); padding:.3rem .6rem; border-radius:2px;
}

/* Card widths are handled by the flex-basis breakpoints above; the thumb keeps
   the poster ratio at every width, so nothing here needs a phone override. */

/* ---------------------------------------------------------- YEAR RAIL
   A film-strip edge marking: the year of the card currently crossing the
   viewport middle, set vertically in the right margin. Decorative, wide
   screens only — the same information is in every card caption. */
#films{ position:relative; }
.year-rail{
  position:fixed; right:.9rem; top:50%; transform:translateY(-50%);
  z-index:40; pointer-events:none; display:none;
}
.year-rail span{
  display:block; writing-mode:vertical-rl;
  font-family:var(--mono); font-size:.75rem; letter-spacing:.34em;
  color:var(--ink-faint); font-variant-numeric:tabular-nums;
  opacity:0; transition:opacity .4s var(--ease);
}
.year-rail.on span{ opacity:1; }
@media(min-width:1200px){ .year-rail{ display:block; } }

/* ---------------------------------------------------------- SUB-PAGES
   Recognition / About / Contact are their own documents since September 2026 —
   the nav navigates instead of scrolling. They have no hero, so the first
   section has to clear the fixed header itself, and the header reads in dark
   ink over their paper background exactly as it does on a film page. */
.page main > .section:first-child{ padding-top:clamp(7.5rem,15vh,10.5rem); }

/* 404's "back to the films" link. This rule used to live in the film-page
   block and was deleted with it; 404.html still carries the class, so the
   link spent that time as unstyled body text. It is the only user now. */
.back-link{
  display:inline-flex; align-items:center; gap:.5rem; min-height:44px;
  font-size:.76rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-soft); transition:gap .25s var(--ease), color .25s var(--ease);
}
.back-link:hover{ color:var(--accent); gap:.85rem; }

/* Contact is paper-on-INK and runs the full page, so the header has to invert or
   the brand and nav are invisible. Mirrors the .home rule, in the other
   direction — but note this covers BOTH header states, because the page stays
   dark all the way down.

   The scrolled state is the subtle one. `.site-header.solid` paints a PAPER bar
   with dark ink; on a dark page that both flashed a bright slab over the design
   AND — because `.page-dark .site-header` and `.site-header.solid` have equal
   specificity, so the later rule wins — left the text white on near-white paper,
   with the backdrop blur smearing it further. So .solid is restyled here rather
   than merely re-coloured: an ink bar, a light hairline instead of the paper
   `--line` one, and the language segment flipped back to dark-on-white. */
.page-dark .site-header{ color:#fff; }
.page-dark .site-header:not(.solid){
  background:linear-gradient(180deg, rgba(26,24,21,.6) 0%, rgba(26,24,21,0) 100%);
}
.page-dark .site-header.solid{
  background:rgba(26,24,21,.9);
  color:#fff;
  box-shadow:0 1px 0 rgba(255,255,255,.13);
}
/* the filled language segment is currentColor-on-currentColor; over a dark
   header that means a white fill, which needs dark text (cf. the .contact rule) */
.page-dark .site-header .lang-opt.on span{ color:var(--ink); }

/* Editorial hand-off at the foot of a sub-page — the same device the film
   lightbox's arrows give the grid
   on a film page, so the site never dead-ends without a next move. */
.page-next{ border-top:1px solid var(--line); background:var(--paper); }
.page-dark .page-next{ border-top-color:#3a362f; background:var(--ink); color:var(--paper); }
.page-next-link{
  display:block; max-width:var(--maxw); margin-inline:auto;
  padding:clamp(2.6rem,7vw,4.5rem) var(--gut);
}
.page-next-lbl{
  display:block; font-family:var(--sans); font-size:.72rem; font-weight:600;
  letter-spacing:.22em; text-transform:uppercase; color:var(--accent); margin-bottom:.7rem;
}
.page-next-title{
  display:block; font-family:var(--serif); font-weight:400;
  font-size:clamp(2rem,5.5vw,3.6rem); line-height:1.05;
  transition:color .35s var(--ease), transform .35s var(--ease);
}
.page-next-link:hover .page-next-title{ color:var(--accent); transform:translateX(10px); }

/* ---------------------------------------------------------- RECOGNITION
   The awards + festivals the marquee only gestures at. Real text, crawlable,
   and, since the festival marquee was removed, the site's only festival list. */
.recognition{ background:var(--paper-pure); border-block:1px solid var(--line); }

/* ---- Stat band: REMOVED (client's call, September 2026) ----
   There was a counted stat row here — prizes won / festivals & institutions,
   `.rec-title-row` + `.rec-stats`, beside the title on one line like the home
   masthead's ledger. It is gone: `renderRecognition()` still computes both
   counts, but `#rec-stats` is no longer in `recognition.html`'s markup, and
   that function has always guarded on the host node's presence (`if
   (statsHost)`), so deleting the element needed no JS change. What replaced it
   is a plain sentence under the title (`.lead-note`, the same component
   `contact.html` uses under its own h1) naming the film that carries most of
   the ledger. If a stat row ever comes back, `.hero-stats` on the home page is
   still the device to match — same figure size, row gap, label size and the
   narrow right-aligned number column — so the two read as one thing used
   twice, not two things that happen to look similar. */

/* ---- Layout: film on the left, ledger on the right ----
   The aside is sticky on wide screens so the poster holds the ledger's context
   while you scroll 19 rows of it — every one of those prizes belongs to the film
   it is sitting beside. Below 1000px it stacks and stops being sticky. */
.rec-layout{ display:grid; grid-template-columns:1fr; gap:clamp(2.4rem,5vw,4rem); align-items:start; }
@media(min-width:1000px){
  .rec-layout{ grid-template-columns:minmax(0,.42fr) minmax(0,1fr); gap:clamp(3rem,6vw,5.5rem); }
  .rec-aside{ position:sticky; top:7.5rem; }
}
.rec-figure{ margin:0; }
/* height:auto is load-bearing: the <img> carries explicit width/height attributes
   (so the box is reserved before load and nothing shifts), and without it the
   poster paints at its intrinsic 1668px against a 319px column — a 5x vertical
   stretch that made the sticky aside 2890px tall. */
.rec-figure img{ width:100%; height:auto; border-radius:2px; }
.rec-figure-poster img{ box-shadow:0 26px 60px -30px rgba(0,0,0,.55); }
.rec-figure-still{ margin-top:1.6rem; }
.rec-figure-still img{ aspect-ratio:16/9; object-fit:cover; filter:grayscale(1) saturate(.9); transition:filter .6s var(--ease); }
.rec-figure-still.in img{ filter:grayscale(0) saturate(.95); }
.rec-figure figcaption{
  margin-top:.7rem; font-family:var(--serif); font-style:italic;
  font-size:.92rem; color:var(--ink-faint); line-height:1.45;
}
.rec-film-link{
  display:inline-flex; align-items:center; gap:.6rem; min-height:44px; margin-top:1.2rem;
  font-size:.74rem; font-weight:600; letter-spacing:.16em; text-transform:uppercase;
  transition:color .3s var(--ease);
}
.rec-film-link svg{ width:15px; height:15px; transition:transform .3s var(--ease); }
.rec-film-link:hover{ color:var(--accent); }
.rec-film-link:hover svg{ transform:translateX(4px); }

/* ---- Filter bar ---- */
.rec-bar{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:1rem 1.6rem; margin-bottom:1.8rem;
  padding-bottom:1.1rem; border-bottom:1px solid var(--line);
}
.rec-head{
  font-family:var(--sans)!important; font-size:.72rem!important; font-weight:600;
  letter-spacing:.2em; text-transform:uppercase; color:var(--ink); margin:0;
}
.rec-filter{ display:flex; flex-wrap:wrap; gap:.4rem; }
.rec-chip{
  display:inline-flex; align-items:center; gap:.45rem; min-height:36px;
  padding:.35rem .7rem; border:1px solid var(--line); border-radius:2px;
  background:transparent; color:var(--ink-soft); cursor:pointer;
  font-family:var(--mono); font-size:.75rem; letter-spacing:.04em;
  font-variant-numeric:tabular-nums;
  transition:background .3s var(--ease), border-color .3s var(--ease), color .3s var(--ease);
}
.rec-chip:hover{ border-color:var(--ink-faint); color:var(--ink); }
.rec-chip.on{ background:var(--ink); border-color:var(--ink); color:var(--paper); }
.rec-chip-n{
  font-size:.66rem; opacity:.6;
  /* the count is a secondary detail — never let it outweigh the year itself */
}
.rec-chip.on .rec-chip-n{ opacity:.75; }

/* Filtered-out groups are given [hidden] by applyRecFilter(), so they leave the
   flow entirely and no gap is left behind. Un-hiding restarts these keyframes,
   which is what fades the surviving group in. */
.aw-year{ animation:awIn .35s var(--ease-out) both; }
@keyframes awIn{ from{ opacity:0; transform:translateY(6px); } to{ opacity:1; transform:none; } }

/* Visually-hidden live region: the filter changes a list further down the page,
   which is silent to a screen reader without this. */
.vh{
  position:absolute!important; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; margin:-1px; padding:0; border:0;
}

.rec-film{
  font-family:var(--serif); font-style:italic; font-size:1.05rem; color:var(--ink-faint);
  margin:0 0 1.4rem;
}

/* ---- Award ledger ----
   Year rail on the left, prize as the headline, awarding body beneath. Reads as
   a record rather than a bullet list, and the year grouping makes the 2014
   festival run legible as the burst it actually was. */
.award-ledger{ display:flex; flex-direction:column; gap:1.8rem; }
.aw-year{ display:grid; grid-template-columns:1fr; gap:.2rem; }
@media(min-width:560px){ .aw-year{ grid-template-columns:4.5rem 1fr; gap:0 1.6rem; } }
.aw-y{
  margin:0; font-family:var(--mono); font-size:.95rem; color:var(--accent);
  font-variant-numeric:tabular-nums; letter-spacing:.02em; padding-top:.7rem;
  display:flex; align-items:baseline; gap:.5rem;
}
/* How many prizes that year — the thing the ledger is actually showing you when
   2014 runs to ten rows and 2013 to one. */
.aw-n{ font-size:.7rem; color:var(--ink-faint); }
.aw-n::before{ content:"\00d7"; margin-right:.15em; }
.aw-rows{ list-style:none; padding:0; margin:0; }
.aw{
  display:flex; flex-direction:column; gap:.15rem;
  padding:.7rem 0; border-top:1px solid var(--line);
  /* staggered entrance; index capped in JS so the tail never feels laggy. These
     rows have no hover state, so a CSS transition-delay is safe here. */
  opacity:0; transform:translateY(8px);
  transition:opacity .5s var(--ease-out), transform .5s var(--ease-out);
  transition-delay:calc(var(--i,0) * 40ms);
}
/* The ledger's own container is what reveals now (.rec-col is gone with the
   two-column split), so the rows key off it. */
.rec-main.in .aw, .award-ledger.in .aw{ opacity:1; transform:none; }
.aw-prize{ font-size:.95rem; font-weight:500; color:var(--ink); line-height:1.35; }
.aw-body{ font-size:.82rem; color:var(--ink-faint); }
.aw-place{ font-family:var(--mono); font-size:.75rem; letter-spacing:.02em; }
.aw-place::before{ content:" · "; }

/* ---------------------------------------------------------- TEACHING */
.teaching{ background:var(--paper-shade); border-block:1px solid var(--line); }
.teach-body{ display:flex; flex-direction:column; align-items:flex-start; gap:1.6rem; }
/* Photo of her lecturing at the European Parliament. Two-column beside the copy
   on wide screens, stacked above it on phones. Removing the image in data.js
   sets [hidden] on this figure; the global [hidden] rule near the top is what
   makes that actually hide, over the flex display below. */
.teach-figure{ margin:0; width:100%; max-width:760px; }
.teach-figure img{
  width:100%; aspect-ratio:16/9; object-fit:cover;
  border-radius:2px; filter:grayscale(1) saturate(.9);
  transition:filter .65s var(--ease);
}
.teach-body.in .teach-figure img, [data-reveal].in .teach-figure img{ filter:grayscale(0) saturate(.95); }
.teach-figure figcaption{
  font-family:var(--mono); font-size:.72rem; letter-spacing:.04em;
  color:var(--ink-faint); margin-top:.7rem;
}
@media(min-width:900px){
  .teach-body{
    display:grid; grid-template-columns:1fr minmax(0,.85fr);
    gap:clamp(2rem,4vw,3.5rem); align-items:start;
  }
  .teach-copy{ grid-column:1; grid-row:1; }
  .teach-cta{ grid-column:1; grid-row:2; align-self:start; }
  .teach-figure{ grid-column:2; grid-row:1 / span 2; }
}
.teach-copy{
  font-family:var(--serif); font-size:clamp(1.15rem,2.2vw,1.5rem); line-height:1.5;
  color:var(--ink-soft); max-width:60ch; margin:0;
}
.teach-cta{
  display:inline-flex; align-items:center; gap:.6rem; min-height:44px;
  font-size:.78rem; font-weight:600; letter-spacing:.16em; text-transform:uppercase;
  border-bottom:1px solid var(--line); padding-bottom:.35rem;
  transition:color .3s var(--ease), border-color .3s var(--ease), gap .3s var(--ease);
}
.teach-cta:hover{ color:var(--accent); border-color:var(--accent); gap:1rem; }
.teach-cta svg{ width:15px; height:15px; }

/* ---------------------------------------------------------- ABOUT */
.about-grid{ display:grid; grid-template-columns:1fr; gap:clamp(2rem,5vw,4rem); align-items:start; }
.about-portrait{ position:relative; margin:0; }
/* max-height matters in the stacked layout: this file is 998x2048, so at a
   tablet's full column width the 4/5 box would still be ~865px tall.

   RADIUS — deliberate exception to the site-wide 2px. This is the only
   photograph of a person on the site (everything else at 2px is a film still, a
   button or a video frame), and softening the one human image reads as warmth
   rather than inconsistency. Kept at 6px on purpose: past ~8px it starts to
   read like a profile card, which works against the subject matter. */
.about-portrait img{ width:100%; height:auto; aspect-ratio:4/5; object-fit:cover; object-position:center 10%; border-radius:6px; filter:grayscale(.15); }

/* Stacked layout (below the 860px two-column breakpoint): cap the figure's
   WIDTH, not the image's height.

   This used to be `max-height:60svh` on the image, which quietly broke the
   crop: max-height wins over aspect-ratio, so as the column got wider the box
   turned LANDSCAPE while the source file is 998x2048 (1:2 portrait). At ~850px
   the box was 854x425 and object-fit:cover threw away roughly three quarters of
   the picture — the portrait rendered as an extreme close-up of her eyes.
   Capping the width keeps the 4/5 box intact at every size, and the height
   follows from the ratio instead of fighting it. */
@media(max-width:859px){
  .about-portrait{ max-width:min(340px,100%); }
}

/* NOTE: this block must stay AFTER the base .about-portrait rules above — media
   queries add no specificity, so an equal-specificity base rule placed later
   would silently win and the portrait would keep its fixed 4/5 box.

   Narrow portrait column + a full-width row underneath for the broadcasters.
   `align-items:stretch` lets the figure take the row height and the image flexes
   to fill it, so the portrait starts at the lead paragraph and ends exactly
   where the biography ends instead of overhanging it by ~150px. */
@media(min-width:860px){
  .about-grid{
    grid-template-columns:minmax(0,clamp(230px,23vw,330px)) minmax(0,1fr);
    align-items:stretch;
    column-gap:clamp(2rem,5vw,4rem);
    row-gap:clamp(2.5rem,5vw,4rem);
  }
  .about-collab{ grid-column:1 / -1; }
  .about-portrait{ display:flex; flex-direction:column; }
  /* flex-basis 0 (not auto) is the important bit: with `auto` the image's own
     intrinsic height — this file is 998x2048, very tall — becomes the figure's
     content height and drives the grid row, which is what made the portrait
     overshoot the biography. At 0 it contributes nothing to row sizing and
     simply grows into whatever height the bio column sets. */
  .about-portrait img{ flex:1 1 0; min-height:0; aspect-ratio:auto; }
}
.about-portrait figcaption{ font-size:.72rem; color:var(--ink-faint); margin-top:.6rem; letter-spacing:.14em; text-transform:uppercase; }
.about-body .lead{ font-family:var(--serif); font-size:clamp(1.3rem,2.4vw,1.7rem); line-height:1.4; font-weight:400; margin:0 0 1.6rem; max-width:34ch; }
/* Measure cap — without it the biography runs to unreadable line lengths once
   the container is allowed to grow on large displays. */
.about-body p{ color:var(--ink-soft); margin:0 0 1.1rem; max-width:68ch; }
.about-sub{ font-family:var(--sans)!important; font-size:.72rem!important; font-weight:600; letter-spacing:.2em; text-transform:uppercase; color:var(--ink); margin:2.4rem 0 .6rem; }
/* first thing in its own grid row now — no leading margin to collapse against */
.about-collab .about-sub{ margin-top:0; }
/* broadcasters as a credits roll, not pill chips. Three columns at full width
   lays the nine names out as exactly three rows. */
.collab-list{ display:grid; grid-template-columns:1fr 1fr; gap:0 2.4rem; padding:0; margin:0; }
@media(min-width:860px){ .collab-list{ grid-template-columns:repeat(3,1fr); gap:0 clamp(2rem,4vw,3.5rem); } }
.collab-list li{
  list-style:none; font-size:.9rem; color:var(--ink-soft);
  padding:.6rem 0; border-bottom:1px solid var(--line);
}
@media(max-width:520px){ .collab-list{ grid-template-columns:1fr; } }

/* ---------------------------------------------------------- CONTACT */
.contact{ background:var(--ink); color:var(--paper); }
.contact .head-top::after{ background:#3a362f; }
.contact .head-meta{ color:#b7b1a4; }
.contact .section-head h1, .contact .section-head h2{ color:var(--paper); }
.contact .section-head .lead-note{ color:#c8c2b5; }
.contact-grid{ display:grid; grid-template-columns:1fr; gap:clamp(2.2rem,5vw,4rem); }
@media(min-width:860px){ .contact-grid{ grid-template-columns:1.3fr .7fr; } }
.form-row{ display:grid; gap:1rem; grid-template-columns:1fr 1fr; }
@media(max-width:560px){ .form-row{ grid-template-columns:1fr; } }
.field{ display:flex; flex-direction:column; margin-bottom:1rem; }
.field label{ font-size:.75rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase; color:#b7b1a4; margin-bottom:.45rem; }
.field input, .field textarea{
  background:transparent; border:0; border-bottom:1px solid #3a362f; color:var(--paper);
  font-family:var(--sans); font-size:1rem; padding:.7rem 0; min-height:44px; transition:border-color .3s;
}
.field .req{ color:#e6a3a3; margin-left:.15rem; } /* required-field marker */
.field input:focus, .field textarea:focus{ border-color:#e6a3a3; } /* outline handled by :focus-visible */
.field textarea{ resize:vertical; min-height:120px; }
.btn-submit{
  margin-top:.6rem; align-self:flex-start; cursor:pointer;
  font-family:var(--sans); font-size:.8rem; font-weight:600; letter-spacing:.16em; text-transform:uppercase;
  background:var(--paper); color:var(--ink); border:0; border-radius:2px; padding:1rem 2rem;
  transition:background .3s, transform .2s;
}
.btn-submit:hover{ background:#fff; transform:translateY(-2px); }
.btn-submit[disabled]{ opacity:.55; cursor:progress; transform:none; }
/* success: the label crossfades to a checkmark, so confirmation isn't only in
   the aria-live line (which sighted users routinely miss) */
.btn-submit .btn-label{ display:inline-flex; align-items:center; gap:.5rem; transition:opacity .25s var(--ease); }
.btn-submit.swapping .btn-label{ opacity:0; }
.btn-submit.sent{ background:#d8e6d4; color:#1d3a22; }
.btn-submit.sent svg{ width:14px; height:14px; }
.form-note{ margin:1rem 0 0; font-size:.9rem; min-height:1.2em; }
.form-note:empty{ margin:0; }
/* honeypot — visually and programmatically removed from the human flow */
.form-hp{ position:absolute!important; left:-9999px!important; width:1px; height:1px; opacity:0; pointer-events:none; }
.contact-direct{ border-top:1px solid #3a362f; padding-top:1.6rem; }
@media(min-width:860px){ .contact-direct{ border-top:0; border-left:1px solid #3a362f; padding-top:0; padding-left:clamp(2rem,4vw,3.5rem); } }
.contact-direct .lbl{ font-size:.7rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase; color:#b7b1a4; }
.contact-direct a{ font-family:var(--serif); font-size:clamp(1.3rem,2.5vw,1.75rem); display:flex; align-items:center; min-height:44px; margin:.3rem 0 1.6rem; transition:color .25s; }
.contact-direct a:hover{ color:#e6a3a3; }

/* ---------------------------------------------------------- FOOTER */
.site-footer{ background:var(--black); color:#8a8478; padding-block:2.4rem; }
.site-footer .wrap{ display:flex; flex-wrap:wrap; gap:1rem; justify-content:space-between; align-items:center; font-size:.8rem; }
.site-footer .brand{ color:var(--paper); }
.site-footer .cities{ letter-spacing:.16em; text-transform:uppercase; font-size:.72rem; font-weight:600; }

/* ---------------------------------------------------------- REVEAL ANIM */
[data-reveal]{ opacity:0; transform:translateY(26px); transition:opacity .7s var(--ease-out), transform .7s var(--ease-out); }
[data-reveal].in{ opacity:1; transform:none; }
/* Grid entrance stagger lives in JS (observeReveal batches by viewport),
   so hover transitions never inherit a reveal delay. */
/* Safety net: if JS/IntersectionObserver is unavailable, never leave content hidden */
.no-js [data-reveal], [data-reveal].in{ opacity:1; transform:none; }

/* ---------------------------------------------------------- FILM LIGHTBOX
   The film opened over the grid: the card's own artwork at full size with a play
   control on it, arrows through the filmography, an enlarge toggle, and the
   film's copy in a column beside it.

   Paper, not the usual cinema black. A near-opaque paper scrim keeps the
   overlay inside the site's own material - the page has been warm off-white
   from the first screen, and dropping to a black theatre for one interaction
   would read as a different product. It also keeps the running copy in the
   column dark-on-light, which is where this typeface is set up to be legible.

   Built by ensureLightbox() in main.js; every string in it comes from data.js. */
.lb{
  position:fixed; inset:0; z-index:120;
  opacity:0; transition:opacity .26s var(--ease);
}
.lb.on{ opacity:1; }
.lb-scrim{ position:absolute; inset:0; background:var(--paper); }
/* Only a phone pins the arrows to the foot of the screen, so only a phone needs
   the paper band under them - see the max-width:899px block. */
.lb-foot{ display:none; }

.lb-panel{
  position:relative; height:100%;
  padding:clamp(3.4rem,7vh,4.6rem) clamp(3.2rem,6vw,5.5rem);
  display:flex; flex-direction:column;
}

/* Image left, copy right - the proportion the reference sets, and the one that
   lets a portrait one-sheet stand at full height without squeezing the text
   below a readable measure. */
.lb-grid{
  display:grid; gap:clamp(1.4rem,3.5vw,3.2rem);
  grid-template-columns:minmax(0,1fr);
  min-height:0; flex:1;
}
@media(min-width:900px){
  .lb-grid{ grid-template-columns:minmax(0,1fr) clamp(300px,26vw,400px); align-items:center; }
}

/* ---- stage: the artwork, and the trailer that replaces it ---- */
.lb-stage{
  display:flex; align-items:center; justify-content:center; min-width:0; min-height:0;
  /* Vertical scrolling stays with the browser; the horizontal axis is ours, for
     the swipe-to-change-film gesture in initLightboxSwipe(). Without this the
     browser can claim the gesture and cancel the pointer stream mid-swipe. */
  touch-action:pan-y;
}
/* No percentage max-height here or on the image below: the stage is an auto
   height flex/grid item, so a percentage has nothing to resolve against and
   silently drops - which let a 1180x1668 one-sheet render at its intrinsic
   height and run off both ends of the panel. Viewport units are the only
   dependable cap in this box. */
.lb-media{
  position:relative; display:block; max-width:100%;
  padding:0; border:0; background:none; cursor:pointer; line-height:0;
  border-radius:2px; overflow:hidden;
}
.lb-media[disabled]{ cursor:default; }
.lb-img{
  max-height:72vh; max-width:100%; width:auto; height:auto;
  object-fit:contain;
  box-shadow:0 18px 50px rgba(21,19,15,.18);
}
/* Play control - a ring over the middle of the still, the way a projected frame
   is marked. Brightens the art under it on hover rather than dimming the page. */
.lb-play{
  position:absolute; inset:0; display:grid; place-items:center;
  background:linear-gradient(rgba(11,10,8,.12), rgba(11,10,8,.28));
  transition:background .35s var(--ease);
}
.lb-play svg{
  width:26px; height:26px; margin-left:4px;   /* optical centring of the triangle */
  color:var(--paper);
}
.lb-play::before{
  content:""; position:absolute; width:76px; height:76px; border-radius:50%;
  border:1px solid rgba(245,242,236,.85); background:rgba(21,19,15,.42);
  backdrop-filter:blur(2px);
  transition:background .35s var(--ease), transform .35s var(--ease), border-color .35s var(--ease);
}
.lb-play svg{ position:relative; z-index:1; }
.lb-media:hover .lb-play{ background:linear-gradient(rgba(11,10,8,.06), rgba(11,10,8,.2)); }
.lb-media:hover .lb-play::before{ background:var(--accent); border-color:var(--accent); transform:scale(1.06); }

.lb-player{ width:100%; max-width:min(100%, calc(72vh * 16 / 9)); aspect-ratio:16/9; background:var(--black); border-radius:2px; }
.lb-player iframe{ width:100%; height:100%; border:0; display:block; }

/* ---- copy column ---- */
.lb-info{
  min-width:0; max-height:100%; overflow-y:auto;
  padding-right:.8rem;
  /* The column scrolls on its own; a hairline marks where it starts so it does
     not read as loose text floating beside the picture. */
  border-left:1px solid var(--line); padding-left:clamp(1.2rem,2vw,1.8rem);
}
.lb-eyebrow{
  margin:0 0 .7rem; font-family:var(--mono); font-size:.72rem; letter-spacing:.06em;
  color:var(--ink-faint); font-variant-numeric:tabular-nums;
}
.lb-info h2{ font-size:clamp(1.5rem,2.4vw,2.05rem); font-weight:500; margin:0; }
.lb-tl{
  font-family:var(--serif); font-style:italic; font-size:1.05rem; color:var(--ink-soft);
  margin:.7rem 0 0;
}
.lb-info h3{
  font-family:var(--sans); font-size:.68rem; font-weight:700; letter-spacing:.18em;
  text-transform:uppercase; color:var(--ink-faint);
  margin:1.6rem 0 .5rem;
}
.lb-info p{ margin:0; font-size:.94rem; line-height:1.7; color:var(--ink-soft); }
.lb-facts{ margin:1.3rem 0 0; display:grid; gap:.55rem; }
.lb-facts div{ display:grid; gap:.1rem; }
.lb-facts dt{
  font-family:var(--sans); font-size:.64rem; font-weight:700; letter-spacing:.16em;
  text-transform:uppercase; color:var(--ink-faint);
}
.lb-facts dd{ margin:0; font-size:.9rem; color:var(--ink-soft); }
.lb-awards{ list-style:none; margin:0; padding:0; display:grid; gap:.55rem; }
.lb-awards li{ font-size:.86rem; line-height:1.5; color:var(--ink-soft); }
.lb-awards strong{ color:var(--ink); font-weight:600; }
/* The watch button. These rules were shared with the film detail page until it
   was removed in September 2026 — the lightbox is their only user now, so they
   live here rather than in a general block that nothing else reaches. */
.btn{
  display:inline-flex; align-items:center; gap:.55rem;
  font-size:.76rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  padding:.9rem 1.5rem; min-height:48px; border-radius:2px;
  transition:transform .2s, background .3s, color .3s, border-color .3s;
}
.btn svg{ width:15px; height:15px; }
.btn-primary{ background:var(--accent); color:#fff; }
.btn-primary:hover{ background:var(--accent-dk); transform:translateY(-2px); }

.lb-actions{ display:flex; flex-wrap:wrap; align-items:center; gap:.9rem 1.4rem; margin-top:1.8rem; }
/* The column scrolls, and on a long synopsis the two links would sit below its
   fold - i.e. the one control that takes you to the film would be the one part
   of the panel you have to hunt for. Pinned to the bottom of the column, with
   the paper carried out past its edges so the text scrolls away underneath
   rather than through it. */
@media(min-width:900px){
  .lb-actions{
    position:sticky; bottom:0; margin-top:1.8rem;
    padding:1rem 0 .2rem; background:var(--paper);
    box-shadow:0 -14px 16px 0 var(--paper);
  }
}

/* ---- controls ---- */
.lb-icon, .lb-arrow{
  position:absolute; z-index:2;
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px; padding:0;
  background:none; border:0; color:var(--ink-soft); cursor:pointer;
  transition:color .3s var(--ease), transform .3s var(--ease), opacity .3s var(--ease);
}
.lb-icon:hover, .lb-arrow:hover{ color:var(--accent); }
.lb-icon svg, .lb-arrow svg{ width:24px; height:24px; }
.lb-zoom{ top:clamp(.9rem,2.4vh,1.5rem); left:clamp(.9rem,2vw,1.6rem); }
.lb-close{ top:clamp(.9rem,2.4vh,1.5rem); right:clamp(.9rem,2vw,1.6rem); }
.lb-close svg{ width:28px; height:28px; }
.lb-arrow{ top:50%; transform:translateY(-50%); width:48px; height:48px; }
.lb-prev{ left:clamp(.2rem,1vw,1rem); }
.lb-next{ right:clamp(.2rem,1vw,1rem); }
.lb-prev:hover{ transform:translateY(-50%) translateX(-3px); }
.lb-next:hover{ transform:translateY(-50%) translateX(3px); }
/* On the widest screens the copy column already ends well short of the edge, so
   the forward arrow sits between the two rather than off in the margin. */
@media(min-width:900px){
  .lb-next{ right:clamp(.2rem,1vw,1rem); }
}

/* Enlarge: the copy column drops out and the artwork takes the panel. */
.lb.zoomed .lb-info{ display:none; }
.lb.zoomed .lb-grid{ grid-template-columns:minmax(0,1fr); }
.lb.zoomed .lb-img{ max-height:86vh; }
.lb.zoomed .lb-player{ max-width:min(100%, calc(86vh * 16 / 9)); }

/* Phones: one column and the whole PANEL scrolls, so the picture and the copy
   read as one page rather than two scrollers stacked on each other.

   Two things have to give way for that. The grid stops being a flex:1 row (a
   stretched row plus align-items:center pushes overflowing content UP, off the
   top of the screen — which is exactly what a tall one-sheet did here), and the
   stage aligns to the start instead of the centre.

   The controls are laid out in two places, not one. The top bar keeps only what
   closes or reframes the panel; PREV/NEXT go to the FOOT of the screen, fixed to
   the viewport, one in each bottom corner. They were in that top bar too
   ([enlarge] [prev] [next] ....... [close]) and read as four unrelated glyphs
   crowded into the top-left, the farthest corner from a thumb, while the thing
   they change was the full height of the screen below them. Fixed rather than
   in flow because the panel itself scrolls on a phone - in flow they would be
   under the synopsis, and stepping through the films would mean scrolling to the
   bottom for every step. The artwork also takes a horizontal swipe
   (initLightboxSwipe), which is the gesture reached for first; the arrows are
   what makes that discoverable and what serves anyone not using touch. */
@media(max-width:899px){
  /* Bottom padding clears the fixed arrow bar so the last line of the synopsis
     is not sitting underneath it. */
  .lb-panel{ padding:3.9rem 1.1rem 5.9rem; overflow-y:auto; }
  .lb-grid{ display:block; flex:none; }
  .lb-stage{ align-items:flex-start; }
  .lb-img{ max-height:54vh; }
  .lb-info{
    border-left:0; padding-left:0; padding-right:0;
    border-top:1px solid var(--line); padding-top:1.4rem; margin-top:1.6rem;
    max-height:none; overflow:visible;
  }
  /* Paper chips rather than bare glyphs: these float over whatever part of the
     panel has scrolled under them, artwork included, so they need their own
     ground to stay legible. 2px radius and a hairline, like every other bordered
     thing on the site. */
  .lb-arrow{
    position:fixed; top:auto; bottom:max(1rem, env(safe-area-inset-bottom));
    transform:none; width:52px; height:52px;
    background:var(--paper); border:1px solid var(--line); border-radius:2px;
    color:var(--ink); box-shadow:0 6px 20px rgba(11,10,8,.10);
  }
  .lb-prev{ left:clamp(.9rem,4vw,1.6rem); right:auto; }
  .lb-next{ right:clamp(.9rem,4vw,1.6rem); left:auto; }
  /* The band the chips sit on. It has to be a real element, not a shadow on the
     buttons: without it a line of the synopsis scrolls up INTO the gap between
     them and reads as text sliced in half by two floating squares. z-index 1
     keeps it under the arrows (z-index 2) and over the panel, which carries no
     z-index of its own. */
  .lb-foot{
    display:block; position:fixed; left:0; right:0; bottom:0; z-index:1;
    height:calc(5.9rem + env(safe-area-inset-bottom));
    background:linear-gradient(0deg, var(--paper) 58%, rgba(245,242,236,0) 100%);
    pointer-events:none;
  }
  .lb-prev:hover, .lb-next:hover{ transform:none; }
  .lb-arrow svg{ width:22px; height:22px; }
  .lb-play::before{ width:62px; height:62px; }
  /* Enlarged on a phone means "the picture, nothing else" — the copy is already
     below it rather than beside it, so only the cap changes. */
  .lb.zoomed .lb-img{ max-height:82vh; }
}

/* No full screen on a phone (client's call, September 2026). The control opened
   a poster onto a display that is already almost entirely poster - the panel is
   one column here, so "enlarge" bought a few percent of height and took away the
   copy - and iOS Safari refuses requestFullscreen() on anything but a <video>
   anyway, so on a large slice of the phones that reach it the button did nothing
   at all. `display:none` also takes it out of the tab order and out of
   trapTab()'s list, which filters on offsetParent, so nothing else needs saying.
   Kept from 760px up, where the copy column beside the artwork is real and
   dropping it genuinely changes the frame. */
@media(max-width:759px){
  .lb-zoom{ display:none; }
}

/* ---- real full screen ----
   The enlarge control puts the browser itself into full screen with the panel as
   the full-screen element, so the film fills the display with no page and no
   browser chrome around it. `.zoomed` (set from the fullscreenchange event) has
   already dropped the copy column; these rules only take back the padding the
   panel needs when it is a window and does not when it is the whole screen.

   Prefixed and unprefixed are written as SEPARATE rules on purpose: one
   unrecognised selector invalidates a whole selector list, so pairing them would
   silently kill the rule in every browser that knows only one of the two.

   The paper background is not decorative — the UA stylesheet paints the
   full-screen element black unless it sets its own, and the scrim inside it
   would then be a paper sheet on a black ground during the transition. */
.lb:fullscreen{ background:var(--paper); }
.lb:-webkit-full-screen{ background:var(--paper); }
.lb:fullscreen .lb-panel{ padding:clamp(2rem,4vh,3rem) clamp(2rem,4vw,4rem); }
.lb:-webkit-full-screen .lb-panel{ padding:clamp(2rem,4vh,3rem) clamp(2rem,4vw,4rem); }
.lb:fullscreen .lb-img{ max-height:90vh; }
.lb:-webkit-full-screen .lb-img{ max-height:90vh; }
.lb:fullscreen .lb-player{ max-width:min(100%, calc(90vh * 16 / 9)); }
.lb:-webkit-full-screen .lb-player{ max-width:min(100%, calc(90vh * 16 / 9)); }

/* ---------------------------------------------------------- SMALL SCREENS
   Fixes from the 320/360/390px audit (August 2026). Nothing here was causing
   horizontal overflow — the document never exceeded its viewport width. The
   defects were all "technically fits, reads badly": right-aligned monospace
   wrapping into ragged blocks, a table clipped inside its own scroller with no
   affordance, dark nav glyphs over a dark poster, and section padding so deep
   that tapping a nav link landed you on empty paper. */
@media(max-width:600px){
  /* Section meta is set right-aligned in mono, which at this width wraps into a
     ragged two-line block hugging the right edge with nothing to align to.
     Left-align it so it reads as an eyebrow above the heading. */
  .head-meta{ text-align:left; margin-inline:0 auto; max-width:100%; }
  .head-top{ flex-direction:column; align-items:flex-start; gap:.5rem; }

  /* Deep section padding + a 90px scroll-margin meant a nav tap landed the
     heading roughly 500px down a 720px viewport — you saw paper, not the
     section you asked for. */
  .section{ padding-block:clamp(3rem,7vh,4.5rem); }
  .section-head{ margin-bottom:1.9rem; }
}

@media(max-width:767px){
  /* The masthead used to open with a DARK poster band exactly where the fixed
     header sits, which swallowed the dark-ink brand and hamburger until the
     first scroll. The poster is gone (Sept 2026) and the page is paper to the
     top edge, so the faint gradient is enough again — but the opening is tight
     on a phone, so pull the header clearance in. (The padding lives on
     .films-section now: the masthead and the grid are one section.) */
  .films-section{ padding-top:clamp(5.5rem,12vh,7rem); }
}

@media(max-width:400px){
  /* Keep the masthead from pushing the first film row far below the fold. */
  .hero-title{ font-size:clamp(2rem,10.5vw,2.6rem); }
  .hero .tagline{ font-size:1.02rem; margin-top:1rem; }
  .hero-stats .l{ letter-spacing:.1em; overflow-wrap:break-word; }
  .btn-hero{ padding:.85rem 1.2rem; font-size:.72rem; }

  .section-head h1, .section-head h2{ font-size:clamp(2rem,10vw,2.6rem); }
  /* 3 language segments + brand + burger is tight at 320px */
  .lang-opt{ padding:.45rem .5rem; font-size:.66rem; }
}

/* ---------------------------------------------------------- MOBILE NAV */
@media(max-width:760px){
  .nav-toggle{ display:inline-flex; }
  .nav{
    position:fixed; inset:0; flex-direction:column; justify-content:center; gap:2rem;
    background:var(--ink); color:#fff; transform:translateX(100%);
    visibility:hidden;
    transition:transform .45s var(--ease), visibility 0s linear .45s;
  }
  .nav.open{ transform:none; visibility:visible; transition:transform .45s var(--ease), visibility 0s; }
  .nav a{ font-size:1.1rem; }
  /* In the dark overlay the switcher inherits white, so the filled segment
     needs dark text — see the .nav.open rule beside .lang-opt.on. */
  .lang-switch{ color:#fff; }
  .lang-opt{ font-size:.8rem; padding:.55rem 1rem; }
  /* backdrop-filter on the solid header would become the containing block
     for the fixed-position nav overlay — drop it while the menu is open */
  .site-header.nav-is-open{ color:#fff; background:transparent; backdrop-filter:none; box-shadow:none; }

  /* Close control. The overlay (.nav) is a positioned sibling of the toggle in
     the header's stacking context, so without an explicit z-index the toggle's
     paint order over it is not guaranteed. Pin it above, and give it a visible
     box so it reads as "close" rather than a stray hairline glyph. */
  .site-header.nav-is-open .nav-toggle{
    position:relative; z-index:60;
    border:1px solid rgba(255,255,255,.45); border-radius:2px;
    background:rgba(255,255,255,.06);
    transition:background .3s var(--ease), border-color .3s var(--ease);
  }
  .site-header.nav-is-open .nav-toggle:hover{ background:rgba(255,255,255,.16); border-color:#fff; }
  /* the overlay itself is a dismiss target — tapping any empty space closes it */
  .nav.open{ cursor:default; }
}

/* 404 clears the fixed header itself.
   The SUB-PAGES rule that does this for the other documents is
   `.page main > .section:first-child` — and on 404 the `main` element IS the
   section, not a child of it, so that selector never matched and the "Error 404"
   line sat behind the header at every width below 760px. (At <=600px the
   narrow-screen rule trims .section's padding further, which is why the heading
   itself went under the bar too.) The header also gets taller as the brand and
   the inline nav below wrap, so the clearance is stepped rather than fixed. */
.page-404 main.section{ padding-top:clamp(7rem,14vh,9rem); }
@media(max-width:760px){ .page-404 main.section{ padding-top:9rem; } }
@media(max-width:360px){ .page-404 main.section{ padding-top:11rem; } }

/* 404 keeps its nav inline on a phone.
   The rule above turns .nav into a fixed off-canvas panel that only a
   .nav-toggle can open — and 404.html deliberately ships NO JavaScript, so it
   has no toggle and the four links simply vanished below 760px, leaving the
   back-link and the footer as the only ways off the page. It can afford to stay
   inline where the other pages cannot: four short links and no language
   switcher. */
@media(max-width:760px){
  .page-404 .site-header{ flex-wrap:wrap; gap:.5rem 1rem; }
  .page-404 .nav{
    position:static; inset:auto; visibility:visible; transform:none;
    flex-direction:row; flex-wrap:wrap; justify-content:flex-start;
    gap:.2rem 1.1rem; background:none; color:inherit;
  }
  .page-404 .nav a{ font-size:.72rem; min-height:38px; }
}

/* ---------------------------------------------------------- REDUCED MOTION
   The preference means "stop moving things", not "make the interface feel
   dead". Movement (transform, animation, the marquee, parallax) is removed;
   colour/opacity feedback on hover and focus is kept, just fast. Anything whose
   resting state is hidden-until-animated is forced to its final state here so a
   reveal can never strand content invisible. */
@media(prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *{ animation:none!important; }
  *{
    transition-property:color, background-color, border-color, outline-color, opacity, box-shadow !important;
    transition-duration:.18s !important;
    transition-delay:0s !important;
  }
  [data-reveal]{ opacity:1!important; transform:none!important; }
  .head-top::after{ transform:none!important; }
  .aw{ opacity:1!important; transform:none!important; }
  .film-card .thumb img{ filter:grayscale(0) saturate(.9)!important; transform:none!important; }
  /* the hover underline stays — background-size isn't in the transition list
     above, so it simply appears instantly instead of drawing. That's feedback,
     not motion, and removing it would cost the affordance for no benefit. */
  .year-rail span{ opacity:1; }
}
