/* ============================================================================
   EasyTransfer — the on-card library app.

   Constraints this stylesheet is written under, all of them load-bearing:

   · It runs from file:// on a microSD card as often as from a Raspberry Pi, so
     there is no CDN, no webfont and no build step. Everything is here or on the
     card next to it.
   · It is read outdoors, in sun, on a cracked shared phone, by someone who may
     be reading slowly. Hence the size of everything, the contrast floor, and the
     refusal to use grey text on grey.
   · Two of its three languages are cursive Arabic script, where habits that are
     harmless in Latin — letter-spacing, mid-word breaks — render words broken.
     See "Arabic script" at the bottom; those rules are not optional.
   ========================================================================= */

/* Dark is the default, following the Bible app this was modelled on. Light is
   still a tap away in the header, and matters outdoors: a bright screen is
   easier to read in the sun than a dark one. All three theme states are
   covered — no choice (system), an explicit light, an explicit dark. */
:root {
  --ground:      #0E0B12;
  --surface:     #191520;
  --sunk:        #221C2C;
  --raised:      #2A2333;

  --ink:         #F7F3FA;
  --ink-soft:    #A99EB6;

  --purple:      #C79AD9;
  --purple-deep: #8A55A6;
  --purple-wash: #2C2038;

  --gold:        #F0B942;
  --gold-deep:   #C38F1E;
  --gold-wash:   #33281A;

  --green:       #4FBE72;
  --green-deep:  #379053;
  --green-wash:  #16281D;

  --sky:         #5BAFD0;
  --sky-deep:    #3A88A8;
  --sky-wash:    #17262E;

  --line:        #2E2739;
  --line-strong: #43394F;

  --r-sm: 12px;
  --r:    18px;
  --r-lg: 26px;
  --r-pill: 999px;

  --press: 4px;
  --tap:   56px;
  --tabbar: 78px;

  --wrap: 62rem;
  --gap:  1rem;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ground: #FFFBF2;  --surface: #FFFFFF;  --sunk: #F6EEE1;  --raised: #FFFFFF;
    --ink: #2A1B33;     --ink-soft: #6B5A75;
    --purple: #6B2E86;  --purple-deep: #4E1F64; --purple-wash: #F3EAF7;
    --gold: #E8A317;    --gold-deep: #B87D06;   --gold-wash: #FDF3DC;
    --green: #3FA65C;   --green-deep: #2E7D45;  --green-wash: #E9F6ED;
    --sky: #2E86AB;     --sky-deep: #1F6182;    --sky-wash: #E7F2F8;
    --line: #EADFD0;    --line-strong: #D9C8B2;
  }
}
:root[data-theme="light"] {
  --ground: #FFFBF2;  --surface: #FFFFFF;  --sunk: #F6EEE1;  --raised: #FFFFFF;
  --ink: #2A1B33;     --ink-soft: #6B5A75;
  --purple: #6B2E86;  --purple-deep: #4E1F64; --purple-wash: #F3EAF7;
  --gold: #E8A317;    --gold-deep: #B87D06;   --gold-wash: #FDF3DC;
  --green: #3FA65C;   --green-deep: #2E7D45;  --green-wash: #E9F6ED;
  --sky: #2E86AB;     --sky-deep: #1F6182;    --sky-wash: #E7F2F8;
  --line: #EADFD0;    --line-strong: #D9C8B2;
}

/* ------------------------------------------------------------------ base */

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

/* ---------------------------------------------------------------- locked

   The page does not move except by scrolling down. On a shared phone, held
   one-handed, half the accidental gestures are pinches and two-finger drags,
   and an app that slides off-centre and stays there reads as broken to someone
   who has no idea they did it — and no idea how to undo it.

   · touch-action: pan-y      one axis. Pinch-zoom and double-tap zoom cannot fire.
   · overscroll-behavior      no rubber-band past the ends, no pull-to-refresh.
   · overflow-x: clip         nothing can push the layout sideways.
   · text-size-adjust         stops mobile Safari reflowing type on rotate.

   Paired with `user-scalable=no` in every page's viewport tag.

   The trade: pinch-to-zoom is gone, which is a real accessibility loss. It is
   paid for by a 17px base, a 56px minimum touch target and a contrast floor
   around 14:1 — the page is built to be readable without zooming rather than
   readable once you have zoomed. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  touch-action: pan-y;
  overscroll-behavior: none;
  overflow-x: clip;
  height: 100%;
}

body {
  touch-action: pan-y;
  overscroll-behavior: none;
  overflow-x: clip;
  min-height: 100%;
  /* iOS shows a grey flash and a magnifier on long-press. Neither means
     anything here, and both look like a fault. */
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 500 17px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

h1, h2, h3 { line-height: 1.2; margin: 0 0 .5em; font-weight: 800; letter-spacing: -.015em; }
h1 { font-size: clamp(1.8rem, 5vw, 2.6rem); }
h2 { font-size: clamp(1.35rem, 3.4vw, 1.8rem); }
h3 { font-size: 1.15rem; }
p  { margin: 0 0 1em; }
a  { color: var(--purple); }

img, svg, video { max-width: 100%; display: block; }

/* A dragged image or button is a UI that has come loose. Prose stays
   selectable — someone may want to copy an address or a filename out of it. */
img, svg, video, .btn, .tile, .res, .fchip, .chip, .top, .steps > li::before {
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
}
p, li, h1, h2, h3, code, input { -webkit-user-select: text; user-select: text; }

/* Taps register immediately instead of waiting out a double-tap that can no
   longer happen. */
.btn, .tile, .res, .fchip, a, button, select, input { touch-action: manipulation; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 1.1rem; }
.stack > * + * { margin-top: var(--gap); }
.muted { color: var(--ink-soft); }
.center { text-align: center; }
[hidden] { display: none !important; }

.sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
  border-radius: 6px;
}

/* ============================================================ app chrome

   Shaped after the Bible app in the reference recording: a fixed bar of four
   destinations at the bottom, thumbs-first, so no screen needs its own way
   back. Anything deeper than a destination (a resource, a guided flow) keeps
   its own Back control. */

.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  height: calc(var(--tabbar) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  align-items: center;
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}
.tabbar a {
  display: grid; justify-items: center; gap: 3px;
  padding: 8px 4px; min-height: 58px;
  color: var(--ink-soft); text-decoration: none;
  font-size: .72rem; font-weight: 700;
  border-radius: var(--r);
}
.tabbar a svg { width: 25px; height: 25px; }
.tabbar a[aria-current="page"] { color: var(--ink); }
.tabbar a[aria-current="page"] .pipe {
  background: var(--purple-wash); color: var(--purple);
}
.tabbar .pipe {
  display: grid; place-items: center;
  width: 58px; height: 32px; border-radius: var(--r-pill);
}
body.has-tabs { padding-bottom: calc(var(--tabbar) + env(safe-area-inset-bottom) + 1rem); }
html[dir="rtl"] .tabbar a { font-size: .78rem; }

/* ------------------------------------------------------------- greeting */
.greet { padding: 1.2rem 0 .3rem; display: grid; gap: .15rem; }
.greet h1 { font-size: clamp(1.9rem, 7vw, 2.4rem); margin: 0; }
.greet .eyebrow {
  font-size: .76rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--purple); margin: 0;
}
html[dir="rtl"] .greet .eyebrow { text-transform: none; letter-spacing: 0; font-size: .95rem; }
.greet .muted { margin: .25rem 0 0; }

/* ------------------------------------------------------------- list row

   One row, one thing, one action — thumbnail, two lines of text, and a pill
   that says what tapping it does. Repeated down the library and inside every
   shelf, so learning one row teaches the whole app. */
.row-list { display: grid; gap: 2px; }
.rowi {
  display: grid; grid-template-columns: 62px 1fr auto; gap: .85rem;
  align-items: center;
  padding: .7rem .2rem;
  color: inherit; text-decoration: none;
  border-radius: var(--r);
}
.rowi:hover { background: var(--sunk); }
.rowi:active { background: var(--raised); }
.rowi .art {
  width: 62px; height: 62px; border-radius: 14px; overflow: hidden;
  background: var(--sunk); position: relative;
  display: grid; place-items: center; color: var(--purple);
}
.rowi .art svg { width: 26px; height: 26px; opacity: .65; }
.rowi .art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; max-width: none; }
.rowi .meta { min-width: 0; display: grid; gap: 2px; }
.rowi .kicker {
  font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft);
}
html[dir="rtl"] .rowi .kicker { text-transform: none; letter-spacing: 0; font-size: .82rem; }
.rowi .name { font-weight: 800; font-size: 1.02rem; line-height: 1.25; }
.rowi .sub { color: var(--ink-soft); font-size: .88rem; }
.rowi .go {
  flex: none; min-height: 40px; padding: .5rem 1.1rem;
  border-radius: var(--r-pill);
  background: var(--raised); color: var(--ink);
  font-weight: 800; font-size: .92rem;
  border: 1px solid var(--line-strong);
  display: inline-flex; align-items: center; gap: .4rem;
}
.rowi .go svg { width: 16px; height: 16px; }
.rowi:hover .go { background: var(--purple); border-color: var(--purple); color: #fff; }

/* ------------------------------------------------------ batch downloads */

.bulk-start { margin-top: 1rem; }
.bulk-tools {
  display: flex; align-items: center; justify-content: space-between;
  gap: .7rem; flex-wrap: wrap; margin-top: 1rem;
  padding: .8rem; border: 2px solid var(--green); border-radius: var(--r);
  background: var(--green-wash);
}
.bulk-tools strong { font-size: 1.05rem; }
.bulk-tools .btn { min-height: 48px; padding: .65rem 1rem; }
button.bulk-row {
  width: 100%; border: 2px solid transparent; background: transparent;
  color: inherit; font: inherit; text-align: start; cursor: pointer;
}
button.bulk-row:hover { border-color: var(--line-strong); }
button.bulk-row[aria-checked="true"] {
  border-color: var(--green); background: var(--green-wash);
}
.bulk-check {
  width: 42px; height: 42px; display: grid; place-items: center;
  border: 3px solid var(--line-strong); border-radius: 50%;
  color: transparent; background: var(--surface);
}
.bulk-check svg { width: 25px; height: 25px; stroke-width: 3; }
.bulk-row[aria-checked="true"] .bulk-check {
  border-color: var(--green); background: var(--green); color: #fff;
}
.bulk-dock {
  position: fixed; z-index: 46; left: 0; right: 0;
  bottom: calc(var(--tabbar) + env(safe-area-inset-bottom));
  padding: .8rem max(1.1rem, calc((100% - var(--wrap)) / 2 + 1.1rem));
  background: color-mix(in srgb, var(--ground) 96%, transparent);
  border-top: 2px solid var(--line); backdrop-filter: blur(12px);
}
body.bulk-mode { padding-bottom: calc(var(--tabbar) + 92px + env(safe-area-inset-bottom)); }

/* ------------------------------------------------ shared language pages */

.shared-hero {
  margin: 1rem 0; padding: 1.25rem;
  border: 2px solid var(--purple); border-radius: var(--r-lg);
  background: linear-gradient(145deg, var(--purple-wash), var(--surface));
}
.shared-hero .eyebrow {
  display: inline-flex; padding: .25rem .65rem; margin: 0 0 .65rem;
  border-radius: var(--r-pill); background: var(--purple); color: #fff;
  font-size: .8rem; font-weight: 800;
}
.shared-hero h1 { margin: 0; }
.shared-hero p { margin: .45rem 0 0; }
.shared-actions { margin-top: 1rem; }
.shared-actions .btn { flex-basis: 13rem; }
.send-library { margin-top: 1rem; }
.send-library[hidden] { display: none; }
.send-library .flabel { display: block; margin: 0 0 .4rem; }
.send-library-row { display: flex; flex-wrap: wrap; gap: .7rem; }
.send-library-row input { flex: 1 1 14rem; width: auto; }
.send-library-row .btn { flex: 0 1 9rem; }
.send-library .send-library-status:empty { display: none; }

.share-language-grid { display: grid; gap: .8rem; margin-top: 1.2rem; }
.share-language-card {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 1rem;
}
.share-language-card h2, .share-language-card p { margin: 0; }
.share-language-card .btn { min-width: 10rem; }
@media (max-width: 680px) {
  .share-language-card { grid-template-columns: 1fr; }
  .share-language-card .btn-row { width: 100%; }
}

/* ---------------------------------------------------------------- shelves */
.shelf { display: grid; gap: .6rem; margin-top: 1.7rem; }
.shelf-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.shelf-head h2 { font-size: 1.32rem; margin: 0; }
.shelf-head a { font-size: .92rem; font-weight: 700; text-decoration: none; color: var(--purple); }
.rail-x {
  display: grid; grid-auto-flow: column; grid-auto-columns: 46%;
  gap: .7rem; overflow-x: auto; scroll-snap-type: x mandatory;
  /* Kept inside the page gutter: bleeding to the screen edge clipped the
     first poster, which is the one most likely to be tapped. */
  padding-bottom: .3rem;
  scrollbar-width: none;
}
.rail-x::-webkit-scrollbar { display: none; }
.rail-x > * { scroll-snap-align: start; }
@media (min-width: 40rem) { .rail-x { grid-auto-columns: 22%; } }
.poster { display: grid; gap: .45rem; color: inherit; text-decoration: none; }
.poster .art {
  aspect-ratio: 3 / 4; border-radius: 16px; overflow: hidden; position: relative;
  background: var(--sunk); display: grid; place-items: center; color: var(--purple);
}
.poster .art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; max-width: none; }
.poster .art svg { width: 30px; height: 30px; opacity: .6; }
.poster .name { font-weight: 700; font-size: .93rem; line-height: 1.25; }
.poster .sub { color: var(--ink-soft); font-size: .8rem; }
.poster:active { opacity: .7; }

/* ------------------------------------------------------------ hero card */
.hero-card {
  position: relative; overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--purple-wash);
  padding: 1.3rem;
  display: grid; gap: .5rem;
  color: inherit; text-decoration: none;
  min-height: 150px; align-content: end;
  border: 1px solid var(--line);
}
.hero-card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; max-width: none; }
.hero-card .veil { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(14,11,18,.15), rgba(14,11,18,.88)); }
.hero-card > * { position: relative; }
.hero-card.shot { color: #fff; }
.hero-card .kicker { font-size: .74rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; opacity: .9; }
.hero-card h2 { font-size: 1.5rem; margin: 0; }
.hero-card .sub { opacity: .85; font-size: .93rem; }
/* The headline film: player on top, caption below, so nothing covers the controls. */
.hero-card.headline { padding: 0; gap: 0; min-height: 0; align-content: start; }
.hero-card.headline video { display: block; width: 100%; aspect-ratio: 16 / 9; background: #000; }
.hero-card.headline .cap { display: grid; gap: .35rem; padding: 1rem 1.3rem 1.2rem; }

/* ---------------------------------------------------------------- header */

.top {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--ground) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 2px solid var(--line);
}
.top .wrap {
  display: flex; align-items: center; gap: .6rem;
  min-height: 64px;
}
.brand {
  display: flex; align-items: center; gap: .55rem;
  font-weight: 800; font-size: 1.05rem; color: var(--ink);
  text-decoration: none; margin-inline-end: auto;
}
.brand svg { width: 30px; height: 30px; flex: none; }

/* ----------------------------------------------------- the language hint

   An arrow under the language button. The one control a reader stranded in a
   language they cannot read must be able to find, so it points at itself and
   says so in every language in turn. */
.lang-hint {
  position: fixed; z-index: 45;
  display: flex; align-items: center; gap: .4rem;
  padding: .55rem .85rem;
  border-radius: var(--r-pill);
  background: var(--gold); color: #2A1B33;
  font-weight: 800; font-size: .88rem; line-height: 1.3;
  white-space: nowrap; cursor: pointer;
  box-shadow: 0 10px 24px -10px rgba(0, 0, 0, .7);
  opacity: 0;
}
.lang-hint.in { animation: hint-say 2.6s ease-in-out; }
@keyframes hint-say {
  0%   { opacity: 0; transform: translateY(-4px); }
  14%  { opacity: 1; transform: none; }
  80%  { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-2px); }
}
/* The little arrow, sitting under the button it points at. */
.lang-hint .beak {
  position: absolute; top: -7px; left: var(--beak, 50%);
  width: 14px; height: 14px; margin-left: -7px;
  background: var(--gold);
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.lang-hint .say { display: block; }
/* Held still for anyone who asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .lang-hint, .lang-hint.in { opacity: 1; animation: none; }
}

/* --------------------------------------------------------------- buttons */

/* The chunky button: a solid face sitting on a darker edge, which the press
   collapses. It reads as physically pressable at arm's length in sunlight,
   which a flat rectangle does not. */
.btn {
  --face: var(--purple);
  --edge: var(--purple-deep);
  display: inline-flex; align-items: center; justify-content: center;
  gap: .5rem;
  min-height: var(--tap);
  padding: .85rem 1.4rem;
  border: 0;
  border-radius: var(--r);
  background: var(--face);
  box-shadow: 0 var(--press) 0 var(--edge);
  color: #fff;
  font: inherit; font-weight: 800; font-size: 1.02rem;
  text-decoration: none; text-align: center;
  cursor: pointer;
  transform: translateY(0);
  transition: transform .06s ease, box-shadow .06s ease, filter .15s ease;
}
.btn:hover { filter: brightness(1.06); }
.btn:active {
  transform: translateY(var(--press));
  box-shadow: 0 0 0 var(--edge);
}
.btn[disabled] { opacity: .45; pointer-events: none; }

.btn.green { --face: var(--green); --edge: var(--green-deep); }
.btn.gold  { --face: var(--gold);  --edge: var(--gold-deep); color: #3A2A05; }
.btn.sky   { --face: var(--sky);   --edge: var(--sky-deep); }

.btn.ghost {
  --face: var(--surface); --edge: var(--line-strong);
  color: var(--ink);
  border: 2px solid var(--line-strong);
}
.btn.block { width: 100%; }
.btn.big   { min-height: 64px; font-size: 1.12rem; padding: 1rem 1.6rem; }
.btn svg   { width: 22px; height: 22px; flex: none; }

.btn-row { display: flex; flex-wrap: wrap; gap: .7rem; }
.btn-row .btn { flex: 1 1 10rem; }

/* ----------------------------------------------------------------- cards */

.card {
  background: var(--surface);
  border: 2px solid var(--line);
  border-radius: var(--r-lg);
  padding: 1.2rem;
}
.card.flat { box-shadow: none; }

/* A tile is a card you tap: the whole thing is the target. */
.tile {
  display: flex; align-items: center; gap: .9rem;
  background: var(--surface);
  border: 2px solid var(--line);
  border-bottom-width: 5px;
  border-radius: var(--r-lg);
  padding: 1rem 1.1rem;
  color: inherit; text-decoration: none;
  min-height: 84px;
  transition: transform .08s ease, border-color .15s ease, background .15s ease;
}
.tile:hover { border-color: var(--purple); background: var(--purple-wash); }
.tile:active { transform: translateY(3px); border-bottom-width: 2px; margin-bottom: 3px; }
.tile .ico {
  width: 54px; height: 54px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r);
  background: var(--purple-wash); color: var(--purple);
}
.tile .ico svg { width: 30px; height: 30px; }
.tile > span:not(.ico):not(.chev) { min-width: 0; }
.tile .t { display: block; font-weight: 800; font-size: 1.08rem; }
.tile .s { display: block; color: var(--ink-soft); font-size: .92rem; }
.tile .chev { margin-inline-start: auto; color: var(--line-strong); flex: none; }

.grid {
  display: grid; gap: .8rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
}

/* ------------------------------------------------------- resource cards */

.res {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 2px solid var(--line);
  border-bottom-width: 5px;
  border-radius: var(--r-lg);
  overflow: hidden;
  color: inherit; text-decoration: none;
  transition: transform .08s ease, border-color .15s ease;
}
.res:hover { border-color: var(--purple); }
.res:active { transform: translateY(3px); border-bottom-width: 2px; margin-bottom: 3px; }
.thumb {
  aspect-ratio: 16 / 10;
  background: var(--sunk) center/cover no-repeat;
  display: grid; place-items: center;
  color: var(--purple);
}
.res .thumb { border-bottom: 2px solid var(--line); }
.thumb { position: relative; overflow: hidden; }
.thumb > svg { width: 46px; height: 46px; opacity: .5; }
.thumb > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; max-width: none;
  object-fit: cover;
}
.res .body { padding: .85rem .95rem 1rem; display: flex; flex-direction: column; gap: .3rem; flex: 1; }
.res .name { font-weight: 800; font-size: 1.02rem; line-height: 1.3; }
.res .meta { color: var(--ink-soft); font-size: .88rem; }
.res .foot { margin-top: auto; padding-top: .55rem; display: flex; gap: .4rem; flex-wrap: wrap; }

/* ----------------------------------------------------------------- chips */

.chip {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .3rem .7rem;
  border-radius: var(--r-pill);
  background: var(--sunk); color: var(--ink-soft);
  border: 1.5px solid transparent;
  font-size: .82rem; font-weight: 700;
  white-space: nowrap;
}
.chip svg { width: 15px; height: 15px; }
.chip.offline { background: var(--green-wash); color: var(--green-deep); }
.chip.online  { background: var(--gold-wash);  color: var(--gold-deep); }
.chip.lang    { background: var(--purple-wash); color: var(--purple); }
.chip.stream  { background: var(--sky-wash); color: var(--sky-deep); }

.player {
  width: 100%; aspect-ratio: 16 / 9;
  border-radius: var(--r-lg); background: #000;
  border: 2px solid var(--line);
}
.picker { display: flex; gap: .6rem; align-items: end; flex-wrap: wrap; margin-top: .7rem; }
.picker > label { width: 100%; margin: 0; }
.picker > select { flex: 1 1 12rem; }
.picker.two > div { flex: 1 1 9rem; }
.picker .flabel { margin: 0 0 .35rem; }
.pdf {
  width: 100%; height: 70vh; margin-top: 1rem;
  border: 2px solid var(--line); border-radius: var(--r-lg);
  background: var(--sunk);
}
.pdf-note { margin: .55rem 0 0; font-size: .9rem; }

.flabel {
  margin: 1rem 0 .45rem;
  font-size: .8rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-soft);
}
/* Uppercase-with-tracking is a Latin device; in Arabic script it breaks the
   joins, and there is no case to change. */
html[dir="rtl"] .flabel { text-transform: none; font-size: .95rem; }

/* A filter chip is a chip you press. */
.filters { display: flex; gap: .5rem; flex-wrap: wrap; }
.library-language { margin-top: 1rem; }
.library-language summary {
  display: flex; align-items: center; gap: .75rem;
  min-height: 48px; padding: .55rem .9rem;
  border: 2px solid var(--line-strong); border-radius: var(--r);
  background: var(--surface); color: var(--ink);
  font-weight: 700; cursor: pointer;
  list-style: none;
}
.library-language summary::-webkit-details-marker { display: none; }
.library-language summary strong {
  min-width: 0; margin-inline-start: auto; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; color: var(--purple);
}
.library-language summary::after {
  content: ''; width: .55rem; height: .55rem; flex: none;
  border-inline-end: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg); margin: -.25rem .2rem 0 0;
}
.library-language[open] summary::after { transform: rotate(225deg); margin: .25rem .2rem 0 0; }
.library-language .filters { padding: .8rem 0 .25rem; }
.library-search { margin-bottom: .9rem; }
.library-actions { margin-top: 1rem; }
.library-actions .bulk-start { margin-top: 1rem; }
.save-guide-entry {
  display: inline-flex; align-items: center; gap: .6rem; min-height: 48px;
  margin-top: .8rem; padding: .3rem 0; font-weight: 700; font-size: .9rem;
}
.save-guide-entry svg { width: 22px; height: 22px; flex: none; }
.save-guide-entry small { font-weight: 500; white-space: nowrap; }
.library-count { margin: 1rem 0 .6rem; }
@media (max-width: 39.99rem) {
  #type-filters {
    flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: none; scroll-snap-type: x proximity;
    padding-bottom: .2rem;
  }
  #type-filters::-webkit-scrollbar { display: none; }
  #type-filters .fchip { flex: none; scroll-snap-align: start; }
  .library-actions.has-bulk {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .5rem; margin-top: .8rem;
  }
  .library-actions.has-bulk > *, .library-actions.has-bulk .btn { min-width: 0; }
  .library-actions.has-bulk .btn {
    width: 100%; min-height: 56px; padding: .4rem .3rem;
    font-size: .78rem; line-height: 1.2; gap: .25rem;
  }
  .library-actions.has-bulk .btn svg { width: 18px; height: 18px; }
  .library-actions.has-bulk .bulk-start { margin-top: 0; }
  .library-count { margin: .55rem 0 .35rem; }
}
@media (max-width: 360px) {
  .library-search { margin-bottom: .45rem; }
  .library-kind { margin: .55rem 0 .3rem; }
  .library-language { margin-top: .6rem; }
  .library-actions.has-bulk { margin-top: .5rem; }
  .library-count { margin: .25rem 0 0; }
}
.fchip {
  display: inline-flex; align-items: center; justify-content: center;
  gap: .4rem;
  min-height: 46px;
  padding: .55rem 1rem;
  border-radius: var(--r-pill);
  border: 2px solid var(--line-strong);
  background: var(--surface); color: var(--ink);
  font: inherit; font-weight: 700; font-size: .95rem;
  cursor: pointer;
}
.fchip svg { width: 20px; height: 20px; flex: none; }
.fchip[aria-pressed="true"] {
  background: var(--purple); border-color: var(--purple-deep); color: #fff;
}

/* ----------------------------------------------------------------- input */

.search { position: relative; }
.search svg {
  position: absolute; inset-inline-start: 1rem; top: 50%;
  transform: translateY(-50%); width: 22px; height: 22px;
  color: var(--ink-soft); pointer-events: none;
}
input[type="search"], input[type="text"], input[type="email"], select {
  width: 100%;
  min-height: var(--tap);
  /* Logical, not physical: in an RTL page the icon sits on the right and the
     text must start clear of it. `padding-left` would put the gap on the wrong
     side and the placeholder would run straight through the magnifier. */
  padding: .9rem 1rem;
  padding-inline-start: 3rem;
  border: 2px solid var(--line-strong);
  border-radius: var(--r);
  background: var(--surface); color: var(--ink);
  font: inherit; font-weight: 600;
  -webkit-appearance: none; appearance: none;
}
select, input[type="email"] { padding-inline-start: 1rem; }
input[type="search"]::-webkit-search-cancel-button { margin-inline-start: .5rem; }
input::placeholder { color: var(--ink-soft); font-weight: 500; }

/* ----------------------------------------------------------------- steps */

/* The share and view walkthroughs. Numbered, one action each, big enough to
   follow while holding two phones. */
.steps { list-style: none; margin: 0; padding: 0; counter-reset: s; }
.steps > li {
  counter-increment: s;
  position: relative;
  /* Logical: the number circle sits on the inline-start edge, which is the
     right in an RTL page. Physical padding-left leaves no room for it there
     and the heading runs underneath the number. */
  padding: 0 0 1.5rem;
  padding-inline-start: 3.4rem;
  border-inline-start: 3px solid var(--line);
  margin-inline-start: 1.35rem;
}
.steps > li:last-child { border-color: transparent; padding-bottom: 0; }
.steps > li::before {
  content: counter(s);
  position: absolute; inset-inline-start: -1.5rem; top: -.15rem;
  width: 2.9rem; height: 2.9rem;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--purple); color: #fff;
  font-weight: 800; font-size: 1.1rem;
  box-shadow: 0 0 0 5px var(--ground);
}
.steps h3 { margin: .35rem 0 .3rem; }
.steps p  { margin: 0 0 .4rem; color: var(--ink-soft); }

/* ----------------------------------------------------------------- notes */

.note {
  border: 2px solid var(--line);
  border-inline-start: 6px solid var(--gold);
  border-radius: var(--r);
  background: var(--gold-wash);
  padding: .9rem 1.1rem;
}
.note.good { border-inline-start-color: var(--green); background: var(--green-wash); }
.note.info { border-inline-start-color: var(--sky);   background: var(--sky-wash); }
.note strong { display: block; margin-bottom: .15rem; }
.note strong svg { width: 20px; height: 20px; display: inline-block; vertical-align: -4px; }

/* --------------------------------------------------------------- sheets */

.sheet-back {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(20, 10, 26, .55);
  display: grid; align-items: end; justify-items: center;
}
.sheet {
  width: min(40rem, 100%);
  max-height: 88vh; overflow: auto;
  /* The sheet scrolls; the page behind it must not scroll with it. */
  overscroll-behavior: contain;
  background: var(--ground);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  border-top: 3px solid var(--line);
  padding: 1.4rem 1.1rem 2rem;
  animation: rise .18s ease-out;
}
@keyframes rise { from { transform: translateY(14px); opacity: .6 } }
@media (min-width: 40rem) {
  .sheet-back { align-items: center; }
  .sheet { border-radius: var(--r-lg); border: 3px solid var(--line); }
}
@media (prefers-reduced-motion: reduce) {
  .sheet { animation: none; }
  .meter > span.busy { animation: none; }
  * { transition-duration: .01ms !important; }
}

/* --------------------------------------------------------- guided flows

   Save and Nearby share one shape: a row of numbered dots, "Step 2 of 4", one
   question per screen, and a big bar while anything is moving. */

.stepper { display: flex; align-items: center; margin: .2rem 0 .4rem; }
.stepper .dot {
  width: 34px; height: 34px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 2.5px solid var(--line-strong);
  background: var(--surface); color: var(--ink-soft);
  font-weight: 800; font-size: .95rem;
}
.stepper .dot svg { width: 18px; height: 18px; }
.stepper .dot.on   { border-color: var(--purple); background: var(--purple); color: #fff; }
.stepper .dot.done { border-color: var(--green-deep); background: var(--green); color: #fff; }
.stepper .bar { flex: 1; height: 3px; background: var(--line); margin: 0 .3rem; border-radius: 2px; }
.step-of { margin: 0 0 .2rem; color: var(--ink-soft); font-size: .85rem; font-weight: 700; }

.meter {
  height: 22px; border-radius: var(--r-pill);
  background: var(--sunk); border: 2px solid var(--line);
  overflow: hidden;
}
.meter > span {
  display: block; height: 100%; width: 0;
  background: var(--green);
  border-radius: var(--r-pill);
  transition: width .2s linear;
}
/* No content-length: say "working" honestly instead of a bar that lies. */
.meter > span.busy {
  width: 40% !important;
  animation: sweep 1.2s ease-in-out infinite;
}
@keyframes sweep { 0% { transform: translateX(-100%) } 100% { transform: translateX(250%) } }
html[dir="rtl"] .meter > span.busy { animation-direction: reverse; }

.done-mark {
  width: 84px; height: 84px; margin: .6rem auto .4rem;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--green); color: #fff;
  box-shadow: 0 5px 0 var(--green-deep);
}
.done-mark svg { width: 46px; height: 46px; stroke-width: 3; }

/* A pairing code people read aloud across a room. */
.code {
  font: 800 clamp(2.4rem, 12vw, 3.4rem)/1 ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: .18em !important;
  text-align: center; direction: ltr; unicode-bidi: isolate;
  padding: 1rem .5rem;
  border: 3px dashed var(--purple);
  border-radius: var(--r-lg);
  background: var(--purple-wash); color: var(--purple);
}
.code-input {
  font: 800 2rem/1 ui-monospace, "SF Mono", Menlo, monospace !important;
  letter-spacing: .3em !important;
  text-align: center; direction: ltr;
  padding: .9rem !important;
}

.spin {
  display: inline-block; width: 1em; height: 1em; vertical-align: -.15em;
  margin-inline-end: .5em;
  border: 2.5px solid var(--line-strong); border-top-color: var(--purple);
  border-radius: 50%;
  animation: turn .8s linear infinite;
}
@keyframes turn { to { transform: rotate(360deg) } }

/* ---------------------------------------------------------------- footer */

.foot-nav {
  margin-top: 3rem; padding: 1.6rem 0 2.5rem;
  border-top: 2px solid var(--line);
  color: var(--ink-soft); font-size: .92rem;
}

/* Chinese sets in the reader's own system face — Simplified for Mandarin,
   Traditional for Cantonese — and wants a little more line height than Latin
   because the characters are dense. */
html[lang="cmn"] body,
html[lang="yue"] body {
  font-family: "PingFang SC", "PingFang HK", "Hiragino Sans GB", "Microsoft YaHei",
               "Noto Sans CJK SC", "Source Han Sans SC", system-ui, sans-serif;
  line-height: 1.75;
}
html[lang="yue"] body { font-family: "PingFang HK", "PingFang TC", "Microsoft JhengHei",
               "Noto Sans CJK TC", "Source Han Sans TC", system-ui, sans-serif; }

/* Devanagari needs a face with the full headline and conjunct repertoire. */
html[lang="hi"] body,
html[lang="mr"] body,
html[lang="ne"] body {
  font-family: "Noto Sans Devanagari", "Kohinoor Devanagari", Mangal,
               "Nirmala UI", system-ui, sans-serif;
  line-height: 1.75;
}
html[lang="pa"] body {
  font-family: "Noto Sans Gurmukhi", "Gurmukhi MN", "Mukta Mahee", Raavi,
               "Nirmala UI", system-ui, sans-serif;
  line-height: 1.75;
}
/* Chinese has no letter case and no word spaces: uppercase tracking on a label
   just pushes the characters apart. */
html[lang="cmn"] .kicker, html[lang="yue"] .kicker,
html[lang="cmn"] .flabel, html[lang="yue"] .flabel,
html[lang="cmn"] .eyebrow, html[lang="yue"] .eyebrow {
  text-transform: none; letter-spacing: 0; font-size: .82rem;
}
html[lang="hi"] .kicker, html[lang="hi"] .flabel, html[lang="hi"] .eyebrow,
html[lang="mr"] .kicker, html[lang="mr"] .flabel, html[lang="mr"] .eyebrow,
html[lang="ne"] .kicker, html[lang="ne"] .flabel, html[lang="ne"] .eyebrow,
html[lang="pa"] .kicker, html[lang="pa"] .flabel, html[lang="pa"] .eyebrow {
  text-transform: none; letter-spacing: 0; font-size: .86rem;
}

/* ============================================================ Arabic script

   Urdu and Sindhi join their letters. Several defaults that are invisible in
   Latin actively break them, so they are switched off wherever the interface
   is running in an Arabic script — and Latin runs opt back in via .latin.  */

html[dir="rtl"] body {
  font-family: "Noto Nastaliq Urdu", "Noto Naskh Arabic", "Geeza Pro",
               "Urdu Typesetting", serif;
  line-height: 2.1;                 /* Nastaliq hangs far below the baseline */
}
html[lang="snd"] body,
html[lang="ps"] body,
html[lang="ar"] body {
  font-family: "Noto Naskh Arabic", "Geeza Pro", "Traditional Arabic", serif;
  line-height: 1.95;                /* Naskh is shallower and sits tighter */
}

/* Any tracking at all pulls the joins apart and renders words as loose glyphs. */
html[dir="rtl"] * { letter-spacing: 0 !important; }

/* break-word would sever a word between two joined letters. */
html[dir="rtl"] body { overflow-wrap: normal; word-break: normal; }

/* Headings at body leading open a chasm; step them back down. */
html[dir="rtl"] h1 { line-height: 1.45; }
html[dir="rtl"] h2 { line-height: 1.55; }
html[dir="rtl"] h3 { line-height: 1.7; }

/* Descenders collide with control borders without extra room. */
html[dir="rtl"] .btn,
html[dir="rtl"] .fchip { padding-top: 1.05rem; padding-bottom: 1.05rem; }

/* Catalogue data — titles, publisher names — stays English. Inside an RTL page
   it needs its own face and its own direction or it zig-zags between the two. */
.latin {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  direction: ltr;
  unicode-bidi: isolate;
  text-align: start;
  letter-spacing: normal !important;
  line-height: 1.4;
}

/* An element whose entire content is a numeral: "112", "06:00", "2:07:53".
   Without isolation the bidi algorithm renders "200+" as "+200". Only ever put
   this on elements that hold nothing but the number. */
.num { direction: ltr; unicode-bidi: isolate; display: inline-block; }

/* Chevrons and arrows point the other way in an RTL page. */
html[dir="rtl"] .flip { transform: scaleX(-1); }

/* ============================================================ first run

   A whole screen rather than a sheet: nothing behind it matters until a
   language is chosen, and it must not be dismissable by a stray tap. */
html.welcoming, html.welcoming body { overflow: hidden; }
html.welcoming #et-hint { display: none; }
.welcome {
  position: fixed; inset: 0; z-index: 200;
  background: var(--ground);
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: max(1.25rem, env(safe-area-inset-top)) 16px max(1.5rem, env(safe-area-inset-bottom));
}
.welcome-in { max-width: 34rem; margin: 0 auto; }
.welcome-mark { display: flex; justify-content: center; margin: .75rem 0 .5rem; }
.welcome h1 { text-align: center; font-size: clamp(1.6rem, 6vw, 2.1rem); margin: 0 0 .35rem; }
.welcome-also {
  text-align: center; color: var(--ink-soft); font-size: .92rem; line-height: 1.8;
  margin: 0 0 .6rem;
}
.welcome > .welcome-in > .muted { text-align: center; margin: 0 auto 1.1rem; max-width: 28rem; }
.welcome-search { margin-bottom: .8rem; }
.welcome-list { display: grid; gap: .5rem; }
.welcome-lang {
  display: flex; align-items: center; gap: .75rem; width: 100%;
  min-height: 4.1rem; padding: .7rem .9rem;
  text-align: start; color: inherit; font: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  cursor: pointer; transition: border-color .15s, transform .1s, background .15s;
}
.welcome-lang:hover { border-color: var(--purple); }
.welcome-lang:active { transform: scale(.985); }
.welcome-lang:focus-visible { outline: 3px solid var(--purple); outline-offset: 2px; }
.welcome-lang.guess { border-color: var(--purple); background: var(--purple-wash); }
.welcome-lang[hidden] { display: none; }
.wl-text { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.wl-native { font-size: 1.22rem; font-weight: 700; line-height: 1.5; }
.wl-name { font-size: .84rem; color: var(--ink-soft); }
.wl-count { font-size: .8rem; color: var(--purple); line-height: 1.45; margin-top: .1rem; }
.wl-go { display: flex; color: var(--ink-soft); flex: none; }
.wl-go svg { width: 18px; height: 18px; }
html[dir="rtl"] .wl-go svg { transform: scaleX(-1); }

/* ------------------------------------------------------------ generated covers
   Every item has one; a real cover image, when it loads, sits on top. */
.gen {
  position: absolute; inset: 0; display: flex; align-items: flex-end;
  color: rgba(255, 255, 255, .95); overflow: hidden;
}
.gen svg { position: absolute; inset: 0; width: 100% !important; height: 100% !important; opacity: 1 !important; }
.gen-title {
  position: relative; z-index: 1; width: 100%;
  padding: .7rem .75rem .8rem;
  font-weight: 800; font-size: 1.02rem; line-height: 1.3; color: #fff;
  text-shadow: 0 1px 8px rgba(0, 0, 0, .35);
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
  background: linear-gradient(to top, rgba(0, 0, 0, .38), transparent);
}
.gen-year {
  position: absolute; top: .6rem; inset-inline-end: .65rem; z-index: 1;
  font-weight: 800; font-size: .8rem; letter-spacing: .04em; color: #fff;
  background: rgba(0, 0, 0, .25); padding: .15rem .45rem; border-radius: 999px;
}
.poster .art img, .rowi .art img, .thumb > img { z-index: 2; }

/* Ethiopic faces are supplied by the device so the offline app needs no font download. */
html[lang="am"] body {
  font-family: "Noto Sans Ethiopic", Nyala, Kefa, "Abyssinica SIL", system-ui, sans-serif;
  line-height: 1.75;
}
html[lang="am"] .latin { font-family: inherit; }
html[lang="am"] .kicker, html[lang="am"] .flabel, html[lang="am"] .eyebrow {
  text-transform: none; letter-spacing: 0; font-size: .86rem;
}
