/* The Film Timeline design system.
   Dark, filmic, typography led. Deliberately nothing like the blue/green
   Letterboxd palette: warm ivory on near black with a projector amber accent,
   and a hot/cool pair used only for "you rated above/below the crowd". */

@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600;700&display=swap');

:root {
  --ink: #0a0a0c;
  --panel: #131317;
  --panel-2: #1b1b21;
  --line: #2a2a32;
  --text: #f4f1ea;
  --muted: #a5a29a;
  --amber: #e9b949;
  --hot: #e05242;
  --cool: #5b93d6;
  --radius: 14px;
  --serif: 'Instrument Serif', ui-serif, Georgia, serif;
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --wrap: 1120px;
  --on-amber: #17130a;   /* text that sits on an amber fill */
  /* Amber used AS TEXT, as opposed to as a fill. In dark mode they are the same
     colour; in light mode the brand amber only reaches 4.31:1 on the page
     background, which fails AA for anything at body size. Splitting the token
     keeps buttons and the logo their proper colour while small amber text gets
     a value that is actually readable. */
  --amber-text: var(--amber);
  --shadow: rgba(0, 0, 0, .5);
  color-scheme: dark;
}

/* Light theme.
   Declared twice on purpose. The media query serves people who never touch the
   toggle, and the attribute rule serves people who did; without both, the
   toggle cannot override a light OS setting. `:not([data-theme='dark'])` is what
   keeps an explicit dark choice from being clobbered by the OS. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) {
    --ink: #f7f4ed;
    --panel: #fffdf9;
    --panel-2: #efeae0;
    --line: #ded6c8;
    --text: #16140f;
    --muted: #5c5648;
    --amber: #9a6a08;
    --hot: #b03a2d;
    --cool: #2c6398;
    --on-amber: #fffaf0;
    --amber-text: #6d4b06;
    --shadow: rgba(60, 50, 30, .16);
    color-scheme: light;
  }
}
:root[data-theme='light'] {
  --ink: #f7f4ed;
  --panel: #fffdf9;
  --panel-2: #efeae0;
  --line: #ded6c8;
  --text: #16140f;
  --muted: #5c5648;
  --amber: #9a6a08;
  --hot: #b03a2d;
  --cool: #2c6398;
  --on-amber: #fffaf0;
  --amber-text: #6d4b06;
  --shadow: rgba(60, 50, 30, .16);
  color-scheme: light;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* The ink lives on html, and body stays transparent. This is not cosmetic:
   painting order puts a negative z-index pseudo-element BEHIND its own
   element's background, so an opaque body background hid the backdrop
   completely while every computed style looked correct. */
html { background-color: var(--ink); }

body {
  background-color: transparent;
  min-height: 100dvh;
  /* Column flex so the footer can be pushed to the bottom of the viewport on
     short pages. Without it the footer ended wherever the content ended and
     left a strip of bare backdrop underneath, which reads as a broken layout
     rather than a design choice.
     `overflow-x: clip` above is load bearing here: `hidden` would turn body
     into a scroll container and break the sticky nav. */
  display: flex;
  flex-direction: column;
  /* clip, not hidden: hidden turns body into a scroll container and silently
     kills position: sticky on the nav and the year rail. */
  overflow-x: clip;
}

/* ------------------------------------------------------------- backdrop --- */
/* A wall of public domain posters, blurred into colour. Real poster art, and
   art the site is allowed to use: the source images are the PD and CC0 files
   already in the database, composed into one 33KB JPEG at build time rather
   than two dozen requests in the browser.
   Two layers, and both are load bearing. The image sits at low opacity, and a
   scrim sits over it, because colour this saturated behind body text fails
   contrast immediately without one. */
body::before,
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
}
body::before {
  /* WebP first at less than half the bytes; the plain url() is the fallback for
     anything that cannot parse image-set. */
  background: url('/img/backdrop.jpg') center / cover no-repeat;
  background-image: image-set(
    url('/img/backdrop.webp') type('image/webp'),
    url('/img/backdrop.jpg') type('image/jpeg'));
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Very nearly full strength. Opacity multiplies the image's own internal
     contrast range, so anything much below this turns poster title type into
     mush regardless of how bright the result looks. Legibility is handled by
     the plates below, not by dimming the wall. */
  opacity: .95;
}
/* One scrim, not two stacked ones. Two gradients both resolving to solid ink
   cancelled the image out entirely and the backdrop may as well not have been
   there. This is light at the top, where the hero has room to breathe, and
   solid by the time the content gets dense further down. */
body::after {
  z-index: -1;
  /* Never reaches solid. The old scrim hit 100% ink partway down, which is why
     the posters vanished the moment you scrolled: most of the page was simply
     a flat colour with an image hidden behind it. */
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--ink) 8%, transparent) 0%,
    color-mix(in srgb, var(--ink) 28%, transparent) 50%,
    color-mix(in srgb, var(--ink) 42%, transparent) 100%);
}

/* The posters are bright, cream-heavy stock. On the light theme they wash the
   page out rather than tint it, so the image drops back and the scrim thins. */
/* Light theme gets its own numbers, not a scaled-down copy of the dark ones.
   These are cream-stock posters on a cream page, so the failure mode is the
   opposite: they wash out into the background rather than glaring off it, and
   they need MORE opacity to read at all, with a thinner scrim over them. */
:root[data-theme='light'] body::before { opacity: .88; }
:root[data-theme='light'] body::after {
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--ink) 16%, transparent) 0%,
    color-mix(in srgb, var(--ink) 36%, transparent) 50%,
    color-mix(in srgb, var(--ink) 52%, transparent) 100%);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) body::before { opacity: .88; }
  :root:not([data-theme='dark']) body::after {
    background: linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) 16%, transparent) 0%,
      color-mix(in srgb, var(--ink) 36%, transparent) 50%,
      color-mix(in srgb, var(--ink) 52%, transparent) 100%);
  }
}

/* Honour a reduced-data or reduced-motion preference by skipping the image
   entirely: it is decoration, and it is the single largest asset on the page. */
@media (prefers-reduced-data: reduce) {
  body::before { background-image: none; }
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }

.wrap { width: min(100% - 2.5rem, var(--wrap)); margin-inline: auto; }

/* ------------------------------------------------------------------ nav --- */
.nav {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.1rem 0; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--ink) 86%, transparent);
  backdrop-filter: blur(14px);
}
/* Logo lockup: a strip of film whose frames are the rising bars of the timeline
   rail, so the mark is drawn from the site's own signature visual rather than
   bolted on. The strip and sprockets take currentColor and the bars take the
   amber token, which is what lets one SVG work in both themes. */
/* The gap is generous on purpose. The mark ends in a hard stroked edge and the
   serif cap T has almost no left side bearing, so at .6rem the two read as
   touching even though they are not. */
.brand { display: inline-flex; align-items: center; gap: .85rem; color: var(--text); }
.brand .mark { flex: 0 0 auto; width: 34px; height: 25px; display: block; }
.brand-words {
  font-family: var(--serif); font-size: 1.42rem; line-height: 1;
  letter-spacing: .005em; white-space: nowrap;
}
.brand em { color: var(--amber); font-style: italic; }
.brand:hover .mark { opacity: .85; }

/* On a narrow screen the mark carries the brand on its own; three words of
   serif beside it would push the nav links off the edge. */
@media (max-width: 620px) {
  .brand-words { font-size: 1.1rem; }
  .brand { gap: .65rem; }
  .brand .mark { width: 28px; height: 21px; }
}
@media (max-width: 520px) {
  .brand-words { display: none; }
}

.nav-links { display: flex; gap: 1.4rem; font-size: .9rem; color: var(--muted); font-weight: 500; }
.nav-links a:hover, .nav-links a[aria-current] { color: var(--text); }

/* The navigation outgrew the phone. Seven destinations at 1.4rem apart came to
   631px inside a 390px viewport, which pushed every page into sideways scroll
   — not just the header, the whole document.
   
   It scrolls itself instead of pushing the page. Every destination stays
   reachable, which a hamburger behind a tap would not manage any better at
   this count, and the row keeps its position under the thumb. */
@media (max-width: 780px) {
  .nav .wrap { gap: .6rem; }
  .nav-links {
    gap: .95rem;
    font-size: .82rem;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* The row is the flex item that must be allowed to shrink; without this it
       takes its content width and overflows the header regardless. */
    min-width: 0;
    padding-bottom: .1rem;
    -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent);
            mask-image: linear-gradient(90deg, #000 88%, transparent);
  }
  .nav-links::-webkit-scrollbar { display: none; }
  .nav-links > * { flex: none; }
}

/* --------------------------------------------------------------- typography */
h1, h2, h3 { font-family: var(--serif); font-weight: 400; letter-spacing: .005em; margin: 0; }
h1 { font-size: clamp(2.4rem, 6vw, 4.2rem); line-height: 1.02; }
h2 { font-size: clamp(1.6rem, 3.4vw, 2.4rem); line-height: 1.1; }
.kicker {
  font-family: var(--sans); font-size: .72rem; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--amber-text);
}
.muted { color: var(--muted); }
.lede { font-size: 1.05rem; line-height: 1.6; color: var(--muted); max-width: 56ch; }

/* ------------------------------------------------------------------ cards -- */
.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.grid { display: grid; gap: 1rem; }
.games-grid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

.game-card { padding: 1.4rem; display: flex; flex-direction: column; gap: .5rem; min-height: 190px;
  transition: border-color .18s ease, transform .18s ease; }
.game-card:hover { border-color: color-mix(in srgb, var(--text) 24%, var(--line)); transform: translateY(-2px); }
.game-card[data-status='soon'] { opacity: .55; }
.game-card h3 { font-size: 1.7rem; }
.game-card .tag {
  align-self: flex-start; margin-top: auto; font-size: .68rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; padding: .3rem .55rem;
  border: 1px solid var(--line); border-radius: 999px; color: var(--muted);
}
.game-card[data-status='live'] .tag { color: var(--amber-text); border-color: color-mix(in srgb, var(--amber) 40%, transparent); }

/* ------------------------------------------------------------------ button - */
.btn {
  font-family: var(--sans); font-size: .95rem; font-weight: 600;
  padding: .85rem 1.4rem; border-radius: 999px; border: 1px solid var(--line);
  background: var(--panel-2); color: var(--text); cursor: pointer;
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.btn:hover { border-color: color-mix(in srgb, var(--text) 30%, var(--line)); }
.btn:active { transform: scale(.985); }
.btn-primary { background: var(--amber); color: var(--on-amber); border-color: var(--amber); }
.btn-primary:hover { background: color-mix(in srgb, var(--amber) 82%, var(--text)); border-color: transparent; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn-ghost { background: transparent; }

/* --------------------------------------------------------- poster / card - */
/* Two things live in the same 2/3 box: a real licensed poster (an <img>) and a
   generated one for the ~95% of films where no free artwork exists. The
   generated card is built to read as a poster rather than as a database row,
   because a grid of sixty plain labels is what makes a catalogue feel dead. */
.poster {
  position: relative; aspect-ratio: 2/3; border-radius: 10px; overflow: hidden;
  border: 1px solid var(--line); background: var(--panel-2);
  transition: border-color .18s ease, transform .18s ease;
}
.poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
a:hover > .poster { border-color: color-mix(in srgb, var(--text) 28%, var(--line)); transform: translateY(-3px); }

.poster.gen {
  display: flex; flex-direction: column; padding: 1rem .85rem .9rem;
  /* Three stacked layers, top to bottom:
     1. a soft accent glow where the light falls,
     2. a vignette so the type stays legible over any colour pair,
     3. the genre gradient itself, running from --a to --b.
     For a single-genre film --b is that genre's own dark shade, so the card
     looks exactly as it did. For a rom-com --b is the second genre's colour and
     the card reads as both at once. */
  background:
    radial-gradient(110% 80% at var(--gx, 50%) var(--gy, 0%),
      color-mix(in srgb, var(--ac) 22%, transparent) 0%, transparent 58%),
    radial-gradient(150% 108% at 50% 40%,
      transparent 30%, color-mix(in srgb, var(--dk, #000) 70%, transparent) 100%),
    linear-gradient(158deg, var(--a) 6%, var(--b) 94%);
  color: #f6f3ec;
}
/* Light source moves per film so a screenful is not one repeated gradient. */
.poster.gen[data-light='0'] { --gx: 22%; --gy: 8%; }
.poster.gen[data-light='1'] { --gx: 78%; --gy: 10%; }
.poster.gen[data-light='2'] { --gx: 50%; --gy: 96%; }
.poster.gen[data-light='3'] { --gx: 88%; --gy: 62%; }

/* Film grain. An SVG turbulence tile rather than an image request: it is a few
   hundred bytes, scales to any card size, and stops the flat gradients from
   looking like CSS. */
.poster.gen::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  opacity: .32; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}
/* A hairline frame inset from the edge, the way a printed one-sheet is trimmed. */
.poster.gen::before {
  content: ''; position: absolute; inset: 6px; border-radius: 5px;
  border: 1px solid color-mix(in srgb, var(--ac) 22%, transparent);
  pointer-events: none;
}

.gen-top {
  position: relative; z-index: 1; flex: 0 0 auto;
  font-size: .58rem; letter-spacing: .18em; text-transform: uppercase;
  color: color-mix(in srgb, #fff 46%, transparent);
}
.gen-mid { position: relative; z-index: 1; margin: auto 0; text-align: center; }
.gen-tag {
  font-size: .55rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ac); margin-bottom: .5rem;
  /* Never let a long genre run past the card edge and get clipped. */
  overflow-wrap: anywhere; line-height: 1.5;
}
/* On a two-genre card each label wears its own colour, so the gradient is
   readable as a pairing rather than as an arbitrary wash. */
.gen-tag .g2 { color: var(--ac2, var(--ac)); }
.gen-tag .sep { color: color-mix(in srgb, #fff 34%, transparent); margin: 0 .35em; }
.gen-title {
  font-family: var(--serif); font-weight: 400; line-height: 1.04;
  font-size: clamp(.92rem, var(--ts, 1.4rem), 1.7rem);
  text-wrap: balance;
  /* Five lines is the most a 2/3 card holds before the billing block is pushed
     off. Beyond that the title truncates rather than breaking the layout. */
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
  text-shadow: 0 1px 18px rgba(0, 0, 0, .45);
}
.gen-foot {
  position: relative; z-index: 1; flex: 0 0 auto; text-align: center;
  font-size: .54rem; letter-spacing: .08em; text-transform: uppercase;
  line-height: 1.35;
  color: color-mix(in srgb, #fff 52%, transparent);
  /* Wraps to two lines instead of truncating. "Directed by Francis F…" reads as
     a bug; the billing block on a real poster wraps. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.gen-foot .rule {
  display: block; width: 26px; height: 1px; margin: 0 auto .55rem;
  background: color-mix(in srgb, var(--ac) 70%, transparent);
}
.poster.is-notable { border-color: color-mix(in srgb, var(--amber) 40%, var(--line)); }

/* Grows to fill whatever the footer does not need, which is what pins the
   footer to the bottom. The breathing room above the footer lives here as
   padding rather than as a margin on the footer itself: a margin would push the
   footer past the bottom of a short viewport and introduce a scrollbar on a
   page that has nothing to scroll. */
main { flex: 1 0 auto; padding-bottom: 5rem; }

footer { flex: 0 0 auto; border-top: 1px solid var(--line); padding: 2rem 0 3rem;
  color: var(--muted); font-size: .82rem; line-height: 1.7; }

/* ------------------------------------------------------------ theme togg --- */
.theme-btn {
  width: 34px; height: 34px; border-radius: 50%; padding: 0;
  display: grid; place-items: center; font-size: .95rem; line-height: 1;
  background: transparent; border: 1px solid var(--line); color: var(--muted);
  cursor: pointer; transition: color .15s ease, border-color .15s ease;
}
.theme-btn:hover { color: var(--text); border-color: color-mix(in srgb, var(--text) 30%, var(--line)); }
.nav-links { align-items: center; }

/* The generated posters keep their own dark palette in both themes. A printed
   one-sheet does not change colour depending on the wall it hangs on, and
   lightening them made the grid look washed out rather than bright. */

/* ------------------------------------------------------ streak account --- */
.claim {
  border: 1px solid color-mix(in srgb, var(--amber) 30%, var(--line));
  background: color-mix(in srgb, var(--amber) 5%, var(--panel));
  border-radius: var(--radius); padding: 1.4rem; margin-top: 1.6rem;
}
.claim-title { font-family: var(--serif); font-size: 1.35rem; margin: 0 0 .4rem; }
.claim-note { color: var(--muted); font-size: .88rem; line-height: 1.55; margin: 0 0 1.1rem; max-width: 48ch; }
.claim-row { display: flex; gap: .6rem; flex-wrap: wrap; }
.claim-input {
  flex: 1 1 15rem; min-width: 0; padding: .8rem 1rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--ink); color: var(--text);
  font-family: var(--sans); font-size: .95rem;
}
.claim-input:focus { outline: none; border-color: var(--amber); }
.claim-actions { margin-top: .7rem; }
.claim-no { font-size: .82rem; padding: .5rem .9rem; color: var(--muted); }
.claim-status { font-size: .85rem; color: var(--muted); margin: .9rem 0 0; line-height: 1.5; }
.signed-in { font-size: .82rem; color: var(--muted); }
.signed-in b { color: var(--text); font-weight: 600; }

/* ----------------------------------------------------------------- plates --- */
/* Frosted surfaces under the running text.
   The old approach dimmed the entire backdrop to keep a few paragraphs legible,
   which meant the posters paid for the text everywhere on the page, including
   the two thirds of a wide screen where there is no text at all. Putting the
   contrast where the words are instead lets the wall run at nearly full
   strength. Sites with strong photographic backgrounds all do this; it only
   looked optional while the backdrop was too dim to matter. */
.hero-copy,
.section-head,
.section > .lede,
.today-meta,
.year-head,
.stage .film,
main .head {
  position: relative;
  border-radius: 16px;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: blur(20px) saturate(.9);
  -webkit-backdrop-filter: blur(20px) saturate(.9);
  border: 1px solid color-mix(in srgb, var(--text) 7%, transparent);
  box-shadow: 0 18px 50px -28px var(--shadow);
}
.hero-copy { padding: 2rem 2.1rem 2.1rem; }
.section-head { padding: 1rem 1.3rem; }
.section > .lede { padding: 1rem 1.3rem; max-width: none; }
.today-meta { padding: .7rem .9rem; }
.year-head { padding: 1.4rem 1.6rem; }
.stage .film { padding: 1.5rem 1.7rem; }
main .head { padding: 1.6rem 1.8rem; }

/* Light theme needs a denser plate: cream on cream has far less separation to
   work with than white on ink. */
:root[data-theme='light'] .hero-copy,
:root[data-theme='light'] .section-head,
:root[data-theme='light'] .section > .lede,
:root[data-theme='light'] .today-meta,
:root[data-theme='light'] .year-head,
:root[data-theme='light'] .stage .film,
:root[data-theme='light'] main .head { background: color-mix(in srgb, var(--ink) 93%, transparent); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) .hero-copy,
  :root:not([data-theme='dark']) .section-head,
  :root:not([data-theme='dark']) .section > .lede,
  :root:not([data-theme='dark']) .today-meta,
  :root:not([data-theme='dark']) .year-head,
  :root:not([data-theme='dark']) .stage .film,
  :root:not([data-theme='dark']) main .head { background: color-mix(in srgb, var(--ink) 93%, transparent); }
}

/* Where blur is unavailable the tint alone has to carry it, so make it opaque
   enough to stand on its own. */
@supports not (backdrop-filter: blur(1px)) {
  .hero-copy, .section-head, .section > .lede, .today-meta,
  .year-head, .stage .film, main .head {
    background: color-mix(in srgb, var(--ink) 92%, transparent);
  }
}

footer { position: relative; }
footer::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background: color-mix(in srgb, var(--ink) 82%, transparent);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}

/* --------------------------------------------------- name and preferences --- */
.settings {
  max-width: 42rem;
  margin: 3rem auto 0;
  padding: 1.6rem 1.5rem;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: blur(20px) saturate(.9);
  -webkit-backdrop-filter: blur(20px) saturate(.9);
}
.settings h2 { font-size: 1.15rem; margin: 0 0 1.1rem; }
.pref-block { padding: 1.1rem 0; border-top: 1px solid var(--line); }
.pref-block:first-child { padding-top: 0; border-top: 0; }
.pref-block .claim { margin: 0; }

.switch-row {
  display: flex; gap: .7rem; align-items: flex-start;
  cursor: pointer; font-size: .92rem; line-height: 1.55;
}
.switch-row input { margin-top: .28rem; width: 1.05rem; height: 1.05rem; accent-color: var(--amber); flex: none; }
.switch-row input:disabled { cursor: not-allowed; }
.switch-row:has(input:disabled) { cursor: not-allowed; opacity: .62; }
.switch-note { display: block; color: var(--muted); font-size: .84rem; }

/* Availability feedback while the name is being typed. Colour alone would not
   carry it for anyone who cannot see the difference, so the wording says which
   it is too. */
.claim-status.ok { color: var(--cool); }
.claim-status.no { color: var(--hot); }
.name-form .claim-input { font-variant-ligatures: none; }

/* Signed out: the one nav item that is an invitation rather than a place. */
.nav-links a.nav-signin { color: var(--amber-text); }
/* A name held by this browser alone. The dot is a question, not an error, so it
   is quiet; the tooltip and the settings page carry the explanation. */
.nav-links a.nav-at-risk::after {
  content: '';
  display: inline-block;
  width: .38rem; height: .38rem;
  margin-left: .34rem;
  vertical-align: .32em;
  border-radius: 50%;
  background: var(--amber);
  opacity: .85;
}

/* ------------------------------------------------------------ share sheet --- */
.share-sheet {
  position: absolute;
  z-index: 60;
  min-width: 15rem;
  max-width: min(20rem, calc(100vw - 20px));
  padding: .4rem;
  border-radius: 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: 0 24px 60px -20px var(--shadow), 0 0 0 1px color-mix(in srgb, var(--text) 4%, transparent);
  /* Opaque, unlike the frosted plates: a menu that the wallpaper shows through
     is harder to read at exactly the moment precision matters. */
  backdrop-filter: none;
}
.sheet-item {
  display: block; width: 100%; text-align: left;
  padding: .6rem .7rem; border: 0; border-radius: 10px;
  background: transparent; color: var(--text); cursor: pointer;
  font: inherit; font-size: .92rem;
}
.sheet-item:hover, .sheet-item:focus-visible {
  background: var(--panel-2); outline: none;
}
.sheet-label { display: block; }
.sheet-hint { display: block; font-size: .74rem; color: var(--muted); margin-top: .12rem; line-height: 1.35; }

/* ------------------------------------------------------------ film picker --- */
.picker-backdrop {
  position: fixed; inset: 0; z-index: 80;
  display: grid; place-items: start center;
  padding: max(3vh, 1rem) 1rem;
  background: color-mix(in srgb, #000 62%, transparent);
  backdrop-filter: blur(3px);
  overflow-y: auto;
}
.picker {
  width: min(46rem, 100%);
  padding: 1.3rem;
  border-radius: 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: 0 40px 90px -30px #000;
}
.picker-head { margin-bottom: .9rem; }
.picker-input { width: 100%; }
.picker-results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: .8rem;
  margin-top: 1rem;
}
.picker-tile {
  background: transparent; border: 0; padding: .3rem; border-radius: 10px;
  cursor: pointer; text-align: left; color: var(--text); font: inherit;
}
.picker-tile:hover, .picker-tile:focus-visible { background: var(--panel-2); outline: none; }
.picker-tile p { margin: .45rem 0 0; font-size: .8rem; line-height: 1.3; }
.picker-tile .sub { color: var(--muted); font-size: .72rem; margin-top: .1rem; }

/* ---------------------------------------------------------- profile editor --- */
.editor-row { display: flex; gap: 1.4rem; flex-wrap: wrap; align-items: flex-start; margin-top: .8rem; }
.editor-slot { flex: 0 0 auto; text-align: center; }
.editor-slot > p { margin: .45rem 0 0; font-size: .74rem; color: var(--muted); }
.slot-avatar { width: 92px; }
.slot-banner { width: 150px; }
/* An empty slot has to read as a thing you can press. A dashed hairline in
   --line is almost invisible against the panel it sits on, which made the
   editor look like it had failed to load rather than like it was waiting. */
.slot-empty {
  aspect-ratio: 2/3; border-radius: 12px; display: grid; place-items: center;
  border: 1px dashed color-mix(in srgb, var(--text) 26%, transparent);
  background: color-mix(in srgb, var(--text) 4%, transparent);
  color: var(--muted); font-size: .74rem; padding: .4rem; text-align: center;
  transition: border-color .15s ease, color .15s ease;
}
.slot-btn:hover .slot-empty, .slot-btn:focus-visible .slot-empty {
  border-color: var(--amber); color: var(--amber-text);
}
.editor-slot > p { font-weight: 600; color: var(--text); }
.slot-banner .slot-empty { aspect-ratio: 16/9; }
.slot-btn { display: block; width: 100%; padding: 0; border: 0; background: transparent; cursor: pointer; }
.slot-btn .poster { border-radius: 12px; }
.slot-banner .poster { aspect-ratio: 16/9; }
.slot-banner .poster img { width: 100%; height: 100%; object-fit: cover; }

.accents { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .4rem; }
.accent-dot {
  width: 30px; height: 30px; border-radius: 50%; cursor: pointer;
  border: 2px solid transparent; padding: 0;
}
.accent-dot[aria-pressed='true'] { border-color: var(--text); }

.fav-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: .7rem; margin-top: .5rem; }
.fav-item { position: relative; }
.fav-item .poster { border-radius: 10px; }
.fav-item p { margin: .35rem 0 0; font-size: .74rem; line-height: 1.25; }
.fav-drop {
  position: absolute; top: .25rem; right: .25rem;
  width: 22px; height: 22px; border-radius: 50%; border: 0; cursor: pointer;
  background: color-mix(in srgb, #000 70%, transparent); color: #fff; font-size: .8rem; line-height: 1;
}
.fav-add {
  aspect-ratio: 2/3; border-radius: 10px; border: 1px dashed var(--line);
  background: transparent; color: var(--muted); cursor: pointer; font: inherit; font-size: .78rem;
}
.fav-add:hover { border-color: var(--amber); color: var(--amber-text); }

/* ---------------------------------------------------------- avatar upload --- */
.uploader { margin: .9rem 0 1.2rem; }
.upload-preview { width: 96px; margin-bottom: .7rem; }
.upload-preview img {
  width: 96px; height: 96px; object-fit: cover;
  border-radius: 14px; display: block;
  border: 1px solid var(--line);
}
.upload-preview .slot-empty { aspect-ratio: 1; }
.uploader .claim-row { gap: .5rem; }

/* An uploaded picture is square, unlike a poster. */
.poster.as-avatar.is-upload { aspect-ratio: 1; }

/* ======================================================== the profile card ===
   The social-media shape: a banner, a face overlapping it, a name, a line, a
   row of counts, a shelf. Used as a live preview on /me/ and mirrored by the
   public page, so the two read as the same object. */
.pcard {
  position: relative;
  margin: 1.4rem 0 2rem;
  border-radius: 20px;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: 0 26px 60px -34px var(--shadow);
}
.pcard-banner { position: relative; height: clamp(120px, 20vw, 190px); overflow: hidden; }
.pcard-banner .poster {
  position: absolute; inset: -35%; width: 170%; height: 170%;
  border-radius: 0; border: 0; opacity: .6; filter: blur(1px) saturate(1.05);
}
.pcard-banner .poster img { width: 100%; height: 100%; object-fit: cover; }
.pcard-banner-plain {
  position: absolute; inset: 0;
  background: linear-gradient(120deg, var(--accent), transparent 70%);
  opacity: .4;
}
.pcard-banner::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 30%, var(--panel) 100%);
}

.pcard-id {
  position: relative; z-index: 1;
  display: flex; align-items: flex-end; gap: 1rem;
  padding: 0 1.4rem; margin-top: clamp(-58px, -7vw, -44px);
}
/* Round, not poster shaped. A circle is what reads as "a person" at a glance,
   and it is the one cue that makes this feel like a profile rather than a
   database row. Film posters are letterboxed inside it. */
.pcard-face {
  width: clamp(76px, 11vw, 104px); height: clamp(76px, 11vw, 104px);
  flex: none; border-radius: 50%; overflow: hidden;
  border: 3px solid var(--accent);
  background: var(--panel-2);
  box-shadow: 0 10px 30px -12px #000;
}
.pcard-face img, .pcard-face .poster { width: 100%; height: 100%; border-radius: 0; border: 0; }
.pcard-face img { object-fit: cover; display: block; }
.pcard-face .poster { aspect-ratio: auto; }
.pcard-face .poster img { object-fit: cover; }
.pcard-letter {
  width: 100%; height: 100%; display: grid; place-items: center;
  font-family: var(--serif); font-size: 2.2rem; color: var(--ink);
  background: var(--accent);
}
.pcard-name { flex: 1 1 auto; padding-bottom: .2rem; min-width: 0; }
.pcard-name h3 {
  /* Wraps rather than truncating. In a 23rem column beside a button, nowrap
     turned "zack haynie" into "zack hay…", which is a worse answer than two
     lines for the one piece of text the page is actually about. */
  margin: 0; font-size: clamp(1.2rem, 3.2vw, 1.6rem); line-height: 1.15;
  overflow-wrap: anywhere;
}
.pcard-at { margin: .1rem 0 0; font-size: .8rem; color: var(--muted); }
.pcard-view { flex: none; font-size: .78rem; padding: .35rem .8rem; margin-bottom: .3rem; }

.pcard-bio { margin: .9rem 1.4rem 0; font-size: .95rem; line-height: 1.6; }

.pcard-counts { display: flex; gap: 1.8rem; padding: 1rem 1.4rem 0; flex-wrap: wrap; }
.pcard-counts b { font-family: var(--serif); font-size: 1.15rem; font-weight: 400; }
.pcard-counts span { color: var(--muted); font-size: .78rem; margin-left: .35rem; }

/* ------------------------------------------------------------------ stats ---
   Lived only inside /me/'s inline <style>, which meant the profile page used
   the same class names and got none of the rules: the number and its label ran
   together as "1film rated". Anything two pages share belongs here. */
.stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.stat-cell { background: var(--panel); padding: 1.1rem 1.2rem; }
.stat-cell .n { font-family: var(--serif); font-size: 2rem; line-height: 1.05; display: block; }
.stat-cell .k {
  font-size: .64rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); margin-top: .3rem; display: block;
}

/* -------------------------------------------------------- the record strip ---
   Demoted from the headline of the page to a note underneath the profile. */
.me-h2 { font-size: 1.05rem; margin: 0 0 1rem; }
.record {
  max-width: 42rem; margin: 2.2rem auto 0; padding: 1.4rem 1.5rem;
  border: 1px solid var(--line); border-radius: 14px;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: blur(20px) saturate(.9);
  -webkit-backdrop-filter: blur(20px) saturate(.9);
}
.record-line { margin: 0 0 .9rem; font-size: .9rem; color: var(--muted); }
.record-note { font-size: .88rem; line-height: 1.6; color: var(--muted); margin: .9rem 0 0; }
.record-note b { color: var(--text); }
.stats-compact { gap: .4rem; }
.stats-compact .stat-cell { padding: .5rem .6rem; }
.stats-compact .stat-cell .n { font-size: 1.05rem; }
.stats-compact .stat-cell .k { font-size: .64rem; }
/* The history is a long list and it is no longer the point of the page. */
.record #log { max-height: 22rem; overflow-y: auto; margin-top: 1rem; scrollbar-width: thin; }

/* ------------------------------------------------ editing on the profile --- */
/* The place people expect to change a picture is the picture. */
.pcard-face.is-editable { cursor: pointer; position: relative; }
.face-hint {
  position: absolute; inset: auto 0 0 0;
  padding: .2rem 0 .28rem;
  font-size: .6rem; letter-spacing: .1em; text-transform: uppercase;
  text-align: center; color: #fff;
  background: color-mix(in srgb, #000 62%, transparent);
  opacity: 0; transition: opacity .15s ease;
}
.pcard-face.is-editable:hover .face-hint,
.pcard-face.is-editable:focus-visible .face-hint { opacity: 1; }
.pcard-face.is-editable:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }

.banner-edit {
  position: absolute; top: .6rem; right: .7rem; z-index: 2;
  font: inherit; font-size: .72rem; padding: .3rem .7rem;
  border-radius: 999px; cursor: pointer;
  color: #fff; border: 1px solid color-mix(in srgb, #fff 35%, transparent);
  background: color-mix(in srgb, #000 55%, transparent);
  backdrop-filter: blur(6px);
}
.banner-edit:hover { background: color-mix(in srgb, #000 75%, transparent); }

/* Counts double as buttons that open the follower lists. */
.count-btn {
  background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  color: var(--text); text-align: left;
}
.count-btn:hover b, .count-btn:focus-visible b { color: var(--amber-text); }

.picker-narrow { width: min(22rem, 100%); }
.people-list { display: grid; gap: .2rem; margin-top: .8rem; max-height: 60vh; overflow-y: auto; }
.person {
  display: flex; align-items: center; gap: .7rem;
  padding: .5rem .6rem; border-radius: 10px;
  text-decoration: none; color: var(--text); font-size: .92rem;
}
.person:hover { background: var(--panel-2); }
.person img, .person-letter {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  object-fit: cover; display: grid; place-items: center;
  background: var(--panel-2); font-size: .85rem; color: var(--muted);
}

/* ------------------------------------------------------- editing the card --- */
.pcard-bio.is-editable { cursor: pointer; border-radius: 8px; }
.pcard-bio.is-editable:hover { background: color-mix(in srgb, var(--text) 6%, transparent); }
.pcard-bio.is-empty { color: var(--muted); font-style: italic; }
.pcard-bio.is-editable:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.bio-edit { margin: .9rem 1.4rem 0; }
.pcard-tools { padding: .9rem 1.4rem 0; }
.pcard-tools .btn { font-size: .74rem; padding: .25rem .7rem; }

/* ------------------------------------------------------------ find people --- */
.people-page { max-width: 44rem; margin: 2.5rem auto 0; }
.people-search { width: 100%; margin: 1.4rem 0 1rem; }
.people-list-page {
  border: 1px solid var(--line); border-radius: 14px; overflow: hidden;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: blur(20px) saturate(.9);
  -webkit-backdrop-filter: blur(20px) saturate(.9);
}
.people-row {
  display: flex; align-items: center; gap: 1rem;
  padding: .75rem .9rem; border-bottom: 1px solid var(--line);
}
.people-row:last-child { border-bottom: 0; }
.people-who {
  display: flex; align-items: center; gap: .8rem; flex: 1 1 auto; min-width: 0;
  text-decoration: none; color: var(--text);
}
.people-who img, .people-who .person-letter {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  object-fit: cover; display: grid; place-items: center;
  background: var(--panel-2); color: var(--muted); font-size: .95rem;
}
.people-who b { display: block; font-weight: 600; }
.people-meta { display: block; font-size: .74rem; color: var(--muted); margin-top: .1rem; }
.people-who:hover b { color: var(--amber-text); }

/* ------------------------------------- generated posters at shelf size --- */
/* The typographic cards are designed for a ~150px tile and their title has a
   .92rem floor. Dropped into a 62px shelf tile that floor sets the element's
   min-content width, and because a flex item defaults to min-width:auto the
   tile is forced wider than its basis. Result: neighbouring films came out
   different widths, and long genres like "psychological thriller" spilled.
   So: let the tile hit its basis, and give the card a type scale that fits. */
.pane-shelf > a, .shelf-tile { min-width: 0; }


.pane-shelf .poster.gen { padding: .6rem .45rem .55rem; }
.pane-shelf .poster.gen .gen-top { font-size: .44rem; letter-spacing: .1em; }
.pane-shelf .poster.gen .gen-tag { font-size: .42rem; letter-spacing: .1em; margin-bottom: .35rem; }
.pane-shelf .poster.gen .gen-title { font-size: .78rem; line-height: 1.08; }
.pane-shelf .poster.gen .gen-foot { font-size: .42rem; letter-spacing: .04em; }
.pane-shelf .poster.gen .gen-foot .rule { width: 16px; margin-bottom: .3rem; }

/* An uploaded cover is already the right shape; it must not be blown up and
   blurred the way a poster is. */
.pcard-banner .poster.is-upload {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 1; filter: none; border-radius: 0; border: 0;
}
.pcard-banner .poster.is-upload img { width: 100%; height: 100%; object-fit: cover; }

/* --------------------------------------------------------------- badges --- */
/* Restrained on purpose. A badge is the site vouching for somebody, and a
   saturated gold gradient reads as a game reward rather than a credential.
   Hairline border, faint wash, a small seal, and the amber text token that is
   already tuned to pass contrast in both themes. */
.badge {
  /* It is a <button> now, so the browser's own button styling has to go. */
  appearance: none; -webkit-appearance: none; font-family: inherit;
  cursor: pointer; position: relative;
  display: inline-flex; align-items: center; gap: .3rem;
  margin-left: .45rem;
  padding: .16rem .44rem .16rem .36rem;
  border-radius: 4px;
  font-family: var(--sans);
  font-size: .58rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  white-space: nowrap;
  vertical-align: .16em;
  border: 1px solid color-mix(in srgb, var(--amber-text) 34%, transparent);
  background: color-mix(in srgb, var(--amber-text) 8%, transparent);
  color: var(--amber-text);
}
.badge-mark { width: 9px; height: 9px; flex: none; display: block; }
/* The badge is 18px tall, which is right beside a name and too small for a
   thumb. This grows the hit area to roughly 34px without moving anything or
   changing how the badge looks. */
.badge::before { content: ''; position: absolute; inset: -8px -5px; border-radius: 8px; }
.badge-static { cursor: inherit; }
.badge-static::before { content: none; }
.badge-static:hover { border-color: color-mix(in srgb, var(--amber-text) 34%, transparent);
  background: color-mix(in srgb, var(--amber-text) 8%, transparent); }
.badge:hover, .badge:focus-visible {
  border-color: color-mix(in srgb, var(--amber-text) 60%, transparent);
  background: color-mix(in srgb, var(--amber-text) 14%, transparent);
}
/* What the badge says when you tap it. Resets the badge's own uppercase and
   letter-spacing, which are right for a label and wrong for a sentence. */
.badge-note {
  position: absolute; top: calc(100% + .4rem); left: 0; z-index: 40;
  padding: .4rem .6rem; border-radius: 8px;
  font-size: .72rem; font-weight: 500;
  letter-spacing: normal; text-transform: none; white-space: nowrap;
  color: var(--text);
  border: 1px solid color-mix(in srgb, var(--amber-text) 30%, transparent);
  background: color-mix(in srgb, var(--ink) 94%, transparent);
  backdrop-filter: blur(18px) saturate(.9);
  -webkit-backdrop-filter: blur(18px) saturate(.9);
  box-shadow: 0 12px 30px -18px var(--shadow);
}

/* ------------------------------------------------------------------ feed --- */
.feed-list {
  border: 1px solid var(--line); border-radius: 14px; overflow: hidden;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: blur(20px) saturate(.9);
  -webkit-backdrop-filter: blur(20px) saturate(.9);
}
.feed-item {
  display: flex; gap: .9rem; align-items: flex-start;
  padding: .95rem 1rem; border-bottom: 1px solid var(--line);
}
.feed-item:last-child { border-bottom: 0; }
.feed-face { flex: none; }
.feed-face img, .feed-face .person-letter {
  width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center;
  object-fit: cover; background: var(--panel-2); color: var(--muted); font-size: .9rem;
}
.feed-body { flex: 1 1 auto; min-width: 0; display: flex; gap: 1rem; align-items: flex-start; }
.feed-said { margin: 0; font-size: .92rem; line-height: 1.55; flex: 1 1 auto; }
.feed-who { color: var(--text); font-weight: 600; text-decoration: none; }
.feed-who:hover { color: var(--amber-text); }
.feed-when { display: block; font-size: .72rem; color: var(--muted); margin-top: .2rem; }
.feed-film { flex: 0 0 54px; text-decoration: none; }
.feed-film .poster { border-radius: 7px; }
.feed-film .gen-top, .feed-film .gen-foot { display: none; }
.feed-film .poster.gen { padding: .4rem .28rem .35rem; }
.feed-film .poster.gen .gen-tag { font-size: .32rem; letter-spacing: .06em; margin-bottom: .16rem; }
.feed-film .poster.gen .gen-title { font-size: .44rem; line-height: 1.08; -webkit-line-clamp: 4; }

/* --------------------------------------------------------- the watchlist --- */
.wl-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .7rem; }
.wl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: .8rem; }
.wl-tile { position: relative; }
.wl-grid a { text-decoration: none; color: var(--text); position: relative; display: block; }
.wl-grid p { margin: .35rem 0 0; font-size: .72rem; line-height: 1.25; }
.wl-seen { position: absolute; top: .25rem; right: .25rem;
  background: color-mix(in srgb, #000 70%, transparent); color: #fff;
  font-size: .55rem; letter-spacing: .08em; text-transform: uppercase;
  padding: .12rem .3rem; border-radius: 4px; }
/* These used to appear only on :hover, which meant that on a phone they did
   not exist at all and the watchlist was add-only. Controls that change data
   need a resting state. Both are comfortably tappable at 28px. */
.wl-ctl { display: flex; gap: .25rem; margin-top: .35rem; }
.wl-btn {
  font: inherit; font-size: .64rem; letter-spacing: .02em;
  min-height: 28px; padding: .2rem .3rem; cursor: pointer;
  border-radius: 7px; border: 1px solid var(--line);
  background: var(--panel-2); color: var(--muted);
  transition: border-color .12s ease, color .12s ease, background .12s ease;
}
.wl-btn:hover, .wl-btn:focus-visible { color: var(--text); border-color: var(--accent); }
.wl-toggle { flex: 1; min-width: 0; }
.wl-tile[data-status="watched"] .wl-toggle {
  color: var(--text);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 16%, var(--panel-2));
}
.wl-remove { flex: 0 0 28px; line-height: 1; font-size: .82rem; }
/* A mis-tap on a 28px target should not silently delete something, and a
   native confirm() would block the page, so the button asks with itself. */
.wl-remove.armed { flex: 0 0 auto; font-size: .62rem; padding: .2rem .45rem;
  color: var(--text); border-color: var(--bad, #e0685f); }
.wl-btn[disabled] { opacity: .5; cursor: default; }

/* Nothing on the page may be wider than the phone. A single overflowing child
   turns the whole document into a sideways scroll, and the reader blames the
   site rather than the element. */
@media (max-width: 780px) {
  html, body { overflow-x: hidden; }
  .wrap { width: min(100% - 1.6rem, var(--wrap)); }
}

/* ------------------------------------------------------------- the cropper --- */
.cropper { max-width: 420px; text-align: center; }
.crop-title { font-size: 1.05rem; margin: 0 0 .25rem; }
.crop-hint { font-size: .78rem; color: var(--muted); margin: 0 0 1rem; }
.crop-frame {
  position: relative; margin: 0 auto; overflow: hidden;
  border-radius: 12px; background: #000;
  /* The browser must not claim the gesture for panning the page, or a drag
     inside the frame scrolls the page instead of moving the picture. */
  touch-action: none; cursor: grab; user-select: none;
}
.crop-frame:active { cursor: grabbing; }
.crop-canvas { display: block; }
.crop-canvas.round { border-radius: 50%; }
/* A hairline inside the frame edge, so the crop boundary is visible against a
   dark photo. */
.crop-frame::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  border-radius: 12px;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #fff 22%, transparent);
}
.crop-zoom {
  -webkit-appearance: none; appearance: none;
  width: 100%; max-width: 340px; margin: 1rem auto .2rem; display: block;
  height: 26px; background: transparent; cursor: pointer;
}
.crop-zoom::-webkit-slider-runnable-track {
  height: 4px; border-radius: 3px; background: var(--panel-2);
  border: 1px solid var(--line);
}
.crop-zoom::-moz-range-track {
  height: 4px; border-radius: 3px; background: var(--panel-2);
  border: 1px solid var(--line);
}
.crop-zoom::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 20px; height: 20px; margin-top: -9px; border-radius: 50%;
  background: var(--amber-text); border: 2px solid var(--ink);
}
.crop-zoom::-moz-range-thumb {
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--amber-text); border: 2px solid var(--ink);
}
.crop-acts { display: flex; gap: .6rem; justify-content: center; margin-top: 1rem; }

/* ------------------------------------------------- the defend-shelf tile --- */
/* Shares .wl-ctl and .wl-btn with the watchlist deliberately: same shape, same
   controls, same resting state. Twelve of these fill the pane in even rows. */
.shelf-tile { position: relative; }
.pane-shelf > .shelf-tile > a { text-decoration: none; color: var(--text); display: block; }
.shelf-tile p { margin: .35rem 0 0; font-size: .72rem; line-height: 1.25; }

/* Nothing sits under the posters. Removing is a press and hold, which reveals a
   cross on every tile; adding is the tile at the end of the grid. */
.shelf-tile > a { position: relative; display: block; }

.shelf-x {
  position: absolute; top: -.35rem; right: -.35rem; z-index: 2;
  width: 22px; height: 22px; border-radius: 50%;
  display: none; place-items: center;
  font: inherit; font-size: .85rem; line-height: 1; cursor: pointer;
  color: #fff; border: 1px solid color-mix(in srgb, #fff 30%, transparent);
  background: color-mix(in srgb, #000 78%, transparent);
}
.pane-shelf.is-editing .shelf-x { display: grid; }
/* The tiles lean while the shelf is editable, which is the whole signal that
   something changed; without it a hold looks like it did nothing. */
.pane-shelf.is-editing .shelf-tile[data-film] { animation: shelf-lean .6s ease-in-out infinite alternate; }
.pane-shelf.is-editing .shelf-tile > a { pointer-events: none; }
@keyframes shelf-lean {
  from { transform: rotate(-.7deg); }
  to   { transform: rotate(.7deg); }
}
@media (prefers-reduced-motion: reduce) {
  .pane-shelf.is-editing .shelf-tile[data-film] { animation: none; outline: 1px dashed var(--line); outline-offset: 3px; }
}

/* Shaped like the films it sits beside, not like a button. align-self keeps it
   the height of a poster rather than stretching to match a tile that also has a
   title underneath it. */
.shelf-add {
  align-self: start; aspect-ratio: 2/3; width: 100%;
  display: grid; place-items: center; cursor: pointer;
  border-radius: 8px; padding: 0;
  border: 1px dashed color-mix(in srgb, var(--text) 26%, transparent);
  background: transparent; color: var(--muted);
  transition: border-color .12s ease, color .12s ease;
}
.shelf-add:hover, .shelf-add:focus-visible {
  border-color: var(--accent); color: var(--text);
}
.shelf-add-mark { font-size: 1.5rem; line-height: 1; }

.shelf-how { font-size: .74rem; margin: .9rem 0 0; }
.shelf-how a { color: var(--amber-text); }

/* ---------------------------------------------------- the how-to section --- */
.help { margin-top: 1.6rem; }
.help-item { padding: .9rem 0; border-top: 1px solid var(--line); }
.help-item:first-of-type { border-top: 0; }
.help-item h3 {
  font-size: .74rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 .45rem;
}
.help-item p { margin: 0 0 .4rem; font-size: .9rem; line-height: 1.65; max-width: 44rem; }
.help-item p:last-child { margin-bottom: 0; }
.help-note { font-size: .8rem !important; color: var(--muted); }
