/* Self-hosted so that no request — and no visitor IP — reaches Google. One variable file per family
   and subset covers every weight the app uses. */
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:100 1000; font-display:swap; src:url('fonts/dm-sans-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'DM Sans'; font-style:normal; font-weight:100 1000; font-display:swap; src:url('fonts/dm-sans-latin.woff2') format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:300 700; font-display:swap; src:url('fonts/space-grotesk-latin-ext.woff2') format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family:'Space Grotesk'; font-style:normal; font-weight:300 700; font-display:swap; src:url('fonts/space-grotesk-latin.woff2') format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

/* The palette is the one this app has always had — a dark auditorium and the chain's own red — laid
   out the way Libri's is. --accent is the colour anything interactive is drawn in. The other two are
   reserved and always mean the same thing, which is the whole reason a ticket state reads at a
   glance: --ok is a ticket you can actually buy, --warn is a screening that exists and is locked. */
:root { --bg:#0b0d12; --bg-soft:#121620; --card:#161b27; --card-2:#1c2231; --line:#262d3d;
        --ink:#e8ecf4; --muted:#8c97ad; --accent:#e2001a; --accent-soft:#2a1119;
        --ok:#22c55e; --ok-soft:rgba(34,197,94,.13); --warn:#f59e0b; --warn-soft:rgba(245,158,11,.12); }
* { box-sizing:border-box; }
/* Defensive: user-select inherits, so any future none on an ancestor would silently take away the
   long-press "paste" menu inside text fields. */
input,textarea { user-select:text; -webkit-user-select:text; }
/* Beats any display rule below: .login-shell and .check-row set display:flex, which otherwise
   outranks the browser's own [hidden] { display:none } and leaves hidden panels showing. */
[hidden] { display:none !important; }
/* Android browsers "boost" text they take for body copy, which silently overrides small sizes. */
html { -webkit-text-size-adjust:100%; text-size-adjust:100%; }
/* A sideways drag would otherwise engage the browser's own horizontal overscroll — the
   swipe-to-go-back gesture — which nudges the page and drops fixed elements for a few frames. */
html,body { overscroll-behavior-x:none; }
/* The document does not scroll; the body inside it does. Browsers hang their own furniture off
   whatever the *root* scroller is, and one piece of it is a floating "scroll to top" button that
   lands on top of the tab bar in an installed app. A page cannot move that button or turn it off. It
   can stop being the root scroller, and then there is nothing for the button to attach to. */
html { height:100%; overflow:hidden; }
body { height:100%; overflow-x:hidden; overflow-y:auto; }
.shell { overflow-x:clip; }
body { margin:0; color:var(--ink); font-family:'DM Sans', sans-serif;
       background:radial-gradient(1100px 600px at 12% -10%, #1b2440 0, transparent 60%),
                  radial-gradient(900px 500px at 100% 0%, #2a1020 0, transparent 55%), var(--bg); }
button,input,select,textarea { font:inherit; }
button { cursor:pointer; }
.shell,.login-shell { width:min(100% - 32px, 680px); margin:0 auto; }
.topbar { display:flex; align-items:center; justify-content:space-between; padding:28px 0 18px; gap:10px; }
h1,h2 { font-family:'Space Grotesk', sans-serif; margin:0; letter-spacing:0; }
h1 { font-size:clamp(1.7rem, 6vw, 2.3rem); }
h2 { font-size:1.55rem; }
/* The wordmark is the easter egg's target, so it must not look like a link and must not be
   selectable — five quick taps on a word otherwise start a text selection on the third. */
#wordmark { cursor:default; user-select:none; -webkit-user-select:none; }
#wordmark em { color:var(--accent); font-style:normal; }
#wordmark.found { animation:reel .6s cubic-bezier(.3,1.4,.5,1); }
@keyframes reel { from { transform:none; } 40% { transform:translateY(-5px) rotate(-2deg); } to { transform:none; } }
@media (prefers-reduced-motion: reduce) { #wordmark.found { animation:none; } }
/* The back link is styled as an eyebrow, not merely like one: sharing the rule guarantees the gap
   between the small line and the heading below it is identical on every page. */
.kicker,.eyebrow { color:var(--accent); font-size:.68rem; font-weight:700; letter-spacing:.14em; margin:0 0 7px; text-transform:uppercase; }
.muted { color:var(--muted); }
.icon-button,.small-button { border:0; background:transparent; color:var(--muted); }
.icon-button { font-size:1.15rem; padding:8px; position:relative; }
.imprint-button { background:transparent; border:0; color:var(--muted); font-size:.78rem; padding:8px 4px; white-space:nowrap; }
.topbar-actions { align-items:center; display:flex; gap:2px; position:relative; }
.badge { background:var(--accent); border-radius:9px; color:#fff; font-size:10px; font-weight:700;
         line-height:17px; min-width:17px; padding:0 4px; position:absolute; right:0; top:1px; }
.menu { background:var(--card); border:1px solid var(--line); border-radius:6px; box-shadow:0 12px 28px rgba(0,0,0,.5); display:flex; flex-direction:column; overflow:hidden; padding:5px; position:absolute; right:0; top:calc(100% + 6px); width:max-content; z-index:40; }
/* nowrap plus a max-content menu keeps every label on one line, whatever the phone's width.
   Absolute px, not rem: immune to a scaled root font size on the phone. */
.menu-item { background:transparent; border:0; border-radius:4px; color:var(--ink); font-size:11px; letter-spacing:.02em; padding:8px 11px; text-align:left; white-space:nowrap; width:100%; }
.menu-item:hover { background:var(--card-2); }
.menu-caption { color:var(--muted); display:block; font-size:.85em; letter-spacing:.08em; text-transform:uppercase; }
.menu-value { display:block; font-weight:600; margin-top:2px; }
.menu-value:after { content:' ▸'; color:var(--muted); }
.menu-divider { background:var(--line); height:1px; margin:4px 6px; }
.switch-row { align-items:center; cursor:pointer; display:flex; gap:14px; justify-content:space-between; }
.switch-row input { accent-color:var(--accent); cursor:pointer; flex:none; height:20px; margin:0; width:34px; }
.switch-row.busy { opacity:.5; pointer-events:none; }
.section-heading,.view-title { align-items:end; display:flex; gap:12px; justify-content:space-between; }
.section-heading { margin:24px 0 12px; }
.section-heading h2 { font-size:.78rem; color:var(--muted); font-weight:700; letter-spacing:.12em; text-transform:uppercase; }
.section-heading span { color:var(--muted); font-size:.78rem; }
.view { padding-bottom:100px; }
.view-title { margin:2px 0 18px; }
#view-legal { padding-bottom:40px; }

/* ---- search and filters ---- */
.searchbar { display:flex; gap:8px; }
.searchbar input { background:var(--card); border:1px solid var(--line); border-radius:6px; color:var(--ink); flex:1; min-width:0; padding:12px 13px; }
.searchbar input::placeholder { color:var(--muted); }
.searchbar input:focus-visible,.field input:focus-visible,.ghost-button:focus-visible,.chip:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
/* Scrolls sideways rather than wrapping: a row that grows taller pushes the results down every time
   a filter is added. */
.chips { display:flex; gap:7px; margin:14px 0 4px; overflow-x:auto; padding-bottom:4px; scrollbar-width:none; }
.chips::-webkit-scrollbar { display:none; }
.chip { background:var(--card); border:1px solid var(--line); border-radius:999px; color:var(--muted); flex:none; font-size:.8rem; padding:7px 13px; }
.chip.active { background:var(--accent); border-color:var(--accent); color:#fff; font-weight:700; }
.chip.on { background:var(--ok-soft); border-color:rgba(34,197,94,.4); color:#86efac; }
.chip.locked { background:var(--warn-soft); border-color:rgba(245,158,11,.38); color:#fcd34d; }

/* ---- posters ----
   Every poster sits in a fixed 2:3 frame so a grid of mismatched artwork still lines up. A film with
   no poster gets a glyph rather than a broken image: the grid is read by shape, and a hole in it
   reads as a fault. */
.poster { align-items:center; aspect-ratio:2/3; background:var(--card-2); border-radius:4px; color:#39435c; display:flex; flex:none; font-size:1.4rem; justify-content:center; overflow:hidden; position:relative; }
.poster img { height:100%; object-fit:cover; width:100%; }
/* A ribbon rather than a border, so ticket state reads at a glance without changing the frame the
   posters line up in. */
.poster-badge { border-radius:3px; font-size:.6rem; font-weight:700; left:5px; letter-spacing:.05em; padding:3px 6px; position:absolute; text-transform:uppercase; top:5px; }
.poster-badge.on { background:var(--ok); color:#04240f; }
.poster-badge.off { background:rgba(0,0,0,.7); color:#cbd5e1; }

/* ---- the catalogue, as a grid ---- */
.film-grid { display:grid; gap:14px 12px; grid-template-columns:repeat(auto-fill, minmax(104px, 1fr)); }
.film-card { background:none; border:0; color:var(--ink); display:flex; flex-direction:column; gap:7px; padding:0; text-align:left; }
.film-card .poster { width:100%; }
.film-card strong { font-size:.82rem; font-weight:600; line-height:1.25; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.film-card small { color:var(--muted); font-size:.72rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.film-card .watched { color:var(--ok); font-size:.72rem; font-weight:700; letter-spacing:.05em; text-transform:uppercase; }

/* ---- entries on the watchlist ---- */
.entry-list { display:flex; flex-direction:column; gap:8px; }
.entry { align-items:flex-start; background:var(--card); border:1px solid var(--line); border-radius:7px; color:var(--ink); display:flex; gap:12px; padding:11px 12px; text-align:left; width:100%; }
/* A film with tickets on sale is the one thing on this page worth looking at twice. */
.entry.live { border-color:rgba(34,197,94,.42); background:linear-gradient(100deg, rgba(34,197,94,.06), var(--card) 42%); }
.entry .poster { width:48px; }
/* A search watch has no poster, so it takes a mark in the same frame rather than a second layout
   that would drift from this one. */
.entry .term-mark { align-items:center; aspect-ratio:2/3; background:var(--card-2); border-radius:4px; color:var(--muted); display:flex; flex:none; font-size:1.2rem; justify-content:center; width:48px; }
.entry-copy { display:flex; flex:1; flex-direction:column; gap:5px; min-width:0; }
.entry-copy strong { font-size:.94rem; line-height:1.3; }
.entry-copy small { color:var(--muted); font-size:.76rem; }
.entry-status { align-items:center; display:flex; gap:8px; font-size:.83rem; }
.dot { border-radius:50%; flex:none; height:8px; width:8px; }
.dot.on { background:var(--ok); box-shadow:0 0 0 4px rgba(34,197,94,.16); }
.dot.off { background:#4b5568; }
.entry-tags { display:flex; flex-wrap:wrap; gap:6px; }
.entry-tags .chip { font-size:.72rem; padding:4px 9px; }
.entry-arrow { color:var(--muted); font-size:1.2rem; margin-left:auto; }
/* The films a search watch has caught. They are a strip joined to the bottom of the card rather than
   a row inside it, because each is its own button to its own film and the card is already one button
   to the watch — nesting the two is invalid and browsers do not render it. The left inset lines the
   titles up with the copy above: 48px of mark, the 12px gap, and the card's own 12px of padding. */
.entry-group { display:flex; flex-direction:column; }
.entry-group.with-matches > .entry { border-bottom-left-radius:0; border-bottom-right-radius:0; }
.entry-matches { background:var(--bg-soft); border:1px solid var(--line); border-radius:0 0 7px 7px;
  border-top:0; display:flex; flex-wrap:wrap; gap:6px; padding:9px 12px 10px 72px; }
.entry-group.with-matches > .entry.live + .entry-matches { border-color:rgba(34,197,94,.42); }
.entry-matches .chip { font-size:.72rem; padding:4px 9px; text-align:left; }
.entry-matches .chip.match:hover { border-color:var(--accent); color:var(--ink); }
.entry-matches .chip.on.match:hover { border-color:var(--ok); }
/* Not a film but a way of seeing the rest of them, so it is dashed rather than solid. */
.chip.more { border-style:dashed; }

/* ---- alerts ---- */
.alert { background:var(--card); border:1px solid var(--line); border-left:3px solid var(--accent); border-radius:7px; padding:11px 13px; }
.alert.read { border-left-color:var(--line); opacity:.6; }
.alert h4 { font-family:'Space Grotesk', sans-serif; font-size:.92rem; margin:0 0 5px; }
.alert p { color:var(--muted); font-size:.83rem; margin:0; white-space:pre-line; }
.alert time { color:#6b7689; display:block; font-size:.7rem; margin-top:7px; }
/* Which of the two things this alert is. The quieter level is deliberately the quieter colour: a
   film merely appearing in the catalogue must not look like tickets going on sale. */
.level { border:1px solid var(--line); border-radius:999px; color:var(--muted); display:inline-block; font-size:.62rem; font-weight:700; letter-spacing:.06em; margin-bottom:6px; padding:2px 7px; text-transform:uppercase; }
.level.on { background:var(--ok-soft); border-color:rgba(34,197,94,.4); color:#86efac; }
.level.added { background:var(--warn-soft); border-color:rgba(245,158,11,.38); color:#fcd34d; }

/* ---- the crawl progress card ---- */
.progress-card { background:var(--card); border:1px solid var(--line); border-radius:8px; margin-bottom:16px; padding:14px 16px; }
.progress-head { display:flex; font-size:.85rem; justify-content:space-between; margin-bottom:10px; }
.progress { background:var(--line); border-radius:3px; height:6px; overflow:hidden; }
.progress i { background:var(--accent); display:block; height:100%; transition:width .25s; }
.progress i.indeterminate { animation:slide 1.15s ease-in-out infinite; width:35% !important; }
@keyframes slide { from { margin-left:-35%; } to { margin-left:100%; } }

/* ---- imprint and privacy ---- */
.back-button { background:transparent; border:0; color:inherit; font:inherit; letter-spacing:inherit; margin:0; padding:0; text-transform:inherit; }
.legal-block { background:var(--card); border:1px solid var(--line); border-radius:8px; padding:16px 18px; }
.legal-facts { display:grid; gap:2px 16px; grid-template-columns:auto 1fr; margin:0 0 14px; }
.legal-facts dt { color:var(--muted); font-size:.76rem; letter-spacing:.06em; text-transform:uppercase; }
.legal-facts dd { margin:0 0 8px; }
.legal-block a,.legal-body a { color:var(--accent); }
.legal-details { background:var(--card); border:1px solid var(--line); border-radius:8px; margin-top:12px; }
.legal-details summary { cursor:pointer; font-family:'Space Grotesk', sans-serif; font-size:1.05rem; font-weight:600; list-style:none; padding:15px 18px; }
.legal-details summary::-webkit-details-marker { display:none; }
.legal-details summary:after { color:var(--muted); content:'▾'; float:right; transition:transform .2s; }
.legal-details[open] summary:after { transform:rotate(180deg); }
.legal-body { border-top:1px solid var(--line); padding:4px 18px 18px; }
.legal-body h3 { font-family:'Space Grotesk', sans-serif; font-size:.95rem; margin:18px 0 6px; }
.legal-body p,.legal-body li { color:#c4ccdb; font-size:.88rem; line-height:1.55; margin:6px 0; }
.legal-body ul { margin:6px 0; padding-left:20px; }
.legal-body code { background:var(--card-2); border-radius:3px; font-size:.85em; padding:1px 5px; }

/* ---- tab bar ----
   Opaque, not translucent: translate3d and will-change keep it on its own compositor layer, so it is
   not repainted — and briefly dropped — while the body scrolls under it. */
.tabbar { background:var(--bg-soft); border-top:1px solid var(--line); bottom:0; display:flex; justify-content:space-around; left:50%; max-width:680px; padding:11px 0 max(11px, env(safe-area-inset-bottom)); position:fixed; transform:translate3d(-50%,0,0); width:100%; will-change:transform; z-index:3; }
.tabbar button { background:none; border:0; color:var(--muted); display:flex; flex-direction:column; font-size:.72rem; gap:4px; padding:3px 22px; }
.tabbar button span { font-size:1.15rem; line-height:1; }
.tabbar button.active { color:var(--accent); font-weight:700; }
/* The labels sit in <em> so the glyph above them can be sized on its own; nothing about them is
   emphasis, so the browser's italics are taken back off. */
.tabbar button em { font-style:normal; }

/* ---- buttons, dialogs, the rest of the furniture ---- */
.primary-button { background:var(--accent); border:0; border-radius:5px; color:#fff; font-weight:700; padding:10px 16px; white-space:nowrap; }
.primary-button:disabled,.tiny-button:disabled { cursor:default; opacity:.55; }
.ghost-button { background:transparent; border:1px solid var(--line); border-radius:5px; color:var(--ink); font-weight:500; padding:10px 14px; }
.tiny-button { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); font-size:.78rem; padding:7px 10px; }
.tiny-button.danger { color:#fca5a5; }
.link-button { background:none; border:0; color:var(--accent); font-weight:700; padding:0; text-decoration:underline; }
.empty-state { color:var(--muted); padding:30px 10px; text-align:center; }
.empty-state .big { font-size:2rem; margin-bottom:10px; }
.empty-state .link-button { margin-top:8px; }
.modal-overlay { align-items:center; background:rgba(4,6,10,.62); bottom:0; display:flex; justify-content:center; left:0; opacity:0; padding:20px; position:fixed; right:0; top:0; transition:opacity .16s; z-index:10; }
.modal-overlay.open { opacity:1; }
.modal { background:var(--bg-soft); border:1px solid var(--line); border-radius:10px; box-shadow:0 18px 44px rgba(0,0,0,.55); max-height:calc(100vh - 40px); max-width:440px; overflow-y:auto; padding:22px; transform:translateY(8px); transition:transform .16s; width:100%; }
/* vh on a phone is measured with the browser's toolbars hidden, so a dialog sized in it is taller
   than what can be seen and its last row — where the buttons are — sits under the address bar. */
@supports (max-height: 100dvh) { .modal { max-height:calc(100dvh - 40px); } }
.modal-overlay.open .modal { transform:none; }
.modal h3 { font-family:'Space Grotesk', sans-serif; font-size:1.2rem; margin:0 0 10px; }
.modal-copy { color:var(--muted); font-size:.9rem; line-height:1.45; margin:0 0 18px; }
/* Sits under the title it belongs to, pulled up into the copy's own bottom margin so that hiding it
   for a search watch leaves no gap behind. */
.modal-link { color:var(--accent); display:inline-block; font-size:.83rem; margin:-10px 0 18px;
  text-decoration:none; }
.modal-link:hover { text-decoration:underline; }
/* Wrapping is not cosmetic: a flex row that justifies to the end overflows towards the *start*, and
   overflow in that direction cannot be scrolled to — so on a narrow phone the leftmost button is
   simply gone. */
.modal-actions { display:flex; flex-wrap:wrap; gap:9px; justify-content:flex-end; margin-top:20px; }
.modal-actions .spacer { flex:1 1 0; min-width:0; }
@media (max-width: 460px) {
  .modal-actions { flex-direction:column-reverse; }
  .modal-actions .spacer { display:none; }
  .modal-actions > button { padding:12px 14px; width:100%; }
}
.field { display:flex; flex-direction:column; gap:8px; margin-bottom:16px; }
.field > span { color:var(--muted); font-size:.72rem; font-weight:700; letter-spacing:.09em; text-transform:uppercase; }
.field input[type="text"],.field input[type="search"],.field input[type="date"] { background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink); padding:11px; width:100%; }
.field-note { font-size:.78rem; margin:-10px 0 16px; }
.check-row { align-items:center; cursor:pointer; display:flex; font-size:.88rem; gap:10px; }
.check-row input { accent-color:var(--accent); flex:none; height:17px; width:17px; }
.check-row em { font-style:normal; }
.cinema-list { background:var(--card); border:1px solid var(--line); border-radius:6px; max-height:32vh; overflow-y:auto; padding:4px; }
.cinema-group { color:var(--muted); font-size:.62rem; font-weight:700; letter-spacing:.12em; padding:9px 9px 4px; text-transform:uppercase; }
.cinema-row { align-items:center; border-radius:5px; cursor:pointer; display:flex; font-size:.86rem; gap:10px; padding:7px 9px; }
.cinema-row:hover { background:var(--card-2); }
.cinema-row input { accent-color:var(--accent); flex:none; height:16px; width:16px; }
.cinema-row .city { color:var(--muted); font-size:.76rem; margin-left:auto; }
.cinema-tools { align-items:center; display:flex; flex-wrap:wrap; gap:8px; }
.cinema-tools .muted { font-size:.76rem; }

/* ---- signed out, and the two banners ---- */
.login-shell { align-items:flex-start; display:flex; flex-direction:column; justify-content:center; min-height:100vh; padding:30px 0; }
.login-mark { align-items:center; background:var(--accent); border-radius:50%; color:#fff; display:flex; font-size:1.7rem; height:64px; justify-content:center; margin-bottom:26px; width:64px; }
.login-shell h1 { font-size:clamp(2.2rem, 11vw, 4.5rem); max-width:520px; }
.login-shell > .muted { margin:10px 0 30px; max-width:520px; }
.login-actions { display:flex; gap:9px; margin-top:4px; }
.login-actions .primary-button,.login-actions .ghost-button { display:inline-block; min-height:48px; line-height:28px; text-decoration:none; }
.login-switch { color:var(--muted); font-size:.85rem; margin:14px 0 0; }
.login-switch a { color:var(--accent); }
/* Fixed rather than in the document flow, which would put it behind the tab bar or the header. */
.build-badge { color:var(--muted); font-size:.6rem; letter-spacing:.04em; margin:0; opacity:.7; pointer-events:none; position:fixed; right:12px; top:max(calc(env(safe-area-inset-top) + 6px), 6px); z-index:2; }
.update-banner { align-items:center; background:var(--card); border:1px solid var(--line); border-radius:6px; color:var(--ink); display:flex; font-size:.85rem; gap:12px; left:50%; max-width:calc(100% - 32px); padding:10px 12px; position:fixed; top:12px; transform:translateX(-50%); width:680px; z-index:6; }
.update-banner span { flex:1; }
.update-button { background:var(--accent); border:0; border-radius:5px; color:#fff; font-weight:700; padding:8px 14px; }
#toast { background:var(--card); border:1px solid var(--line); border-radius:5px; bottom:85px; color:var(--ink); left:50%; max-width:calc(100% - 32px); opacity:0; padding:12px 16px; pointer-events:none; position:fixed; transform:translate(-50%, 15px); transition:.2s; z-index:5; }
#toast.show { opacity:1; transform:translate(-50%,0); }
#toast.err { border-color:rgba(239,68,68,.5); }

@media (max-width:520px) {
  .shell,.login-shell { width:min(100% - 24px, 680px); }
  .film-grid { gap:12px 10px; grid-template-columns:repeat(auto-fill, minmax(92px, 1fr)); }
  .imprint-button { display:none; }
}
