/* SPDX-License-Identifier: AGPL-3.0-or-later
 *
 * Gigowl's whole stylesheet. No framework, no build step, no runtime dependency
 * on anyone else: the two typefaces are vendored under `assets/fonts/` and
 * served from this origin (see `src/web/assets.ts`).
 *
 * Two rules run through the colour system and are worth stating before the
 * values, because collapsing either one is the easy mistake:
 *
 *   1. `--accent` is a fill, never small text. The owl amber does not clear
 *      4.5:1 on paper. Accent-coloured *text* uses `--accent-text`, which is a
 *      dark amber in light mode and the amber itself in dark mode.
 *   2. `--ink-faint` is the contrast floor. It clears 4.5:1 on `--paper` and on
 *      `--paper-3`. Lightening it fails metadata under WCAG AA.
 */

/* ─── the two families ─────────────────────────────────────────────────────── */

/* Bricolage Grotesque is the app: navigation, buttons, labels, headings,
   metadata. Newsreader is what people wrote: write-ups, replies, song titles,
   the landing argument. The split carries meaning, so neither crosses over. */

@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/statisk/bricolage-grotesque-normal-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: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/statisk/bricolage-grotesque-normal-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: "Newsreader";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/statisk/newsreader-normal-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: "Newsreader";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/statisk/newsreader-normal-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: "Newsreader";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/statisk/newsreader-italic-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: "Newsreader";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/statisk/newsreader-italic-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;
}

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

:root {
  color-scheme: light dark;

  --paper: #fbf7f0;
  --paper-2: #ffffff;
  --paper-3: #f2ece2;
  --paper-4: #e9e1d4;
  --ink: #17131b;
  --ink-soft: #5c5464;
  --ink-faint: #6e6578;
  --rule: #e3dacd;
  --rule-strong: #cfc3b2;
  --accent: #f2a93b;
  --accent-text: #9a5b00;
  --accent-soft: #fbebd0;
  --on-accent: #1a1204;
  --live: #c4331d;
  --live-soft: #fbe4df;

  --ui:
    "Bricolage Grotesque", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --prose: "Newsreader", ui-serif, Georgia, "Times New Roman", serif;

  /* Screen gutter: 16px on a phone, 24px from the desktop breakpoint up. */
  --gutter: 16px;
  /* Running text is capped so a line stays readable in the middle column. */
  --measure: 62ch;
}

/*
 * Dark mode arrives two ways, and the order matters.
 *
 * `prefers-color-scheme` is the default, because the design follows the system.
 * The `data-theme` attribute is the override a person set in Settings, and it
 * has to win in both directions: `:not([data-theme="light"])` on the media query
 * is what lets someone keep the light theme on a dark system.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --paper: #100e13;
    --paper-2: #191520;
    --paper-3: #221d2b;
    --paper-4: #2c2637;
    --ink: #f6f1e8;
    --ink-soft: #a79eaf;
    --ink-faint: #7a7185;
    --rule: #2b2534;
    --rule-strong: #3d3549;
    --accent: #f2a93b;
    --accent-text: #f2a93b;
    --accent-soft: #2a2015;
    --on-accent: #1a1204;
    --live: #ff6a54;
    --live-soft: #2e1a16;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #100e13;
  --paper-2: #191520;
  --paper-3: #221d2b;
  --paper-4: #2c2637;
  --ink: #f6f1e8;
  --ink-soft: #a79eaf;
  --ink-faint: #7a7185;
  --rule: #2b2534;
  --rule-strong: #3d3549;
  --accent: #f2a93b;
  --accent-text: #f2a93b;
  --accent-soft: #2a2015;
  --on-accent: #1a1204;
  --live: #ff6a54;
  --live-soft: #2e1a16;
}

/* Two keyframes, and no more. Motion is restrained on purpose. */
@keyframes gopulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

@keyframes goblink {
  0%,
  94%,
  98%,
  100% {
    transform: scaleY(1);
  }
  96% {
    transform: scaleY(0.1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .live-dot,
  .owl-pupil {
    animation: none !important;
  }
}

/* ─── base ─────────────────────────────────────────────────────────────────── */

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 15px;
  line-height: 1.4;
  min-height: 100vh;
}

p {
  text-wrap: pretty;
}

a {
  color: var(--accent-text);
  text-underline-offset: 0.15em;
}

a:hover {
  text-decoration-thickness: 2px;
}

/* Focus has to be visible everywhere. Ink rather than accent, because the amber
   ring disappears against an amber fill. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: 6px;
}

img {
  max-width: 100%;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--paper-2);
  color: var(--ink);
  padding: 10px 16px;
  border: 1px solid var(--rule-strong);
  border-radius: 0 0 12px 0;
  font-weight: 600;
}

.skip-link:focus {
  left: 0;
  z-index: 40;
}

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

/* ─── type roles ───────────────────────────────────────────────────────────── */

.hero-title {
  margin: 0 0 14px;
  font-size: 38px;
  line-height: 1.02;
  font-weight: 800;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

.screen-title {
  margin: 0 0 8px;
  font-size: 30px;
  line-height: 1.05;
  font-weight: 800;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

.section-heading {
  margin: 26px 0 12px;
  font-size: 19px;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.card-title {
  margin: 0;
  font-size: 17px;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.eyebrow {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.meta {
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-faint);
}

.meta-strong {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-faint);
}

/* Newsreader, and only for what a person wrote. */
.prose-text {
  font-family: var(--prose);
  font-size: 17px;
  line-height: 1.55;
  text-wrap: pretty;
}

.prose-text-large {
  font-family: var(--prose);
  font-size: 18px;
  line-height: 1.6;
  text-wrap: pretty;
}

.prose-text p:first-child,
.prose-text-large p:first-child {
  margin-top: 0;
}

.prose-text p:last-child,
.prose-text-large p:last-child {
  margin-bottom: 0;
}

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

.accent-text {
  color: var(--accent-text);
}

.stat-figure {
  font-size: 34px;
  line-height: 1;
  font-weight: 800;
  letter-spacing: -0.04em;
}

/* ─── the shell ────────────────────────────────────────────────────────────── */

/*
 * One column and a bottom bar below 900px; three columns and no bottom bar
 * above it. A single breakpoint, as the design asks: two would mean a tablet
 * layout nobody specified.
 */
.shell {
  display: block;
  min-height: 100vh;
}

.column-main {
  min-width: 0;
  /* Room for the bottom bar plus the home-indicator inset. */
  padding-bottom: calc(64px + 34px);
}

.screen {
  padding: 18px var(--gutter) 30px;
}

.screen-flush {
  padding: 0 0 30px;
}

.rail-left,
.rail-right {
  display: none;
}

/* The chrome-free screens: landing and sign-up carry no header and no bar. */
.shell-bare .column-main {
  padding-bottom: 0;
}

/* App header: wordmark, search, avatar. */
.app-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}

.app-header-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px var(--gutter);
  min-height: 56px;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--ink);
  font-weight: 700;
  letter-spacing: -0.02em;
  font-size: 19px;
}

.wordmark:hover {
  color: var(--accent-text);
}

.header-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 2px;
}

.icon-button {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: none;
  background: none;
  padding: 0;
  color: var(--ink-soft);
  cursor: pointer;
  border-radius: 12px;
  text-decoration: none;
}

.icon-button:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.avatar-chip {
  display: block;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--paper-4);
  border: 1px solid var(--rule-strong);
}

/* Back row on a secondary screen. */
.back-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 var(--gutter) 8px;
  min-height: 44px;
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 44px;
  padding-right: 4px;
  color: var(--accent-text);
  font-weight: 700;
  text-decoration: none;
}

.back-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* ─── bottom bar ───────────────────────────────────────────────────────────── */

.bottom-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  align-items: center;
  gap: 2px;
  padding: 6px 8px calc(34px + 6px);
  background: var(--paper);
  border-top: 1px solid var(--rule);
}

.bottom-item {
  min-height: 48px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  text-decoration: none;
  color: var(--ink-soft);
  border-radius: 12px;
  position: relative;
}

.bottom-item:hover {
  color: var(--ink);
}

.bottom-item[aria-current="page"] {
  color: var(--accent-text);
}

.bottom-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.bottom-item[aria-current="page"] .bottom-label {
  font-weight: 700;
}

/* The compose slot is lifted out of the row. */
.bottom-compose {
  width: 48px;
  height: 48px;
  margin: 0 auto;
  display: grid;
  place-items: center;
  border-radius: 14px;
  background: var(--accent);
  color: var(--on-accent);
  text-decoration: none;
}

.bottom-compose:hover {
  filter: brightness(1.07);
}

/* The alert count. Live red, because it is a call to look. */
.count-badge {
  position: absolute;
  top: 2px;
  left: 50%;
  margin-left: 4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--live);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}

/* ─── buttons and controls ─────────────────────────────────────────────────── */

/*
 * Every interactive element clears 44px on its short axis. Primary buttons are
 * 50 to 54px, list rows 52px, bottom-bar items 48px. On a phone-first design
 * that is not negotiable.
 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 50px;
  padding: 0 18px;
  border-radius: 12px;
  border: 1px solid transparent;
  font: inherit;
  font-family: var(--ui);
  font-size: 16px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  background: var(--paper-2);
  color: var(--ink);
}

.btn-primary {
  background: var(--accent);
  color: var(--on-accent);
  border-color: transparent;
}

.btn-primary:hover {
  filter: brightness(1.07);
}

.btn-outline {
  background: var(--paper-2);
  color: var(--ink);
  border-color: var(--rule-strong);
}

.btn-outline:hover {
  border-color: var(--accent);
  color: var(--accent-text);
}

.btn-ink {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

.btn-ink:hover {
  filter: brightness(1.15);
}

.btn-danger {
  background: none;
  color: var(--live);
  border-color: var(--live);
}

.btn-block {
  width: 100%;
}

.btn-tall {
  min-height: 54px;
  font-size: 17px;
  border-radius: 14px;
}

.btn-small {
  min-height: 44px;
  padding: 0 14px;
  font-size: 14px;
  border-radius: 11px;
}

/* A button that has to sit in a row of links and read as one. */
.linklike {
  background: none;
  border: none;
  padding: 0;
  min-height: 44px;
  font: inherit;
  font-family: var(--ui);
  font-weight: 600;
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  cursor: pointer;
}

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 14px 0;
}

.button-row > .btn {
  flex: 1 1 auto;
}

input,
select,
textarea {
  font: inherit;
  font-family: var(--ui);
  font-size: 16px;
  width: 100%;
  min-height: 50px;
  padding: 12px 14px;
  border: 1px solid var(--rule-strong);
  border-radius: 13px;
  background: var(--paper-2);
  color: var(--ink);
}

textarea {
  line-height: 1.5;
  resize: vertical;
}

/* A write-up is prose while it is being typed, too. */
textarea.prose-input {
  font-family: var(--prose);
  font-size: 17px;
  line-height: 1.55;
}

input[type="checkbox"],
input[type="radio"] {
  width: auto;
  min-height: 0;
}

input[type="file"] {
  padding: 12px;
}

::placeholder {
  color: var(--ink-faint);
  opacity: 1;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 16px;
}

.field > label,
.field-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
}

.hint {
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-faint);
}

.stack {
  max-width: var(--measure);
}

form.inline {
  display: inline;
  margin: 0;
}

/* ─── surfaces ─────────────────────────────────────────────────────────────── */

.panel {
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: 16px;
  padding: 16px;
}

.panel-quiet {
  background: var(--paper-3);
  border: none;
  border-radius: 16px;
  padding: 16px;
}

.panel-accent {
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: 16px;
  padding: 16px;
}

.panel-live {
  background: var(--live-soft);
  border: none;
  border-left: 3px solid var(--live);
  border-radius: 0 12px 12px 0;
  padding: 12px 14px;
}

.notice {
  padding: 12px 14px;
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  border-radius: 0 12px 12px 0;
  font-size: 14px;
  margin: 0 0 16px;
}

.notice-error {
  border-left-color: var(--live);
  background: var(--live-soft);
}

.hairline {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 22px 0;
}

/*
 * Pills. The old rule filled this with `--accent` while keeping `color:
 * var(--ink)`, which put dark text on mid-tone amber at under 3:1 and hit both
 * the notification count and the encore marker. Accent-soft with accent-text
 * clears AA in both schemes.
 */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-text);
  font-size: 12px;
  font-weight: 700;
  line-height: 1.5;
  vertical-align: middle;
}

.badge-quiet {
  background: var(--paper-3);
  color: var(--ink-faint);
}

.badge-ink {
  background: var(--ink);
  color: var(--paper);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.badge-live {
  background: var(--live-soft);
  color: var(--live);
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--rule-strong);
  background: var(--paper-2);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

.chip:hover {
  border-color: var(--accent);
  color: var(--accent-text);
}

.chip[aria-pressed="true"],
.chip[aria-current="page"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* Song pills are the setlist, so they are set in Newsreader. */
.chip-song {
  font-family: var(--prose);
  font-size: 16px;
  font-weight: 400;
}

/*
 * A link that is a row's title, not a link in a sentence.
 *
 * Underlined amber is right in running text and wrong for the name of a thing
 * in a list: at that size and density it reads as a page of hyperlinks rather
 * than as a list of gigs. The underline comes back on hover, where it belongs.
 */
.gig-row-artist a,
.rail-card-artist a,
.card-title a,
.attendee-row .name,
.settings-row a,
.year-figure a {
  color: inherit;
  text-decoration: none;
}

.gig-row-artist a:hover,
.rail-card-artist a:hover,
.card-title a:hover,
.attendee-row .name:hover {
  color: var(--accent-text);
  text-decoration: underline;
}

/* ─── avatars ──────────────────────────────────────────────────────────────── */

.avatar {
  display: block;
  border-radius: 50%;
  object-fit: cover;
  background: var(--paper-4);
  flex: 0 0 auto;
}

.avatar-27 {
  width: 27px;
  height: 27px;
}

.avatar-30 {
  width: 30px;
  height: 30px;
}

.avatar-34 {
  width: 34px;
  height: 34px;
}

.avatar-36 {
  width: 36px;
  height: 36px;
}

.avatar-52 {
  width: 52px;
  height: 52px;
}

.avatar-74 {
  width: 74px;
  height: 74px;
}

/* Overlapped social proof: three faces, then the sentence. */
.avatar-stack {
  display: flex;
  align-items: center;
}

.avatar-stack .avatar {
  border: 2px solid var(--paper-2);
}

.avatar-stack .avatar + .avatar {
  margin-left: -10px;
}

.social-proof {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 12px 0 0;
  font-size: 14px;
  color: var(--ink-soft);
}

.social-proof strong {
  color: var(--ink);
}

/* ─── the owl ──────────────────────────────────────────────────────────────── */

.owl {
  flex: 0 0 auto;
  color: var(--accent);
}

.owl-blink .owl-pupil {
  animation: goblink 5s ease-in-out infinite;
  transform-origin: center;
}

/* The owl appears in exactly six places. This is the card for two of them. */
.owl-card {
  text-align: center;
  padding: 26px 16px;
}

.owl-card .owl {
  margin: 0 auto 10px;
  display: block;
}

.owl-card h2,
.owl-card p {
  margin: 0 0 6px;
}

/* ─── images and empty posters ─────────────────────────────────────────────── */

.photo-well {
  display: block;
  width: 100%;
  background: var(--paper-3);
  object-fit: cover;
}

/*
 * No photo yet is an invitation, not a placeholder graphic and never the owl.
 */
.poster-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 16px;
  background: var(--paper-3);
  color: var(--ink-faint);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
}

a.poster-empty:hover {
  color: var(--accent-text);
}

/* ─── feed: tabs ───────────────────────────────────────────────────────────── */

.tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
}

.tabs a {
  min-height: 48px;
  display: grid;
  place-items: center;
  text-decoration: none;
  color: var(--ink-soft);
  font-size: 17px;
  font-weight: 600;
  border-bottom: 3px solid transparent;
  margin-bottom: -1px;
}

.tabs a:hover {
  color: var(--ink);
}

.tabs a[aria-current="page"] {
  color: var(--ink);
  font-weight: 700;
  border-bottom-color: var(--accent);
}

/* Tabs on a page that is not the feed, laid out in a row rather than a grid. */
.tabs-inline {
  display: flex;
  gap: 20px;
  padding: 0 var(--gutter);
}

.tabs-inline a {
  min-height: 48px;
  padding: 0 2px;
}

.feed-lead {
  margin: 0;
  padding: 14px var(--gutter);
  font-size: 15px;
  color: var(--ink-soft);
}

/* ─── feed: doors-open banner ──────────────────────────────────────────────── */

.doors-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px var(--gutter);
  background: var(--live-soft);
  color: var(--ink);
  text-decoration: none;
}

.doors-banner:hover .doors-open {
  text-decoration: underline;
}

.live-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--live);
  flex: 0 0 auto;
  animation: gopulse 2s ease-in-out infinite;
}

.doors-body {
  min-width: 0;
  flex: 1 1 auto;
}

.doors-body strong {
  display: block;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.doors-body span {
  display: block;
  font-size: 14px;
  color: var(--ink-soft);
}

.doors-open {
  flex: 0 0 auto;
  color: var(--live);
  font-weight: 700;
  font-size: 15px;
}

/* ─── feed: the morning-after card ─────────────────────────────────────────── */

.nudge-card {
  margin: 14px var(--gutter);
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: 16px;
  padding: 16px;
}

.nudge-card h3 {
  margin: 0 0 6px;
  font-size: 22px;
  line-height: 1.15;
  font-weight: 800;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.nudge-where {
  margin: 0 0 14px;
  font-size: 15px;
  color: var(--ink-soft);
}

.nudge-actions {
  display: flex;
  gap: 10px;
}

.nudge-actions .btn {
  flex: 1 1 auto;
}

.nudge-actions .btn-outline {
  flex: 0 0 auto;
  background: transparent;
}

/* ─── feed: the hero gig card ──────────────────────────────────────────────── */

.hero-gig {
  margin: 14px var(--gutter);
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: 18px;
  overflow: hidden;
}

.hero-gig-photo {
  position: relative;
  display: block;
}

.hero-gig-photo .photo-well,
.hero-gig-photo .poster-empty {
  height: 208px;
}

/* The "On tonight" pill, laid over the photo. */
.photo-pill {
  position: absolute;
  top: 12px;
  left: 12px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(16, 14, 19, 0.72);
  backdrop-filter: blur(6px);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}

.hero-gig-body {
  padding: 16px;
}

.hero-gig-artist {
  margin: 0 0 2px;
  font-size: 25px;
  line-height: 1.06;
  font-weight: 800;
  letter-spacing: -0.03em;
}

.hero-gig-artist a {
  color: inherit;
  text-decoration: none;
}

.hero-gig-artist a:hover {
  color: var(--accent-text);
}

.hero-gig-support {
  margin: 0 0 2px;
  font-size: 15px;
  color: var(--ink-soft);
}

.hero-gig-venue {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
}

.hero-gig-venue a {
  color: inherit;
  text-decoration: none;
}

.hero-gig-venue a:hover {
  color: var(--accent-text);
}

/* ─── the RSVP triplet ─────────────────────────────────────────────────────── */

/*
 * Three buttons on one row. The chosen one is a solid ink fill, not a tint: it
 * has to read at a glance in a dark room. The old rule set `color: var(--bg)`,
 * an undefined variable, so the label fell back to inherited ink on amber and
 * the state was carried by the outline alone.
 */
.rsvp {
  display: flex;
  gap: 8px;
  margin: 14px 0 0;
}

.rsvp button,
.rsvp a {
  flex: 1 1 0;
  min-height: 48px;
  padding: 0 8px;
  border-radius: 12px;
  border: 1px solid var(--rule-strong);
  background: var(--paper-2);
  color: var(--ink);
  font: inherit;
  font-family: var(--ui);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  display: grid;
  place-items: center;
  text-decoration: none;
}

.rsvp button:hover,
.rsvp a:hover {
  border-color: var(--accent);
  color: var(--accent-text);
}

.rsvp button[aria-pressed="true"],
.rsvp .chosen {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
  font-weight: 700;
}

.rsvp button[aria-pressed="true"]:hover {
  color: var(--paper);
  border-color: var(--ink);
}

.rsvp-large button,
.rsvp-large a {
  min-height: 48px;
  font-size: 16px;
}

/*
 * The RSVP triplet and the owl on one row: the owl is a disclosure that says
 * where the entry came from, and it belongs beside the choice it annotates
 * rather than stranded under it.
 */
.rsvp-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px;
}

.rsvp-row .rsvp {
  flex: 1 1 200px;
  min-width: 0;
}

.rsvp-row .source-disclosure {
  flex: 0 0 44px;
  margin: 14px 0 0;
}

/* Opened, it needs the whole row rather than the 44px the button sat in. */
.rsvp-row .source-disclosure[open] {
  flex: 1 1 100%;
}

/* The owl button beside the triplet discloses where a gig came from. */
.source-disclosure {
  margin: 12px 0 0;
}

.source-disclosure > summary {
  list-style: none;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 12px;
  border: 1px solid var(--rule-strong);
  cursor: pointer;
}

.source-disclosure > summary::-webkit-details-marker {
  display: none;
}

.source-disclosure > summary:hover {
  border-color: var(--accent);
}

.source-disclosure .meta {
  margin: 8px 0 0;
}

/* ─── feed: the rail ───────────────────────────────────────────────────────── */

.rail {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding: 0 var(--gutter) 4px;
  -webkit-overflow-scrolling: touch;
}

.rail-card {
  flex: 0 0 176px;
  scroll-snap-align: start;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: 14px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}

.rail-card:hover {
  border-color: var(--accent);
}

.rail-card .photo-well,
.rail-card .poster-empty {
  height: 108px;
}

.rail-card-body {
  padding: 10px 12px 12px;
}

.rail-card-artist {
  margin: 0 0 2px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

/* ─── feed: write-ups and remote posts ─────────────────────────────────────── */

/*
 * Same feed, two weights. A remote post is quieter by surface and by the
 * absence of a border, never by opacity: real content is not faded.
 */
.feed {
  list-style: none;
  margin: 0;
  padding: 0;
}

.feed > li {
  margin: 14px var(--gutter);
}

.post {
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: 16px;
  padding: 16px;
}

.post-remote {
  background: var(--paper-3);
  border: none;
  border-radius: 14px;
  padding: 14px;
}

.post-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
}

.post-name {
  font-size: 15px;
  font-weight: 700;
  color: inherit;
  text-decoration: none;
  overflow-wrap: anywhere;
}

.post-remote .post-name {
  font-size: 14px;
  font-weight: 600;
}

.post-byline {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--ink-faint);
  flex-wrap: wrap;
}

/*
 * The gig a write-up is about: the act on top, the room and the night under it.
 * The act carries the accent because it is what the eye is hunting for down a
 * column of cards; the line under it is ordinary text, since --accent-text at
 * 13px on this paper is the wrong side of the contrast floor.
 */
.post-gig-ref {
  display: block;
  margin: 0 0 8px;
  text-decoration: none;
  color: inherit;
}

.post-gig-artist {
  display: block;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--accent-text);
}

.post-gig-where {
  display: block;
  margin-top: 1px;
  font-size: 13px;
  color: var(--ink-soft);
}

.post-gig-ref:hover .post-gig-artist {
  text-decoration: underline;
}

.post-remote .prose-text {
  font-size: 16px;
}

.post-photo {
  display: block;
  width: 100%;
  height: auto;
  margin: 12px 0 0;
  border-radius: 10px;
  background: var(--paper-3);
}

/*
 * A picture the card borrowed: somebody else's photo from the same night, or
 * the catalogue's portrait of the headliner. Cropped to a fixed height because
 * a remote portrait arrives without dimensions, and a card that grows a hundred
 * pixels when an image lands pushes the next one out from under the thumb.
 */
.post-figure {
  margin: 0;
}

.post-photo-borrowed {
  height: 220px;
  object-fit: cover;
}

.post-figure-credit {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--ink-faint);
}

/* The credit's own links. `--ink-faint` is the contrast floor (spec §8), so the
   link stays on it rather than going lighter, and carries an underline to be
   distinguishable without relying on the small colour difference alone. */
.post-figure-credit a {
  color: var(--ink-faint);
  text-decoration: underline;
}

.post-figure-credit a:hover {
  color: var(--accent-text);
}

/* A photo on its own page. Unlike every other photo surface this one is not
   cropped to a fixed height: the point of the page is to see the picture, so it
   keeps its own proportions and is bounded by the viewport instead. */
.photo-page-figure {
  margin: 0 0 4px;
}

.photo-page-image {
  height: auto;
  max-height: 70svh;
  object-fit: contain;
  background: var(--paper-3);
}

.post-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 12px 0 0;
  font-size: 13px;
  color: var(--ink-faint);
}

.post-counts a {
  color: inherit;
  text-decoration: none;
}

.post-counts a:hover {
  color: var(--accent-text);
  text-decoration: underline;
}

/* A source badge shows the glyph at rest and the domain when opened. */
.source-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.service-glyph {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
  color: var(--ink-faint);
}

/* Remote HTML, sanitized once at ingest. Long links must not push a phone
   sideways. */
.remote-content {
  overflow-wrap: anywhere;
}

.remote-content p {
  margin: 0.4em 0;
}

.cw > summary {
  cursor: pointer;
  font-weight: 600;
  min-height: 44px;
  display: flex;
  align-items: center;
  padding: 0 12px;
  background: var(--paper-3);
  border-radius: 12px;
}

/* ─── landing ──────────────────────────────────────────────────────────────── */

.alpha-banner {
  padding: 14px var(--gutter);
  background: var(--accent-soft);
  border-bottom: 1px solid var(--rule);
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink);
}

.landing {
  max-width: 640px;
  margin: 0 auto;
}

.landing-section {
  padding: 22px var(--gutter);
  border-bottom: 1px solid var(--rule);
}

.landing-section:last-child {
  border-bottom: none;
}

.landing-hero .wordmark {
  margin: 8px 0 18px;
  font-size: 20px;
}

.landing-argument {
  font-family: var(--prose);
  font-size: 17px;
  line-height: 1.55;
  color: var(--ink-soft);
  text-wrap: pretty;
  margin: 0 0 14px;
}

/*
 * The three phone screens, high up on the landing page.
 *
 * A strip, not a stack. Three phones side by side read as one product seen from
 * three angles; three full-width pictures read as three separate claims and push
 * everything else off the page.
 *
 * The widths are capped by the strip rather than by the image, because the
 * pictures are 540px of real pixels and would otherwise draw at 540px on a
 * desktop, which is a phone screenshot blown up past life size and looks like a
 * mistake. Inside `.landing` (max 640px) three columns come out near 190px each,
 * which is about a third of life size and still legible.
 */
.screen-strip {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* Two rows, pictures and captions, shared by all three items through the
     subgrid below. The screens hold different amounts of page, so their heights
     differ; without a shared row the captions hang at three different levels. */
  grid-template-rows: auto auto;
  gap: 12px;
  align-items: start;
}

.screen-shot {
  display: grid;
  grid-row: span 2;
  grid-template-rows: subgrid;
  gap: 8px;
  margin: 0;
  min-width: 0;
}

/*
 * The frame. A border and a radius, so a light screenshot on light paper still
 * has an edge, and `--paper-2` under it so the box is visible while the bytes
 * are still arriving.
 */
/*
 * Start, not the grid's default stretch: the row is as tall as the tallest of
 * the three, and stretching the other two into it would squash them. The
 * `picture` is there when the reader has stated no appearance preference and the
 * browser is picking; the bare `img` when the cookie already settled it.
 */
.screen-shot > picture,
.screen-shot > .screen-shot-image {
  align-self: start;
  min-width: 0;
}

.screen-shot-image {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--paper-2);
}

.screen-caption {
  margin: 0;
  font-size: 12px;
  line-height: 1.35;
  color: var(--ink-faint);
  text-wrap: pretty;
}

.screen-note {
  margin: 0 0 16px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-faint);
}

@media (max-width: 520px) {
  /*
   * Below this, a third of the width is 150px and nothing in the picture can be
   * read, which defeats the point of showing it. So the grid becomes the same
   * swipeable rail the feed already uses: one screen and a hint of the next.
   */
  .screen-strip {
    display: flex;
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* Bleed to the screen edges, so the rail reads as continuing off the page
       rather than as three cards in a box. */
    margin-left: calc(var(--gutter) * -1);
    margin-right: calc(var(--gutter) * -1);
    padding: 0 var(--gutter) 4px;
    /* Without this the snap pulls the first screen to the container's edge, and
       the leading padding is eaten: the rail starts flush with the phone edge
       while the heading above it starts at the gutter. */
    scroll-padding-left: var(--gutter);
    -webkit-overflow-scrolling: touch;
  }

  .screen-shot {
    flex: 0 0 60%;
    max-width: 230px;
    scroll-snap-align: start;
  }
}

/* Two pull quotes: the first amber, the second a plain rule. */
.pull-quote {
  margin: 0 0 18px;
  padding-left: 14px;
  border-left: 2px solid var(--rule-strong);
}

.pull-quote-lead {
  border-left-color: var(--accent);
}

.pull-quote blockquote {
  margin: 0 0 6px;
  font-family: var(--prose);
  font-size: 17px;
  line-height: 1.5;
  text-wrap: pretty;
}

.pull-quote cite {
  font-family: var(--ui);
  font-style: normal;
  font-size: 13px;
  color: var(--ink-faint);
}

/* What it isn't: a "No" or "Not" in live red, then the line. */
.not-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.not-list li {
  display: flex;
  gap: 10px;
  padding: 12px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 15px;
}

.not-list li:last-child {
  border-bottom: none;
}

.not-list b {
  color: var(--live);
  font-weight: 700;
  flex: 0 0 auto;
}

/* Rows on the landing page and in Discover: 46 to 56px thumbnail, then text. */
.gig-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 8px 0;
  text-decoration: none;
  color: inherit;
}

.gig-row:hover .gig-row-artist {
  color: var(--accent-text);
}

.gig-row-thumb {
  width: 56px;
  height: 56px;
  border-radius: 10px;
  object-fit: cover;
  background: var(--paper-4);
  flex: 0 0 auto;
}

.gig-row-thumb-small {
  width: 46px;
  height: 46px;
}

/* A dateless row gets a text tile: weekday over number. */
.date-tile {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  background: var(--paper-3);
  color: var(--ink-faint);
  display: grid;
  place-items: center;
  align-content: center;
  flex: 0 0 auto;
  line-height: 1.1;
  text-align: center;
}

.date-tile b {
  display: block;
  font-size: 16px;
  color: var(--ink);
}

.date-tile span {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.gig-row-body {
  min-width: 0;
  flex: 1 1 auto;
}

/*
 * Rows are built from spans as often as from paragraphs, because half of them
 * sit inside an anchor and an anchor may not contain a <p>. Laying the children
 * out as blocks here is what keeps the two spellings looking identical.
 */
.gig-row-body > *,
.rail-card-body > * {
  display: block;
  margin: 0;
}

.gig-row-artist {
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

.gig-row-where {
  margin: 0;
  font-size: 14px;
  color: var(--ink-soft);
}

.gig-row-social {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--accent-text);
}

.gig-row-aside {
  margin-left: auto;
  flex: 0 0 auto;
}

.row-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.row-list > li {
  border-bottom: 1px solid var(--rule);
}

.row-list > li:last-child {
  border-bottom: none;
}

/* ─── gig page ─────────────────────────────────────────────────────────────── */

.gig-photo {
  width: 100%;
  height: 224px;
  object-fit: cover;
  background: var(--paper-3);
  display: block;
}

.gig-photo.poster-empty {
  height: 224px;
}

.gig-header {
  padding: 16px var(--gutter) 0;
}

.gig-header .screen-title {
  margin-bottom: 4px;
}

.gig-billing {
  margin: 0;
  font-size: 16px;
  color: var(--ink-soft);
}

.gig-billing a {
  font-weight: 600;
}

.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 18px;
  margin: 18px 0;
  font-size: 15px;
}

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

.facts dd {
  margin: 0;
  font-weight: 600;
}

/* Numbered setlist in Newsreader, with the qualifier in small Bricolage. */
.setlist {
  margin: 0;
  padding-left: 1.4em;
  font-family: var(--prose);
  font-size: 17px;
  line-height: 1.7;
}

.setlist li {
  padding: 1px 0;
}

.setlist .qualifier {
  font-family: var(--ui);
  font-size: 12px;
  color: var(--ink-faint);
}

.provenance {
  margin: 0 0 10px;
  font-size: 14px;
  color: var(--ink-soft);
}

.attendee-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 6px 0;
  text-decoration: none;
  color: inherit;
}

.attendee-row .name {
  font-size: 15px;
  font-weight: 700;
}

.photo-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  /* Each cell is a figure now, and credits differ in length. Without this the
     tallest caption in a row stretches its neighbour's picture. */
  align-items: start;
}

/* The image rule below is a descendant selector, so a figure between the grid
   and the img changes nothing about the picture's geometry. All the figure adds
   is the caption underneath it. */
.photo-grid .post-figure {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.photo-grid .post-figure-credit {
  /* A photographer's name or a Commons host has no spaces to break at, and one
     long word in a 2-column grid on a phone would otherwise push the page
     sideways. */
  overflow-wrap: anywhere;
}

.photo-grid img,
.photo-grid .poster-empty {
  width: 100%;
  height: 132px;
  object-fit: cover;
  border-radius: 10px;
  background: var(--paper-3);
}

.consent-line {
  margin: 10px 0 0;
  font-size: 13px;
  color: var(--ink-faint);
}

/* One pending portrait, and everything needed to decide about it.

   Not `.photo-grid`: the question this page asks is whether the face belongs to
   the artist, and the 132px cropped cell that grid uses is exactly the wrong
   size to answer it. Here the picture keeps its own proportions and gets as much
   width as the column allows. */
.portrait-review {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: 16px;
  padding: 16px 0;
  border-top: 1px solid var(--rule);
  align-items: start;
}

.portrait-review-image img {
  width: 100%;
  height: auto;
  border-radius: 10px;
  background: var(--paper-3);
}

.portrait-review-body h2 {
  margin: 0;
  font-size: 19px;
}

@media (max-width: 640px) {
  /* Below this the two columns would each be too narrow to read, and a portrait
     squeezed to 40% of a phone screen defeats the point of the page. */
  .portrait-review {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A reply from elsewhere. */
.reply {
  background: var(--paper-3);
  border-radius: 14px;
  padding: 14px;
  margin: 0 0 10px;
}

/* ─── composer ─────────────────────────────────────────────────────────────── */

.composer-gig {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border-radius: 14px;
  background: var(--paper-3);
  margin: 0 0 22px;
  text-decoration: none;
  color: inherit;
}

.composer-gig-thumb {
  width: 48px;
  height: 48px;
  border-radius: 10px;
  object-fit: cover;
  background: var(--paper-4);
  flex: 0 0 auto;
}

.composer-section {
  margin: 0 0 22px;
}

.attached-line {
  margin: 10px 0 0;
  font-size: 14px;
  font-weight: 700;
  color: var(--accent-text);
}

/* Slots: one filled, one dashed invitation to add another. */
.photo-slots {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.photo-slot {
  border: 1px dashed var(--rule-strong);
  border-radius: 12px;
  background: var(--paper-3);
  padding: 12px;
  min-height: 132px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  text-align: center;
  font-size: 13px;
  color: var(--ink-faint);
}

.photo-slot-filled {
  border-style: solid;
  text-align: left;
  background: var(--paper-2);
}

.photo-slot img {
  width: 100%;
  height: auto;
  border-radius: 8px;
}

/* A file input is as wide as the browser feels like; it must not burst the slot. */
.photo-slot input {
  max-width: 100%;
  min-height: 44px;
  padding: 8px 10px;
  font-size: 14px;
}

/*
 * A radio dressed as a button.
 *
 * The composer's song pills and its visibility grid are both single-choice, and
 * a radio group is what that is. Styling the label rather than reaching for a
 * click handler keeps the composer working with no script at all, which is the
 * whole point of a server-rendered form: the draft survives, the keyboard works,
 * and the browser remembers the choice on a back navigation.
 */
.option {
  position: relative;
  display: block;
}

.option > input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-height: 0;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
}

.option > span {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 14px;
  border-radius: 12px;
  border: 1px solid var(--rule-strong);
  background: var(--paper-2);
  color: var(--ink);
  font-size: 15px;
  font-weight: 600;
}

.option > input:hover + span {
  border-color: var(--accent);
  color: var(--accent-text);
}

.option > input:focus-visible + span {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.option > input:checked + span {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
  font-weight: 700;
}

/* A song pill is a chip, so it keeps the pill radius and the prose face. */
.option-song > span {
  border-radius: 999px;
  font-family: var(--prose);
  font-size: 16px;
  font-weight: 400;
  min-height: 44px;
}

/* Who can see it: a 2x2 grid, active state a solid ink fill. */
.visibility-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

summary.toggle-row,
.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 52px;
  cursor: pointer;
  list-style: none;
}

summary.toggle-row::-webkit-details-marker {
  display: none;
}

/* ─── archive ──────────────────────────────────────────────────────────────── */

.stat-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 18px 0;
}

.stat-tile {
  border-radius: 16px;
  padding: 16px;
  background: var(--paper-3);
}

.stat-tile-accent {
  background: var(--accent-soft);
}

.stat-tile-label {
  margin: 8px 0 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-soft);
}

.stat-tile-accent .stat-tile-label {
  color: var(--accent-text);
}

.rank-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rank-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 17px;
  font-weight: 700;
}

.rank-list li:last-child {
  border-bottom: none;
}

.rank-list .count {
  margin-left: auto;
  font-weight: 400;
  color: var(--ink-soft);
}

.year-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
}

.year-row:last-child {
  border-bottom: none;
}

.year-row:hover .year-figure {
  color: var(--accent-text);
}

.year-figure {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.archive-year {
  margin: 0 0 6px;
  font-size: 40px;
  line-height: 1;
  font-weight: 800;
  letter-spacing: -0.04em;
}

/* A logged gig is a solid bordered row; one still to write up is dashed with the
   action on the right. The tray and the archive are the same surface. */
.archive-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  margin: 0 0 12px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: var(--paper-2);
  text-decoration: none;
  color: inherit;
}

.archive-row-unlogged {
  border-style: dashed;
  border-color: var(--rule-strong);
  background: none;
}

.archive-row:hover .gig-row-artist {
  color: var(--accent-text);
}

/* ─── alerts ───────────────────────────────────────────────────────────────── */

.alerts {
  list-style: none;
  margin: 0;
  padding: 0;
}

.alert-item {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 14px var(--gutter);
  border-bottom: 1px solid var(--rule);
  font-size: 15px;
}

/* Unread carries a rule as well as a tint, so it does not lean on colour. */
.alert-item.unread {
  background: var(--accent-soft);
  border-left: 3px solid var(--accent);
  padding-left: calc(var(--gutter) - 3px);
}

.alert-body {
  min-width: 0;
  flex: 1 1 auto;
}

.alert-body p {
  margin: 0;
}

/* The sentence is the alert. Underlining all of it makes a list of alerts read
   as a list of links, so the underline waits for a hover. */
.alert-body a {
  color: inherit;
  text-decoration: none;
}

.alert-body a:hover {
  color: var(--accent-text);
  text-decoration: underline;
}

/* ─── profile ──────────────────────────────────────────────────────────────── */

.profile-banner {
  display: block;
  width: 100%;
  height: 116px;
  object-fit: cover;
  background: var(--paper-4);
}

.profile-identity {
  padding: 0 var(--gutter);
}

.profile-identity .avatar-74 {
  margin-top: -28px;
  border: 3px solid var(--paper);
}

.profile-name {
  margin: 10px 0 0;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.03em;
}

.profile-counts {
  display: flex;
  gap: 18px;
  margin: 10px 0 0;
  font-size: 14px;
}

.profile-counts a {
  color: var(--ink-soft);
  text-decoration: none;
}

.profile-counts a:hover {
  color: var(--accent-text);
}

.profile-counts strong {
  color: var(--ink);
}

/* Long URLs wrap rather than push the page sideways on a phone. */
.profile-links dd a {
  overflow-wrap: anywhere;
}

.settings-group {
  margin: 0 0 22px;
}

.settings-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: 16px;
  overflow: hidden;
}

.settings-list > li + li {
  border-top: 1px solid var(--rule);
}

.settings-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 10px 14px;
  text-decoration: none;
  color: inherit;
  font-size: 15px;
  font-weight: 600;
}

.settings-row:hover {
  color: var(--accent-text);
}

.settings-row .chevron {
  margin-left: auto;
  color: var(--ink-faint);
}

.segmented {
  display: flex;
  gap: 8px;
  padding: 10px 14px;
}

.segmented button {
  flex: 1 1 0;
  min-height: 44px;
  border-radius: 11px;
  border: 1px solid var(--rule-strong);
  background: var(--paper-2);
  color: var(--ink);
  font: inherit;
  font-family: var(--ui);
  font-weight: 600;
  cursor: pointer;
}

.segmented button[aria-pressed="true"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* ─── discover ─────────────────────────────────────────────────────────────── */

.search-field {
  position: relative;
}

.search-field input {
  min-height: 50px;
  padding-left: 44px;
  border-radius: 14px;
}

.search-field .search-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  color: var(--ink-faint);
  pointer-events: none;
}

.filter-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 4px 0 6px;
}

.filter-row .chip {
  flex: 0 0 auto;
}

/* ─── toast ────────────────────────────────────────────────────────────────── */

/*
 * A toast confirms; it never carries the only copy of an action. It sits above
 * the bottom bar, and it is a plain element rather than a timed overlay because
 * there is no client runtime here to clear it.
 */
.toast {
  position: fixed;
  left: var(--gutter);
  right: var(--gutter);
  bottom: calc(64px + 34px + 10px);
  z-index: 35;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: 13px;
  background: var(--ink);
  color: var(--paper);
  font-size: 14px;
  font-weight: 600;
}

.toast .owl {
  color: var(--accent);
}

/* ─── the review queue and edit history ────────────────────────────────────── */

.review-item {
  border: 1px solid var(--rule);
  border-radius: 16px;
  padding: 16px;
  margin: 0 0 18px;
}

.diff {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  display: block;
  overflow-x: auto;
}

.diff th,
.diff td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}

.diff .before {
  color: var(--ink-faint);
  text-decoration: line-through;
}

.review-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-top: 12px;
}

.history {
  list-style: none;
  margin: 0;
  padding: 0;
}

.history li {
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 14px;
}

/* ─── odds and ends ────────────────────────────────────────────────────────── */

.empty {
  color: var(--ink-faint);
  font-size: 15px;
}

.pager {
  display: flex;
  gap: 12px;
  margin: 22px var(--gutter);
}

.listing {
  list-style: none;
  margin: 0;
  padding: 0;
}

.listing > li {
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
}

.listing > li:last-child {
  border-bottom: none;
}

fieldset {
  border: 1px solid var(--rule);
  border-radius: 14px;
  padding: 14px;
  margin: 0 0 16px;
}

legend {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-soft);
  padding: 0 6px;
}

.site-footer {
  padding: 22px var(--gutter) 30px;
  color: var(--ink-faint);
  font-size: 13px;
  line-height: 1.6;
}

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

.site-footer a:hover {
  color: var(--accent-text);
}

/* ─── desktop: three columns, one breakpoint ───────────────────────────────── */

@media (min-width: 900px) {
  :root {
    --gutter: 24px;
  }

  .shell {
    display: grid;
    grid-template-columns: 236px minmax(0, 1fr) 316px;
    max-width: 1120px;
    margin: 0 auto;
  }

  /* Landing and sign-up stay a single centred column. */
  .shell-bare {
    display: block;
    max-width: 760px;
  }

  .column-main {
    padding-bottom: 40px;
    border-left: 1px solid var(--rule);
    border-right: 1px solid var(--rule);
    min-height: 100vh;
  }

  .shell-bare .column-main {
    border: none;
  }

  /* The bottom bar is a phone control. Above the breakpoint the left column
     carries navigation, so the bar and the mobile header both go. */
  .bottom-bar,
  .app-header,
  .toast {
    display: none;
  }

  .rail-left,
  .rail-right {
    display: block;
    padding: 22px var(--gutter);
    align-self: start;
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow-y: auto;
  }

  .rail-left {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-height: 100vh;
  }

  .rail-left .wordmark {
    margin: 0 0 18px;
    font-size: 22px;
  }

  .nav-item {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 48px;
    padding: 0 14px;
    border-radius: 12px;
    text-decoration: none;
    color: var(--ink-soft);
    font-size: 16px;
    font-weight: 600;
  }

  .nav-item:hover {
    background: var(--paper-3);
    color: var(--ink);
  }

  .nav-item[aria-current="page"] {
    background: var(--accent-soft);
    color: var(--accent-text);
    font-weight: 700;
  }

  .nav-item .count-badge {
    position: static;
    margin: 0 0 0 auto;
  }

  .rail-left .btn-primary {
    margin-top: 16px;
  }

  .rail-foot {
    margin-top: auto;
    padding-top: 22px;
    font-size: 13px;
    color: var(--ink-faint);
  }

  .rail-right .eyebrow {
    margin-top: 22px;
  }

  .rail-right > .eyebrow:first-child {
    margin-top: 0;
  }

  /* The tab bar stays put while the feed scrolls under it. */
  .tabs {
    position: sticky;
    top: 0;
    z-index: 20;
  }

  .tabs {
    display: flex;
    gap: 24px;
    padding: 0 var(--gutter);
  }

  .tabs a {
    min-width: 0;
    padding: 0 2px;
  }

  /* Full-width strips keep their bleed; cards lose the phone side margin. */
  .feed > li,
  .hero-gig,
  .nudge-card,
  .pager {
    margin-left: var(--gutter);
    margin-right: var(--gutter);
  }

  /* The hero card becomes a split, photo on the left. */
  .hero-gig-split {
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    align-items: stretch;
  }

  .hero-gig-split .hero-gig-photo .photo-well,
  .hero-gig-split .hero-gig-photo .poster-empty {
    height: 100%;
    min-height: 260px;
  }

  /* Prose is capped so a line does not run the width of the column. */
  .prose-text,
  .prose-text-large,
  .landing-argument,
  .feed-lead {
    max-width: var(--measure);
  }

  .nudge-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 18px;
  }

  .nudge-card .nudge-actions {
    margin: 0;
  }

  .doors-banner {
    padding-left: var(--gutter);
    padding-right: var(--gutter);
  }
}
