/* Session Sets - stylesheet.

   Built around paper: the page is the same off-white as engraved notation, so
   a score sits on the screen rather than floating in a card on a dark ground.
   Titles, buttons and controls are a deep green against it.

   Touch targets are kept at 44px or larger throughout, and every screen
   respects the iPhone safe areas so nothing hides under the notch or the
   home indicator. */

/* ============================= fonts ============================= */

/* Fraunces is vendored rather than linked from Google Fonts. A CDN link would
   silently fall back to a system serif the moment there is no signal, which is
   precisely when this app is being used. Variable 400-900; optical sizing is
   left on auto, so the larger a title is set, the more display-cut it gets. */

@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../vendor/fonts/fraunces-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;
}

/* Carries the accented characters Irish and Scottish tune names need. */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../vendor/fonts/fraunces-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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ============================= tokens ============================= */

:root {
  /* The page is the same off-white as the notation paper, so a score sits on
     the screen the way ink sits on a page rather than floating in a card. */
  --paper:         #f8f6f0;
  --paper-ink:     #15181d;

  --bg:            var(--paper);
  --bg-raised:     #fffefa;
  --bg-row:        #fffefa;
  --bg-row-hover:  #f0ece0;
  --line:          #dcd5c5;
  --line-soft:     #e8e2d4;

  /* Body text is a near-black with a green cast, so it sits with the accent
     rather than fighting it. */
  /* All three clear 4.5:1 against the page. The pale greys that worked on a
     dark ground fall well short of that on off-white. */
  --text:          #22302a;
  --text-dim:      #5d6b63;
  --text-faint:    #657068;

  --accent:        #1b4332;
  --accent-strong: #2d6a4f;
  --accent-ink:    #f8f6f0;
  --danger:        #a32218;
  --danger-ink:    #fff6f5;
  --good:          #2d6a4f;

  /* Shadows have to be far lighter on a pale ground or everything looks
     grubby; tint them green rather than grey. */
  --shadow-sm:     0 6px 18px rgba(27, 67, 50, .10);
  --shadow-md:     0 14px 34px rgba(27, 67, 50, .16);
  --shadow-lg:     0 16px 44px rgba(27, 67, 50, .20);

  --radius:        14px;
  --radius-sm:     10px;
  --radius-lg:     20px;

  --tap:           44px;

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  --topbar-h: 60px;

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
          Arial, sans-serif;

  /* Used only for the app's name and for tune titles. */
  --font-display: 'Fraunces', Georgia, "Times New Roman", serif;
}

/* ============================= reset ============================= */

* { box-sizing: border-box; }

/* The HTML `hidden` attribute only sets `display: none` at the UA level, so any
   rule here that sets `display` would silently beat it. Several elements below
   (.stage, .icon-btn, .btn) do exactly that, and the app toggles them via
   `hidden`. Make the attribute win outright. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

body {
  min-height: 100vh;
  overscroll-behavior-y: none;
}

body.modal-open,
body.stage-open { overflow: hidden; }

h1, h2, h3, p { margin: 0; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}

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

#view:focus { outline: none; }

/* ============================= topbar ============================= */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: calc(var(--topbar-h) + var(--safe-t));
  padding: var(--safe-t) calc(12px + var(--safe-r)) 0 calc(12px + var(--safe-l));
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
  border-bottom: 1px solid var(--line-soft);
}

.topbar-titles {
  flex: 1;
  min-width: 0;
  padding: 8px 0;
}

.topbar h1 {
  font-size: 19px;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- brand ---- */

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

/* Escapes the global `svg` reset, which sizes icons and strokes rather than
   fills them. */
.logo-mark {
  width: 28px;
  height: 28px;
  fill: currentColor;
  stroke: none;
  flex: none;
}

.brand-name {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.1;
}

/* A title that is the name of a musical thing rather than a UI label, so it
   gets the display face the wordmark and the stage titles use. Slightly
   larger than the sans default to match its smaller x-height. */
.title-name {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.subtitle {
  margin-top: 2px;
  font-size: 12.5px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

/* offline marker in the topbar */
body.is-offline .topbar::after {
  content: "offline";
  position: absolute;
  right: calc(12px + var(--safe-r));
  bottom: -9px;
  padding: 1px 7px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bg);
  background: var(--text-faint);
  border-radius: 99px;
}

/* ============================= buttons ============================= */

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  border-radius: var(--radius-sm);
  color: var(--accent);
  transition: background .15s, color .15s;
}

.icon-btn:hover { background: var(--bg-row-hover); color: var(--text); }
.icon-btn:active { transform: scale(.94); }
.icon-btn:disabled { opacity: .3; pointer-events: none; }
.icon-btn-accent { color: var(--accent); }

/* Heart: outlined until it is a favourite, then filled. */
.icon-btn-heart svg { fill: none; }
.icon-btn-heart.is-on svg { fill: currentColor; }
.icon-btn-heart.is-on { color: var(--danger); }
.icon-btn-heart:disabled { opacity: .45; }
.icon-btn-lg { width: 52px; height: 52px; }
.icon-btn-lg svg { width: 26px; height: 26px; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--tap);
  padding: 0 16px;
  border-radius: var(--radius-sm);
  background: var(--bg-row);
  border: 1px solid var(--line);
  color: var(--accent);
  font-size: 15px;
  font-weight: 600;
  transition: background .15s, border-color .15s, transform .08s;
}

.btn:hover { background: var(--bg-row-hover); }
.btn:active { transform: scale(.98); }
.btn:disabled { opacity: .45; pointer-events: none; }
.btn svg { width: 18px; height: 18px; }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.btn-primary:hover { background: var(--accent-strong); }

.btn-ghost { background: transparent; }

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

.btn-compact {
  min-height: 38px;
  padding: 0 12px;
  font-size: 14px;
}
.btn-compact svg { width: 17px; height: 17px; }

.btn-wide { width: 100%; }

/* ============================= main view ============================= */

.view {
  padding: 14px calc(12px + var(--safe-r)) calc(40px + var(--safe-b)) calc(12px + var(--safe-l));
  max-width: 780px;
  margin: 0 auto;
}

.hint {
  margin: 14px 4px 0;
  font-size: 12.5px;
  color: var(--text-faint);
  text-align: center;
}

.hint-about { margin-top: 26px; line-height: 1.6; }
.hint strong, .hint-about strong { color: var(--text-dim); }

.notes-banner {
  margin-bottom: 12px;
  padding: 11px 14px;
  background: var(--bg-raised);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-dim);
  white-space: pre-wrap;
}

/* ============================= lists & rows ============================= */

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

/* Section heading above a group of rows, with a rule under it. */
.list-section {
  margin: 22px 2px 10px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--line);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.list-section:first-of-type { margin-top: 4px; }

.row {
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--bg-row);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  /* transform is animated for the reorder gap, so keep it cheap */
  transition: transform .18s cubic-bezier(.2, .8, .2, 1), background .15s, box-shadow .18s;
  will-change: transform;
}

.row:hover { background: var(--bg-row-hover); }

.list.is-sorting .row { transition: transform .18s cubic-bezier(.2, .8, .2, 1); }

.row.is-dragging {
  position: relative;
  background: var(--bg-row-hover);
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
  transition: none;
  cursor: grabbing;
}

.list.is-sorting { cursor: grabbing; }

.row-main {
  flex: 1;
  min-width: 0;
  text-align: left;
  padding: 12px 6px 12px 2px;
  min-height: var(--tap);
}

.row-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: 650;
  color: var(--accent);
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.row-meta,
.row-date {
  margin-top: 3px;
  font-size: 12.5px;
  color: var(--text-dim);
}

.row-date { color: var(--text-faint); }

/* A note may be a whole set of lyrics, so its line breaks are kept - but only
   the first couple of lines are shown in a list, where a row has to stay a
   row. The stage view gives them in full. */
.row-note {
  margin-top: 5px;
  font-size: 12.5px;
  color: var(--accent);
  opacity: .85;
  white-space: pre-wrap;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.row-preview {
  margin-top: 4px;
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.45;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.row-preview.is-empty { color: var(--text-faint); font-style: italic; }

.badge {
  flex: none;
  min-width: 22px;
  padding: 1px 7px;
  background: var(--line);
  border-radius: 99px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text-dim);
  text-align: center;
}

/* ---- sessions / gigs switch ---- */

.kind-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 12px;
  padding: 4px;
  background: var(--bg-row-hover);
  border-radius: 99px;
}

.kind-tab {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 38px;
  border-radius: 99px;
  font-size: 14.5px;
  font-weight: 650;
  color: var(--text-dim);
  transition: background .15s, color .15s;
}

.kind-tab:hover { color: var(--accent); }

.kind-tab.is-on {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-sm);
}

.kind-tab-count {
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 99px;
  background: var(--line);
  color: var(--text-dim);
  font-size: 11.5px;
  font-weight: 700;
}

.kind-tab.is-on .kind-tab-count {
  background: color-mix(in srgb, var(--accent-ink) 26%, transparent);
  color: var(--accent-ink);
}

/* Keeps gig rows aligned with session rows, which have a drag handle. */
.grip-spacer { flex: none; width: 38px; }

/* ---- filter by tune type ---- */

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
}

/* Sized to the content rather than the column: a dropdown stretched the full
   width of a laptop reads as an input waiting to be filled in. They do grow
   to share a phone's width, and wrap rather than overflow if the labels are
   long. */
.filter-bar .input {
  flex: 0 1 auto;
  width: auto;
  min-width: 150px;
  max-width: 100%;
  min-height: 38px;
  padding-top: 0;
  padding-bottom: 0;
  font-size: 14px;
  font-weight: 650;
  color: var(--accent);
}

/* Sits with the dropdowns rather than reading as a primary action. */
.filter-reset {
  flex: none;
  min-height: 38px;
  padding: 0 13px;
  font-size: 14px;
}

.filter-reset svg { width: 16px; height: 16px; }

/* On a phone the two dropdowns plus a labelled button overflow the width and
   wrap to a second row. The circular arrow carries the meaning on its own, so
   the word is dropped rather than the whole row being pushed down. */
@media (max-width: 520px) {
  .filter-bar .input { min-width: 0; flex: 1 1 0; }
  .filter-reset { padding: 0; width: 40px; }
  .filter-reset span { display: none; }
  .filter-reset svg { width: 18px; height: 18px; }
}

/* A gig whose date has passed stays listed but recedes. */
.row.is-past { opacity: .62; }
.row.is-past:hover { opacity: 1; }

.row-fee {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 5px;
  font-size: 13px;
  font-weight: 650;
  color: var(--text);
}

.pill {
  padding: 1px 8px;
  border-radius: 99px;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.pill-good { background: var(--good); color: var(--accent-ink); }
.pill-warn { background: var(--line); color: var(--text-dim); }

/* Two short inputs side by side (start time / load in). */
.field-row { display: flex; gap: 10px; }
.field-row .field { flex: 1; min-width: 0; }

.tune-pos {
  flex: none;
  width: 24px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-faint);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* ----- drag handle ----- */

.grip {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  min-height: var(--tap);
  align-self: stretch;
  color: var(--text-faint);
  border-radius: var(--radius) 0 0 var(--radius);
  cursor: grab;
  /* critical: stops the browser scrolling the page during a drag */
  touch-action: none;
  transition: color .15s, background .15s;
}

.grip:hover { color: var(--text); background: var(--line-soft); }
.grip:active { cursor: grabbing; }
.grip svg { width: 20px; height: 20px; stroke-width: 2.6; }

/* ----- nudge (move up/down) ----- */

.nudge {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 3px;
}

.nudge-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 25px;
  border-radius: 7px;
  color: var(--text-faint);
  transition: background .13s, color .13s;
}

.nudge-btn:hover { background: var(--line); color: var(--text); }
.nudge-btn:active { background: var(--accent); color: var(--accent-ink); }
.nudge-btn:disabled { opacity: .22; pointer-events: none; }
.nudge-btn svg { width: 17px; height: 17px; stroke-width: 2.6; }

/* ============================= empty state ============================= */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 54px 24px;
  text-align: center;
}

.empty-icon {
  display: grid;
  place-items: center;
  width: 62px;
  height: 62px;
  margin-bottom: 4px;
  border-radius: 50%;
  background: var(--bg-raised);
  color: var(--text-faint);
}
.empty-icon svg { width: 28px; height: 28px; }

.empty-title { font-size: 18px; font-weight: 700; color: var(--accent); }

.empty-text {
  max-width: 34ch;
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-dim);
  /* A blank line is allowed to separate paragraphs here; no empty state puts
     a single line break anywhere it would not want one. */
  white-space: pre-line;
}

.empty-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; margin-top: 6px; }

/* ============================= search ============================= */

.search-bar {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 10px;
}

.search-input-wrap { position: relative; }

.search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 19px;
  height: 19px;
  color: var(--text-faint);
  pointer-events: none;
}

.search-input-wrap .input { padding-left: 40px; }

.search-filters { display: flex; gap: 8px; }
.search-filters .input { flex: 1; min-width: 0; }

/* ---- searching your own library ---- */

/* Same furniture as the thesession.org search above, so the two read as the
   same kind of thing: an icon inside the field on the left, and here a clear
   button on the right once there is something to clear. */
.find-field {
  position: relative;
  display: block;
  margin-bottom: 12px;
}

.find-field > svg {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 19px;
  height: 19px;
  color: var(--text-faint);
  pointer-events: none;
}

.find-field .input {
  padding-left: 40px;
  padding-right: calc(var(--tap) + 4px);
  font-size: 16px;        /* iOS zooms the page for anything smaller */
}

/* The browser's own clear affordance would sit under ours. */
.find-field .input::-webkit-search-cancel-button,
.find-field .input::-webkit-search-decoration { -webkit-appearance: none; }

.icon-btn-clear {
  position: absolute;
  right: 2px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-faint);
}

.icon-btn-clear:active { transform: translateY(-50%) scale(.94); }

/* Where a found tune lives, under its name. */
.row-where {
  margin-top: 3px;
  font-size: 12.5px;
  color: var(--text-faint);
}

.find-more {
  margin: 10px 2px 0;
  font-size: 12.5px;
  color: var(--text-faint);
}

/* The matched run of characters, wherever it was found. Tinted rather than
   boxed - a row can hold several hits and blocks of colour would shout. */
mark.hit {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: inherit;
  border-radius: 3px;
  padding: 0 1px;
}

.set-strip {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  padding: 9px 13px;
  background: var(--bg-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  font-size: 13.5px;
}

.set-strip-label { color: var(--text-dim); }
.set-strip strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.search-count {
  margin: 0 4px 10px;
  font-size: 12.5px;
  color: var(--text-faint);
  min-height: 16px;
}

.row-result .row-main { padding-left: 14px; }

.btn-add {
  flex: none;
  margin-right: 8px;
  min-height: 38px;
  padding: 0 13px;
  font-size: 14px;
}

.btn-add.is-busy { opacity: .6; }
.btn-add.is-added { background: var(--good); border-color: var(--good); }

.search-more { margin-top: 12px; }

.tunebook-cta { margin-top: 20px; }

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

.skeleton {
  height: 66px;
  border-radius: var(--radius);
  background: linear-gradient(100deg, var(--bg-row) 30%, var(--bg-row-hover) 50%, var(--bg-row) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.3s infinite linear;
}

@keyframes shimmer {
  from { background-position: 180% 0; }
  to   { background-position: -40% 0; }
}

/* ============================= cards (settings) ============================= */

.card {
  margin-bottom: 12px;
  padding: 16px;
  background: var(--bg-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}

.card-title { font-size: 15.5px; font-weight: 700; margin-bottom: 8px; color: var(--accent); }

.card-text {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-dim);
}

.card-actions { display: flex; gap: 8px; margin-top: 13px; flex-wrap: wrap; }

/* ---- gig details ---- */

.detail-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-bottom: 4px;
}

.detail-row {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 9px 2px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 14.5px;
}

.detail-row:last-child { border-bottom: 0; }

.detail-label {
  flex: none;
  width: 92px;
  color: var(--text-dim);
  font-size: 12.5px;
  font-weight: 650;
}

.detail-value { flex: 1; min-width: 0; overflow-wrap: anywhere; }

.detail-heading {
  margin: 18px 0 8px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.detail-sets { display: flex; flex-direction: column; gap: 6px; }

.detail-set {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 9px 12px;
  background: var(--bg-row);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  font-size: 14.5px;
}

.detail-set-pos {
  flex: none;
  color: var(--text-faint);
  font-weight: 700;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}

.detail-set-name { flex: 1; min-width: 0; font-weight: 650; overflow-wrap: anywhere; }
.detail-set-count { flex: none; color: var(--text-dim); font-size: 12.5px; }

/* Character count under a share link. */
.share-meta {
  margin: -4px 0 4px;
  font-size: 12.5px;
  color: var(--text-faint);
}

/* The link itself: monospace so a long opaque string is easier to eyeball,
   and small enough that a decent part of it is visible at once. */
.share-link {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
}

.card-danger { border-color: color-mix(in srgb, var(--danger) 40%, var(--line)); }
.card-danger .btn-danger { margin-top: 13px; }

.switch {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 7px 0 11px;
  font-size: 14.5px;
  cursor: pointer;
}

.switch input { width: 22px; height: 22px; accent-color: var(--accent); flex: none; }

/* ============================= forms ============================= */

.form { display: flex; flex-direction: column; gap: 14px; }

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

.field-label {
  font-size: 12.5px;
  font-weight: 650;
  color: var(--text-dim);
  letter-spacing: .01em;
}

.field-hint { font-size: 12px; color: var(--text-faint); line-height: 1.5; }

.input {
  width: 100%;
  min-height: var(--tap);
  padding: 10px 13px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text);
  /* 16px minimum stops iOS Safari zooming the page on focus */
  font-size: 16px;
  font-family: inherit;
}

.input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

.input::placeholder { color: var(--text-faint); }

.textarea { min-height: 76px; resize: vertical; line-height: 1.5; }

select.input {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  /* Arrow is drawn in the accent green; keep in step with --accent. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231b4332' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 17px;
}

input[type="file"].input { padding: 10px; font-size: 14px; }

/* ============================= modal ============================= */

.modal-host { position: fixed; inset: 0; z-index: 60; }

.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(23, 40, 32, .38);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  animation: fade .18s ease;
}

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

.modal-panel {
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: min(620px, 100%);
  max-height: min(88vh, 860px);
  display: flex;
  flex-direction: column;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-lg);
  animation: sheet-up .24s cubic-bezier(.2, .9, .25, 1);
}

@keyframes sheet-up { from { transform: translate(-50%, 18px); opacity: .4; } }

@media (min-width: 680px) {
  .modal-panel {
    bottom: auto;
    top: 50%;
    transform: translate(-50%, -50%);
    border-bottom: 1px solid var(--line);
    border-radius: var(--radius-lg);
  }
  @keyframes sheet-up { from { transform: translate(-50%, -46%); opacity: .4; } }
}

.modal-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 10px 14px 18px;
  border-bottom: 1px solid var(--line-soft);
}

.modal-head h2 {
  flex: 1;
  min-width: 0;
  font-size: 16.5px;
  font-weight: 700;
  color: var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.modal-body {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 16px 18px calc(18px + var(--safe-b));
}

.modal-text {
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-dim);
  margin-bottom: 12px;
}

.modal-text.is-error { color: var(--danger); }
.modal-status { min-height: 20px; }

.modal-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 4px;
}

/* ============================= popover menu ============================= */

.menu {
  position: fixed;
  z-index: 80;
  min-width: 212px;
  padding: 6px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  animation: pop .13s ease;
}

@keyframes pop { from { opacity: 0; transform: scale(.96); } }

.menu-item {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 42px;
  padding: 0 11px;
  border-radius: 9px;
  font-size: 14.5px;
  text-align: left;
  color: var(--text);
}

.menu-item:hover { background: var(--bg-row-hover); }
.menu-item svg { width: 18px; height: 18px; color: var(--text-faint); }
.menu-item-danger { color: var(--danger); }
.menu-item-danger svg { color: var(--danger); }

.menu-divider { height: 1px; margin: 5px 7px; background: var(--line-soft); }

/* ============================= setting picker ============================= */

.setting-list { display: flex; flex-direction: column; gap: 10px; }

.setting-card {
  display: block;
  width: 100%;
  padding: 11px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-align: left;
  transition: border-color .15s, background .15s;
}

.setting-card:hover { border-color: var(--accent); background: var(--bg-row); }
.setting-card.is-current { border-color: var(--accent); }

.setting-head {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 9px;
}

.setting-key { font-size: 15px; font-weight: 700; }
.setting-idx { font-size: 12px; color: var(--text-faint); }

.setting-current {
  margin-left: auto;
  padding: 1px 8px;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--accent-ink);
  background: var(--accent);
  border-radius: 99px;
}

/* ============================= notation "paper" ============================= */

.setting-notation,
.slide-notation {
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* The picker's previews sit on the raised modal surface, so they need the
   paper tint to read as sheets. The stage is already paper-coloured, so a
   background there would be invisible except for one thing: the score is
   pulled up to meet its caption, and the fill would paint straight over it. */
.setting-notation { background: var(--paper); }

/* Holds the card's shape until the score is engraved on scroll, so the list
   does not jump as previews fill in. */
.setting-notation.is-pending { min-height: 120px; }

/* The engraved SVG. This class exists chiefly to escape the global `svg`
   reset further up, which sizes icons at 22x22 - without it the whole score
   collapses to an icon-sized speck. Sizing comes from the viewBox. */
.notation-svg {
  display: block;
  width: 100%;
  height: auto;
}

/* abcjs engraves nearly everything as filled paths, so a blanket ink colour
   covers noteheads, stems, beams, slurs, clefs and staff lines alike. */
.notation-svg path,
.notation-svg text,
.notation-svg tspan {
  fill: var(--paper-ink);
  stroke: none;
}

/* Staff and ledger lines are the exception - abcjs strokes those. */
.notation-svg path.abcjs-staff,
.notation-svg path.abcjs-ledger {
  stroke: var(--paper-ink);
}

/* ...and so is anything abcjs explicitly asked to stroke rather than fill.

   First and second time endings are the case that matters: the bracket over
   the bars is the one thing in a whole score drawn as an open stroked path
   (two downward ticks and the line joining them), so the blanket `stroke:
   none` above erased it while leaving every filled glyph untouched - the
   volta numbers showed, the bracket over them did not.

   Matching on the attribute rather than on a class is deliberate: abcjs
   leaves this path unclassed, and the same treatment is owed to tuplet
   brackets, hairpins and 8va lines, which it draws the same way.

   stroke-width is reset because the global icon rule sets 2, which is for a
   22px icon; at score scale that is a fat line where a hairline belongs. */
.notation-svg path[stroke="currentColor"] {
  stroke: var(--paper-ink);
  stroke-width: 1;
  stroke-linecap: butt;
}

/* abcjs engraves the R: field ("jig") above the stave. The tune type is
   already shown as a chip right above the score, so drop the duplicate. */
.notation-svg text.abcjs-rhythm { display: none; }

.notation-empty {
  padding: 14px 4px;
  font-size: 13px;
  color: var(--text-faint);
  text-align: center;
}

/* ============================= set footer ============================= */

.set-footer {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* ============================= tunebook import ============================= */

.tunebook-list { display: flex; flex-direction: column; gap: 9px; margin-top: 4px; }

.tunebook-rows {
  max-height: 46vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
}

.tunebook-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 14.5px;
  cursor: pointer;
}

.tunebook-row:last-child { border-bottom: 0; }
.tunebook-row:hover { background: var(--bg-row); }
.tunebook-row input { width: 21px; height: 21px; accent-color: var(--accent); flex: none; }
.tunebook-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.tunebook-type { font-size: 12px; color: var(--text-faint); flex: none; }

.tunebook-count { margin-right: auto; font-size: 13px; color: var(--text-dim); }

/* ============================= toast ============================= */

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(22px + var(--safe-b));
  z-index: 90;
  transform: translate(-50%, 14px);
  max-width: min(520px, calc(100vw - 32px));
  padding: 12px 18px;
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: 99px;
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  box-shadow: var(--shadow-md);
  opacity: 0;
  transition: opacity .2s, transform .2s;
  pointer-events: none;
}

.toast.toast-in { opacity: 1; transform: translate(-50%, 0); }
.toast-error { background: var(--danger); color: var(--danger-ink); }

/* ============================= stage ============================= */

.stage {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--bg);
  display: flex;
  flex-direction: column;
}

.stage-track {
  flex: 1;
  min-height: 0;
}

/* ----- single: horizontal snap carousel ----- */

.stage-track-single {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.stage-track-single::-webkit-scrollbar { display: none; }

.slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  display: flex;
  /* Top-anchored, not centred: the title, buttons and caption must hold their
     position when the stave is swapped for the shorter ABC text. Centring
     would re-centre the whole block and make the header jump. */
  align-items: flex-start;
  justify-content: center;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.slide-inner {
  width: 100%;
  max-width: 940px;
  /* Top padding clears the stage chrome and the notch; bottom clears the
     dots. Both are trimmed to the minimum that still does so, since every
     pixel here comes off the music. */
  padding: calc(64px + var(--safe-t)) calc(18px + var(--safe-r))
           calc(80px + var(--safe-b)) calc(18px + var(--safe-l));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  text-align: center;
}

.slide-pos {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.slide-name {
  /* Big enough to read at arm's length, but restrained enough to leave the
     stave room - notation shares this screen by default. */
  font-family: var(--font-display);
  /* Deliberately restrained on a phone: the notation is what you are reading,
     and the title only has to confirm which tune you are on. */
  font-size: clamp(19px, 5.2vw, 34px);
  font-weight: 600;
  color: var(--accent);
  line-height: 1.04;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
}

/* A caption on the stave below rather than a pair of controls: plain text,
   type on the left, key on the right, in the same ink as the notes. Kept
   small and light so the music gets the room. */
.slide-meta {
  display: flex;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  font-size: clamp(13px, 2.7vw, 16px);
  font-weight: 700;
  color: var(--paper-ink);
  /* The score is pulled up far enough to overlap this row's box, so lift the
     caption above it in the stacking order. */
  position: relative;
  z-index: 1;
}

.slide-type,
.slide-key {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
}

/* Keeps the key hard right even when a tune has no type to balance against. */
.slide-key { margin-left: auto; }

.slide-note {
  max-width: 44ch;
  font-size: clamp(14px, 3.6vw, 18px);
  line-height: 1.5;
  color: var(--accent-strong);
  white-space: pre-wrap;
}

/* An entry with no notation - a song - puts its words where the stave would
   have been. Left-aligned, because lyrics are read line by line, and sized
   for arm's length like everything else on the stage. It scrolls within the
   slide so a long song does not break the swipe. */
.slide-lyrics {
  width: 100%;
  /* A column of a comfortable reading width, left-aligned inside it. The
     slide itself scrolls when a song runs long, as it does for a long score -
     a scroll area nested inside that one would be a trap mid-song. */
  max-width: 44ch;
  margin-top: 4px;
  text-align: left;
  white-space: pre-wrap;
  font-size: clamp(16px, 4.4vw, 23px);
  line-height: 1.55;
  color: var(--text);
}

/* Lifted above the notation for the same reason as the caption: the score is
   pulled up to meet it, and anything it overlapped would stop taking taps.
   Belt and braces - the measurement should prevent any overlap at all. */
.slide-controls {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  position: relative;
  z-index: 1;
}

/* No margin-top here: the stave view overwrites it with the measured pull-up
   while the ABC view would keep it, leaving the two boxes 4px out of step. The
   flex gap already provides the spacing. */
.slide-notation { width: 100%; }

/* The ABC source, as an alternative to the engraved stave. Monospaced and
   left-aligned - it is a text document, not a picture. */
.notation-abc {
  margin: 0;
  text-align: left;
  /* `pre`, not `pre-wrap`: lines must stay unwrapped so their natural width
     can be measured, and the size is then fitted to the space. */
  white-space: pre;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 20px;            /* starting point; JS fits from here */
  line-height: 1.55;
  color: var(--paper-ink);
}

.slide-next {
  margin-top: 2px;
  font-size: 13px;
  color: var(--text-faint);
  overflow-wrap: anywhere;
}

/* ----- all: whole set on one screen ----- */

.stage-track-all { overflow-y: auto; -webkit-overflow-scrolling: touch; }

/* Top-anchored and scrollable. The set no longer has to fit one screen -
   the notation under each heading is the point, so headings are kept small
   and the page simply runs on. */
.all-panel {
  display: flex;
  flex-direction: column;
  gap: 26px;
  padding: calc(76px + var(--safe-t)) calc(18px + var(--safe-r))
           calc(84px + var(--safe-b)) calc(18px + var(--safe-l));
  max-width: 940px;
  margin: 0 auto;
}

.all-item { scroll-margin-top: 80px; }

/* One compact line per tune: number, name, then type and key hard right. */
.all-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  padding: 4px 2px 6px;
  text-align: left;
  border-bottom: 1px solid var(--line-soft);
  transition: border-color .15s;
}

.all-head:hover { border-bottom-color: var(--accent); }

.all-item.is-current .all-head { border-bottom-color: var(--accent); }

.all-pos {
  flex: none;
  color: var(--text-faint);
  font-weight: 700;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.all-name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: clamp(16px, 3.3vw, 21px);
  font-weight: 700;
  line-height: 1.2;
  overflow-wrap: anywhere;
  color: var(--accent);
}

.all-meta {
  flex: none;
  color: var(--paper-ink);
  font-weight: 700;
  font-size: clamp(12px, 2.5vw, 15px);
}

.all-notation { margin-top: 8px; }

/* Prompt: the same layout, tightened up. Each tune is only a few bars, so the
   rows pack closer and more of the set lands on one screen. */
.all-panel.is-prompt { gap: 14px; }
.all-panel.is-prompt .all-notation { margin-top: 2px; }
.all-panel.is-prompt .all-head { padding-bottom: 2px; border-bottom: 0; }
.all-panel.is-prompt .all-name { font-size: clamp(15px, 3vw, 18px); }

.all-notation .notation-empty { text-align: left; padding: 6px 2px; }

/* The words of a song, in the stacked views, where notation would sit. */
.all-lyrics {
  margin-top: 8px;
  white-space: pre-wrap;
  font-size: 15px;
  line-height: 1.55;
  color: var(--text);
}

/* Prompt shows the opening lines only, as it shows the opening bars of a
   tune, so it is held to two lines however long they are. */
.all-lyrics.is-clamped {
  margin-top: 2px;
  font-size: 14px;
  color: var(--text-dim);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

/* ----- stage chrome ----- */

.stage-chrome {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  pointer-events: none;
}

.stage-chrome > * { pointer-events: auto; }

.stage-chrome-top {
  top: 0;
  padding: calc(10px + var(--safe-t)) calc(12px + var(--safe-r)) 10px calc(12px + var(--safe-l));
  background: linear-gradient(to bottom, var(--bg) 42%, transparent);
}

.stage-chrome-bottom {
  bottom: 0;
  justify-content: center;
  padding: 10px calc(12px + var(--safe-r)) calc(12px + var(--safe-b)) calc(12px + var(--safe-l));
  background: linear-gradient(to top, var(--bg) 42%, transparent);
}

/* The All view scrolls, so staves pass behind the chrome. A soft gradient was
   fine when nothing moved under it; now it needs to actually cover. */
.stage.mode-all .stage-chrome-top {
  background: linear-gradient(to bottom, var(--bg) 78%, transparent);
}

.stage.mode-all .stage-chrome-bottom {
  background: linear-gradient(to top, var(--bg) 78%, transparent);
}

/* The set's name used to sit here. It was costing a line of vertical space on
   a phone to say something you already knew, so the middle is now just the
   gap that pushes Done left and the view switch right. */
.stage-crumb-spacer { flex: 1; min-width: 0; }

.stage-modes { display: flex; gap: 4px; flex: none; }

.chip {
  min-height: 36px;
  padding: 0 14px;
  border-radius: 99px;
  background: var(--bg-row);
  border: 1px solid var(--line);
  color: var(--text-dim);
  font-size: 13.5px;
  font-weight: 650;
  transition: background .15s, color .15s, border-color .15s;
}

.chip:hover { background: var(--bg-row-hover); color: var(--text); }

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

.chip-solid { background: var(--bg-row-hover); color: var(--text); flex: none; }
.chip-notation { min-height: 40px; font-size: 14px; }

/* Transpose: one segmented control, [ - | Transpose | + ].

   The steppers must never move, so the middle cell has a fixed width that
   fits both of its labels. Reset living inside it, rather than appearing
   beside it, is what keeps the layout still. */
.transpose-group {
  display: inline-flex;
  align-items: stretch;
  flex: none;
  background: var(--bg-row);
  border: 1px solid var(--line);
  border-radius: 99px;
  overflow: hidden;
}

.transpose-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 46px;
  min-height: 40px;
  font-size: 21px;
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
  transition: background .15s, color .15s;
}

.transpose-step:hover { background: var(--bg-row-hover); }
.transpose-step:active { background: var(--accent); color: var(--accent-ink); }

.transpose-mid {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Wide enough for "Key" and for the reset glyph, so swapping one for the
     other still cannot shift the - and + either side. */
  min-width: 58px;
  min-height: 40px;
  padding: 0 10px;
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  font-size: 13.5px;
  font-weight: 650;
  color: var(--text-dim);
  transition: background .15s, color .15s;
}

/* At zero shift it is a caption, not a control - so no dimming, no pointer. */
.transpose-mid:disabled { opacity: 1; cursor: default; }

.transpose-mid.is-active {
  background: var(--accent);
  color: var(--accent-ink);
}

.transpose-mid.is-active:hover { background: var(--accent-strong); }

.transpose-mid svg { width: 18px; height: 18px; stroke-width: 2.2; }

/* The semitone shift, trailing the key it produced. */
.key-shift {
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 99px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: .78em;
  font-weight: 700;
}

.dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 34px;
  padding: 0 10px;
  flex-wrap: wrap;
  max-width: 62vw;
}

.dot {
  width: 11px;
  height: 11px;
  padding: 0;
  border-radius: 50%;
  background: var(--line);
  border: 0;
  transition: background .18s, transform .18s;
}

.dot:hover { background: var(--text-faint); }

.dot.is-on {
  background: var(--accent);
  transform: scale(1.38);
}

.dots-count {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* hide the prev/next arrows on touch-primary devices - swiping is the gesture,
   and the arrows just cover the tune */
@media (hover: none) and (pointer: coarse) {
  #stagePrev, #stageNext { display: none; }
}

/* ============================= motion preference ============================= */

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