/* VYBE.FIT — the one design system.
 *
 * Used by the marketing site (vybe.fit), the club and member portal
 * (<club>.vybe.fit and app.vybe.fit) and the platform console (mc.vybe.fit).
 * The public site's own stylesheet, public/assets/vybe.css, is the source of
 * these tokens; they are repeated here on purpose so the three surfaces can
 * never drift apart.
 *
 * Josh, 2026-09-13: the whole platform follows the www branding, and it has to
 * look like a full platform, not an admin tool.
 *
 * Light theme only. That is a hard rule across everything Josh ships.
 *
 * A club supplies a logo, one accent colour and a hero image; those flow into
 * --accent and the slots that use it. Everything else — layout, type, motion —
 * is always VYBE.FIT.
 */

:root {
  color-scheme: light;

  --ground: #fffaf6;
  --card: #ffffff;
  --ink: #1c1633;
  --ink2: #5c5773;
  --ink3: #9a95ad;
  --line: #f0e7e1;
  --line-soft: #f7f1ec;

  --coral: #ff4d6d;
  --orange: #ff8c42;
  --sun: #ffc93c;
  --mint: #1fc8a0;
  --sky: #3fb6ff;
  --violet: #7c5cff;

  --hot: linear-gradient(100deg, var(--coral), var(--orange) 50%, var(--sun));
  --cool: linear-gradient(120deg, var(--violet), var(--sky) 55%, var(--mint));

  /* A club may replace this one colour. Nothing else. */
  --accent: var(--coral);

  --display: "Unbounded", "Arial Rounded MT Bold", "Segoe UI", system-ui, sans-serif;
  --body: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --radius: 26px;
  --radius-sm: 14px;
  --shadow: 0 1px 2px rgba(28,22,51,.04), 0 12px 40px -12px rgba(28,22,51,.14);
  --shadow-lift: 0 2px 4px rgba(28,22,51,.05), 0 24px 60px -20px rgba(28,22,51,.26);
}

* { box-sizing: border-box; }

/* ⚠️⚠️ THE `hidden` ATTRIBUTE MUST ACTUALLY HIDE THINGS.
   The browser's own rule is [hidden]{display:none}, and ANY display of ours
   overrides it — every box below has one. On the watch page that put the
   countdown and the live class SIDE BY SIDE: the script set hidden, the
   stylesheet said `display:grid`, and the stylesheet won. It read as a layout
   bug and was a specificity one. */
[hidden] { display: none !important; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--ground);
  color: var(--ink);
  font: 16px/1.6 var(--body);
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
img, svg { max-width: 100%; }

h1, h2, h3 { font-family: var(--display); font-weight: 700; letter-spacing: .005em; margin: 0; }
h1 { font-size: clamp(1.5rem, 3.4vw, 2.1rem); }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }
p  { margin: 0 0 14px; color: var(--ink2); }

/* ---------------------------------------------------------------- the aura
   The drifting colour behind everything, exactly as on the public site. It is
   decoration only, so it never takes a click. */
.aura { position: fixed; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; }
.aura i {
  position: absolute; border-radius: 50%; filter: blur(80px); opacity: .42;
  animation: drift 22s ease-in-out infinite alternate;
}
.aura i:nth-child(1) { width: 44vmax; height: 44vmax; background: #ffd0d9; top: -20vmax; left: -14vmax; }
.aura i:nth-child(2) { width: 36vmax; height: 36vmax; background: #ffe3b8; top: 4vmax;  right: -16vmax; animation-duration: 26s; animation-delay: -6s; }
.aura i:nth-child(3) { width: 32vmax; height: 32vmax; background: #d6f5ec; bottom: -18vmax; left: 16vmax; animation-duration: 30s; animation-delay: -12s; }
@keyframes drift {
  0%   { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(4vmax, 3vmax) scale(1.08); }
  100% { transform: translate(-3vmax, 5vmax) scale(.96); }
}
@media (prefers-reduced-motion: reduce) {
  .aura i { animation: none; }
}

/* ------------------------------------------------------------- the top bar */
.app-bar {
  position: sticky; top: 0; z-index: 30;
  background: rgba(255,250,246,.78);
  backdrop-filter: saturate(160%) blur(14px);
  -webkit-backdrop-filter: saturate(160%) blur(14px);
  border-bottom: 1px solid var(--line);
}
/* ⚠️ min-height, NOT height. A fixed height does not contain anything taller
   than itself — it lets it spill out below the bar, which is exactly what the
   signed-in person's two lines did: the word CLUB ADMIN hung underneath the
   border, outside the bar entirely. */
/* ⚠️ THE BAR SPANS THE WINDOW; the PAGES stay at a reading width. Capping the
   bar at the same 1240px as an article threw away 40-odd pixels at every common
   laptop size, which was the difference between the last menu item fitting and
   bleeding off the end. A navigation bar is furniture, not prose. */
.app-bar .wrap {
  display: flex; align-items: center; gap: 12px; min-height: 68px; flex-wrap: nowrap;
  max-width: none;
}

.wrap { width: 100%; max-width: 1240px; margin: 0 auto; padding-inline: clamp(18px, 4vw, 36px); }

.brand { display: flex; align-items: center; gap: 10px; font-family: var(--display); font-weight: 800; font-size: 1.12rem; }
.brand .mark {
  width: 32px; height: 32px; border-radius: 10px; background: var(--hot);
  display: grid; place-items: center; flex: none;
  box-shadow: 0 6px 18px -6px rgba(255,77,109,.6);
}
.brand .mark svg { width: 19px; height: 19px; display: block; }
.brand .dot { background: var(--hot); -webkit-background-clip: text; background-clip: text; color: transparent; }

/* Which club you are looking at. On the console it says so in words, because
   an operator working across clubs must never have to guess. */
.club-chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 12px 6px 7px; border-radius: 999px;
  background: var(--card); box-shadow: inset 0 0 0 1.5px var(--line);
  font-size: .85rem; font-weight: 600; color: var(--ink2); white-space: nowrap;
}
.club-chip .swatch { width: 16px; height: 16px; border-radius: 6px; background: var(--accent); flex: none; }

.club-chip.console { background: var(--ink); color: #fff; box-shadow: none; }
.club-chip.console .swatch { background: var(--cool); }

/* ⚠️⚠️ THE LINKS MUST NEVER WRAP. Without this, "Your classes" broke across two
   lines INSIDE its pill and the pill swelled into a lozenge twice the height of
   everything beside it. A row of navigation is a row: when it stops fitting it
   should slide, not grow.

   min-width:0 on a flex child is what actually permits it to shrink below its
   content and scroll. Without it the nav simply refuses to be narrower than the
   sum of its links and pushes everything else off the edge. */
/* ⚠️⚠️ TWO SEPARATE FAULTS LIVED IN THIS ONE RULE, and the second was uglier.

   1. `margin-left: auto` with no grow: the auto margin swallowed the spare room
      and the nav was never offered any of it. At 1400px the bar had 118px going
      begging while the nav sat 52px short of its contents and clipped "Apps".
      `flex: 1 1 auto` takes the slack when there is some, and min-width:0 lets
      it shrink into a slider when there is none.

   2. ⚠️ `justify-content: flex-end` AND `overflow-x: auto` TOGETHER ARE A TRAP.
      Right-aligning the row means any overflow spills off the LEFT edge — where
      it is clipped AND unreachable, because scrolling in a left-to-right page
      only ever goes the other way. Josh saw the active "Home" pill sliced down
      the middle, a black half-button hanging off the end of the club's name.
      The row starts from the left: overflow then goes right, where it can at
      least be scrolled to, and the first item is never the one that is cut. */
.app-nav {
  display: flex; gap: 2px; align-items: center;
  flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none;
}
.app-nav::-webkit-scrollbar { display: none; }

/* The person, and the way out. Pinned beside the scrolling links rather than
   inside them, so neither can ever be clipped off the end of the row. */
.app-side { display: flex; align-items: center; gap: 8px; flex: none; margin-left: 4px; }
.app-side .btn { white-space: nowrap; }
.app-nav a {
  padding: 8px 13px; border-radius: 999px; font-weight: 600; font-size: .92rem;
  color: var(--ink2); transition: background .18s, color .18s;
  white-space: nowrap; flex: none;
}
.app-nav a:hover { background: #fff; color: var(--ink); }
.app-nav a.on { background: var(--ink); color: #fff; }

/* The signed-in person is now a LINK to their own account, so it has to opt
   out of the pill shape every other link in the bar wears — otherwise the
   avatar sits inside a second, larger, pointless pill. */
.app-nav a.who { padding: 4px 8px 4px 0; border-radius: 14px; }
.app-nav a.who:hover { background: #fff; }

/* Somebody is waiting for this club to let them in. It travels on every page,
   because a request nobody notices is a member who thinks they were ignored. */
.app-nav a.waiting, .app-nav a.waiting:hover { padding: 4px 6px; background: transparent; }

/* ⚠️ A PERSON'S NAME IS ANY LENGTH, and the bar is not. It is given a ceiling
   and truncated with an ellipsis rather than being allowed to wrap, push the
   nav off the edge, or spill below the border. */
.who { display: flex; align-items: center; gap: 9px; padding-left: 6px; max-width: 13rem; min-width: 0; }
/* The two lines, stacked and centred against the avatar beside them. A column
   rather than two blocks and a line break, so the height is exactly the height
   of what is in it. */
.who .whom { display: flex; flex-direction: column; justify-content: center; min-width: 0; }
.who .avatar {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  background: var(--cool); color: #fff; display: grid; place-items: center;
  font-weight: 700; font-size: .82rem; letter-spacing: .02em;
}
.who .name, .who .role {
  display: block; max-width: 8rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.who .name { font-size: .88rem; font-weight: 600; line-height: 1.2; }
.who .role { font-size: .72rem; color: var(--ink3); text-transform: uppercase; letter-spacing: .05em; font-weight: 700; line-height: 1.2; }

/* ------------------------------------------------------------------ layout */
main { padding-block: clamp(26px, 5vw, 46px) 80px; }

.page-head { display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap; margin-bottom: 26px; }
.page-head .lead { flex: 1 1 320px; }
.page-head p { margin: 6px 0 0; }
.page-head .actions { display: flex; gap: 10px; flex-wrap: wrap; }

.grid { display: grid; gap: 18px; }
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }

/* ------------------------------------------------------------------- cards */
.card {
  background: var(--card); border-radius: var(--radius);
  padding: clamp(20px, 3vw, 28px); box-shadow: var(--shadow);
}
.card.flush { padding: 0; overflow: hidden; }
.card h2 { margin-bottom: 4px; }
.card-head { display: flex; align-items: center; gap: 12px; padding: 20px clamp(20px, 3vw, 28px) 14px; }
/* ⚠️ THE FOOTER HAD NO PADDING OF ITS OWN. Inside a flush card — one whose own
   padding is removed so a table can reach the edges — its text sat hard against
   the left edge and ran out under the last row, outside the rounded corner.
   It takes the same inset as the head above it. */
.card-foot { padding: 14px clamp(20px, 3vw, 28px) 20px; border-top: 1px solid var(--line-soft); }
.card-foot p:last-child { margin-bottom: 0; }
.card-head h2 { flex: 1; }

/* A number that matters, shown large. */
.tile { background: var(--card); border-radius: var(--radius-sm); padding: 18px 20px; box-shadow: var(--shadow); }
.tile .k { font-size: .76rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink3); }
.tile .v { font-family: var(--display); font-size: 1.9rem; font-weight: 700; line-height: 1.15; margin-top: 4px; }
.tile .v small { font-size: .9rem; font-weight: 600; color: var(--ink3); font-family: var(--body); }

/* ----------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 22px; border: 0; border-radius: 999px; cursor: pointer;
  font: inherit; font-weight: 700; font-size: .95rem; white-space: nowrap;
  transition: transform .18s, box-shadow .18s, background .18s;
}
.btn:hover { transform: translateY(-2px); }
.btn-hot { background: var(--hot); color: #fff; box-shadow: 0 10px 28px -10px rgba(255,77,109,.7); }
.btn-hot:hover { box-shadow: 0 16px 36px -12px rgba(255,77,109,.8); }
.btn-solid { background: var(--ink); color: #fff; }
.btn-ghost { background: var(--card); color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--line), var(--shadow); }
.btn-danger { background: var(--card); color: #d1002b; box-shadow: inset 0 0 0 1.5px #f3d7de; }
.btn-danger:hover { background: #fff1f3; }
.btn-sm { padding: 8px 16px; font-size: .86rem; }
.btn-block { width: 100%; }
.btn[disabled] { opacity: .5; cursor: not-allowed; transform: none; }

/* ------------------------------------------------------------------ fields */
.field { margin-bottom: 16px; }
.field label { display: block; font-weight: 700; font-size: .86rem; margin-bottom: 7px; }
.field .hint { font-size: .82rem; color: var(--ink3); margin-top: 6px; }
/* ⚠️ STYLED BY EXCLUSION, NOT BY LIST. This was a list of the input types we
   happened to have used, and `datetime-local` was not on it — so the one
   control on the whole platform that asks when a class starts rendered as bare
   browser furniture, in a monospace font, beside a page of rounded fields.
   A list like that is wrong again the moment anybody uses a new type; naming
   the handful that must NOT look like a text box cannot rot the same way. */
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]):not([type=color]):not([type=submit]):not([type=button]),
select, textarea {
  width: 100%; padding: 13px 15px; font: inherit; color: inherit;
  background: #fffdfb; border: 1.5px solid var(--line); border-radius: var(--radius-sm);
  transition: border-color .18s, box-shadow .18s;
}

/* A colour well is a swatch, not a text box: it keeps the same frame and
   radius but none of the padding, so the colour fills it. */
input[type=color] {
  width: 100%; height: 48px; padding: 4px; background: #fffdfb; cursor: pointer;
  border: 1.5px solid var(--line); border-radius: var(--radius-sm);
}
input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
input[type=color]::-webkit-color-swatch { border: 0; border-radius: 9px; }

/* The little calendar and clock buttons the browser draws inside a date field.
   Left alone they are pure system grey against our own palette. */
input::-webkit-calendar-picker-indicator {
  cursor: pointer; opacity: .5; transition: opacity .15s;
}
input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: transparent;
  box-shadow: 0 0 0 2.5px color-mix(in srgb, var(--accent) 55%, transparent);
}

/* ------------------------------------------------------------------ tables */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: .94rem; }
thead th {
  text-align: left; padding: 12px clamp(14px, 2vw, 20px);
  font-size: .74rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink3); background: var(--line-soft); white-space: nowrap;
}
tbody td { padding: 14px clamp(14px, 2vw, 20px); border-top: 1px solid var(--line-soft); vertical-align: middle; }
tbody tr:hover td { background: #fffdfb; }
td.right, th.right { text-align: right; white-space: nowrap; }
td .sub { display: block; font-size: .82rem; color: var(--ink3); }

/* ------------------------------------------------------------------ badges */
.badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: 999px;
  font-size: .72rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
}
.badge-live { background: #e8f9f0; color: #0f7a4f; }
.badge-live .pulse {
  width: 7px; height: 7px; border-radius: 50%; background: #22b260;
  animation: breathe 1.5s ease-in-out infinite;
}
@keyframes breathe { 0%,100% { opacity: .45; } 50% { opacity: 1; } }
.badge-soon   { background: #eef4ff; color: #2b5bd7; }
.badge-draft  { background: #fff6e6; color: #9a6100; }
.badge-done   { background: var(--line-soft); color: var(--ink3); }
.badge-off    { background: #fff1f3; color: #b4123a; }

/* ------------------------------------------------------------- empty state */
.empty { text-align: center; padding: clamp(34px, 7vw, 64px) 20px; }
.empty .art { width: 74px; height: 74px; margin: 0 auto 16px; border-radius: 22px; background: var(--hot); opacity: .18; }
.empty h2 { margin-bottom: 6px; }
.empty p { max-width: 38ch; margin-inline: auto; }

/* ------------------------------------------------------------- small print */
.muted { color: var(--ink3); font-size: .88rem; }
.notice { padding: 13px 16px; border-radius: var(--radius-sm); margin-bottom: 16px; font-size: .93rem; }
.notice-error { background: #fff1f3; color: #b4123a; }
.notice-ok    { background: #e8f9f0; color: #0f7a4f; }
.notice-info  { background: #eef4ff; color: #2b5bd7; }

/* ------------------------------------------------------- the sign-in scene
   Two panels: the brand on the left, the form on the right. On a phone the
   brand panel becomes a short header so the form is reachable without
   scrolling. */
body.auth { display: grid; grid-template-columns: 1.05fr 1fr; min-height: 100vh; }
.auth-art {
  position: relative; overflow: hidden; background: var(--hot); color: #fff;
  padding: clamp(30px, 5vw, 60px); display: flex; flex-direction: column; justify-content: space-between;
}
.auth-art::after {
  content: ""; position: absolute; inset: auto -20% -35% -20%; height: 70%;
  background: radial-gradient(closest-side, rgba(255,255,255,.3), transparent 70%);
}
.auth-art .brand { color: #fff; font-size: 1.25rem; position: relative; z-index: 1; }
.auth-art .brand .mark { background: rgba(255,255,255,.22); box-shadow: none; }
.auth-art .brand .dot { background: none; -webkit-text-fill-color: #fff; color: #fff; }
.auth-art .pitch { position: relative; z-index: 1; max-width: 22ch; }
.auth-art .pitch h1 { font-size: clamp(1.9rem, 3.6vw, 3rem); line-height: 1.08; color: #fff; }
.auth-art .pitch p { color: rgba(255,255,255,.92); font-size: 1.02rem; margin-top: 14px; }
.auth-art .foot { position: relative; z-index: 1; font-size: .84rem; color: rgba(255,255,255,.8); }
.auth-form { display: grid; place-items: center; padding: clamp(26px, 5vw, 56px) clamp(18px, 4vw, 40px); }
.auth-form .inner { width: 100%; max-width: 25rem; }
.auth-form h2 { font-size: 1.6rem; margin-bottom: 6px; }

@media (max-width: 860px) {
  body.auth { grid-template-columns: 1fr; }
  .auth-art { padding: 22px clamp(18px, 5vw, 30px) 26px; }
  .auth-art .pitch h1 { font-size: 1.6rem; }
  .auth-art .pitch p, .auth-art .foot { display: none; }
}
/* Room runs out long before a phone. A club admin has seven places to go, a
   club name beside the logo and their own name at the end — by about 1200px
   the name is the first thing that can go without costing anybody a
   destination. The avatar stays, because it is still the way to the account. */
@media (max-width: 1280px) {
  .who .name, .who .role { display: none; }
  .who { max-width: none; padding-left: 2px; }
}
/* Then the club's name, which the page itself says anyway. */
@media (max-width: 1280px) {
  .app-bar .club-chip { display: none; }
}
@media (max-width: 720px) {
  .app-bar .wrap { min-height: 0; padding-block: 12px; flex-wrap: wrap; gap: 10px; }
  .app-nav { order: 3; width: 100%; margin-left: 0; }
  .app-side { margin-left: auto; }
}

/* ------------------------------------------------------- the rest of the kit
   Small pieces the portal already uses: a tag, a yes/no, a form that sits in a
   table row, and a bare <button> that finishes a form. */
.tag {
  background: var(--accent); color: #fff; font-size: .7rem; font-weight: 800;
  letter-spacing: .05em; text-transform: uppercase; padding: 3px 9px;
  border-radius: 999px; vertical-align: middle;
}
.yes { color: #0f9d78; font-weight: 700; }
.no  { color: #c9c4d4; }
form.inline { display: inline; margin: 0 0 0 6px; }

/* A button with no class of its own finishes a form, so it looks like the
   primary action rather than unstyled browser chrome. */
button:not([class]) {
  margin-top: 18px; width: 100%; padding: 13px 22px; font: inherit; font-weight: 700;
  color: #fff; background: var(--hot); border: 0; border-radius: 999px; cursor: pointer;
  box-shadow: 0 10px 28px -10px rgba(255,77,109,.7);
}
form.inline button:not([class]) { width: auto; margin: 0; padding: 8px 16px; font-size: .86rem; }

/* Older spellings kept meaningful rather than left to fall through. */
.error { background: #fff1f3; color: #b4123a; padding: 13px 16px; border-radius: var(--radius-sm); margin-bottom: 14px; }
.btn.danger { background: var(--card); color: #d1002b; box-shadow: inset 0 0 0 1.5px #f3d7de; }
.btn.danger:hover { background: #fff1f3; }

/* ------------------------------------------------------------- the timetable
   A week: days across, times down, drawn in the club's own time zone.

   Built on CSS grid rather than absolute positioning, so a class that runs from
   nine to nine forty-five simply spans rows. The half hour is the unit because
   it is the smallest thing a club timetable ever uses. */
.grid-wrap {
  --slot: 26px;
  display: grid; grid-template-columns: 3.2rem repeat(7, minmax(7rem, 1fr));
  gap: 6px; overflow-x: auto; padding-bottom: 6px;
}
.grid-wrap .rail {
  display: grid; grid-auto-rows: var(--slot);
  padding-top: 44px;   /* clears the day headings */
}
.grid-wrap .rail span {
  font-size: 11px; color: var(--ink3); text-align: right; padding-right: 8px;
  line-height: 1; transform: translateY(-4px);
}
.grid-wrap .day { min-width: 7rem; }
.grid-wrap .dayhead {
  height: 44px; display: flex; flex-direction: column; justify-content: center;
  align-items: center; line-height: 1.15;
}
.grid-wrap .dayhead b { font-size: .84rem; }
.grid-wrap .dayhead span { font-size: .74rem; color: var(--ink3); }
.grid-wrap .day.today .dayhead b { color: var(--accent); }

/* One column of half-hour cells, with the classes laid over them. */
.grid-wrap .col {
  display: grid; position: relative;
  background: #fff; border-radius: 14px; overflow: hidden;
  box-shadow: inset 0 0 0 1.5px var(--line);
}
.grid-wrap .day.today .col { box-shadow: inset 0 0 0 1.5px var(--accent); }

/* Every empty half hour is somewhere to put a class. Clicking is the mechanism,
   not a shortcut: the front desk is often on a tablet. */
.grid-wrap .slot {
  grid-column: 1; border-top: 1px solid #f6efe9; transition: background .12s;
}
.grid-wrap .slot:first-child { border-top: 0; }
.grid-wrap .slot:hover { background: #fffaf6; }

.grid-wrap .blk {
  grid-column: 1; margin: 1px 3px; padding: 5px 7px; border-radius: 9px;
  background: color-mix(in srgb, var(--blk) 14%, #fff);
  box-shadow: inset 3px 0 0 var(--blk);
  color: var(--ink); text-decoration: none; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-start;
  font-size: .76rem; line-height: 1.25; z-index: 1;
}
.grid-wrap .blk:hover { background: color-mix(in srgb, var(--blk) 26%, #fff); }
.grid-wrap .blk b { font-weight: 700; }
.grid-wrap .blk span { color: var(--ink2); font-size: .72rem; }
.grid-wrap .blk em { font-style: normal; color: var(--ink3); font-size: .72rem; }

/* A draft is visibly not a real class — it has no public link, sends no
   reminders and cannot go live. A cancelled one is visibly still there,
   because members may have been told about it. Neither is hidden. */
.grid-wrap .blk.draft {
  background: repeating-linear-gradient(135deg, #fff, #fff 6px, #fdf4ec 6px, #fdf4ec 12px);
  box-shadow: inset 3px 0 0 #d9cfc6;
}
.grid-wrap .blk.draft em { color: #9a6100; font-weight: 600; }
.grid-wrap .blk.off { opacity: .5; }
.grid-wrap .blk.off b { text-decoration: line-through; }
.grid-wrap .blk.live { box-shadow: inset 3px 0 0 var(--blk), 0 0 0 2px var(--blk); }

/* The weekday picker, which is the whole of the repeat rule: weekly, on chosen
   days. No monthly patterns — a club timetable does not work that way. */
.days { display: flex; gap: 6px; flex-wrap: wrap; }
.daybox {
  display: inline-flex; align-items: center; gap: 5px; padding: 8px 12px;
  border-radius: 999px; background: #fff; box-shadow: inset 0 0 0 1.5px var(--line);
  font-size: .85rem; font-weight: 600; cursor: pointer;
}
.daybox:has(input:checked) { background: var(--ink); color: #fff; box-shadow: none; }

@media (max-width: 720px) {
  /* A week does not fit on a phone, and squeezing it makes it unreadable.
     It scrolls sideways instead, which is what a paper timetable does too. */
  .grid-wrap { grid-template-columns: 2.6rem repeat(7, 8rem); }
}

/* ------------------------------------------------------ a club's shop window
   The club's own address: what is on, and what can be watched again. Read by
   the whole internet, most of it on a phone, by people who have not signed in
   and may never have heard of us. */
.club-hero { text-align: center; padding-block: clamp(18px, 4vw, 40px) clamp(24px, 4vw, 44px); }
.club-hero h1 {
  font-size: clamp(1.9rem, 4.4vw, 3rem); line-height: 1.08; margin: 18px auto 0;
  max-width: 18ch;
}
.club-hero .lead { max-width: 38ch; margin: 16px auto 0; font-size: 1.05rem; }
.club-cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 26px; }

.club-band { margin-bottom: clamp(28px, 5vw, 48px); }
.band-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px; }
.band-head h2 { flex: 1; }
.club-band > h2 { margin-bottom: 16px; }

/* What is on. A card per class — the time first, because that is what somebody
   scanning a schedule is actually looking for. */
.on-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.on-card {
  display: flex; flex-direction: column; gap: 5px;
  background: var(--card); border-radius: var(--radius-sm); padding: 16px 18px;
  box-shadow: var(--shadow); border-left: 5px solid var(--blk, var(--accent));
  transition: transform .16s, box-shadow .2s;
}
.on-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }
.on-card .when { font-size: .8rem; font-weight: 700; color: var(--ink3); text-transform: uppercase; letter-spacing: .04em; }
.on-card strong { font-size: 1.02rem; line-height: 1.25; }
/* One line. An instructor's name and a room name together are as long as they
   are, and wrapping them turns a tidy grid of cards into a ragged one. */
.on-card .who {
  font-size: .88rem; color: var(--ink2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.on-card .len { font-size: .8rem; color: var(--ink3); margin-top: 2px; }

/* Watch again. Pictures, because a class is judged on how it looks. */
.replay-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
.replay { display: block; }
.replay .frame {
  position: relative; display: block; aspect-ratio: 16/9; overflow: hidden;
  border-radius: var(--radius-sm); background: var(--ink); box-shadow: var(--shadow);
  transition: transform .16s, box-shadow .2s;
}
.replay:hover .frame { transform: translateY(-2px); box-shadow: var(--shadow-lift); }
.replay .frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.replay .noart {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: rgba(255,255,255,.55); font-size: .85rem; font-weight: 600;
}
.replay .tag { position: absolute; top: 9px; left: 9px; }
.replay .dur {
  position: absolute; right: 9px; bottom: 9px; background: rgba(0,0,0,.62); color: #fff;
  font-size: .74rem; padding: 3px 8px; border-radius: 999px;
}
.replay strong { display: block; margin-top: 10px; font-size: .97rem; line-height: 1.3; }
.replay .who { display: block; font-size: .85rem; color: var(--ink3); }

/* The one line somebody arriving early actually needs: how long until the
   class. Prominent, under the title, before the player — not a technical
   complaint further down the page in the browser's own words. */
.standing {
  display: inline-block; margin: 0 0 18px;
  font-family: var(--display); font-weight: 700; font-size: clamp(1.05rem, 2vw, 1.3rem);
  padding: 12px 20px; border-radius: 999px;
}
.standing.soon { background: #fff6e6; color: #9a6100; }
.standing.done { background: #eef4ff; color: #2b5bd7; }

/* -------------------------------------------------------- the waiting room
   Before a class, the frame is not empty — it holds a countdown, at the size
   the class itself will be. Somebody who opens the link early should see the
   thing they came for getting closer, not a small black rectangle and a
   complaint.

   ⚠️ This box is also where a sponsor's video will play before a class (Josh,
   2026-09-14). It is a full frame of its own for that reason: when that
   arrives it replaces what is INSIDE here and nothing around it moves. */
.waiting {
  position: relative; width: 100%; max-width: 62rem; aspect-ratio: 16/9;
  max-height: 56vh; margin-inline: auto;
  border-radius: var(--radius); overflow: hidden;
  background: radial-gradient(120% 120% at 50% 10%, #2a2145 0%, #14101f 60%);
  box-shadow: var(--shadow-lift);
  display: grid; place-items: center; text-align: center;
}
/* A slow sweep of the brand's colour, so the box is alive without demanding
   anything. It is behind everything and takes no clicks. */
.waiting::before {
  content: ""; position: absolute; inset: -40%;
  background: conic-gradient(from 0deg, transparent 0deg, var(--accent) 70deg, transparent 150deg);
  opacity: .16; animation: waitsweep 14s linear infinite;
}
@keyframes waitsweep { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .waiting::before { animation: none; } }

/* ---------------------------------------------------------- after a class
   ★ A FINISHED CLASS GETS ITS OWN FRAME (Josh, 2026-09-14). It used to keep
   the countdown up — "Class starts in / Starting shortly" over a class that
   had already ended — and, if it fell through to the player, an empty video
   element in landscape, which is not the shape anything was filmed in.

   Same size and shape as the waiting room so the page does not jump, but
   plainly finished rather than plainly about to happen: no sweep, no clock. */
.ended {
  position: relative; width: 100%; max-width: 62rem; aspect-ratio: 16/9;
  border-radius: var(--radius); overflow: hidden;
  background: linear-gradient(160deg, #2a2145 0%, #14101f 70%);
  box-shadow: var(--shadow-lift);
  display: grid; place-items: center; text-align: center;
  /* ⚠️⚠️ A PORTRAIT CLASS MUST NOT FILL THE WHOLE SCREEN (Josh, 2026-09-14:
     "this enormous PORTRAIT video holder is way too big"). The frame takes the
     shape the class was filmed in, which is right — but an instructor films on
     a phone held upright, so 9:16 at full width is a box taller than the
     display, with three lines of text marooned in the middle of it.

     Height is capped and the width follows from the aspect ratio, so the shape
     stays honest and the thing stays a panel rather than a wall. margin-inline
     centres what is left. */
  max-height: 56vh;
  margin-inline: auto;
}
.ended .inner { padding: 28px; max-width: 34rem; }
.ended .kicker {
  margin: 0 0 10px; color: rgba(255,255,255,.62);
  font-size: clamp(.8rem, 1.4vw, .95rem); font-weight: 700;
  text-transform: uppercase; letter-spacing: .16em;
}
.ended h2 {
  margin: 0 0 12px; color: #fff;
  font-family: var(--display); font-weight: 800;
  font-size: clamp(1.4rem, 3.4vw, 2.1rem); line-height: 1.15;
}
.ended p { margin: 0 0 18px; color: rgba(255,255,255,.72); font-size: .98rem; line-height: 1.5; }
.ended .btn { margin: 0 6px; }
/* "We are still putting it together" — a state, not a failure, so it reads as
   a quiet note rather than a warning. */
.ended .working {
  display: inline-flex; align-items: center; gap: 9px;
  background: rgba(255,255,255,.12); color: #fff;
  padding: 10px 18px; border-radius: 999px; font-weight: 700; font-size: .9rem;
}
.ended .working i {
  width: 8px; height: 8px; border-radius: 50%; background: #ffc93c;
  animation: breathe 1.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) { .ended .working i { animation: none; } }

.wait-inner { position: relative; padding: 24px; }
.waiting .kicker {
  margin: 0 0 10px; color: rgba(255,255,255,.62);
  font-size: clamp(.8rem, 1.4vw, .95rem); font-weight: 700;
  text-transform: uppercase; letter-spacing: .16em;
}
.waiting .clock {
  display: flex; align-items: baseline; justify-content: center; gap: 2px;
  font-family: var(--display); font-weight: 800; color: #fff;
  font-size: clamp(2.6rem, 9vw, 6rem); line-height: 1; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
/* ⚠️ Each pair is a fixed-width box. Without it the whole clock shuffles
   sideways every time a 1 becomes a 2, which is the difference between a
   countdown and a fidget. */
.waiting .clock span {
  min-width: 1.15em; display: inline-block;
  background: rgba(255,255,255,.07); border-radius: 14px; padding: .06em .1em;
}
.waiting .clock i { font-style: normal; opacity: .45; padding: 0 .04em; animation: tickblink 1s steps(1) infinite; }
@keyframes tickblink { 50% { opacity: .12; } }
@media (prefers-reduced-motion: reduce) { .waiting .clock i { animation: none; } }

.waiting .waitsub {
  margin: 16px 0 0; color: rgba(255,255,255,.72);
  font-size: clamp(.9rem, 1.6vw, 1.05rem);
}
.waiting.due .clock { font-size: clamp(1.5rem, 4vw, 2.4rem); }

/* The class itself, once it starts. The frame shrinks to whatever was actually
   filmed — an instructor holds a phone upright, so a class is usually tall. */
.stage { display: flex; justify-content: center; }

/* ---------------------------------------------------------- a club's week
   ★ A TIMETABLE, NOT A PILE OF CARDS. A flat grid of identical cards each
   carrying its own date makes the reader do the sorting: you cannot see where
   Tuesday ends and Wednesday begins, or that Thursday has two classes and
   Saturday one. Days as columns answers "when can I come?" at a glance, which
   is the only question anybody brings to this part of the page. */
.calendar { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 10px; }
.cal-day {
  background: var(--card); border-radius: var(--radius-sm); padding: 10px;
  box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 8px;
  min-height: 7rem;
}
.cal-day.today { box-shadow: var(--shadow), inset 0 0 0 2px var(--accent); }
.cal-head { text-align: center; line-height: 1.15; padding-bottom: 6px; border-bottom: 1px solid var(--line); }
.cal-head b { display: block; font-size: .82rem; }
.cal-head span { font-size: .72rem; color: var(--ink3); }
.cal-day.today .cal-head b { color: var(--accent); }
.cal-none { margin: auto; color: var(--ink3); font-size: 1.1rem; }

/* ⚠️ STACKED, NOT SIDE BY SIDE. A day column is narrow, and in three columns
   the instructor's name was ellipsised down to "Sarah …" — which tells a member
   nothing and reads as a bug. Down the card there is room for all of it. */
.slot-card {
  display: block; padding: 9px 10px; border-radius: 10px;
  background: color-mix(in srgb, var(--blk, var(--accent)) 12%, #fff);
  box-shadow: inset 3px 0 0 var(--blk, var(--accent));
  transition: background .15s;
}
.slot-card:hover { background: color-mix(in srgb, var(--blk, var(--accent)) 26%, #fff); }
.slot-card .at {
  display: flex; align-items: baseline; gap: 6px;
  font-weight: 800; font-size: .84rem; font-variant-numeric: tabular-nums;
}
.slot-card .at i { font-style: normal; font-weight: 600; font-size: .68rem; color: var(--ink3); }
.slot-card strong { display: block; font-size: .88rem; line-height: 1.25; margin-top: 2px; }
.slot-card .under {
  display: block; font-size: .76rem; color: var(--ink2); line-height: 1.3; margin-top: 1px;
}
/* A class earlier today that has finished stays in its day, greyed. A
   timetable with this morning missing reads as wrong. */
.slot-card.gone { opacity: .42; }

.cal-nav { display: inline-flex; align-items: center; gap: 8px; }
.cal-label { font-weight: 700; font-size: .9rem; color: var(--ink2); white-space: nowrap; }

@media (max-width: 900px) {
  /* A week does not fit across a phone. The days become a list instead, which
     is what a printed timetable does when it runs out of paper. */
  .calendar { grid-template-columns: 1fr; }
  .cal-day { min-height: 0; }
  .cal-day:has(.cal-none) { display: none; }
  .cal-head { display: flex; gap: 8px; align-items: baseline; text-align: left; }
}
