/* =====================================================================
   Rock The Bluff — shared design system
   =====================================================================
   Carried over from the 2026 WordPress pages so the rebuilt site looks
   like the same festival. The palette, the glass cards, and the six-stop
   spectrum are lifted from legacy/2026/, not reinvented.

   Deliberately single-theme. The festival's identity is a night show on
   a bluff; every legacy page was dark, including the admin trackers.
   Every colour is stated explicitly rather than inherited, so nothing
   depends on the host's theme.

   Load order on a page:
     rtb.css                    (this file)
     rtb-admin.css              (admin screens only)
   ===================================================================== */

:root {
  /* ---- spectrum: the festival's signature run of colour ---- */
  --rtb-1: #ff6b6b;   /* coral   */
  --rtb-2: #f06595;   /* pink    */
  --rtb-3: #cc5de8;   /* violet  */
  --rtb-4: #845ef7;   /* purple  */
  --rtb-5: #5c7cfa;   /* indigo  */
  --rtb-6: #339af0;   /* blue    */

  --spectrum: linear-gradient(90deg,
    var(--rtb-1), var(--rtb-2), var(--rtb-3),
    var(--rtb-4), var(--rtb-5), var(--rtb-6));
  --spectrum-135: linear-gradient(135deg,
    var(--rtb-1) 0%, var(--rtb-2) 20%, var(--rtb-3) 40%,
    var(--rtb-4) 60%, var(--rtb-5) 80%, var(--rtb-6) 100%);

  /* ---- ground ---- */
  --bg-0: #0a0a1a;
  --bg-1: #1a1a2e;
  --bg-2: #16213e;
  --page-bg: linear-gradient(135deg, var(--bg-0) 0%, var(--bg-1) 50%, var(--bg-2) 100%);

  /* The haze over the background illustration, lifted stop for stop from
     the 2026 site. Nearly opaque on purpose: the picture is texture
     behind the words, not a photograph competing with them. Set all
     three to `transparent` to see it bare. */
  --veil-top: rgba(7, 7, 15, .92);
  --veil-mid: rgba(10, 10, 26, .90);
  --veil-bot: rgba(7, 7, 15, .94);

  /* ---- glass surfaces ---- */
  --surface:       rgba(255, 255, 255, 0.05);
  --surface-hover: rgba(255, 255, 255, 0.08);
  --surface-solid: #1c1c30;
  --line:          rgba(255, 255, 255, 0.10);
  --line-strong:   rgba(255, 255, 255, 0.18);

  /* ---- type ---- */
  --ink:      #ffffff;
  --ink-mute: #b8c1ec;
  --ink-dim:  #7a7a9e;
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
          'Helvetica Neue', Arial, sans-serif;

  /* ---- semantic ---- */
  --good:    #20c997;
  --good-2:  #51cf66;
  --warn:    #ffd43b;
  --warn-2:  #fab005;
  --bad:     #fa5252;
  --info:    #22b8cf;

  /* ---- geometry ---- */
  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-pill: 999px;

  --gap-1: 8px;
  --gap-2: 15px;
  --gap-3: 25px;
  --gap-4: 40px;
  --gap-5: 60px;

  --shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  --shadow-lift: 0 18px 44px rgba(0, 0, 0, 0.45);

  --max-w: 1400px;
  --max-w-narrow: 900px;
}

/* ---------------------------------------------------------------------
   Base
   --------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }


/* ---------------------------------------------------------------------
   The page background
   ---------------------------------------------------------------------
   One declaration, on the element that holds the page, exactly as the
   2026 site had it:

     background: <dark gradient>, url(bluff.png) center top / cover;

   That is the whole thing. The picture is sized to cover the page — so
   it is anchored at the top, scaled to fill the width, and whatever runs
   past the foot of the page is simply not seen. Backgrounds are clipped
   to their own box, so nothing here can stretch the scrollbar.

   WHAT THIS REPLACED, AND WHY
   ---------------------------
   A fixed 5000px layer inside an absolutely-positioned clipping box,
   plus a viewport-sized coloured haze on body::after, plus a dark panel
   behind every section on the front page. Four layers of ground where
   the original had one, which is why it read as far darker than the site
   it was copied from and why the artwork looked cropped in a way the old
   one never did.

   The gradient is the original's, stop for stop. It is nearly opaque on
   purpose: the picture is meant to read as texture behind the words, not
   as a photograph competing with them. To see more of the artwork, lift
   the middle stop; to see the whole picture rather than a cover crop,
   change `cover` to `100% auto`.

   `html` carries the flat colour underneath so that overscroll, and any
   page shorter than the viewport, still land on festival navy rather
   than on white.
   --------------------------------------------------------------------- */
html { background-color: var(--bg-0); }

body {
  margin: 0;
  position: relative;
  min-height: 100vh;
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;

  background:
    linear-gradient(180deg, var(--veil-top) 0%, var(--veil-mid) 50%, var(--veil-bot) 100%),
    url('../img/background.png') center top / cover no-repeat;
  background-color: var(--bg-1);
}

a { color: var(--rtb-6); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 2px solid var(--rtb-2);
  outline-offset: 3px;
  border-radius: 4px;
}

img { max-width: 100%; display: block; }

::selection { background: var(--rtb-4); color: #fff; }

/* ---------------------------------------------------------------------
   Layout
   --------------------------------------------------------------------- */
.rtb-page {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: var(--gap-5) var(--gap-3) 100px;
}
.rtb-page--narrow { max-width: var(--max-w-narrow); }

.rtb-header {
  text-align: center;
  margin-bottom: var(--gap-4);
  padding-bottom: var(--gap-3);
  border-bottom: 3px solid var(--line);
  position: relative;
}
.rtb-header::after {
  content: '';
  position: absolute;
  bottom: -3px; left: 50%;
  transform: translateX(-50%);
  width: 250px; height: 3px;
  background: var(--spectrum);
  border-radius: var(--r-pill);
}

/* The signature gradient wordmark. */
.rtb-title {
  font-size: clamp(2.2rem, 6vw, 4rem);
  font-weight: 900;
  line-height: 1.1;
  margin: 0 0 var(--gap-2);
  padding: 0 10px;
  text-transform: uppercase;
  letter-spacing: 4px;
  display: inline-block;
  background: var(--spectrum-135);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--rtb-3); /* fallback where background-clip is unsupported */
}

.rtb-subtitle {
  color: var(--ink-mute);
  font-size: 1.15rem;
  line-height: 1.6;
  max-width: 800px;
  margin: 0 auto;
}

.rtb-section { margin-top: var(--gap-5); }

.rtb-section-title {
  font-size: 1.6rem;
  font-weight: 800;
  margin: 0 0 var(--gap-2);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.rtb-section-desc { color: var(--ink-dim); margin: 0 0 var(--gap-3); }

/* Auto-fitting card grid — the shape every listing page uses. */
.rtb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: var(--gap-3);
}
.rtb-grid--wide { grid-template-columns: repeat(auto-fill, minmax(420px, 1fr)); }
.rtb-grid--tight { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--gap-2); }

/* ---------------------------------------------------------------------
   Cards
   --------------------------------------------------------------------- */
.rtb-card {
  background: var(--surface);
  backdrop-filter: blur(10px);
  border: 2px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--gap-3);
  display: flex;
  flex-direction: column;
  gap: var(--gap-2);
  min-width: 0;
  position: relative;
  transition: transform .3s ease, border-color .3s ease, box-shadow .3s ease;
}
.rtb-card:hover {
  transform: translateY(-4px);
  border-color: var(--line-strong);
  box-shadow: var(--shadow-lift);
}
.rtb-card--flat:hover { transform: none; box-shadow: none; }

.rtb-card-media {
  height: 220px;
  margin: calc(var(--gap-3) * -1) calc(var(--gap-3) * -1) 0;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background-size: cover;
  background-position: center;
  background-color: var(--bg-1);
  position: relative;
}
.rtb-card-media::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, transparent 40%, rgba(10,10,26,.75) 100%);
}

.rtb-card-name { font-size: 1.35rem; font-weight: 800; margin: 0; line-height: 1.25; }
.rtb-card-kicker {
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--rtb-2);
}
.rtb-card-body { color: var(--ink-mute); font-size: .95rem; margin: 0; }

/* Cards in a row are already the same height — the grid stretches them.
   What made them look ragged is that the detail rows and the buttons sat
   directly under a bio, so a two-line bio put its buttons half way up the
   card while the long one put them at the foot. `margin-top: auto` in a
   flex column eats the leftover space above the footer instead of below
   it, so every card's footer lands on the same line.

   Two rules, because a card may have rows, buttons, or both: whichever
   comes first takes the space, and the sibling selector stops the second
   one from claiming a share and stranding the pair in the middle. */
.rtb-card > .rtb-rows,
.rtb-card > .rtb-actions { margin-top: auto; }
.rtb-card > .rtb-rows ~ .rtb-actions { margin-top: 0; }

/* Label/value rows inside a card. */
.rtb-rows { display: flex; flex-direction: column; gap: 6px; }
.rtb-row {
  display: flex;
  gap: 10px;
  align-items: baseline;
  font-size: .9rem;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(255,255,255,.06);
}
.rtb-row:last-child { border-bottom: 0; padding-bottom: 0; }
.rtb-row-label { color: var(--ink-dim); flex: 0 0 auto; min-width: 110px; }
.rtb-row-value { color: var(--ink); min-width: 0; word-break: break-word; }
.rtb-row-value--hl { color: var(--warn); font-weight: 700; }

/* ---------------------------------------------------------------------
   Social circles
   ---------------------------------------------------------------------
   RTB.ui.socialLinks(record, { round: true }). The 2026 lineup put these
   under a rule at the foot of each card, as marks rather than labelled
   buttons, which is what let a card carry five of them without turning
   into a toolbar.
   --------------------------------------------------------------------- */
.rtb-socials { display: flex; gap: 12px; flex-wrap: wrap; }
.rtb-social {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--line);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
  transition: transform .3s ease, background .3s ease,
              border-color .3s ease, box-shadow .3s ease;
}
.rtb-social:hover,
.rtb-social:focus-visible {
  background: linear-gradient(135deg, var(--rtb-4), var(--rtb-5));
  border-color: var(--rtb-4);
  transform: translateY(-3px);
  box-shadow: 0 5px 15px rgba(132, 94, 247, .4);
  text-decoration: none;
}

/* ---------------------------------------------------------------------
   Badges and pills
   --------------------------------------------------------------------- */
.rtb-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: var(--r-pill);
  font-size: .75rem;
  font-weight: 800;
  letter-spacing: .5px;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
}

/* One class per application status, so every screen reads the same. */
/* Quieter than every other status on purpose. A prospect is asking
   nothing of you, and should not compete with a real application for
   attention in a list. */
.rtb-badge--prospect     { background: rgba(255,255,255,.05); color: var(--ink-dim); border-color: var(--line); }
.rtb-badge--submitted    { background: rgba(34,184,207,.15);  color: var(--info);   border-color: rgba(34,184,207,.35); }
.rtb-badge--under_review { background: rgba(92,124,250,.15);  color: var(--rtb-5);  border-color: rgba(92,124,250,.35); }
.rtb-badge--shortlisted  { background: rgba(204,93,232,.15);  color: var(--rtb-3);  border-color: rgba(204,93,232,.35); }
.rtb-badge--approved     { background: rgba(32,201,151,.15);  color: var(--good);   border-color: rgba(32,201,151,.35); }
.rtb-badge--confirmed    { background: rgba(81,207,102,.18);  color: var(--good-2); border-color: rgba(81,207,102,.4); }
.rtb-badge--waitlisted   { background: rgba(250,176,5,.15);   color: var(--warn-2); border-color: rgba(250,176,5,.35); }
.rtb-badge--declined,
.rtb-badge--cancelled,
.rtb-badge--withdrawn    { background: rgba(250,82,82,.15);   color: var(--bad);    border-color: rgba(250,82,82,.35); }
.rtb-badge--neutral      { background: rgba(255,255,255,.08); color: var(--ink-mute); border-color: var(--line); }

.rtb-badge--count {
  background: var(--spectrum-135);
  color: #fff;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------- */
.rtb-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 24px;
  border: 2px solid transparent;
  border-radius: var(--r-pill);
  font: inherit;
  font-weight: 700;
  color: #fff;
  background: var(--surface);
  border-color: var(--line);
  cursor: pointer;
  text-decoration: none;
  transition: transform .2s ease, background .2s ease, border-color .2s ease, opacity .2s ease;
}
.rtb-btn:hover { transform: translateY(-2px); background: var(--surface-hover); text-decoration: none; }
.rtb-btn:disabled, .rtb-btn[aria-disabled='true'] {
  opacity: .5; cursor: not-allowed; transform: none;
}

.rtb-btn--primary {
  background: linear-gradient(135deg, var(--rtb-1), var(--rtb-4));
  border-color: transparent;
}
.rtb-btn--good   { background: linear-gradient(135deg, var(--good), var(--good-2)); border-color: transparent; color: #05271c; }
.rtb-btn--danger { background: rgba(250,82,82,.15); border-color: rgba(250,82,82,.45); color: var(--bad); }
.rtb-btn--sm     { padding: 7px 14px; font-size: .85rem; }
.rtb-btn--block  { width: 100%; }

.rtb-actions { display: flex; flex-wrap: wrap; gap: var(--gap-1); }

/* ---------------------------------------------------------------------
   Forms
   --------------------------------------------------------------------- */
.rtb-form { display: flex; flex-direction: column; gap: var(--gap-3); }

.rtb-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.rtb-field--row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-2); }
@media (max-width: 640px) { .rtb-field--row { grid-template-columns: 1fr; } }

.rtb-label { font-weight: 700; font-size: .95rem; }
.rtb-label .req { color: var(--rtb-1); }
.rtb-hint { color: var(--ink-dim); font-size: .85rem; }

.rtb-input, .rtb-select, .rtb-textarea {
  width: 100%;
  padding: 13px 16px;
  background: rgba(255,255,255,.06);
  border: 2px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink);
  font: inherit;
  transition: border-color .2s ease, background .2s ease;
}
.rtb-input::placeholder, .rtb-textarea::placeholder { color: var(--ink-dim); }
.rtb-input:focus, .rtb-select:focus, .rtb-textarea:focus {
  outline: none;
  border-color: var(--rtb-4);
  background: rgba(255,255,255,.09);
}
.rtb-textarea { min-height: 130px; resize: vertical; }
.rtb-select { appearance: none; cursor: pointer; }
/* The arrow has to be inline — a strict CSP blocks external assets. */
.rtb-select {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23b8c1ec'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 40px;
}
.rtb-select option { background: var(--surface-solid); color: var(--ink); }

.rtb-input--invalid, .rtb-select--invalid, .rtb-textarea--invalid {
  border-color: var(--bad);
}
.rtb-error-text { color: var(--bad); font-size: .85rem; }

/* Checkbox / radio groups */
.rtb-choices { display: flex; flex-direction: column; gap: 10px; }
.rtb-choices--grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
}
.rtb-choice {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 14px;
  background: rgba(255,255,255,.04);
  border: 2px solid var(--line);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color .2s ease, background .2s ease;
}
.rtb-choice:hover { background: var(--surface-hover); }
.rtb-choice input { margin-top: 3px; accent-color: var(--rtb-4); flex: 0 0 auto; }
.rtb-choice:has(input:checked) { border-color: var(--rtb-4); background: rgba(132,94,247,.12); }

.rtb-file {
  padding: 20px;
  border: 2px dashed var(--line-strong);
  border-radius: var(--r-md);
  text-align: center;
  cursor: pointer;
  background: rgba(255,255,255,.03);
}
.rtb-file:hover { border-color: var(--rtb-4); }

/* ---------------------------------------------------------------------
   Stats
   --------------------------------------------------------------------- */
.rtb-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--gap-2);
  margin-bottom: var(--gap-4);
}
.rtb-stat {
  background: var(--surface);
  border: 2px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--gap-2) var(--gap-3);
  text-align: center;
}
.rtb-stat-num {
  font-size: 2.2rem;
  font-weight: 900;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  background: var(--spectrum-135);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--rtb-3);
}
.rtb-stat-label {
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-top: 4px;
}

.rtb-bar { height: 10px; background: rgba(255,255,255,.08); border-radius: var(--r-pill); overflow: hidden; }
.rtb-bar-fill { height: 100%; background: var(--spectrum); border-radius: inherit; transition: width .6s ease; }

/* ---------------------------------------------------------------------
   Tables — wide content scrolls in its own container, never the body
   --------------------------------------------------------------------- */
.rtb-scroller {
  overflow-x: auto;
  border: 2px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
}
.rtb-table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.rtb-table th, .rtb-table td {
  padding: 12px 16px;
  text-align: left;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.rtb-table thead th {
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--ink-dim);
  background: rgba(255,255,255,.04);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 1;
}
.rtb-table tbody tr:last-child td { border-bottom: 0; }
.rtb-table tbody tr:hover { background: rgba(255,255,255,.03); }
.rtb-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rtb-table .neg { color: var(--bad); }

/* ---------------------------------------------------------------------
   States: loading, empty, error
   Every list renders one of these instead of a blank screen.
   --------------------------------------------------------------------- */
.rtb-state {
  text-align: center;
  padding: var(--gap-5) var(--gap-3);
  color: var(--ink-mute);
  border: 2px dashed var(--line);
  border-radius: var(--r-lg);
  background: rgba(255,255,255,.02);
}
.rtb-state-icon { font-size: 2.6rem; margin-bottom: var(--gap-1); }
.rtb-state-title { font-size: 1.2rem; font-weight: 800; color: var(--ink); margin: 0 0 6px; }
.rtb-state--error { border-color: rgba(250,82,82,.4); background: rgba(250,82,82,.06); }
.rtb-state--error .rtb-state-title { color: var(--bad); }

.rtb-spinner {
  width: 34px; height: 34px;
  margin: 0 auto var(--gap-2);
  border: 3px solid var(--line);
  border-top-color: var(--rtb-2);
  border-radius: 50%;
  animation: rtb-spin .8s linear infinite;
}
@keyframes rtb-spin { to { transform: rotate(360deg); } }

/* Skeleton rows while data loads. */
.rtb-skel {
  background: linear-gradient(90deg, rgba(255,255,255,.05), rgba(255,255,255,.12), rgba(255,255,255,.05));
  background-size: 200% 100%;
  animation: rtb-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--r-sm);
  height: 14px;
}
@keyframes rtb-shimmer { to { background-position: -200% 0; } }

/* ---------------------------------------------------------------------
   Notices and toasts
   --------------------------------------------------------------------- */
.rtb-notice {
  border-left: 4px solid var(--rtb-4);
  background: rgba(132,94,247,.10);
  padding: var(--gap-2) var(--gap-3);
  border-radius: 0 var(--r-md) var(--r-md) 0;
}
.rtb-notice--good { border-color: var(--good); background: rgba(32,201,151,.10); }
.rtb-notice--warn { border-color: var(--warn); background: rgba(255,212,59,.10); }
.rtb-notice--bad  { border-color: var(--bad);  background: rgba(250,82,82,.10); }

.rtb-toasts {
  position: fixed;
  right: 20px; bottom: 20px;
  z-index: 999;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: min(420px, calc(100vw - 40px));
}
.rtb-toast {
  background: var(--surface-solid);
  border: 2px solid var(--line-strong);
  border-left-width: 4px;
  border-radius: var(--r-md);
  padding: 13px 18px;
  box-shadow: var(--shadow);
  animation: rtb-slide-in .25s ease;
}
.rtb-toast--good { border-left-color: var(--good); }
.rtb-toast--bad  { border-left-color: var(--bad); }
.rtb-toast--info { border-left-color: var(--rtb-6); }
@keyframes rtb-slide-in { from { opacity: 0; transform: translateX(20px); } }

/* ---------------------------------------------------------------------
   Preview mode  (?preview=1)
   ---------------------------------------------------------------------
   Sections switched off in the admin still render, marked, so you can
   look at one before putting it in front of anybody. Deliberately ugly:
   nobody should mistake a preview for the live page.
   --------------------------------------------------------------------- */
.rtb-preview-bar {
  background: repeating-linear-gradient(
    135deg, rgba(250,176,5,.16) 0 12px, rgba(250,176,5,.08) 12px 24px);
  border-bottom: 2px solid var(--warn-2);
  color: var(--ink);
  padding: 10px var(--gap-3);
  font-size: .9rem;
  text-align: center;
}
.rtb-preview-off {
  position: relative;
  border: 2px dashed var(--warn-2);
  border-radius: var(--r-md);
  padding: 26px var(--gap-2) var(--gap-2);
  margin-block: var(--gap-2);
  background: rgba(250,176,5,.05);
}
.rtb-preview-tag {
  position: absolute;
  top: 0; left: 0;
  background: var(--warn-2);
  color: #241a00;
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  padding: 3px 12px;
  border-radius: 0 0 var(--r-sm) 0;
}
/* A nav link that the public is not currently being shown. */
.rtb-nav-links a.rtb-nav-hidden {
  opacity: .45;
  text-decoration: line-through;
}

/* ---------------------------------------------------------------------
   Photo marquees — the scrolling card rows on the front page
   ---------------------------------------------------------------------
   Carried over from the 2026 site, where every group of people got one:
   bands, artists, vendors, sponsors. Each card is a link into that
   group's page.

   The track holds the set twice and slides exactly one set-width, which
   is what makes the loop seamless — half the track is a copy of the
   other half, so the moment it wraps is the moment it repeats.
   --------------------------------------------------------------------- */
/* One gallery block: the heading, then the rail. Generous space above so
   the rows read as separate sections rather than one long conveyor. */
/* One gallery block: the heading, then the rail. Generous space above so
   the rows read as separate sections rather than one long conveyor. */
.rtb-gallery { padding: 46px 0 10px; max-width: 100%; overflow: hidden; }
.rtb-kicker {
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--rtb-2);
  margin-bottom: 6px;
}
.rtb-marquee-head { display: flex; align-items: baseline; gap: var(--gap-2); flex-wrap: wrap; padding: 0 var(--gap-3); }
.rtb-marquee-head .rtb-section-title { margin: 0; }
.rtb-marquee-more { margin-left: auto; font-weight: 700; font-size: .9rem; white-space: nowrap; }

.rtb-photo-marquee {
  overflow: hidden;
  margin: 14px 0 0;
  /* Faded at both ends so cards enter and leave rather than being cut. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
.rtb-photo-track {
  display: flex;
  gap: 18px;
  width: max-content;
  animation: rtb-photoscroll 60s linear infinite;
}
/* Stop it so people can actually click a card. */
.rtb-photo-marquee:hover .rtb-photo-track,
.rtb-photo-marquee:focus-within .rtb-photo-track { animation-play-state: paused; }
@keyframes rtb-photoscroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-50% - 9px)); }   /* half the track, minus half a gap */
}

.rtb-photo {
  position: relative;
  flex: 0 0 auto;
  width: 230px; height: 290px;
  border-radius: var(--r-lg);
  background-size: cover;
  background-position: center;
  background-color: var(--surface-solid);
  border: 1px solid var(--line);
  box-shadow: 0 14px 40px rgba(0,0,0,.4);
  filter: grayscale(.25);
  transition: transform .35s ease, filter .35s ease, border-color .35s ease;
  overflow: hidden;
  text-decoration: none;
  display: block;
}
.rtb-photo:hover, .rtb-photo:focus-visible {
  transform: translateY(-8px) scale(1.03);
  filter: grayscale(0);
  border-color: var(--line-strong);
  text-decoration: none;
}
.rtb-photo-name {
  position: absolute;
  inset: auto 0 0 0;
  padding: 26px 14px 12px;
  background: linear-gradient(180deg, transparent, rgba(10,10,26,.9));
  color: var(--ink);
  font-weight: 800;
  font-size: .92rem;
  line-height: 1.25;
}
.rtb-photo-sub {
  display: block;
  font-weight: 600;
  font-size: .76rem;
  color: var(--ink-mute);
  margin-top: 2px;
}
@media (max-width: 700px) {
  .rtb-photo { width: 172px; height: 220px; }
  .rtb-marquee-head { padding: 0 var(--gap-2); }
}


/* ---------------------------------------------------------------------
   Last year's photographs
   ---------------------------------------------------------------------
   A mosaic rather than another scrolling rail: these are memories, not a
   catalogue, and they deserve to sit still long enough to look at.

   Every image is loading="lazy". The full set is 111 photographs and 22
   megabytes, so the page shows a spread of them and links to the rest
   rather than making every visitor pay for the lot.
   --------------------------------------------------------------------- */
.rtb-photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
.rtb-photos a {
  position: relative;
  display: block;
  aspect-ratio: 3 / 2;
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--surface);
  transition: transform .3s ease, border-color .3s ease;
}
.rtb-photos img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .5s ease, filter .4s ease;
  filter: saturate(.9);
}
.rtb-photos a:hover { border-color: var(--line-strong); transform: translateY(-3px); }
.rtb-photos a:hover img { transform: scale(1.05); filter: saturate(1.1); }
/* The first one gets the room it deserves on a wide screen. */
@media (min-width: 900px) {
  .rtb-photos a:first-child { grid-column: span 2; grid-row: span 2; }
}

/* ---------------------------------------------------------------------
   The poster
   ---------------------------------------------------------------------
   Drawn, not photographed: the 2026 site built its poster out of type so
   it could never disagree with the lineup. Same here, except the dates
   and the headliners come from the database, so it cannot go stale at
   all — change a headliner in the admin and the poster changes with it.
   --------------------------------------------------------------------- */
.rtb-poster {
  position: relative;
  max-width: 620px;
  margin: 0 auto;
  padding: 42px 30px 36px;
  text-align: center;
  border-radius: var(--r-lg);
  border: 1px solid var(--line-strong);
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(132,94,247,.28), transparent 60%),
    linear-gradient(180deg, rgba(10,10,26,.92), rgba(22,33,62,.92));
  box-shadow: var(--shadow-lift);
  overflow: hidden;
}
.rtb-poster::before {
  content: '';
  position: absolute;
  inset: 8px;
  border: 1px solid rgba(255,255,255,.12);
  border-radius: var(--r-md);
  pointer-events: none;
}
.rtb-poster-eyebrow {
  font-size: .72rem; font-weight: 800; letter-spacing: 2.5px;
  text-transform: uppercase; color: var(--ink-mute);
}
.rtb-poster-name {
  font-size: clamp(1.9rem, 6vw, 3rem);
  font-weight: 900;
  letter-spacing: 2px;
  line-height: 1.05;
  margin: 14px 0 6px;
  background: var(--spectrum-135);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--rtb-3);
}
.rtb-poster-dates {
  font-size: 1rem; font-weight: 800; letter-spacing: 3px;
  text-transform: uppercase; color: var(--ink);
}
.rtb-poster-rule {
  height: 2px; margin: 20px auto; max-width: 240px;
  background: var(--spectrum);
  border-radius: var(--r-pill);
}
.rtb-poster-act {
  font-weight: 900;
  letter-spacing: 1px;
  text-transform: uppercase;
  line-height: 1.25;
}
.rtb-poster-act--top { font-size: clamp(1.15rem, 3.4vw, 1.6rem); color: var(--ink); }
.rtb-poster-act--rest { font-size: 1rem; color: var(--ink-mute); }
.rtb-poster-day {
  display: block;
  font-size: .66rem; font-weight: 800; letter-spacing: 2px;
  text-transform: uppercase; color: var(--rtb-2);
  margin-bottom: 3px;
}
.rtb-poster-rows { display: flex; flex-direction: column; gap: 16px; }
.rtb-poster-foot { margin-top: 22px; font-size: .8rem; color: var(--ink-dim); letter-spacing: 1px; }

/* Said on the poster itself as well as on the page, because the poster
   is the half that gets shared and it has to carry its own caveat. */
.rtb-poster-more {
  margin-top: 20px;
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--rtb-2);
}
.rtb-poster-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 20px;
}
.rtb-poster-hint { text-align: center; font-size: .85rem; color: var(--ink-dim); margin: 10px 0 0; }

/* ---------------------------------------------------------------------
   Explore grid
   --------------------------------------------------------------------- */
.rtb-explore { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.rtb-explore-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 22px 20px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  transition: transform .25s ease, border-color .25s ease, background .25s ease;
}
.rtb-explore-card:hover {
  transform: translateY(-4px);
  border-color: var(--line-strong);
  background: var(--surface-hover);
  text-decoration: none;
}
.rtb-explore-icon { font-size: 1.9rem; line-height: 1; }
.rtb-explore-title { font-size: 1.05rem; font-weight: 800; margin: 4px 0 0; }
.rtb-explore-desc { font-size: .9rem; color: var(--ink-mute); margin: 0; flex: 1; }
.rtb-explore-go { font-size: .85rem; font-weight: 700; color: var(--rtb-6); margin-top: 6px; }

/* ---------------------------------------------------------------------
   Site chrome
   --------------------------------------------------------------------- */
/* The stickiness lives on the wrapper, not on the nav itself.
   shell.js mounts <nav class="rtb-nav"> inside <div id="rtb-nav">, and a
   sticky element can only travel within its own parent's box — a parent
   exactly as tall as the nav gives it nowhere to go, so it scrolled away
   like any static header. The wrapper is a child of <body>, which is the
   full height of the page, so this is where the rule belongs.

   .rtb-nav keeps a sticky of its own for any page that renders the bar
   without the wrapper; inside the wrapper it is a no-op. */
#rtb-nav {
  position: sticky;
  top: 0;
  z-index: 50;
}
.rtb-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(10,10,26,.85);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}
.rtb-nav-inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 10px var(--gap-3);
  display: flex;
  align-items: center;
  gap: var(--gap-2);
  flex-wrap: wrap;
}
.rtb-nav-brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.rtb-nav-brand:hover { text-decoration: none; }
/* The logo is a stacked wordmark — ROCK / THE BLUFF / MUSIC FESTIVAL —
   so it needs height to stay readable. Squeezed into a 22px strip it is
   a red smudge. 46px also makes the whole brand a comfortable tap. */
/* 75px, and capped so it can never be bigger. The cap matters because
   the <img> also carries width/height attributes: if the stylesheet is
   slow, cached stale, or missing entirely, the browser still draws it at
   75px instead of the file's native 512. */
.rtb-nav-logo {
  height: 75px;
  max-height: 75px;
  width: auto;
  max-width: 100%;
  display: block;
}
/* Only rendered when there is no logo, or the logo fails to load. */
.rtb-nav-word { font-weight: 900; text-transform: uppercase; letter-spacing: 2px; }

.rtb-nav-links {
  display: flex;
  align-items: center;
  gap: var(--gap-2);
  row-gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
  margin-left: auto;
}
.rtb-nav-links a {
  color: var(--ink-mute);
  font-weight: 600;
  font-size: .95rem;
  text-decoration: none;
  padding: 4px 2px;
  border-bottom: 2px solid transparent;
}
.rtb-nav-links a:hover { color: var(--ink); text-decoration: none; }
.rtb-nav-links a[aria-current='page'] { color: var(--ink); border-bottom-color: var(--rtb-2); }

.rtb-foot {
  border-top: 1px solid var(--line);
  padding: var(--gap-4) var(--gap-3);
  text-align: center;
  color: var(--ink-dim);
  font-size: .9rem;
}
.rtb-foot a { color: var(--ink-mute); }

/* ---------------------------------------------------------------------
   Utilities
   --------------------------------------------------------------------- */
.rtb-stack   { display: flex; flex-direction: column; gap: var(--gap-2); }
.rtb-inline  { display: flex; align-items: center; gap: var(--gap-1); flex-wrap: wrap; }
.rtb-between { display: flex; align-items: center; justify-content: space-between; gap: var(--gap-2); flex-wrap: wrap; }
.rtb-spread  { margin-left: auto; }
.rtb-mute    { color: var(--ink-mute); }
.rtb-dim     { color: var(--ink-dim); }
.rtb-num     { font-variant-numeric: tabular-nums; }
.rtb-hidden  { display: none !important; }
.rtb-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------------------------------------------------------------------
   The nav on a narrow screen
   ---------------------------------------------------------------------
   No hamburger. Ten links behind a button is ten links a first-time
   visitor has to guess are there, and this is a festival site read by
   people who have never seen it before — the menu is the map.

   So every link stays on screen and the row wraps. Two things make a
   wrapped row look deliberate rather than broken: the links become
   pills, so a short last row reads as a shape instead of a stranded
   sentence, and they sit in a block of their own under a hairline
   rather than trailing off the end of the brand.

   The switch happens at 1024px rather than at a phone width because that
   is where ten links plus the logo stop fitting on one line comfortably;
   below it they were technically on one row and crammed against both
   edges, which is the ugly middle this was meant to remove.

   The cost is height, which is why the nav stops being sticky here.
   Three rows of pills welded to the top of a 667px phone would cost a
   third of every screen below it; at this width the nav is something
   you scroll back to, not something that follows you.
   --------------------------------------------------------------------- */
@media (max-width: 1024px) {
  #rtb-nav, .rtb-nav { position: static; }
  .rtb-nav-inner {
    padding: 10px var(--gap-2) 12px;
    gap: 10px;
  }
  /* The year belongs to the brand — "Rock The Bluff 2027" — so it stays
     beside the logo rather than being pushed to the far edge of an empty
     row. The links wrap to a line of their own regardless. */
  .rtb-nav-logo { height: 64px; max-height: 64px; }

  .rtb-nav-links {
    width: 100%;
    margin-left: 0;
    justify-content: center;
    gap: 7px;
    padding-top: 11px;
    border-top: 1px solid var(--line);
  }
  .rtb-nav-links a {
    padding: 9px 14px;
    font-size: .85rem;
    line-height: 1;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface);
    color: var(--ink-mute);
  }
  .rtb-nav-links a:hover { background: var(--surface-hover); }
  /* Same pink as the desktop underline, as a filled pill. Written with
     the attribute in the selector so it outranks the base rule's
     border-bottom-color and the pill keeps an even edge. */
  .rtb-nav-links a[aria-current='page'] {
    color: var(--ink);
    background: rgba(240,101,149,.18);
    border-color: rgba(240,101,149,.55);
  }
}

@media (max-width: 700px) {
  .rtb-page { padding: var(--gap-4) var(--gap-2) 80px; }
  .rtb-grid { grid-template-columns: 1fr; }
  .rtb-title { letter-spacing: 2px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ---------------------------------------------------------------------
   The boot screen
   ---------------------------------------------------------------------
   Every page on this site is empty markup until the database answers, so
   there is always a moment to cover. It used to be covered by the word
   "Loading…" and then a lurch as each block landed. This holds a stage
   over the whole thing and lifts it once the page has drawn.

   It is built out of the two pseudo-elements of <html> rather than an
   element in the markup, for one reason that matters: the stylesheet is
   in the <head> and blocks the first paint, so the curtain is already
   down before anything behind it has been drawn. Markup injected by a
   script at the foot of the body would arrive after the browser had
   painted, which is a flash of the very thing it is meant to hide.

   ::before  the ground and two slow stage lights
   ::after   the wordmark, with the spectrum sweeping through it

   Lifted by adding `rtb-ready` to <html>, which RTB.ui.page() does once
   the page's own work has settled — including when it settles by
   failing, because an error screen behind a curtain helps nobody.

   AND, IF THAT NEVER HAPPENS: the `rtb-boot-giveup` animation takes the
   curtain up by itself after eight seconds. A page whose scripts never
   loaded must not be a locked door — the boot guard's own message is
   behind this.
   --------------------------------------------------------------------- */
html::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 9998;
  background:
    radial-gradient(closest-side, rgba(132,94,247,.45), transparent 70%) 18% 18%/70vmax 70vmax no-repeat,
    radial-gradient(closest-side, rgba(255,107,107,.32), transparent 70%) 82% 74%/62vmax 62vmax no-repeat,
    linear-gradient(135deg, var(--bg-0) 0%, var(--bg-1) 50%, var(--bg-2) 100%);
  animation:
    rtb-boot-lights 3.4s ease-in-out infinite alternate,
    rtb-boot-giveup 1ms linear 8s forwards;
}

html::after {
  content: 'ROCK THE BLUFF';
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: grid;
  place-content: center;
  text-align: center;
  padding: 0 24px;
  font-family: var(--font);
  font-weight: 900;
  font-size: clamp(1.5rem, 6.5vw, 3.4rem);
  line-height: 1.1;
  /* The trailing letter-space would throw the centring off by a fifth of
     a character; the indent puts it back on the other side. */
  letter-spacing: .22em;
  text-indent: .22em;
  background: linear-gradient(90deg,
    var(--rtb-1), var(--rtb-2), var(--rtb-3), var(--rtb-4),
    var(--rtb-5), var(--rtb-6), var(--rtb-1));
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--rtb-3);   /* fallback where background-clip: text is unsupported */
  animation:
    rtb-boot-sweep 2.6s linear infinite,
    rtb-boot-giveup 1ms linear 8s forwards;
}

@keyframes rtb-boot-lights {
  from { background-position: 18% 18%, 82% 74%, 0 0; }
  to   { background-position: 34% 30%, 66% 62%, 0 0; }
}
@keyframes rtb-boot-sweep { to { background-position: -200% 0; } }
@keyframes rtb-boot-giveup { to { opacity: 0; visibility: hidden; } }

/* Up it goes. `animation: none` matters as much as the opacity: without
   it the give-up animation would still be sitting on the property and
   the transition would have nothing to move. */
html.rtb-ready::before,
html.rtb-ready::after {
  animation: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity .5s ease, visibility 0s linear .5s;
}

@media print {
  body { background: #fff; color: #000; }
  .rtb-nav, .rtb-toasts, .rtb-actions { display: none !important; }
  .rtb-card { border-color: #ccc; break-inside: avoid; }
}
