/* GigRally — the pieces the screens are assembled from.
 *
 * Sizes are the literal values off the artboards in docs/design/, so a rebuilt screen
 * matches its design rather than approximating it.
 */

/* --- buttons ---------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: var(--tap-primary);
  padding-inline: 1.25rem;
  border: none;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--text-xl);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.btn--primary { background: var(--accent); color: var(--on-accent); }
.btn--secondary {
  background: transparent;
  color: var(--ink);
  border: 1.5px solid var(--ink);
}
.btn--ghost {
  background: transparent;
  color: var(--muted);
  border: none;
  min-height: var(--tap-min);
  font-size: var(--text-md);
}
.btn--sm { min-height: var(--tap-min); font-size: var(--text-lg); padding-inline: 1rem; }
.btn--xs {
  min-height: 40px;
  font-size: var(--text-sm);
  padding-inline: 0.875rem;
  border: 1.5px solid var(--ink);
  background: transparent;
  color: var(--ink);
}
.btn--block { display: flex; width: 100%; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }

@media (hover: hover) and (pointer: fine) {
  .btn:hover { filter: brightness(0.94); }
  .btn--ghost:hover { filter: none; color: var(--ink); }
}

/* --- cards ------------------------------------------------------------------------ */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 1rem;
}
.card--ink {
  background: var(--ink);
  color: var(--paper);
  border: none;
}
.card--ink .label { color: var(--on-ink); }
.card--dashed {
  background: transparent;
  border: 1.5px dashed var(--muted);
}
.card--xl { border-radius: var(--radius-card); }

/* The headline card: "3 dates everyone's free". */
.tally {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--radius-xl);
  padding: 1rem 1.125rem;
}
.tally__count {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-h1-sm);
  letter-spacing: -0.02em;
  line-height: 1.05;
}
.tally__sub { font-size: var(--text-base); color: var(--on-ink); }

/* --- pills and chips --------------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: 36px;
  padding-inline: 0.75rem;
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  background: var(--card);
  color: var(--ink);
  border: 1px solid var(--line);
}
.chip--ink { background: var(--ink); color: var(--paper); border: none; }
.chip--ink .chip__icon { color: var(--accent); }

/* A DM Mono uppercase status badge: ADMIN, FREE PLAN, 5 WKS LEFT, STALE. */
.badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}
.badge--accent { color: var(--accent-ink, var(--muted)); }
.badge--warn { color: var(--error); }
.badge--ink {
  background: var(--ink);
  color: var(--paper);
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm);
}

/* --- avatar ------------------------------------------------------------------------ */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--text-label-lg);
  text-decoration: none;
}
.avatar--lg {
  width: 52px;
  height: 52px;
  border-radius: var(--radius-md);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-h2);
  background: var(--accent);
  color: var(--on-accent);
}

/* --- forms -------------------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: 0.5rem; }
.field__label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}
.input {
  min-height: 48px;
  border-radius: var(--radius-md);
  border: 1.5px solid var(--line);
  background: var(--card);
  color: var(--ink);
  padding-inline: 0.875rem;
  /* ⚠️ 16px minimum, or iOS Safari zooms the page on focus and the layout jumps. */
  font-size: var(--text-lg);
  font-family: var(--font-body);
}
.input--lg { min-height: 54px; border-color: var(--ink); font-size: var(--text-xl); }
.input:focus-visible { border-color: var(--ink); }
.field__error { color: var(--error); font-size: var(--text-base); }
.input[aria-invalid="true"] { border-color: var(--error); }

/* A choice presented as a whole tappable card (the marking-mode picker on Join). */
.choice {
  display: flex;
  gap: 0.875rem;
  padding: 1rem;
  border-radius: var(--radius-lg);
  border: 1.5px solid var(--line);
  background: var(--card);
  cursor: pointer;
}
.choice:has(input:checked) { border: 2px solid var(--ink); }
.choice__dot {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  border: 2px solid var(--muted);
  display: grid;
  place-items: center;
}
.choice:has(input:checked) .choice__dot { border-color: var(--ink); }
.choice:has(input:checked) .choice__dot::after {
  content: "";
  width: 11px;
  height: 11px;
  border-radius: var(--radius-pill);
  background: var(--ink);
}
.choice input { position: absolute; opacity: 0; width: 1px; height: 1px; }

/* role="switch" — the toggle on Account. The native checkbox stays in the accessibility
 * tree and keeps its keyboard behaviour; only its appearance is replaced. */
.switch { display: flex; align-items: center; gap: 0.75rem; min-height: 52px; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.switch__track {
  width: 46px;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: var(--line);
  padding: 3px;
  transition: background 120ms ease;
}
.switch__thumb {
  /* ⚠️ display:block is load-bearing. A <span> is inline, and width/height do not apply to
   * inline boxes — the thumb rendered at zero size and the toggle looked like a plain
   * amber pill with no state at all. The track escapes this only because .switch is a
   * flex container, which blockifies its children. */
  display: block;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  background: var(--card);
  transition: transform 120ms ease;
}
.switch:has(input:checked) .switch__track { background: var(--accent); }
.switch:has(input:checked) .switch__thumb { transform: translateX(18px); }
.switch:has(input:focus-visible) .switch__track { outline: var(--focus-ring); outline-offset: 2px; }

/* --- segmented control (Mark free / Mark busy, and the theme picker) ------------------ */
.segmented {
  display: flex;
  padding: 4px;
  border-radius: var(--radius-pill);
  background: var(--card);
  border: 1px solid var(--line);
}
.segmented__option {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  border-radius: var(--radius-pill);
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-md);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.segmented__option[aria-current="true"],
.segmented__option[aria-checked="true"] { background: var(--ink); color: var(--paper); }

/* ⚠️ The RADIO version of the same control, which was broken.
   The event sheet's Gig / Rehearsal / Other picker wraps a real radio in each label, but
   the filled pill was drawn from an inline style rendered SERVER-SIDE from the saved
   value. So tapping "Rehearsal" moved the radio and left the black pill on "Gig" — the
   control looked stuck, and the only way to find out it had actually worked was to save.
   The pill now follows the radio, with no JavaScript. */
.segmented__option:has(input[type="radio"]:checked) {
  background: var(--ink);
  color: var(--paper);
}
/* The radio is hidden but NOT removed: it keeps its place in the tab order and stays
   operable by keyboard and to a screen reader, which is the whole reason these are real
   radios rather than links.
   ⚠️ DO NOT add `pointer-events: none` here. It looks harmless on a 1px invisible input
   — nobody can click it directly anyway — but clicking a LABEL works by the browser
   forwarding the click to its control, and Chromium will not forward to a control that is
   not taking pointer events. Adding it left the pill stuck exactly as before, with
   `:has(input:checked)` working perfectly and the radio simply never changing. Tried it;
   found it by clicking, not by reading. */
.segmented input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.segmented__option:has(input:focus-visible) { outline: var(--focus-ring); outline-offset: 2px; }

/* --- app frame ------------------------------------------------------------------------ */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
  padding: 0.875rem var(--gutter) 0.625rem;
  /* ⚠️ Capped like .page, or above 760px the brand drifts to the far left and the avatar
   * to the far right while the content stays centred — the header visibly detaches from
   * the page under it. The artboards are drawn at 390px, where this never shows. */
  max-width: var(--page-max);
  margin-inline: auto;
}
/* The tab bar stays full-bleed — it sits on the bottom edge of the screen — but its four
 * destinations are capped and centred, so between 760px and 1024px they stay under the
 * content they belong to rather than stretching across an empty width. */
.tab-bar { justify-content: center; }
.tab { max-width: calc(var(--page-max) / 4); }
.band-switch {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  min-height: var(--tap-min);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-h2);
  letter-spacing: -0.01em;
  color: inherit;
  text-decoration: none;
}

/* The phone tab bar. Four destinations, aria-current="page" on the live one. */
.tab-bar {
  display: flex;
  border-top: 1px solid var(--line);
  background: var(--paper);
  position: sticky;
  bottom: 0;
  /* Clears the iPhone home indicator. */
  padding-bottom: env(safe-area-inset-bottom, 0);
}
.tab {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  min-height: var(--tap-tab);
  color: var(--muted);
  font-size: var(--text-label-lg);
  font-weight: 500;
  text-decoration: none;
}
/* EVERY tab wears a pill, matching the desktop top-nav exactly (Tank, 27th Sept: "make
   the 4 menu items at the bottom look more apparent as the grey pills like they are on
   the desktop version"). This reverses an earlier judgement of mine that a solid ink pill
   in a bottom bar would read as a button to press; on a phone the four bare words read as
   text rather than as navigation, which is the worse of the two problems. The two navs
   now use one set of values, so "selected" looks the same wherever you are. */
.tab[aria-current="page"] { color: var(--ink); font-weight: 600; }
.tab__label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  /* ⚠️ Narrower than the desktop pill and allowed to shrink. Four pills share the width
     of a 375px phone, and "My dates" is the longest — at the desktop 0.875rem it wraps
     inside its own pill. */
  padding: 0 0.625rem;
  max-width: 100%;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--line) 55%, var(--paper));
  color: var(--ink);
  white-space: nowrap;
}
.tab[aria-current="page"] .tab__label {
  background: var(--ink);
  color: var(--paper);
}

/* --- bottom sheet (Lock a date) -------------------------------------------------------- */
.sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: grid;
  align-items: end;
  z-index: 50;
}
.sheet {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  background: var(--paper);
  color: var(--ink);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  padding: 1.25rem var(--gutter) calc(1.25rem + env(safe-area-inset-bottom, 0));
  max-height: 88vh;
  overflow-y: auto;
}

/* --- save state ------------------------------------------------------------------------ */
.save-state {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-family: var(--font-mono);
  font-size: var(--text-label-lg);
  color: var(--muted);
}
.save-state__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--saved);
}
.save-state[data-state="error"] { color: var(--error); }
.save-state[data-state="error"] .save-state__dot { background: var(--error); }

/* ⚠️ `.notice` had NO rules at all — only `.notice--error` did — so every non-error flash
 * rendered as a bare line of body text above the page heading. "Updated. 27 busy days
 * found." looked like a stray sentence rather than an answer to what you just did. The
 * base class now carries the shape and each modifier only changes the colour. */
.notice {
  border-radius: var(--radius-md);
  padding: 0.75rem 0.875rem;
  background: var(--card);
  border: 1.5px solid var(--line);
  color: var(--ink);
  font-size: var(--text-base);
}
.notice--error {
  border-color: var(--error);
}
.notice--success {
  border-color: var(--success);
  /* A wash of the green rather than a fill: the banner sits directly above the page
   * heading, and a solid block there competes with it for the eye. */
  background: color-mix(in srgb, var(--success) 10%, var(--card));
}
/* The tick is decorative — the sentence already says what happened, and a screen reader
 * announcing "check mark" before it adds nothing.
 *
 * ⚠️ The CSS ESCAPE, not a literal ✓ byte. Two reasons, both met for real. A raw UTF-8
 * character is decoded with whatever charset the stylesheet is served as, and a server that
 * omits the charset renders it as mojibake — which is what the local harness did. And
 * writing the escape through a nested script ate a backslash: `¹` was taken as an OCTAL
 * escape and became ¹, leaving a stray 3 on screen. Written as bytes and verified with xxd. */
.notice--success::before {
  content: "\2713";
  color: var(--success);
  font-weight: 700;
  padding-right: 0.4rem;
}

/* --- desktop --------------------------------------------------------------------------- */
/* The artboards switch to a top nav and a 380px sidebar at 1280. Below that the phone
 * layout simply widens to its 760px cap. */
@media (min-width: 1024px) {
  .tab-bar { display: none; }
  /* Pills rather than bare links (Tank, 27th Sept). The resting fill is a soft mix of
     `--line` so it reads as a control without competing with the page, and the current
     page gets the same solid `--ink` pill the segmented controls use — so "selected"
     looks the same everywhere in the app rather than being a third idea. */
  .top-nav { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
  .top-nav a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min);
    padding: 0 0.875rem;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--line) 55%, var(--paper));
    color: var(--ink);
    font-weight: 600;
    font-size: var(--text-md);
    text-decoration: none;
  }
  .top-nav a:hover { background: var(--line); }
  .top-nav a[aria-current="page"] { background: var(--ink); color: var(--paper); }
  .desktop-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--desktop-aside);
    gap: 2rem;
    align-items: start;
  }
  .page--wide { max-width: var(--desktop-width); }
  /* ⚠️ The header has to widen WITH it. `.app-header` is capped at --page-max for exactly
   * the reason written above its rule — "the header visibly detaches from the page under
   * it" — and `.page--wide` then moves the page out from under it, reproducing that fault
   * in the other direction. On the band view the nav and content started 270px to the LEFT
   * of the logo and ran 270px past the avatar, which reads as the page being blown out.
   * Anything capped at --page-max that sits above a --wide page needs this pair. */
  .app-header--wide { max-width: var(--desktop-width); }

}
@media (max-width: 1023px) {
  .top-nav { display: none; }
}

/* The logo/wordmark as a way home. Deliberately looks like nothing — it is the thing
 * people already try, not a control that needs advertising. */
.app-header__home {
  display: inline-flex;
  align-items: center;
  color: inherit;
  text-decoration: none;
}

/* "← Your bands" above a page heading, for screens with no tab bar to fall back on.
 * Full tap height: it sits near the top edge where a thumb reaches, and a 20px link there
 * is a miss waiting to happen. */
.back-link {
  display: inline-block;
  min-height: var(--tap-min);
  line-height: var(--tap-min);
  color: var(--muted);
  font-size: var(--text-md);
  text-decoration: none;
}
.back-link:hover { color: var(--ink); }
