/* Encorely — the book, as a stylesheet.
 *
 * Four themes, one design. `ink` is the signature and the one the launcher icon
 * and the feature graphic were drawn from: a near-black violet ground with a pool
 * of lifted violet in it, and gold as the only colour the app ever spends. The
 * other three are the same picture under different light — a theatre, a club, a
 * page — not three other apps, so every token below has one meaning and every
 * theme fills the same eight slots.
 *
 * The shape language is a ticket stub, because that is what the app keeps. A stub
 * is a rectangle with two bites out of its long edges, a perforation and a punch;
 * the hero card, the show header and the year poster are drawn as one, with the
 * bites and the punch cut out of the element's own alpha so whatever is behind
 * shows through (`.stub`, near the bottom of this file). List rows carry only the
 * echo of it — a gold rail and a dashed perforation — because forty stubs on one
 * screen is a texture, and a texture is not an identity.
 *
 * THE ONE RULE THAT MAKES THEMES WORK: exactly one gold object per screen. The
 * newest show is gold with dark ink on it; everything else is the ground's own
 * light with the accent used for hairlines, numbers and the one button that
 * matters. A screen with three gold things has no hero.
 *
 * No web fonts and no images: the app promises it cannot reach the network, and a
 * font would be a request. Everything here is system faces, gradients and fields.
 */

/* ── tokens ────────────────────────────────────────────────────────────── */

:root {
  /* The tilt is the icon's own: the stub leans the way the light falls. It is a
     token because the hero card, the poster and the share card all lean together,
     and three hand-written rotations drift apart the first time one is nudged. */
  --tilt: -3deg;

  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 22px;
  --r-xl: 30px;

  --pad: 18px;
  --gap: 12px;

  --t-fast: 120ms;
  --t-mid: 260ms;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-num: ui-rounded, "SF Pro Rounded", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --wordmark-track: 0.22em;

  color-scheme: dark;
}

/* ink — the signature. Violet-black ground, lit violet pool, gold. */
:root,
[data-theme="ink"] {
  --ground-deep: #100c18;
  --ground: #1a1426;
  --ground-lit: #2e2144;
  --surface: #1f1830;
  --surface-lit: #2a2140;
  --ink: #f3ede4;
  --ink-dim: #b9afc6;
  --ink-faint: #7c7290;
  --accent: #e29b35;
  --accent-lit: #f8d286;
  --on-accent: #241703;
  --line: rgb(255 255 255 / 0.09);
  --line-strong: rgb(255 255 255 / 0.18);
  --shadow: 0 18px 40px -22px rgb(0 0 0 / 0.9);
  --glow: 0 0 0 1px rgb(226 155 53 / 0.28), 0 16px 44px -26px rgb(226 155 53 / 0.5);
}

/* velvet — the same book read in a theatre: crimson ground, aged gold. */
[data-theme="velvet"] {
  --ground-deep: #150a12;
  --ground: #20101b;
  --ground-lit: #3e1a34;
  --surface: #25121f;
  --surface-lit: #331827;
  --ink: #f6ede9;
  --ink-dim: #c2a9b8;
  --ink-faint: #8b6e80;
  --accent: #e0a24e;
  --accent-lit: #f6d49b;
  --on-accent: #241505;
  --line: rgb(255 236 226 / 0.1);
  --line-strong: rgb(255 236 226 / 0.2);
  --shadow: 0 18px 40px -22px rgb(0 0 0 / 0.9);
  --glow: 0 0 0 1px rgb(224 162 78 / 0.3), 0 16px 44px -26px rgb(224 162 78 / 0.5);
}

/* neon — the same book read in a club: cold blue ground, electric cyan. */
[data-theme="neon"] {
  --ground-deep: #070f1b;
  --ground: #0c1727;
  --ground-lit: #14375c;
  --surface: #0f1d30;
  --surface-lit: #14273e;
  --ink: #eaf4f8;
  --ink-dim: #a3bccb;
  --ink-faint: #6b8496;
  --accent: #4fd6e0;
  --accent-lit: #b4f4f8;
  --on-accent: #032124;
  --line: rgb(214 240 248 / 0.1);
  --line-strong: rgb(214 240 248 / 0.2);
  --shadow: 0 18px 40px -22px rgb(0 0 0 / 0.9);
  --glow: 0 0 0 1px rgb(79 214 224 / 0.3), 0 16px 44px -26px rgb(79 214 224 / 0.5);
}

/* daylight — the same book read on paper. The roles do not invert: the ground is
   still the page and the ink is still the text, which is what keeps every rule in
   this file written once. Only the values move, and the accent darkens enough to
   read as type on paper rather than glow like a lamp. */
[data-theme="daylight"] {
  --ground-deep: #efe6d7;
  --ground: #f7f1e6;
  --ground-lit: #fffdf9;
  --surface: #fffaf1;
  --surface-lit: #ffffff;
  --ink: #201a15;
  --ink-dim: #5c5148;
  --ink-faint: #8c8078;
  --accent: #a9621c;
  --accent-lit: #c9832f;
  --on-accent: #fffaf0;
  --line: rgb(32 26 21 / 0.12);
  --line-strong: rgb(32 26 21 / 0.24);
  --shadow: 0 16px 34px -24px rgb(60 44 24 / 0.5);
  --glow: 0 0 0 1px rgb(169 98 28 / 0.3), 0 14px 34px -24px rgb(169 98 28 / 0.45);
  color-scheme: light;
}

/* ── reset ─────────────────────────────────────────────────────────────── */

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

html {
  height: 100%;
  background: var(--ground-deep);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100dvh;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--ground-deep);
  background-image:
    radial-gradient(130% 78% at 16% 2%, var(--ground-lit), transparent 62%),
    radial-gradient(120% 70% at 96% 30%, var(--ground), transparent 70%);
  background-attachment: fixed;
  overflow-x: hidden;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* The pool of light is fixed to the viewport, but a background-image on body with
   `fixed` attachment still scrolls inside the Android WebView on some releases, so
   it is painted on a pseudo-element that is genuinely pinned. One line, and the
   difference between a lit book and a grey one mid-scroll. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image:
    radial-gradient(130% 78% at 16% 2%, var(--ground-lit), transparent 62%),
    radial-gradient(120% 70% at 96% 30%, var(--ground), transparent 70%);
  pointer-events: none;
}

h1,
h2,
h3,
h4,
p,
figure,
blockquote {
  margin: 0;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

a {
  color: var(--accent-lit);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

svg {
  display: block;
}

[hidden] {
  display: none !important;
}

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* The scrollbar, in the app's own colours, on the platforms that draw one. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

/* ── the shell of the page ─────────────────────────────────────────────── */

.app {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  max-width: 700px;
  margin: 0 auto;
}

.top {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--gap);
  padding: 14px var(--pad) 10px;
  background: linear-gradient(var(--ground-deep) 62%, transparent);
  backdrop-filter: blur(14px);
}

.brand {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-right: auto;
  text-align: left;
}

.brand-name {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: var(--wordmark-track);
  text-transform: uppercase;
  color: var(--accent-lit);
}

.brand-sub {
  font-size: 12px;
  color: var(--ink-faint);
  letter-spacing: 0.02em;
}

.iconbtn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  color: var(--ink-dim);
  transition: color var(--t-fast), background var(--t-fast);
}

.iconbtn:hover,
.iconbtn:active {
  color: var(--ink);
  background: var(--line);
}

.iconbtn svg {
  width: 21px;
  height: 21px;
}

main {
  flex: 1;
  padding: 4px var(--pad) 22px;
}

.screen {
  animation: rise var(--t-mid) var(--ease) both;
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(7px);
  }
}

.tabbar {
  position: sticky;
  bottom: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
  padding: 6px 8px 8px;
  background: color-mix(in srgb, var(--ground-deep) 88%, transparent);
  backdrop-filter: blur(18px);
  border-top: 1px solid var(--line);
}

.tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 7px 2px 5px;
  border-radius: var(--r-md);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-faint);
  transition: color var(--t-fast), background var(--t-fast);
}

.tab svg {
  width: 22px;
  height: 22px;
}

.tab[aria-current="page"] {
  color: var(--accent-lit);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.tab-add[aria-current="page"] {
  color: var(--on-accent);
  background: var(--accent);
}

/* ── headings and small type ───────────────────────────────────────────── */

.h1 {
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-wrap: balance;
}

.h2 {
  font-size: 19px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.h3 {
  font-size: 15px;
  font-weight: 650;
}

.eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.lede {
  font-size: 15px;
  color: var(--ink-dim);
  text-wrap: pretty;
}

.mini {
  font-size: 12.5px;
  color: var(--ink-faint);
}

.dim {
  color: var(--ink-dim);
}

.faint {
  color: var(--ink-faint);
}

.num {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.wrap {
  overflow-wrap: anywhere;
}

.section {
  margin-top: 26px;
}

.section-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 11px;
}

.section-head .h2 {
  margin-right: auto;
}

/* ── the hero stub ─────────────────────────────────────────────────────── */

/* The one gold object. Twelve fields deep because a stub is a specific shape and
   nothing about it is decorative: two bites at the perforation, one punch below
   it, and the notches are what say "ticket" rather than "coupon". */
.stub {
  --stub-pad: 20px;
  --notch: 9px;
  --perf: 0px; /* set by the caller: offset of the perforation from the top */
  --punch: 7px;
  --punch-x: 50%;
  /* Three bands stacked: the head, the perforation, the foot. It is a column
     because the bands are meant to sit under each other whatever is in them - a
     venue that wraps to two lines moves the perforation down and the seam
     measures it a frame later - and because the stub has to fill its width
     whether it is a button in the book or a card on one night. */
  display: flex;
  flex-direction: column;
  /* A button does not stretch to its container the way a div does, and this is
     both: without it the ticket is as wide as its longest line. */
  width: 100%;
  position: relative;
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
}

.stub-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 20px 18px;
}

/* The stars or the count sit at the far end of the foot, whichever is there. */
.stub-foot > .stars {
  margin-left: auto;
}

@supports (mask-composite: intersect) {
  .stub {
    -webkit-mask-image:
      radial-gradient(circle var(--notch) at 0 var(--perf), #0000 calc(var(--notch) - 0.5px), #000 calc(var(--notch) + 0.5px)),
      radial-gradient(circle var(--notch) at 100% var(--perf), #0000 calc(var(--notch) - 0.5px), #000 calc(var(--notch) + 0.5px)),
      radial-gradient(circle var(--punch) at var(--punch-x) var(--punch-y, calc(var(--perf) + 40px)), #0000 calc(var(--punch) - 0.5px), #000 calc(var(--punch) + 0.5px)),
      linear-gradient(#000, #000);
    mask-image: -webkit-mask-image;
    mask-composite: intersect;
  }
}

/* The perforation itself: a dashed hairline the bites sit at each end of. */
.perf {
  position: relative;
  height: 1px;
  margin: 0 calc(var(--notch) * -1 + 2px);
  background-image: linear-gradient(90deg, var(--line-strong) 0 42%, transparent 42% 100%);
  background-size: 13px 1px;
  background-repeat: repeat-x;
  opacity: 0.9;
}

.stub-gold {
  color: var(--on-accent);
  background: linear-gradient(158deg, var(--accent-lit), var(--accent) 58%, color-mix(in srgb, var(--accent) 82%, #000) 100%);
  box-shadow: var(--glow);
}

.stub-gold .perf {
  background-image: linear-gradient(90deg, color-mix(in srgb, var(--on-accent) 42%, transparent) 0 42%, transparent 42% 100%);
}

.stub-gold .mini,
.stub-gold .faint {
  color: color-mix(in srgb, var(--on-accent) 66%, transparent);
}

.stub-gold .dim {
  color: color-mix(in srgb, var(--on-accent) 82%, transparent);
}

.stub-tilt {
  transform: rotate(var(--tilt));
  transform-origin: 50% 50%;
}

/* ── buttons ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 46px;
  padding: 0 20px;
  border-radius: 999px;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: 0.005em;
  transition: transform var(--t-fast) var(--ease), filter var(--t-fast), background var(--t-fast);
}

.btn svg {
  width: 18px;
  height: 18px;
}

.btn:active {
  transform: scale(0.975);
}

.btn-primary {
  color: var(--on-accent);
  background: linear-gradient(150deg, var(--accent-lit), var(--accent));
  box-shadow: var(--glow);
}

.btn-ghost {
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
}

.btn-quiet {
  color: var(--ink-dim);
  min-height: 40px;
  padding: 0 14px;
}

.btn-quiet:hover {
  color: var(--ink);
  background: var(--line);
}

.btn-wide {
  width: 100%;
}

.btn-danger {
  color: #ff8f7a;
  background: color-mix(in srgb, #ff6b4a 14%, transparent);
  border: 1px solid color-mix(in srgb, #ff6b4a 30%, transparent);
}

.btn[disabled] {
  opacity: 0.45;
  pointer-events: none;
}

.btnrow {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
}

.btnrow > .grow {
  flex: 1 1 auto;
}

/* ── cards, rows, fields ───────────────────────────────────────────────── */

.card {
  padding: 17px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
}

.card-flat {
  /* One plan, one line. A button is inline-level by default, which would set the
     two plans side by side and leave them ragged. */
  display: block;
  width: 100%;
  text-align: left;
  padding: 15px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 62%, transparent);
  border: 1px solid var(--line);
}

/* The price sits at the far end of the plan row, the way a price does. */
.card-flat .section-head .h3 {
  margin-right: auto;
}

/* One show, as a row in the book. The gold rail and the dashed perforation are
   the stub's echo at list scale — see the note at the top of the file. */
.row {
  position: relative;
  display: grid;
  grid-template-columns: 46px 1fr auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px 12px 13px;
  text-align: left;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 68%, transparent);
  border: 1px solid var(--line);
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast) var(--ease);
}

.row::before {
  content: "";
  position: absolute;
  left: 41px;
  top: 9px;
  bottom: 9px;
  width: 1px;
  background-image: linear-gradient(180deg, var(--line-strong) 0 55%, transparent 55% 100%);
  background-size: 1px 8px;
  background-repeat: repeat-y;
}

.row::after {
  content: "";
  position: absolute;
  left: 0;
  top: 10px;
  bottom: 10px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(180deg, var(--accent-lit), var(--accent));
  opacity: 0.5;
}

.row:hover,
.row:active {
  background: var(--surface);
  border-color: var(--line-strong);
}

.row:active {
  transform: scale(0.994);
}

.row-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  gap: 2px;
}

.row-day {
  font-family: var(--font-num);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.row-mon {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.row-main {
  min-width: 0;
}

.row-artist {
  display: block;
  font-size: 15.5px;
  font-weight: 650;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row-where {
  display: block;
  font-size: 12.5px;
  color: var(--ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row-meta {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 3px;
  font-size: 11.5px;
  color: var(--ink-faint);
}

.row svg {
  width: 17px;
  height: 17px;
  color: var(--ink-faint);
}

.rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.yearbar {
  position: sticky;
  top: 62px;
  z-index: 5;
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin: 18px 0 9px;
  padding: 5px 0;
  background: linear-gradient(var(--ground-deep) 62%, transparent);
}

.yearbar-n {
  font-family: var(--font-num);
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--accent-lit);
}

/* ── forms ─────────────────────────────────────────────────────────────── */

.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-bottom: 13px;
}

.field > label,
.field-label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.input,
.textarea,
.select {
  width: 100%;
  min-height: 46px;
  padding: 11px 13px;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  transition: border-color var(--t-fast), background var(--t-fast);
}

.input:focus,
.textarea:focus,
.select:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface);
}

.input::placeholder,
.textarea::placeholder {
  color: color-mix(in srgb, var(--ink-faint) 78%, transparent);
}

.textarea {
  min-height: 132px;
  resize: vertical;
  line-height: 1.5;
  font-family: var(--font-num);
  font-size: 14px;
}

.select {
  appearance: none;
  padding-right: 40px;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-dim) 50%),
    linear-gradient(135deg, var(--ink-dim) 50%, transparent 50%);
  background-position: calc(100% - 19px) 21px, calc(100% - 14px) 21px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.select option {
  color: var(--ink);
  background: var(--ground);
}

.grid2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 11px;
}

/* A field the paste filled in is marked, not decorated: the mark says where the
   value came from, which is the only thing a person needs to decide whether to
   trust it. `guess` is the same chip in the warning colour. */
.from {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
}

.from-guess {
  color: var(--ink-dim);
  background: color-mix(in srgb, var(--ink-dim) 14%, transparent);
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12.5px;
  background: color-mix(in srgb, var(--surface) 80%, transparent);
  border: 1px solid var(--line);
}

.chip button {
  display: grid;
  place-items: center;
  color: var(--ink-faint);
}

.chip button svg {
  width: 13px;
  height: 13px;
}

.check {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 2px;
  cursor: pointer;
}

.check input {
  width: 19px;
  height: 19px;
  accent-color: var(--accent);
}

.stars {
  display: flex;
  gap: 3px;
}

.stars button {
  padding: 3px;
  color: color-mix(in srgb, var(--ink-faint) 55%, transparent);
  transition: color var(--t-fast), transform var(--t-fast) var(--ease);
}

.stars button svg {
  width: 27px;
  height: 27px;
}

.stars button[data-on="1"] {
  color: var(--accent-lit);
}

.stars button:active {
  transform: scale(1.16);
}

/* ── the paste box ─────────────────────────────────────────────────────── */

.paste {
  position: relative;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  border: 1px dashed var(--line-strong);
  padding: 14px;
}

.paste .textarea {
  min-height: 118px;
  background: none;
  border: 0;
  padding: 0;
}

.paste .textarea:focus {
  background: none;
}

.found {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  margin-bottom: 12px;
  border-radius: var(--r-sm);
  font-size: 13.5px;
  color: var(--ink-dim);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
}

.found svg {
  flex: none;
  width: 17px;
  height: 17px;
  color: var(--accent);
}

.found-warn {
  color: var(--ink-dim);
  background: color-mix(in srgb, var(--ink-dim) 9%, transparent);
  border-color: var(--line-strong);
}

.found-warn svg {
  color: var(--ink-dim);
}

/* ── stats ─────────────────────────────────────────────────────────────── */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 9px;
}

.tile {
  padding: 13px 13px 12px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 68%, transparent);
  border: 1px solid var(--line);
}

.tile-n {
  font-family: var(--font-num);
  font-size: 26px;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.03em;
}

.tile-gold .tile-n {
  color: var(--accent-lit);
}

.tile-k {
  margin-top: 3px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.bars {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  align-items: end;
  gap: 4px;
  height: 92px;
  padding-top: 6px;
}

.bar {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 5px;
  height: 100%;
}

.bar-fill {
  width: 100%;
  min-height: 3px;
  border-radius: 3px 3px 2px 2px;
  background: linear-gradient(180deg, var(--accent-lit), var(--accent));
  /* Set from JS through CSSOM: a `style` attribute written as markup would be
     blocked by this app's own `style-src 'self'`, and the height is the data. */
  height: 3px;
  transition: height var(--t-mid) var(--ease);
}

.bar-fill[data-zero="1"] {
  background: var(--line-strong);
}

.bar-lbl {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--ink-faint);
  text-transform: uppercase;
}

.leaders {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.leader {
  display: grid;
  grid-template-columns: 26px 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  text-align: left;
  width: 100%;
}

.leader:hover {
  background: var(--line);
}

.leader-n {
  font-family: var(--font-num);
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-faint);
  text-align: right;
}

.leader-name {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 14.5px;
}

.leader-c {
  font-family: var(--font-num);
  font-size: 14px;
  font-weight: 700;
  color: var(--accent-lit);
}

.kv {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 5px 14px;
  align-items: baseline;
}

.kv dt {
  font-size: 13.5px;
  color: var(--ink-dim);
  min-width: 0;
}

.kv dd {
  margin: 0;
  font-family: var(--font-num);
  font-size: 14.5px;
  font-weight: 650;
  text-align: right;
}

/* ── the setlist and the show ──────────────────────────────────────────── */

.stub-head {
  display: flex;
  flex-direction: column;
  padding: 20px;
}

.stub-head .where {
  font-size: 13.5px;
  margin-top: 2px;
}

.stub-head .date {
  margin-top: 12px;
  font-family: var(--font-num);
  font-size: 13px;
  letter-spacing: 0.02em;
}

.setlist {
  display: flex;
  flex-direction: column;
}

.set-sec {
  margin: 18px 0 8px;
  display: flex;
  align-items: center;
  gap: 10px;
}

.set-sec::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.song {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: 9px;
  padding: 6px 0;
  font-size: 14.5px;
}

.song-n {
  font-family: var(--font-num);
  font-size: 12px;
  color: var(--ink-faint);
  text-align: right;
  padding-top: 3px;
}

.song-t {
  min-width: 0;
}

.song-note {
  display: block;
  font-size: 11.5px;
  color: var(--ink-faint);
}

.notes {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 14.5px;
  line-height: 1.55;
}

/* ── the poster and the year ───────────────────────────────────────────── */

/*
  The year poster is a canvas rather than a stack of elements, and that is the whole
  design of it: the picture on the screen has to be the picture that gets sent, and a
  card assembled out of markup could only be sent by drawing it a second time - a
  second rendering, in a different engine, free to disagree with the one the reader
  approved. So it is drawn once and the same bitmap is shown and shared.

  It is themed like everything else, from the tokens, but read out of them in script:
  a canvas has no stylesheet and no cascade, so the painter reads what the active
  theme resolved to and draws with that. Here it only gets the shape of a print.
*/
.poster {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-md);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow);
}

/* A portrait picture at 4:5 - the widest shape a feed shows whole rather than
   cropping to a square in the middle of the year. */
@media (max-width: 400px) {
  .poster {
    border-radius: var(--r-sm);
  }
}

.yearpick {
  display: flex;
  gap: 7px;
  overflow-x: auto;
  padding: 2px 0 8px;
  scrollbar-width: none;
}

.yearpick::-webkit-scrollbar {
  display: none;
}

.yearpick button {
  flex: none;
  padding: 7px 15px;
  border-radius: 999px;
  font-family: var(--font-num);
  font-size: 14px;
  font-weight: 650;
  color: var(--ink-dim);
  background: color-mix(in srgb, var(--surface) 70%, transparent);
  border: 1px solid var(--line);
}

.yearpick button[aria-pressed="true"] {
  color: var(--on-accent);
  background: var(--accent);
  border-color: transparent;
}

/* ── sheets and dialogs ────────────────────────────────────────────────── */

/* The shell's back gesture asks the page whether a sheet is open and closes the
   first one it finds, so every overlay is a `.sheet` and hidden means hidden. */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  align-items: end;
  background: rgb(0 0 0 / 0.55);
  backdrop-filter: blur(3px);
  animation: fade var(--t-fast) var(--ease) both;
}

@keyframes fade {
  from {
    opacity: 0;
  }
}

.sheet-body {
  max-height: 88dvh;
  overflow-y: auto;
  padding: 20px var(--pad) calc(20px + 8px);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: var(--ground);
  border-top: 1px solid var(--line-strong);
  animation: up var(--t-mid) var(--ease) both;
}

@keyframes up {
  from {
    transform: translateY(18px);
    opacity: 0.4;
  }
}

.sheet-grip {
  width: 42px;
  height: 4px;
  margin: -8px auto 16px;
  border-radius: 999px;
  background: var(--line-strong);
}

.sheet-wide {
  max-width: 700px;
  margin: 0 auto;
}

@media (min-width: 720px) {
  .sheet {
    align-items: center;
  }

  .sheet-body {
    border-radius: var(--r-xl);
    border: 1px solid var(--line-strong);
    max-height: 86dvh;
    margin: 0 auto;
    width: min(560px, 94vw);
  }
}

.toast {
  position: fixed;
  left: 50%;
  bottom: 84px;
  z-index: 80;
  transform: translateX(-50%);
  max-width: min(520px, 90vw);
  padding: 11px 17px;
  border-radius: 999px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface-lit) 92%, transparent);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow);
  animation: toastin var(--t-mid) var(--ease) both;
}

.toast-bad {
  color: var(--on-accent);
  background: var(--accent);
  border-color: transparent;
}

@keyframes toastin {
  from {
    opacity: 0;
    transform: translate(-50%, 10px);
  }
}

/* ── empty state, boot, and the no-script notice ───────────────────────── */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 46px 22px;
  text-align: center;
}

.empty svg {
  width: 54px;
  height: 54px;
  color: color-mix(in srgb, var(--accent) 60%, transparent);
}

.boot {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  background: var(--ground-deep);
}

.boot svg {
  width: 74px;
  height: 74px;
  color: var(--accent);
  animation: pulse 1.5s ease-in-out infinite;
}

@keyframes pulse {
  50% {
    opacity: 0.45;
    transform: scale(0.96);
  }
}

.boot[data-done="1"] {
  display: none;
}

/* The one place the app is allowed to say the script did not run. It is inside a
   `<noscript>` in the markup, so it exists only where that is true. */
.nojs {
  max-width: 34em;
  margin: 18vh auto 0;
  padding: 22px;
  text-align: center;
  color: var(--ink-dim);
}

.nojs strong {
  display: block;
  margin-bottom: 8px;
  font-size: 18px;
  color: var(--ink);
}

/* ── motion, and its absence ───────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .stub-tilt {
    transform: none;
  }
}

/* ── wider screens ─────────────────────────────────────────────────────── */

@media (min-width: 760px) {
  :root {
    --pad: 26px;
  }

  .h1 {
    font-size: 32px;
  }
}

/* ── the setlist ───────────────────────────────────────────────────────── */

/* Numbered like a printed setlist, and numbered again inside each section: the
   encore starts at one because that is how the night was written down, and a list
   that ran 1 to 24 straight through would have thrown the break away. */
.setlist {
  list-style: none;
  margin: 0;
  padding: 0;
}

.set-head {
  margin: 15px 0 4px;
}

.set-head:first-child {
  margin-top: 2px;
}

.set-song {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 7px 2px;
  border-bottom: 1px solid var(--line);
}

.set-song:last-child {
  border-bottom: 0;
}

.set-n {
  flex: none;
  min-width: 17px;
  text-align: right;
  font-family: var(--font-num);
  font-size: 12px;
  font-weight: 650;
  color: var(--ink-faint);
}

.set-title {
  flex: 1;
  min-width: 0;
  font-size: 14.5px;
}

/* A night's own words keep the lines the reader typed into them. */
.notes {
  white-space: pre-wrap;
}

/* A notice that offers a way out of itself: the button goes to the far end. */
.found > .btn {
  margin-left: auto;
}

/* ── screen furniture ──────────────────────────────────────────────────── */

/* The first heading of a screen, and the line under it. One rule for all of them,
   because a heading hard against the top bar reads as a mistake and not as a
   choice. */
.screen > .h1:first-child {
  margin-top: 12px;
}

.screen > .h1 + .lede {
  margin-top: 7px;
}

/* The skip link is for a keyboard, so it stays off the screen until one is used. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 40;
  padding: 10px 16px;
  border-radius: 0 0 var(--r-sm) 0;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 650;
}

.skip:focus {
  left: 0;
}

/* ── settings rows ─────────────────────────────────────────────────────── */

/* A setting is a row: what it is on the left, the control that changes it on the
   right, and the sentence that explains it under its own name. The explanation is
   not decoration - "Sign the poster" says nothing until the note under it says
   what gets signed - so a row with a note is taller and the note stays with it. */
.opt {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 0;
  border-top: 1px solid var(--line);
}

.opt:first-child {
  padding-top: 2px;
  border-top: 0;
}

.opt-main {
  flex: 1;
  min-width: 0;
}

.opt-k {
  font-size: 14.5px;
  font-weight: 550;
}

.opt-note {
  margin-top: 3px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-dim);
}

/* A row whose control is a strip of pills rather than one switch stacks instead
   of sitting side by side: eight languages do not fit beside a label. */
.opt-stack {
  display: block;
}

.opt-stack .opt-k {
  margin-bottom: 9px;
}

.opt-stack .yearpick {
  margin: 0 -2px;
}

.opt > .btn,
.opt > .switch {
  flex: none;
  margin-left: auto;
}

/* A switch drawn from its own state attribute, so the markup carries the state
   and the style follows it rather than the other way round. The knob is a pseudo
   element: no extra node, and nothing for a script to move. */
.switch {
  position: relative;
  width: 46px;
  height: 28px;
  padding: 0;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: color-mix(in srgb, var(--surface) 80%, transparent);
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}

.switch::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: var(--ink-dim);
  transition: transform var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}

.switch[aria-checked="true"] {
  background: var(--accent);
  border-color: transparent;
}

.switch[aria-checked="true"]::after {
  transform: translateX(18px);
  background: var(--on-accent);
}

/* Where a card sends you somewhere else: two documents, side by side rather than
   stacked, because they are the same kind of thing. */
.links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin-top: 14px;
}

.links a {
  font-size: 14px;
  font-weight: 600;
}

/* ── the two documents ─────────────────────────────────────────────────── */

/* Privacy and terms are documents rather than screens: they are linked from the
   about card, they are readable with no script at all, and they are what a store
   reviewer opens. All of their layout lives in this block, so the pages
   themselves carry no style of their own - which is also what this app's own
   `style-src 'self'` requires of them. */
.legal {
  max-width: 62ch;
  margin: 0 auto;
  padding: calc(env(safe-area-inset-top) + 30px) var(--pad) 80px;
}

.legal h1 {
  margin-top: 6px;
}

.legal h2 {
  margin-top: 34px;
}

.legal p,
.legal li {
  margin-top: 10px;
  font-size: 15px;
  line-height: 1.62;
  color: var(--ink-dim);
}

.legal b,
.legal strong {
  color: var(--ink);
}

.legal ul {
  margin-top: 8px;
  padding-left: 0;
  list-style: none;
}

.legal li {
  position: relative;
  padding-left: 20px;
}

.legal li::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 0.68em;
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--accent);
  opacity: 0.8;
}

.legal .lede {
  font-size: 16.5px;
  color: var(--ink);
}

.legal code {
  padding: 1px 5px;
  border-radius: 5px;
  font-family: var(--font-num);
  font-size: 13px;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface) 80%, transparent);
}

.legal hr {
  margin-top: 40px;
  border: 0;
  border-top: 1px solid var(--line);
}

.legal .back {
  display: inline-block;
  margin-top: 26px;
  font-size: 14px;
  font-weight: 600;
}

.legal .updated {
  margin-top: 30px;
  font-size: 13px;
  color: var(--ink-faint);
}

@media print {
  .top,
  .tabbar,
  .btnrow,
  .nojs {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
  }
}
