/* Public Assembly — quiet paper (2026-08-09, replacing the zine look).

   The zine was a costume: uppercase mono body text, hard offset shadows on
   every surface, 3px black borders, type shouted at the reader. It suited a
   site that was mostly a poster for a store. It does not suit a platform
   where the content — a night's cover art, somebody's face, what they just
   said — is the thing worth looking at, and where a member may be reading
   forty rows of it at a time.

   So the palette stays and the theatrics go. Warm paper, near-black ink, and
   brick red held back for accents: links, the ✱, the one button on a page
   that matters. Surfaces are separated by hairlines rather than by shadows.
   Headings are condensed and set in sentence case. Body copy is a reading
   face at a reading size. Mono survives only in the small stuff —
   timestamps, labels, the day names across the calendar — which is where a
   little of the old character can sit without getting in the way.

   The rule for anything new: if a treatment is competing with the content
   for attention, it's wrong. */

:root {
  --paper: #f2efe6;      /* warm paper — page background */
  --paper-2: #e9e5d9;    /* shaded panels, out-of-month calendar cells */
  --white: #fbf9f4;      /* warm white — cards, calendar cells, inputs */
  --concrete: #cac5b8;   /* image placeholder grey */
  --ink: #1a1815;        /* warm near-black — text */
  --ink-2: #3d3931;      /* secondary text that still has to carry weight */
  --brick: #9e3b2f;      /* brick red accent — links, the ✱, one button */
  --brick-2: #7f2c22;
  --brick-wash: #f6e9e5;  /* the faintest brick, for a selected or new row */
  --brick-soft: #d9aba3;  /* brick thinned out, for a hairline that shouldn't shout */
  --olive: #5f6138;
  --wood: #c79a5e;       /* tan — replaces brick on dark bands for contrast */
  --muted: #6f6a5f;
  --line: #ded8c9;       /* the standard hairline: borders, dividers, grids */
  --line-2: #cbc4b1;     /* a hairline that needs to be seen from further off */
  --rule: var(--line);   /* the old name, kept so nothing has to be chased */
  --max-width: 1180px;
  --radius: 3px;         /* barely there — enough to read as a surface, not a pill */
  --mono: ui-monospace, "SFMono-Regular", "JetBrains Mono", "Roboto Mono",
          Menlo, Consolas, "Liberation Mono", monospace;
  /* Arial Narrow where it exists (Mac/Windows); Oswald downloads elsewhere. */
  --display: "Arial Narrow", "Oswald", "Helvetica Neue Condensed",
             "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* The reading face. A system stack: it renders instantly, it looks native
     on the machine it's read on, and the character in this design is meant
     to come from colour and spacing rather than from a downloaded font. */
  --text: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* Flat paper. The newsprint speckle is gone: it was texture behind every
     word on the site, and at a feed's line count it reads as dirt. */
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 26px 26px 64px;
}

/* The full-width frame. The homepage runs the calendar and the feed side by
   side, and both want every pixel there is: the month grid can't go under
   880px without scrolling sideways, and a feed of faces and sentences is
   better at 380 than at 300. So this page isn't centred in a reading column
   at all — it uses the window, with a ceiling only so the calendar doesn't
   turn into a billboard on a 32-inch display. */
main.wide { max-width: 1900px; padding-left: 30px; padding-right: 30px; }

/* The header and footer take the same frame as the page under them. A wide
   page that left these at the reading width put the wordmark well inside the
   left edge of its own content. */
.site-header.wide .nav-inner,
.site-footer.wide .footer-inner {
  max-width: 1900px;
  padding-left: 30px;
  padding-right: 30px;
}

a { color: var(--ink); }
a:hover { color: var(--brick); }
:focus-visible { outline: 2px solid var(--brick); outline-offset: 2px; }

/* Headings are condensed and sentence case. The old ones were uppercase at
   64px, which is a poster's job — on a page with a calendar and a feed under
   it, the heading only has to say where you are. */
h1, h2 {
  font-family: var(--display);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: 0.005em;
}
h1 { font-size: clamp(28px, 3.2vw, 40px); margin: 0 0 18px; }
h2 { font-size: clamp(20px, 2vw, 26px); margin: 0 0 14px; }

/* red asterisk before section headings — always the mono glyph */
.starred::before {
  content: "\2731"; /* ✱ */
  font-family: var(--mono);
  font-size: 0.6em;
  color: var(--brick);
  margin-right: 14px;
  vertical-align: 0.25em;
}

.star { color: var(--brick); font-family: var(--mono); font-size: 0.7em; }

.c-label, .section-label {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--muted);
  margin: 40px 0 14px;
}

.kicker {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--brick);
  margin: 0 0 10px;
}

.fineprint { color: var(--muted); font-size: 12px; }
.optional { color: var(--muted); font-weight: 400; }
.empty { color: var(--muted); }

/* ---- header ---- */

.site-header {
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}

.site-header .nav-inner {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 26px;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 18px 26px 14px;
}
.site-header.wide .nav-inner { padding-top: 18px; padding-bottom: 14px; }

.brand {
  display: inline-flex;
  flex-direction: column;
  line-height: 1;
  text-decoration: none;
}
.brand-mark {
  font-family: var(--display);
  font-weight: 700;
  font-size: 30px;
  text-transform: uppercase;
  letter-spacing: 0.01em;
}
.brand:hover .brand-mark { color: var(--brick); }
.brand-sub {
  margin-top: 5px;
  font-size: 10px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--muted);
}

.site-header nav {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  padding-bottom: 3px;
}
.site-header nav a {
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  padding-bottom: 2px;
  border-bottom: 2px solid transparent;
}
.site-header nav a:hover { color: var(--brick); }
.site-header nav a.active { border-bottom-color: var(--brick); color: var(--brick); }
.admin-link { color: var(--brick) !important; }

/* boxed nav CTA — the Discord-button treatment */
.nav-cta {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 13px;
  background: var(--brick);
  color: var(--white) !important;
  border: 1px solid var(--line) !important;
  transition: border-color 0.12s, color 0.12s;
}
.nav-cta:hover { background: var(--brick-2); }

.portal-label {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--white);
  background: var(--brick);
  border: 1px solid var(--line);
  padding: 3px 10px;
  align-self: center;
}

/* ---- visit page ---- */

.visit-head { padding: 32px 0 10px; }
.visit-head h1 { margin: 0 0 16px; }

.visit-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 26px;
  margin-top: 12px;
}
.visit-card {
  background: var(--white);
  border: 1px solid var(--line);
  padding: 24px 26px;
}
.visit-card h2 { margin: 0 0 18px; }
.visit-card > p { color: var(--muted); margin: 0 0 18px; }

.visit-hours {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px 24px;
  margin: 0;
  font-size: 14px;
}
.visit-hours dt { color: var(--muted); }
.visit-hours dd { margin: 0; }

.visit-address {
  font-style: normal;
  font-size: 17px;
  line-height: 1.5;
  margin-bottom: 14px;
}
.visit-links { margin: 0 0 18px; font-size: 13px; }
.visit-phone {
  margin: 0;
  font-size: 20px;
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: 0.01em;
}
.visit-phone a, .visit-links a { text-decoration: none; }
.visit-phone a:hover, .visit-links a:hover { color: var(--brick); }

.visit-social-card { margin-top: 26px; }
.visit-social {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.visit-social a {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  border: 1px solid var(--line);
  text-decoration: none;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: border-color 0.12s, color 0.12s;
}
.visit-social a:hover {
  color: var(--brick);
}
.visit-social svg { width: 16px; height: 16px; fill: currentColor; }

/* Icon-only Instagram/Discord on the home feed's head row: at hand without
   outranking the feed. The labeled pair lives in the footer. */
.feed-social {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-left: auto;
  margin-right: 12px;
}
.feed-social a {
  display: inline-flex;
  color: var(--muted);
  transition: color 0.12s;
}
.feed-social a:hover { color: var(--brick); }
.feed-social svg { width: 15px; height: 15px; fill: currentColor; }

/* The footer's copy of the buttons, toned for the dark band. */
.footer-social { margin-top: 14px; }
.site-footer .visit-social { gap: 10px; }
.site-footer .visit-social a {
  border-color: rgba(255, 255, 255, 0.25);
  color: inherit;
  padding: 8px 13px;
}
.site-footer .visit-social a:hover { color: #fff; border-color: #fff; }

.visit-next {
  margin-top: 34px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}
.visit-next p { margin: 6px 0; font-size: 14px; }

/* ---- footer ---- */

/* The footer stopped being a billboard in the 2026-08-09 redesign. It is
   where you are and what else there is to do — a dark band closing the page,
   set at the size of the small print it is. */
.site-footer {
  background: var(--ink);
  color: #cfc7b4;
  padding: 30px 0;
  margin-top: 40px;
}
.site-footer .footer-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 26px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px 34px;
  flex-wrap: wrap;
}
.site-footer p { margin: 0; font-size: 13px; }
.site-footer a { color: #cfc7b4; text-decoration: none; }
.site-footer a:hover { color: var(--wood); }
.footer-where a { border-bottom: 1px solid #3a352b; }
.footer-links {
  display: flex;
  gap: 22px;
  flex-wrap: wrap;
  font-size: 13px;
}

/* ---- flash messages ---- */

.flash {
  padding: 10px 14px;
  border: 1px solid var(--line);
  margin: 0 0 22px;
  font-size: 13px;
  background: var(--white);
  border-radius: var(--radius);
}
.flash-error { border-color: var(--brick); color: var(--brick); }
.flash-info { color: var(--olive); }

/* ---- hero ---- */

.hero { padding: 40px 0 24px; }
.hero h1 {
  font-size: clamp(38px, 5vw, 60px);
  line-height: 1.02;
  letter-spacing: -0.005em;
  margin: 0 0 16px;
}
.hero h1 .accent { color: var(--brick); }
.hero-sub { max-width: 40rem; color: var(--muted); margin: 0 0 26px; }

/* ---- cards ---- */

.notice-card {
  margin-top: 30px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px 24px;
}
.notice-card h2 { margin: 0 0 8px; }
.notice-card p { margin: 0; color: var(--muted); }

/* ---- forms ---- */

form label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  margin: 0 0 16px;
}

input[type="text"], input[type="email"], input[type="password"],
input[type="datetime-local"], select, textarea {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 10px 12px;
  font-family: var(--text);
  font-size: 15px;
  letter-spacing: normal;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brick);
  box-shadow: 0 0 0 3px var(--brick-wash);
}
textarea { resize: vertical; }
input[type="file"] { display: block; margin-top: 6px; font-family: var(--mono); font-size: 12px; }

button {
  font-family: var(--display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.01em;
  padding: 9px 18px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--white);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.12s, color 0.12s, background 0.12s;
}
button:hover { border-color: var(--brick); color: var(--brick); }

/* Primary — the one filled thing on a page. Brick rather than ink: with
   the shadows gone, colour is what says "this is the action". */
.button-primary {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 11px 22px;
  background: var(--brick);
  color: var(--white);
  font-family: var(--display);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.01em;
  text-decoration: none;
  border: 1px solid var(--brick);
  border-radius: var(--radius);
  transition: background 0.12s, border-color 0.12s;
}
.button-primary:hover {
  background: var(--brick-2);
  border-color: var(--brick-2);
  color: var(--white);
}

.link-button {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: none;
  background: none;
  box-shadow: none;
  padding: 0;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}
.link-button:hover { color: var(--brick); background: none; transform: none; box-shadow: none; }

.inline-form { display: inline; }

/* ---- auth card ---- */

.auth-card {
  max-width: 27rem;
  margin: 36px auto 0;
  background: var(--white);
  border: 1px solid var(--line);
  padding: 30px;
}
.auth-card h1 { font-size: clamp(28px, 4vw, 44px); }
.auth-alt { color: var(--muted); font-size: 12px; margin-top: 20px; }

.settings-form { max-width: 27rem; }

/* ---- tabs (account + staff sub-navigation) ---- */

.tabs {
  display: flex;
  gap: 20px;
  border-bottom: 1px solid var(--line);
  margin: 0 0 26px;
  flex-wrap: wrap;
}
.tabs a {
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--muted);
  padding: 0 2px 6px;
  margin-bottom: -2px;
  border-bottom: 2px solid transparent;
}
.tabs a:hover { color: var(--brick); }
.tabs a.active { color: var(--brick); border-bottom-color: var(--brick); }

/* ---- detail list (account overview) ---- */

.detail-list {
  margin: 0;
  background: var(--white);
  border: 1px solid var(--line);
  padding: 6px 22px;
  max-width: 34rem;
}
.detail-list div {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--rule);
}
.detail-list div:last-child { border-bottom: none; }
.detail-list dt {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  align-self: center;
}
.detail-list dd { margin: 0; font-weight: 700; }

/* ---- staff portal ---- */

.stat-row { display: flex; flex-wrap: wrap; gap: 16px; margin: 0 0 26px; }
.stat-card {
  display: flex;
  flex-direction: column;
  min-width: 150px;
  background: var(--white);
  border: 1px solid var(--line);
  padding: 14px 18px;
}
.stat-number {
  font-family: var(--display);
  font-weight: 700;
  font-size: 40px;
  line-height: 1;
}
.stat-label {
  color: var(--muted);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  margin-top: 4px;
}

.roster {
  width: 100%;
  border-collapse: collapse;
  background: var(--white);
  border: 1px solid var(--line);
  font-size: 13px;
}
.roster th, .roster td {
  text-align: left;
  padding: 9px 13px;
  border-bottom: 1px solid var(--rule);
}
.roster th {
  background: var(--ink);
  color: var(--paper);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}
.roster tr:last-child td { border-bottom: none; }
.roster tbody tr:hover { background: var(--paper-2); }

.role-badge {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 2px 8px;
  border-radius: var(--radius);
  background: var(--paper-2);
  color: var(--muted);
}
.role-staff { background: var(--brick); color: var(--white); }

/* ---- events: admin ---- */

.admin-toolbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.status-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: 0 0 22px;
  padding: 12px 16px;
  background: var(--white);
  border: 1px solid var(--line);
}
.status-row .status-actions { margin-left: auto; display: flex; gap: 12px; align-items: center; }
.status-row a { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; }

.status-badge {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 2px 8px;
  border-radius: var(--radius);
  background: var(--paper-2);
  color: var(--muted);
}
.status-published { background: var(--olive); color: var(--white); }
.status-cancelled { background: var(--brick); color: var(--white); }

.event-form { max-width: 34rem; }
.cover-preview {
  width: 220px;
  aspect-ratio: 3 / 2;
  background-size: cover;
  background-position: center;
  background-color: var(--concrete);
  border: 1px solid var(--line);
  margin: 4px 0 16px;
}

/* ---- organized play ---- */

.check-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  margin-top: 8px;
}
.check-grid .check-label { margin: 0 !important; min-width: 12rem; }

.offer-list li.offer {
  align-items: flex-start;
  gap: 16px;
}
.offer-main { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 14rem; }
.offer-state { display: flex; align-items: center; gap: 10px; }
.offer-actions { display: flex; align-items: center; gap: 12px; margin-left: auto; }
.offer-decided { opacity: 0.65; }

.offer-add {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}
.offer-add label { margin: 0; flex: 1; min-width: 9rem; }
.offer-add label:first-child { min-width: 15rem; flex: 2; }
.offer-add button { margin-bottom: 1px; }

/* ---- sharing panel ---- */

.share-panel {
  background: var(--white);
  border: 1px solid var(--line);
  padding: 6px 18px;
  margin-bottom: 18px;
  max-width: 44rem;
}
.share-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 11px 0;
  border-bottom: 1px solid var(--rule);
}
.share-row:last-child { border-bottom: none; }
.share-channel {
  font-weight: 700;
  text-transform: uppercase;
  font-size: 12px;
  letter-spacing: 0.1em;
  min-width: 8rem;
}
.share-state {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 2px 8px;
  border-radius: var(--radius);
  background: var(--paper-2);
  color: var(--muted);
}
.share-ok { background: var(--olive); color: var(--white); }
.share-bad { background: var(--brick); color: var(--white); }
.share-error { color: var(--brick); }
.share-action { margin-left: auto; }
.share-panel p { margin: 11px 0; }

.event-addcal { margin: 0 0 22px; }
.event-aside .event-addcal { padding-top: 16px; border-top: 1px solid var(--rule); }
.event-addcal a {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  text-decoration: none;
  border-bottom: 1.5px solid var(--brick);
  padding-bottom: 2px;
}

/* Staff-only, so it sits quieter than the calendar link above it — same
   small caps, no rule under it, muted ink. */

/* ---- checklists + templates ---- */

.checklist {
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
  max-width: 40rem;
}
.checklist li {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 9px 13px;
  background: var(--white);
  border: 1px solid var(--line);
  margin-bottom: 8px;
}
.checklist-done { opacity: 0.55; }
.checklist-done .checklist-title { text-decoration: line-through; }
.checklist-title { font-weight: 700; }
.checklist-title a { text-decoration: none; }
.checklist-title a:hover { color: var(--brick); }
.checklist-due {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  margin-left: auto;
}
.checklist-overdue { color: var(--brick); font-weight: 700; }
.check-toggle {
  width: 26px;
  height: 26px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  box-shadow: none;
}
.check-toggle:hover { transform: none; box-shadow: none; background: var(--paper-2); }

.checklist-add {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
  max-width: 40rem;
}
.checklist-add label { flex: 1; min-width: 160px; margin: 0; }
.checklist-add button { margin-bottom: 1px; }

.template-card {
  background: var(--white);
  border: 1px solid var(--line);
  padding: 16px 20px;
  margin-bottom: 16px;
}
.template-inactive { opacity: 0.6; box-shadow: none; }
.template-head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.template-name {
  font-family: var(--display);
  font-weight: 700;
  font-size: 20px;
  text-decoration: none;
}
.template-name:hover { color: var(--brick); }
.template-actions {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  flex-wrap: wrap;
}
.generate-form { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.generate-label { margin: 0 !important; }
.generate-label input, .generate-label select { width: auto; }

/* ---- events: public detail page ---- */

/* Two panes: a fixed rail for the picture and the people, a fluid column for
   the read. The rail is 300px because that is where a square cover stops
   dominating a 1440 viewport and starts sitting beside the title — the whole
   reason for the change. The kicker spans both so the back-link stays at the
   top-left of the page rather than the top of a column. */
.event-page {
  max-width: 62rem;
  padding-top: 14px;
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  /* The rail row is the flexible one: main spans the cover and rail rows, and
     without this its height inflates the cover row too, floating the rail far
     below the picture it belongs under. */
  grid-template-rows: auto auto 1fr;
  grid-template-areas:
    "kicker kicker"
    "cover  main"
    "rail   main";
  column-gap: 44px;
  align-content: start;
}
.event-page > .kicker { grid-area: kicker; }
.event-page > .event-cover { grid-area: cover; }
.event-page > .event-aside { grid-area: rail; }
.event-page > .event-main { grid-area: main; }
.event-page .kicker a { color: var(--brick); text-decoration: none; }
.event-page .kicker a:hover { text-decoration: underline; }

/* One column, in source order — cover, the read, then the people. */
@media (max-width: 899px) {
  .event-page { display: block; max-width: 44rem; }
  .event-page > .event-cover { max-width: 20rem; }
  .event-aside { max-width: 24rem; margin-top: 30px; }
}

.aside-block {
  margin: 0 0 20px;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
}
/* Whichever block lands first sits right under the cover, which is already a
   hard edge — a hairline directly beneath it reads as a second border on the
   picture. Which block that is depends on who's looking, so it's :first-child
   rather than a class the template has to guess at. */
.event-aside > .aside-block:first-child { padding-top: 0; border-top: none; }

/* The game this night is, with its own art. Small: it's a pointer to the
   game's page, not a second cover. */
.system-badge {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--ink);
  margin: 0;
}
/* The game's mark beside its name — a 2:1 band of the cover, filled, which is
   how a game's cover is shown everywhere else: the directory tile
   (.game-card-cover) and the follow picker (.pick-cover) both crop the same
   band out of the same art.

   It was contained in a 16:9 box before, on the reasoning that key art is a
   wordmark and a crop eats letters. That holds only for art that arrives at
   16:9. A square cover — Cyberpunk TCG's is 1024×1024 — fits to the box's
   height under `contain`, so the mark shrank to a 38px square adrift in a grey
   68px box while the same file read perfectly on the games page. Filling the
   band gives the two surfaces one answer, and the shallow crop takes about
   5% off the top and bottom of genuinely 16:9 art. */
.system-art {
  flex: none;
  width: 72px;
  height: 36px;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
  background-color: var(--concrete);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.system-name {
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: 0.04em;
  font-size: 15px;
  line-height: 1.1;
}
.system-badge:hover .system-name { color: var(--brick); }
.system-format {
  display: block;
  font-family: var(--mono);
  font-weight: 400;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 2px;
}
/* The games somebody follows, on their profile: a wrapping row of names in
   .system-name's type, so a game reads the same here as it does anywhere
   else. One per line would make four games look like a list of events, and
   the directory's cards would make a person's page look like the directory. */
.play-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin: 0 0 22px;
}

/* The staff strip under a rail block's public links. The rule only appears
   when there is a public list above it to be separated from — for most nights
   there isn't, and a hairline under the block's own heading would read as a
   stray border. */
.link-list + .aside-links-staff {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.aside-label {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--muted);
  margin: 0 0 10px;
}

/* Who is running it. Players pick D&D tables and Clocktower nights by who is
   at the head of them, so this is a draw, not a footnote — but in the rail it
   reads as a credit, not a headline. */
.host-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.host-list a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--ink);
  text-decoration: none;
}
.host-list a span { border-bottom: 1.5px solid var(--rule); }
.host-list a:hover span { border-bottom-color: var(--brick); }

/* Only the host sees this, and only on their own night. */
.event-hosting { margin: 0 0 20px; }
.event-hosting a {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  text-decoration: none;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  padding: 8px 12px;
  transition: 0.1s;
}
.event-hosting a:hover { border-color: var(--brick); color: var(--brick); }
/* Square, in the rail. A 3:2 asset center-crops into it; the calendar tile
   still takes its own 2:1 band from the same file. A real <img> rather than a
   background so the browser's own image menu (open in tab, save) works. */
.event-cover {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  object-position: center;
  background-color: var(--concrete);
  border: 1px solid var(--line);
  margin: 0 0 26px;
}

/* Smaller than a page title elsewhere, because it no longer has the full
   measure to itself — 44px over a 600px column is the same optical weight
   64px had over 700. */
.event-main h1 {
  font-size: clamp(30px, 3.6vw, 46px);
  margin: 0 0 20px;
}

/* When and where, as two rows of the same shape: a marker, a line, a
   subline. */
.event-facts {
  list-style: none;
  margin: 0 0 26px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.event-facts li {
  display: flex;
  align-items: center;
  gap: 14px;
}
.fact-text { display: flex; flex-direction: column; gap: 2px; }
.fact-line {
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: 0.04em;
  font-size: 17px;
  line-height: 1.1;
}
.fact-sub {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.fact-sub a { color: var(--muted); text-decoration: underline; }
.fact-sub a:hover { color: var(--brick); }

/* A torn-off calendar page: month in brick over the numeral. */
.datechip {
  flex: none;
  width: 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
  border: 1px solid var(--line);
  background: var(--white);
  overflow: hidden;
}
.datechip-m {
  width: 100%;
  text-align: center;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--white);
  background: var(--brick);
  padding: 2px 0 1px;
}
.datechip-d {
  font-family: var(--display);
  font-weight: 700;
  font-size: 26px;
  line-height: 1.1;
  padding: 2px 0 4px;
}

/* The where row has no chip of its own — one calendar page per page is the
   point of it. It indents to the chip's width instead so both lines of text
   share a left edge. */
.fact-where { padding-left: 66px; }

.event-about { margin-top: 30px; }
.event-about .section-label { margin-top: 0; }
/* Markdown now, so the breaks are <br> and the paragraphs are <p> —
   pre-line on top of that would double every one of them. */
.event-desc { margin: 0; }
.event-desc p, .game-blurb p { margin: 0 0 1em; }
.event-desc p:last-child, .game-blurb p:last-child { margin-bottom: 0; }
.event-desc ul, .event-desc ol,
.game-blurb ul, .game-blurb ol { margin: 0 0 1em; padding-left: 1.3em; }
.event-desc li, .game-blurb li { margin: 0 0 4px; }
/* A blank line between bullets makes Markdown wrap each one in a paragraph.
   That's a list either way to the reader, so it shouldn't suddenly space out
   like three separate paragraphs. */
.event-desc li p, .game-blurb li p { margin: 0; }
.event-desc a, .game-blurb a { color: var(--brick); }
.event-discord { margin-top: 1em; }

/* The registration block is the one thing on the page that asks for a
   decision, so it gets a card rather than a rule. */
.event-attend {
  background: var(--white);
  border: 1px solid var(--line);
  padding: 18px 20px;
  margin-bottom: 6px;
}

/* Unless there's nothing to decide — a finished night, or a walk-in event —
   in which case an empty card around one grey sentence is a lot of furniture
   for a note. Stated as "no button in here" rather than as a list of the
   states that qualify, so it stays true when the states change. */
.event-attend:not(:has(form, .button-primary, .button-plain)) {
  background: none;
  border: none;
  border-top: 1px solid var(--line);
  box-shadow: none;
  padding: 14px 0 0;
}
.event-attend:not(:has(form, .button-primary, .button-plain)) .attend-note { margin: 0; }
/* What a seat costs, at the top of the card where the decision is. Label in
   small mono over the figure in display type — the number is the fact. */
.attend-price {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--rule);
}
.price-label {
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--muted);
}
.price-amount {
  font-family: var(--display);
  font-weight: 700;
  font-size: 30px;
  line-height: 1;
}

.attend-note {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  margin: 0 0 14px;
}
/* "Registration opens soon" is a flag, not a shrug — the one attend state
   that asks the reader to come back. Brick ink on the faintest wash with a
   dot for the raised hand: the accent colour doing its one job, no motion,
   no shadow. */
.attend-soon {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--brick-2);
  background: var(--brick-wash);
  border: 1px solid var(--brick-soft);
  border-radius: var(--radius);
  padding: 7px 12px;
}
.attend-soon::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--brick);
}
/* The sentence under "I'm going" that says what it is and isn't. Sentence
   case rather than the tracked-out caps above it: this one is read, not
   scanned, and getting it wrong costs somebody a seat. */
.attend-fine {
  font-size: 13px;
  color: var(--muted);
  margin: 10px 0 0;
}

/* How full it is. Ink on paper inside the same 2px outline everything else
   here wears, so it reads as a gauge and not a progress bar borrowed from
   somewhere else. Brick when it's gone. */
.seat-meter {
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--paper-2);
  margin: -4px 0 16px;
  max-width: 22rem;
}
/* Brick for the fill, and a darker brick once it's full: colour is what
   carries meaning now that nothing on the page has a shadow. */
.seat-meter span {
  display: block;
  height: 100%;
  background: var(--brick);
}
.seat-meter-full span { background: var(--brick-2); }
.attend-state {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--olive);
  margin: 0 0 14px;
}

/* paying is secondary to registering, so it takes the plain surface —
   paper fill, ink shadow — rather than the inverted primary CTA */
.event-pay {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 18px;
}
.button-plain {
  display: inline-flex;
  align-items: center;
  padding: 9px 18px;
  background: var(--white);
  color: var(--ink);
  font-family: var(--display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.01em;
  text-decoration: none;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  transition: border-color 0.12s, color 0.12s;
}
.button-plain:hover {
  border-color: var(--brick);
  color: var(--brick);
}
.pay-where {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* the publisher's own listing — a pointer, so it reads as a link not a CTA */
/* The two-step case: the publisher's portal holds the seat, we hold the
   room, so the page asks for both and says which is which. */
.event-entry {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
}
/* Straight under the price the two rules stack into a double line, and
   first in the card there is nothing above to divide from — either way the
   block doesn't need its own rule. */
.attend-price + .event-entry,
.event-entry:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.entry-note {
  margin: 0 0 12px;
  font-size: 14px;
  color: var(--muted);
  max-width: 46ch;
}
.entry-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}


.attendee-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.attendee-list li {
  font-size: 12px;
  letter-spacing: 0.02em;
  background: var(--paper-2);
  border-radius: 999px;
  padding: 4px 12px 4px 4px;
}
.attendee-list li a { text-decoration: none; }
.attendee-list li a:hover { color: var(--brick); }
.attendee-private { color: var(--muted); border-style: dashed !important; box-shadow: none !important; }

/* ---- results: what happened at a night ----
   Heavier than the attendee chips on purpose — this is the part of the page
   somebody came back to read. The kind is a brick mono label, the name is
   display type, and the host's reason sits under both in body mono. */

.event-results { margin: 0 0 26px; }
.event-results .section-label { margin-top: 0; }
.result-list { list-style: none; margin: 0; padding: 0; max-width: 34rem; }
.result-list li {
  padding: 12px 16px;
  background: var(--white);
  border: 1px solid var(--line);
  margin-bottom: 10px;
}
.result-kind {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--brick);
  margin-bottom: 4px;
}
.result-list li > a,
.result-who {
  font-family: var(--display);
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 2px solid var(--rule);
}
.result-list li > a:hover { border-bottom-color: var(--brick); }
.result-who { border-bottom: none; }
.result-note {
  display: block;
  margin-top: 6px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}

/* ---- my events / player page ---- */

.my-events {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 34rem;
}
.my-events li {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  background: var(--white);
  border: 1px solid var(--line);
  margin-bottom: 10px;
}
.my-events li a { font-weight: 700; text-decoration: none; }
.my-events li a:hover { color: var(--brick); }
.my-events-when {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--brick);
  margin-left: auto;
}

.check-label {
  display: flex !important;
  gap: 10px;
  align-items: flex-start;
  font-weight: 400 !important;
  text-transform: none !important;
  letter-spacing: normal !important;
  font-size: 13px !important;
}
.check-label input { margin-top: 3px; accent-color: var(--brick); }

/* ---- month calendar (the events grid) ---- */

/* ---- the homepage, two panes ----
   Full width, because both panes want the room: the month grid can't go
   under 880px without scrolling sideways, and a feed of faces and sentences
   reads badly at anything narrower than about 360. The calendar takes what's
   left, which on any real screen is the majority — it is still what people
   came to the site for.

   The feed is a plain column beside the calendar — no scroll zone of its
   own. The whole page moves as one piece; if the feed runs long, that's
   what the page scroll is for.

   The stack breakpoint is arithmetic rather than taste: 880 grid + 40 gap +
   380 rail + 60 padding = 1360, and below that the calendar column would
   drop under its floor and start scrolling sideways. A calendar you have to
   drag is worse than a feed you have to scroll to. */
.home-page {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  column-gap: 40px;
  align-items: start;
}
@media (max-width: 1359px) {
  .home-page { display: block; }
  /* Under the calendar rather than beside it, held to a reading width. */
  .home-side {
    margin-top: 34px;
    max-width: 46rem;
  }
}

/* ---- the feed ----
   A face, what happened, when. Two kinds of entry share the row: a post is
   somebody's paragraph and gets a surface to sit on, everything else is a
   sentence on the paper pointing somewhere. */
.feed { display: flex; flex-direction: column; }

.feed-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line-2);
}
.feed-head .section-label { margin: 0; }
.feed-post-btn { padding: 5px 14px; font-size: 14px; }
.feed-note, .feed-note-link {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

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

/* Rows carry a hairline between them and enough air to read as separate
   things being said. The controls having moved into the hover bar is what
   buys the room back — the spacing here is close to what the feed had before
   there was anything to click, which is the point. The bar needs somewhere
   to float, so the row is the positioning context. */
.feed-item {
  position: relative;
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  gap: 11px;
  padding: 10px 0 11px;
  border-bottom: 1px solid var(--line);
}
.feed-item:last-child { border-bottom: none; }
.feed-face .avatar { width: 32px; height: 32px; }
.feed-face .avatar-blank { font-size: 14px; }

.feed-line { margin: 0; font-size: 14px; line-height: 1.45; color: var(--ink-2); }
/* The name is the one piece of display type in the row: down a column of
   sentences, who did it is what the eye is scanning for. */
.feed-who {
  font-family: var(--display);
  font-weight: 700;
  font-size: 15px;
  color: var(--ink);
  text-decoration: none;
}
a.feed-who:hover { color: var(--brick); }
.feed-object { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--line-2); }
.feed-object:hover { color: var(--brick); border-bottom-color: var(--brick); }
.feed-place { color: var(--muted); }
.feed-place a { color: var(--muted); text-decoration: none; border-bottom: 1px solid var(--line); }
.feed-place a:hover { color: var(--brick); border-bottom-color: var(--brick); }

/* Somebody's own words get a surface. It's the one place in the feed where
   the content isn't a pointer at a page, so it's the one place that reads as
   a thing rather than a line. */
.feed-said {
  margin: 5px 0 0;
  padding: 9px 11px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 14px;
  line-height: 1.45;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* A remark under a picture, on its own line in the feed. Not the boxed card a
   post gets: a post is a paragraph addressed to the hall and earns a shape of
   its own, while this is an aside about a photograph. A rule down the left
   marks it as quoted from somewhere else — the picture its sentence links
   to — and costs a line no height. */
.feed-quote {
  margin: 5px 0 0;
  padding: 0 0 0 10px;
  border-left: 2px solid var(--line-2);
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink-2);
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* Reactions that have been left: small square-ish tallies, always on the
   page. They are somebody's answer to the row, so they read as content —
   it's the controls that hide, not the record of who used them.

   Barely rounded rather than pill: a pill reads as a button waiting to be
   pressed, and most of these are a count somebody else already left. The one
   the reader pressed is marked with a thinned brick hairline and the faintest
   wash — enough to find at a glance, not enough to outline the row. */
.feed-react {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  margin: 6px 0 0;
}
.react-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--paper-2);
  padding: 2px 7px;
  font: inherit;
  font-size: 14px;
  line-height: 1.3;
  color: var(--ink-2);
}
/* The tally reads as a number beside the emoji, not as part of it. */
.react-n { font-size: 12px; color: var(--muted); }
.react-chip.on .react-n { color: var(--brick); }
button.react-chip { cursor: pointer; }
button.react-chip:hover { border-color: var(--line-2); }
.react-chip.on {
  border-color: var(--brick-soft);
  background: var(--brick-wash);
}
button.react-chip.on:hover { border-color: var(--brick); }

/* The bar. It floats over the top right of the row, out of the text's way,
   and appears when the row is hovered or something inside it has keyboard
   focus. Held open while the palette is open, so the thing you reached for
   doesn't vanish from under the cursor. */
.feed-acts {
  position: absolute;
  /* Inside the row's own box, not overhanging it: the bar only appears while
     the row is hovered, so a bar that floated above the row would vanish the
     moment the pointer left the row to reach it. */
  top: 1px;
  right: 0;
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 1px 3px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--white);
  box-shadow: 0 1px 3px rgb(0 0 0 / 6%);
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.1s;
}
.feed-item:hover .feed-acts,
.feed-item:focus-within .feed-acts,
.feed-acts:has(details[open]) {
  visibility: visible;
  opacity: 1;
}
.act-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 3px;
  border: none;
  border-radius: 6px;
  background: none;
  font-size: 13px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}
.act-btn:hover { background: var(--paper-2); border-color: transparent; }
.act-more { font-size: 15px; }
.act-del { font-size: 15px; }
.act-del:hover { color: var(--brick); }
/* The comment control is drawn, not typed: a line-art bubble among the
   emoji, so the one button in the bar that isn't a reaction doesn't read as
   one. It's a label — a button in everything but name — toggling the hidden
   checkbox that opens the field further down the row. */
.act-say {
  color: var(--ink-2);
  /* A hairline before it, because everything to the left of that line says
     the same kind of thing and this one doesn't. */
  margin-left: 3px;
  padding-left: 6px;
  border-left: 1px solid var(--line);
  border-radius: 0 6px 6px 0;
}
.act-say:hover { color: var(--brick); }
.act-say svg { display: block; }
.talk-toggle { position: absolute; opacity: 0; pointer-events: none; }
.talk-toggle:focus-visible + .feed-face + .feed-body + .feed-acts .act-say {
  outline: 2px solid var(--brick);
  outline-offset: 1px;
}

/* Touch has no hover to give, so the bar simply sits at the end of the row
   and the rows breathe a little more to keep the targets apart. */
@media (hover: none) {
  .feed-acts {
    position: static;
    visibility: visible;
    opacity: 1;
    grid-column: 2;
    justify-self: start;
    margin-top: 4px;
    box-shadow: none;
  }
  .feed-item { padding: 8px 0 9px; }
}

/* The picker is a sheet rather than a strip: one library, no headings, a
   grid of targets big enough to hit, and its own scroll so a long keyboard
   never pushes the rail around. It hangs off the + and is anchored right,
   because the bar it drops from sits at the right edge of the row. */
.react-picker { position: relative; display: inline-flex; }
.react-sheet {
  position: absolute;
  top: 100%;
  right: -4px;
  z-index: 3;
  width: 292px;
  max-height: 268px;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin-top: 6px;
  padding: 8px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--white);
  box-shadow: 0 6px 18px rgb(0 0 0 / 10%);
  text-align: left;
}
/* Eight across, and the library is written in eights, so the grid never ends
   in a ragged half-row. */
.react-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 1px;
}
.react-option {
  width: 100%;
  border: none;
  background: none;
  font-size: 22px;
  line-height: 1;
  padding: 4px 0;
  border-radius: 4px;
  cursor: pointer;
}
.react-option:hover { background: var(--paper-2); }

/* The talk under a row. Comments are set tight and quiet — the row above is
   the event, these are the murmur around it — and the box is one line high,
   because a comment that wants more than one line wants to be a post. */
.feed-talk { margin: 6px 0 0; }
.feed-talk-more {
  display: inline-block;
  margin-bottom: 1px;
  font-size: 12px;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}
.feed-talk-more:hover { color: var(--brick); border-bottom-color: var(--brick); }
.feed-comment {
  margin: 3px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}
.feed-comment-who {
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}
a.feed-comment-who:hover { color: var(--brick); }
.feed-comment-said { margin-left: 4px; }
/* The × sits at the end of the sentence rather than in a gutter of its own:
   at this size a column for it would be wider than the thing it removes. */
.feed-comment-x {
  margin-left: 5px;
  font-size: 13px;
  color: var(--muted);
  text-decoration: none;
  border-bottom: none;
}
/* The field stays out of the way until the speech bubble in the bar is
   clicked. Nothing to read there, and a rail with an open input under every
   row reads as a form rather than as a feed. */
.feed-comment-form {
  display: none;
  gap: 6px;
  margin: 4px 0 0;
}
.talk-toggle:checked ~ .feed-body .feed-comment-form { display: flex; }
.feed-comment-form input[type="text"] {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: 5px 10px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--white);
  font: inherit;
  font-size: 13px;
}
.feed-comment-form input[type="text"]:focus {
  outline: none;
  border-color: var(--brick);
}
.feed-comment-form .button-plain {
  padding: 5px 12px;
  font-size: 13px;
  white-space: nowrap;
}

/* Off the homepage the feed isn't a rail any more, but it still reads as
   one — a column of short lines, not a page-wide table. */
.feed-page { max-width: 640px; }
.feed-page .feed-item { border-bottom: none; }
.player-page .feed { max-width: 640px; }

/* The timestamp rides at the end of the sentence rather than taking a line
   of its own, the way a chat client sets it: small, quiet, and still the
   permalink to the entry's own page. */
.feed-when {
  margin-left: 2px;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--muted);
  text-decoration: none;
}
.feed-when:hover { color: var(--brick); }

.feed-empty { margin: 18px 0 0; font-size: 14px; }

.cal-block { margin-bottom: 46px; }

.cal-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.cal-title {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(24px, 2.4vw, 30px);
  line-height: 1.1;
  margin: 0;
}
.cal-title .cal-yr { color: var(--brick); }

.cal-scroll { overflow-x: auto; border: 1px solid var(--line); }
/* A container, so a tile's cover height can be worked out from the grid's
   width instead of from its own. See .mev-cover for why that matters. */
.mcal {
  min-width: 880px;
  container-type: inline-size;
}
.mcal-dow {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  background: var(--paper-2);
  color: var(--muted);
  border-bottom: 1px solid var(--line-2);
}
.mcal-dow span {
  padding: 7px 10px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.mcal-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.mcal-cell {
  min-height: 128px;
  height: auto;
  padding: 7px;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: var(--white);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.mcal-cell-blank { background: var(--paper-2); min-height: 0; }
.mcal-daynum {
  flex: none;
  font-size: 12px;
  color: var(--muted);
  font-weight: 700;
  align-self: flex-start;
}
.mcal-cell-today .mcal-daynum {
  color: var(--paper);
  background: var(--brick);
  min-width: 22px;
  height: 22px;
  padding: 0 4px;
  border-radius: 11px;
  display: inline-grid;
  place-items: center;
}
.mcal-cell-past { background: var(--paper-2); }

/* event tile inside a calendar cell */
.mev {
  display: block;
  flex: none;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--white);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.12s;
}
.mev:hover { color: inherit; border-color: var(--brick); }
.mev-cover {
  /* A 2:1 center band of the square asset — the tightest the tile goes before
     the caption outweighs the picture. A quarter is cut off each of the top
     and bottom here, which is why the prompt asks for subjects in the middle
     half and why an event can carry a wide override for the art where that
     crop lands badly.

     The band is computed from the calendar's width rather than declared as an
     `aspect-ratio`, and that is a correctness fix, not a preference. With
     `aspect-ratio` a tile's height depends on its own width, which the grid
     does not know while it is deciding how tall each row should be — so it
     sized rows from the 880px min-width floor (a 55px cover) and then painted
     covers at their real width (93px on a wide screen). Every tile overflowed
     its day by the difference, landing on the day numbers underneath. WebKit
     showed it after a window resize; Blink hid it by relaying out. Deriving
     the height from the container removes the circularity outright, so no
     engine has anything to disagree about.

     100cqw is the grid's width: a cell is a seventh of it, less 14px of cell
     padding and 2px of tile border, and half of that is the 2:1 band —
     hence /14 - 8. The plain px line above it is the fallback for anything
     without container query units. */
  height: 64px;
  height: calc(100cqw / 14 - 8px);
  background-size: cover;
  background-position: center;
  background-color: var(--concrete);
  border-bottom: 1px solid var(--line);
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
}
.mev-t {
  padding: 4px 6px 6px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 12px;
  line-height: 1.02;
}
.mev-time {
  display: block;
  font-family: var(--mono);
  font-weight: 400;
  font-size: 9.5px;
  letter-spacing: 0.06em;
  color: var(--brick);
  margin-top: 2px;
}
.mev-past { filter: grayscale(0.8); opacity: 0.6; }
.mev-past:hover { filter: none; opacity: 1; }
.mev-past .mev-time { color: var(--muted); }

/* Staff only: a sanctioned night with no listing in the publisher's portal.
   The flag is set per viewer in the handler, so a player's grid never
   carries one and this can't leak. Loud on purpose — it's a job, not a
   status, and it should be visible at a glance across a whole month. The
   red is its own alarm red rather than --brick, which is the house accent
   and reads as chrome. */
.mev-cover { position: relative; }
.mev-opflag {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 2px 3px;
  background: #d81f16;
  color: #fff;
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  text-wrap: balance;
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
}
.mev-opgap { border-color: #d81f16; }
.mev-opgap:hover { border-color: #a3150e; }

/* ---- special order request ---- */

.request-card { max-width: 34rem; }
.request-intro {
  font-size: 15px;
  line-height: 1.6;
  color: var(--muted);
  margin: 0 0 22px;
}
.field-note {
  display: block;
  margin-top: 6px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--muted);
}
.request-choice {
  border: 1px solid var(--line);
  background: var(--paper-2);
  padding: 12px 14px;
  margin: 0 0 16px;
}
.request-choice .choice-label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  margin-bottom: 8px;
}
.request-choice .choice {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 6px;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}
.request-choice .choice:last-child { margin-bottom: 0; }
.request-choice input[type="radio"] { accent-color: var(--brick); margin: 0; }

/* ---- player pictures ----
   Square, not round: everything else on this page is a hard-edged box with
   an offset shadow, and a circle would be the one soft thing in the room.
   Size comes from context — the base is the small one, and the places that
   want a bigger face say so. */

.avatar {
  display: inline-block;
  flex: none;
  width: 26px;
  height: 26px;
  object-fit: cover;
  background: var(--concrete);
  border: 1px solid var(--line-2);
  border-radius: 50%;
  vertical-align: middle;
}
.avatar-blank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--display);
  font-weight: 700;
  font-size: 14px;
  line-height: 1;
  color: var(--white);
  background: var(--ink-2);
}
.avatar-xl {
  width: 148px;
  height: 148px;
}
.avatar-xl.avatar-blank { font-size: 72px; }

/* Who's going, and the "hosted by" credit: the face rides inside the chip
   next to the name, so the tile grows rather than the layout changing. */
.attendee-list li a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.attendee-list li { padding: 3px 10px 3px 4px; }
.attendee-private { padding-left: 4px !important; }
.attendee-private .avatar-blank { background: var(--concrete); color: var(--white); }

/* In the rail a full roster is a dozen of these in a column, and a dozen
   offset shadows is a texture, not a list. They keep the outline and drop the
   shadow there. */
.event-aside .attendee-list li { box-shadow: none; border-width: 1px; }
.event-aside .avatar { width: 22px; height: 22px; }
.event-aside .avatar-blank { font-size: 12px; }
/* The underline belongs to the name, not to the picture — a rule running
   under a photograph reads as a stray border. */
.host-list a { vertical-align: middle; text-decoration: none; }

.player-head {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
}
.player-head .avatar {
  width: 96px;
  height: 96px;
  border-width: 2px;
}
.player-head .avatar-blank { font-size: 46px; }

.account-identity {
  display: flex;
  align-items: center;
  gap: 16px;
  margin: 0 0 24px;
}
.account-identity .avatar {
  width: 64px;
  height: 64px;
  border-width: 2px;
}
.account-identity .avatar-blank { font-size: 32px; }
.account-name {
  margin: 0 0 4px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 26px;
  line-height: 1;
}

.settings-photo {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 26px;
}
.settings-photo .avatar {
  width: 64px;
  height: 64px;
  border-width: 2px;
}
.settings-photo .avatar-blank { font-size: 32px; }

/* ---- the picture page ---- */

.photo-page { max-width: 34rem; }
.photo-intro {
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  margin: 0 0 26px;
}
.photo-current { margin: 0 0 30px; }
.photo-name {
  margin: 14px 0 0;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.photo-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 30px;
}
.photo-choice {
  padding: 0;
  border: 1px solid var(--line);
  background: var(--white);
  cursor: pointer;
  line-height: 0;
  transition: border-color 0.12s, color 0.12s;
}
.photo-choice:hover { border-color: var(--brick); }
.photo-choice img {
  display: block;
  width: 76px;
  height: 76px;
  object-fit: cover;
}
.photo-foot {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  border-top: 1px solid var(--line);
  padding-top: 22px;
}

/* Bait for form-filling bots; nobody else ever sees it. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ---- the event finder (/events) and the game pages (/games) ---- */

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The finder's tab strip: which side of now, above the filters that narrow
   whichever list it selects. */
.find-tabs { margin: 20px 0 20px; }

.find-search {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 18px;
  max-width: 42rem;
}
.find-search input {
  flex: 1;
  min-width: 0;
  font-family: var(--text);
  font-size: 15px;
  padding: 9px 12px;
  background: var(--white);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  color: var(--ink);
}
.find-search input:focus {
  outline: none;
  border-color: var(--brick);
  box-shadow: 0 0 0 3px var(--brick-wash);
}
.find-search button {
  font-family: var(--display);
  font-size: 15px;
  letter-spacing: 0.01em;
  font-weight: 700;
  padding: 9px 18px;
  background: var(--white);
  color: var(--ink);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.12s, color 0.12s;
}
.find-search button:hover { border-color: var(--brick); color: var(--brick); }

/* The game picker. Twenty-eight games listed as chips met the reader as a
   wall of names before the first event; a picker says the same thing in one
   line. It submits on change where there's JS, and the Search button is what
   applies it where there isn't. */
.find-search select {
  display: block;
  flex: none;
  width: auto;
  max-width: 15rem;
  margin-top: 0;
  padding: 9px 12px;
  font-size: 15px;
  border-color: var(--line-2);
}

@media (max-width: 30rem) {
  /* The search takes the first line to itself; the picker and the button
     share the second rather than each claiming a line. */
  .find-search input { flex: 1 1 100%; }
  .find-search select { flex: 1 1 0; min-width: 0; max-width: none; }
}

/* Facet chips. Each is a link to the same view with one facet changed, so
   the whole filter round-trips through the URL and stays pasteable. */
.chiprow {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 18px;
  margin: 0 0 10px;
}
.chip-label {
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--muted);
  min-width: 3.4rem;
}
.chip {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-decoration: none;
  color: var(--muted);
  padding: 3px 0;
  border-bottom: 1px solid transparent;
  transition: color 0.12s, border-color 0.12s;
}
.chip:hover { color: var(--ink); border-bottom-color: var(--line-2); }
.chip-on { color: var(--brick); border-bottom-color: var(--brick); }
.chip-on:hover { color: var(--brick); border-bottom-color: var(--brick); }

.find-list {
  list-style: none;
  padding: 0;
  margin: 24px 0 0;
  border-top: 1px solid var(--line);
}
.find-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 11px 10px;
  border-bottom: 1px solid var(--line);
  transition: background 0.12s;
}
.find-row:hover { background: var(--white); }
.find-date {
  flex: none;
  width: 62px;
  text-align: center;
  text-decoration: none;
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  padding: 7px 4px;
  font-family: var(--mono);
}
.find-date:hover { color: var(--brick); border-color: var(--brick); }
.fd-day {
  display: block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--brick);
}
.fd-date {
  display: block;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.fd-year { display: block; font-size: 10px; letter-spacing: 0.1em; color: var(--muted); }
.find-cover {
  flex: none;
  width: 128px;
  aspect-ratio: 2 / 1;
  background-size: cover;
  background-position: center;
  background-color: var(--concrete);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.find-main { flex: 1; min-width: 10rem; }
.find-title {
  font-family: var(--display);
  font-weight: 700;
  font-size: 20px;
  line-height: 1;
  letter-spacing: 0.01em;
  text-decoration: none;
}
/* A campaign's nights all share its name and all lead to its page, so the date
   rides in the title to tell them apart — quieter than the name it follows. */
.find-night {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.find-meta {
  margin: 5px 0 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}
/* Any child, not just a span — the container link is an <a>, and matching on
   the tag left it running into the game name with a bare space. The separator
   leads the item it belongs to, except in front of the container link, where it
   trails the item before instead: generated content anywhere inside that <a>
   sits under the link's underline and inside its hit box. Its colour is set
   rather than inherited, so both dots on a row match whichever element ends up
   carrying them — the time is brick red and would otherwise tint its own. */
.find-meta > *:not(:first-child)::before,
.find-meta > *:has(+ .find-event)::after { content: " · "; color: var(--muted); }
.find-meta > .find-event::before { content: none; }
.find-time { color: var(--brick); }
/* The container link is the least important thing on the row, so it wears the
   quiet underline the feed uses rather than the browser's default. */
.find-event { color: var(--muted); text-decoration: none; border-bottom: 1px solid var(--line); }
.find-event:hover { color: var(--brick); border-bottom-color: var(--brick); }
.find-seats {
  flex: none;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  text-align: right;
}
/* The same flag on a row. Forty rows is too many for a stamp apiece, so
   the ink change and the dot carry it alone. */
.find-seats-soon { color: var(--brick); }
.find-seats-soon::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brick);
  margin-right: 6px;
  vertical-align: 1px;
}
/* Nights that already ran read as history, not as things to do. */
/* A past night's cover goes grey wherever it appears. The dimming is a
   separate class because it only earns its keep where history sits *under*
   what's coming — the game page's "Lately" strip. The finder's past tab is
   the list somebody asked for, and washing it out reads like a fault. */
.find-list-past .find-cover { filter: grayscale(1); }
.find-list-quiet .find-row { opacity: 0.8; }
.find-foot {
  margin: 26px 0 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}

/* The sprite is a definition, not a picture: it holds the chip marks and
   draws nothing itself. Hidden by size rather than display:none, which in
   some browsers takes the symbols out of reach of the <use> pointing at
   them. */
.ico-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* The category picker. On /find the chips are a secondary facet on a page
   whose point is the list below them, so they stay a quiet inline row. Here
   they're the main way through the page — eight shelves, and picking one is
   the shortest route to a game — so they get a surface, a border, and a
   target worth aiming at. Scoped to this row on purpose: /find's chips run
   one per game and would become a wall at this size. */
.games-chips {
  margin-bottom: 30px;
  align-items: center;
  gap: 10px;
}
.games-chips .chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  letter-spacing: 0.1em;
  padding: 11px 16px;
  color: var(--ink-2);
  background: var(--white);
  border: 1px solid var(--line-2);
  /* 3px, like everything else here — a surface, not a pill. */
  border-radius: var(--radius);
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
/* Line art rather than filled shapes: at 14px a filled mark next to 12px
   mono reads as a blob, and stroking on currentColor means the icon turns
   with the text — muted, brick on hover, paper when the chip fills in.
   The die's pips carry their own fill attribute, which beats the inherited
   fill:none here. */
.games-chips .chip-ico {
  width: 14px;
  height: 14px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.games-chips .chip:hover {
  color: var(--brick);
  background: var(--brick-wash);
  border-color: var(--brick);
}
/* The chosen shelf fills in rather than just changing colour: at this size a
   tinted word among seven others is easy to lose. */
.games-chips .chip-on,
.games-chips .chip-on:hover {
  color: var(--paper);
  background: var(--brick);
  border-color: var(--brick);
}
.games-chips .chip:focus-visible {
  outline: 2px solid var(--brick);
  outline-offset: 2px;
}
/* On a phone a category name can be most of the line, so at full size the
   row becomes one chip per row and the first game falls below the fold.
   Tighter type and padding fit two across without dropping under the ~36px
   a thumb needs. */
@media (max-width: 30rem) {
  .games-chips { gap: 8px; }
  .games-chips .chip {
    font-size: 11px;
    letter-spacing: 0.06em;
    padding: 9px 11px;
    gap: 6px;
  }
  .games-chips .chip-ico { width: 13px; height: 13px; }
}

/* Shelf headings. The rule runs to the end of the row so a shelf reads as a
   section of the page rather than a label floating over cards. */
.games-shelf {
  display: flex;
  align-items: baseline;
  gap: 12px;
  font-size: clamp(20px, 2.4vw, 28px);
  margin: 34px 0 14px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line-2);
}
.games-shelf:first-of-type { margin-top: 0; }
.shelf-count {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.14em;
  color: var(--brick);
}
.games-grid + .games-shelf { margin-top: 40px; }

.games-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
  gap: 20px;
}
/* The card is a box holding a link, not a link holding everything: the follow
   star is a form, and a form nested in an anchor is neither valid nor
   clickable. The link still fills the card, so the whole surface leads to the
   game exactly as it did. */
.game-card {
  position: relative;
  display: block;
  background: var(--white);
  border: 1px solid var(--line);
  transition: border-color 0.12s, color 0.12s;
}
.game-card-link { display: block; text-decoration: none; color: inherit; }
.game-card:hover { border-color: var(--line-2); color: var(--ink); }
/* Same latent circularity as the calendar tile: the card sits in an auto-fill
   grid, so the cover's width — and so its height — isn't known while rows are
   being sized. Nothing has misbehaved here yet, but it is the same bug
   waiting for the same engine, so it gets the same cure. */
.game-card { container-type: inline-size; }
.game-card-cover {
  display: block;
  height: 124px;                 /* fallback without container query units */
  height: calc(50cqw - 1px);     /* 2:1 of the card, less its 1px border */
  background-size: cover;
  background-position: center;
  border-bottom: 1px solid var(--line);
}
.game-card-blank {
  background-color: var(--concrete);
  background-image: repeating-linear-gradient(45deg,
    rgba(22,20,15,0.07) 0 8px, transparent 8px 16px);
}
/* The name is the whole body now, so the padding sits even around it rather
   than leaving the room a second line used to take. */
.game-card-body { display: block; padding: 13px 14px; }
.game-card-name {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 22px;
  line-height: 1.1;
}

/* Following, from the shelf.

   The mark sits on the art rather than in the body: the body is where the
   game says what it is, and a control there would be one more line competing
   with the name. On the picture it reads as a mark applied to the card.

   It is deliberately quiet until wanted — a dim outline over the art, filled
   brick once it means something. Twenty-eight cards each shouting a call to
   action is a directory nobody can read. */
.card-follow { position: absolute; top: 8px; right: 8px; margin: 0; }
.mark-button {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: var(--radius);
  /* Its own surface: the art underneath is any colour at all, and an outline
     on white is the only version of this that is legible on every one of
     them. */
  background: rgba(251, 249, 244, 0.86);
  border: 1px solid var(--line-2);
  color: var(--muted);
}
.mark-button:hover { color: var(--brick); border-color: var(--brick); }
/* A thumb is not a mouse pointer: on a phone the mark grows to something
   somebody can hit without opening the game by accident. */
@media (max-width: 640px) {
  .mark-button { width: 38px; height: 38px; }
}
.mark-on { color: var(--brick); border-color: var(--brick); }
.mark-ico {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linejoin: round;
}
/* Filled is the whole difference between "you could" and "you have", so the
   on state fills the shape rather than only recolouring its edge. */
.mark-on .mark-ico, .pick-card input:checked ~ .pick-body .mark-ico { fill: currentColor; }

/* The same fact said in a word, beside the game's name. The button sits on
   the title's baseline rather than under it — following is about this game,
   and this is where a reader is already looking. It wraps to its own line
   before it will squeeze a long name. */
.game-title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  margin-bottom: 6px;
}
.game-title h1 { margin: 0; }
.game-title form { margin: 0; }
.follow-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.follow-button .mark-ico { width: 14px; height: 14px; }
.follow-button.is-following {
  color: var(--brick);
  border-color: var(--brick);
  background: var(--brick-wash);
}
.follow-button.is-following .mark-ico { fill: currentColor; }
.game-follow-note {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

/* Who follows this game. The chips are the who's-going strip's, unchanged:
   the same fact — these people, here — deserves the same shape, and a second
   style for a row of faces would only be a second thing to keep in step. */
.game-followers { margin: 0 0 26px; }
.game-followers .section-label { margin-top: 0; }
/* The named chips get their layout from the anchor inside them; the
   anonymous one has no anchor, so it gets the same one here. Without this it
   sits a few pixels taller than the row it's part of — and the one chip that
   looks like a mistake is the one saying somebody asked not to be named. */
.game-followers .attendee-private {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 3px 10px 3px 4px !important;
}
.attendee-more {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* The chips carry a face's worth of left padding for the picture this one
     hasn't got. */
  padding-left: 12px !important;
  display: flex;
  align-items: center;
}

/* The picker: the step after signing up, and the page that manages it
   afterwards. Same wall of games as the directory, but every card is a
   checkbox — the whole set is one decision, saved once. */
.games-pick .pick-following {
  max-width: 62ch;
  margin: 0 0 6px;
  font-size: 16px;
}
.games-pick .pick-intro {
  max-width: 62ch;
  margin: 0 0 22px;
  color: var(--ink-2);
}
.pick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 14px;
  margin-bottom: 26px;
}
/* A card that happens to be a label, so it has to undo `form label` — the
   form-field caption styling (small, uppercase, muted) is the opposite of
   what a game's name wants here. */
.pick-card {
  position: relative;
  display: block;
  font-size: inherit;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink);
  margin: 0;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  container-type: inline-size;
  transition: border-color 0.12s, background 0.12s;
}
.pick-card:hover { border-color: var(--line-2); }
/* The tick itself is off-screen rather than hidden: display:none would take
   it out of the tab order, and this page has to be usable from a keyboard. */
.pick-card input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
}
.pick-card:has(input:checked) { border-color: var(--brick); background: var(--brick-wash); }
.pick-card:has(input:focus-visible) {
  outline: 2px solid var(--brick);
  outline-offset: 2px;
}
.pick-cover {
  display: block;
  height: 96px;
  height: calc(50cqw - 1px);
  background-size: cover;
  background-position: center;
  border-bottom: 1px solid var(--line);
}
.pick-body {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
}
.pick-mark { display: grid; place-items: center; color: var(--line-2); flex: none; }
.pick-card:has(input:checked) .pick-mark { color: var(--brick); }
.pick-name {
  font-family: var(--display);
  font-weight: 700;
  font-size: 17px;
  line-height: 1.1;
}
.pick-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin-top: 26px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
}

.game-meta { display: flex; flex-wrap: wrap; gap: 8px; margin: -12px 0 20px; }
/* The tags' negative top margin was closing the gap under a bare h1. The
   title row already sets its own, so it isn't there to close any more. */
.game-title + .game-meta { margin-top: 0; }
.game-tag {
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  background: var(--paper-2);
  border-radius: var(--radius);
  padding: 4px 9px;
}
/* How a player gets into the sanctioned nights — a sentence under the tags,
   set like prose rather than like another tag, because it asks the reader to
   do something. */
.game-sanction {
  max-width: 44rem;
  margin: -10px 0 20px;
  color: var(--muted);
}
/* The game's key art, at its own proportions. It was a 5:2 background box
   filling the content width, which made a 16:9 logo 450px tall and pushed
   what's actually on the page below the fold. Capping the height and letting
   the width follow keeps the wordmark whole — cover cropped it, contain left
   grey bars beside it. */
.game-hero {
  display: block;
  max-width: 100%;
  max-height: 220px;
  border: 1px solid var(--line);
  background: var(--concrete);
  margin: 0 0 26px;
}
.game-blurb {
  max-width: 44rem;
  margin: 0 0 30px;
}

/* The art and the links, side by side. The art keeps its own width, so the
   links take what's left — align-items:start stops the column stretching to
   the height of a tall logo. */
.game-top {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  gap: 26px 40px;
  margin: 0 0 26px;
}
.game-top .game-hero { margin: 0; }
/* Pushed to the far edge rather than left against the art: the column is a
   short list of destinations, and hard against the cover it reads as a
   caption for it. Text stays left-aligned inside — a ragged left edge on a
   list of names is harder to scan than the gap it would close. */
.game-links {
  flex: 0 1 auto;
  margin-left: auto;
  min-width: 14rem;
  max-width: 22rem;
}
/* Once the column has wrapped under the art there is no right-hand side to
   sit on, and the push would only indent it away from every other margin on
   the page. */
@media (max-width: 48rem) {
  .game-links { margin-left: 0; max-width: none; width: 100%; }
}
.game-links .section-label { margin-top: 0; }
/* The staff strip reads as an aside to the public set above it: same list,
   held off by a rule rather than by another heading weight. */
.game-links-staff {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

/* One link per line, with the host under it in small type — the name says
   what it is, the host says where it lands, and nobody has to hover to find
   out which publisher's site they're about to open. */
.link-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.link-list li + li { margin-top: 12px; }
.link-list a {
  display: block;
  text-decoration: none;
  font-weight: 600;
  line-height: 1.3;
}
.link-list a:hover { color: var(--brick); }
.link-list a:hover .link-host { color: var(--brick); }
.link-host {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 400;
  color: var(--muted);
  margin-top: 2px;
  overflow-wrap: anywhere;
}

/* ---- discussion boards ----
   An old forum in the zine's own vocabulary. The board is a list of rules
   rather than a grid of cards: a thread row carries no picture and no
   number worth setting in a box, so the card treatment used for events
   would be six shadows saying nothing. Threads get the hard shadow one
   level down (3px) on the thread page itself, where a post is a surface
   somebody wrote. */

.board { margin-top: 40px; border-top: 1px solid var(--line); padding-top: 6px; }
.board .section-label { margin-top: 18px; }

.board-list { list-style: none; margin: 0; padding: 0; }
.board-row {
  padding: 12px 0 13px;
  border-bottom: 1px solid var(--rule);
}
.board-row:first-child { border-top: 1px solid var(--rule); }
/* A pinned thread reads as pinned without a badge doing all the work: the
   brick rule on the left is visible at a glance down the list. */
.board-row-pinned {
  border-left: 3px solid var(--brick);
  padding-left: 12px;
  background: var(--white);
}

.board-title {
  font-family: var(--display);
  font-weight: 700;
  font-size: 20px;
  line-height: 1.05;
  text-decoration: none;
  display: inline-block;
}
.board-title:hover { color: var(--brick); }

.board-meta {
  margin: 5px 0 0;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.board-meta > * + *::before { content: " · "; }
.board-who { color: var(--ink); }
.board-count { color: var(--brick); }
.board-flag {
  color: var(--white);
  background: var(--ink);
  padding: 1px 7px;
  letter-spacing: 0.14em;
}
.board-flag + *::before { content: none; }

.board-empty { margin: 16px 0 0; }

.board-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 22px;
}
.board-note {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}

/* ---- one thread ---- */

.thread { max-width: 46rem; }
.thread-title { font-size: clamp(28px, 3.4vw, 44px); margin-bottom: 8px; }
.thread-flags { margin: 0 0 22px; display: flex; gap: 8px; font-size: 11px; }

.post-list { list-style: none; margin: 0; padding: 0; }
.post {
  background: var(--white);
  border: 1px solid var(--line);
  padding: 16px 18px 18px;
  margin-bottom: 18px;
}

.post-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-bottom: 10px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--rule);
}
.post-who {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 700;
  text-decoration: none;
}
.post-who .avatar { width: 26px; height: 26px; }
.post-who .avatar-blank { font-size: 13px; }
.post-who-private { color: var(--muted); font-weight: 400; }
.post-who-private .avatar-blank { background: var(--concrete); color: var(--white); }
.post-when {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* pre-line is the whole of the formatting on offer: the author's own blank
   lines between paragraphs, and nothing that could arrive as markup. */
.post-body { white-space: pre-line; }
/* Pushed to the right so it reads as a control on the card rather than as
   the last line of what somebody wrote. */
.post-actions { margin-top: 14px; text-align: right; }

.thread-foot { margin-top: 26px; }
.thread-foot .board-note { margin-top: 10px; }

.thread-mod {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 34px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
}
.mod-label {
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--brick);
}

/* ---- writing something ---- */

.write-card { max-width: 40rem; }
.write-card h1 { font-size: clamp(28px, 4vw, 44px); margin-bottom: 8px; }
.write-intro { color: var(--muted); font-size: 12px; margin: 0 0 24px; }

/* ---- responsive ---- */

@media (max-width: 760px) {
  .site-header .nav-inner { flex-direction: column; align-items: flex-start; gap: 12px; }
  .site-header nav { padding-bottom: 0; }
  .find-cover { display: none; }
  .find-row { flex-wrap: wrap; }
  .find-seats { margin-left: 76px; text-align: left; }
  .visit-grid { grid-template-columns: 1fr; }
  .post-head { align-items: flex-start; }
}

/* ---- badges ----
   The permanent record on a player's profile: small mono pills. */

.badge-strip {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 14px 0;
  padding: 0;
}
.badge-strip li {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  padding: 4px 10px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--white);
}

/* ---- the party: who's playing a campaign ----
   Sits above the dates because a campaign is one commitment rather than a
   booking per night. The roster reuses the attendee chips so a member who has
   read a night's "who's going" reads this the same way; only the queue below
   it is new, and it is only ever drawn for the host. */

.party { margin: 0 0 30px; }
.party-host { font-size: 13px; color: var(--muted); margin: 0 0 4px; }
.party-count {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brick);
  margin: 0 0 10px;
}
.party-list { margin-bottom: 14px; }
.party-you { font-size: 13px; color: var(--muted); margin: 0 0 8px; }
.party-act { margin: 0 0 12px; }

.party-queue-label {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 18px 0 8px;
}
.party-queue { list-style: none; margin: 0; padding: 0; max-width: 34rem; }
.party-queue-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 8px 0;
  border-top: 1px solid var(--rule);
}
.party-queue-who { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; }
.party-queue-row form { display: flex; gap: 8px; margin: 0; }

/* ---- a campaign's running order ----
   Numbered rather than dated-first, because "Session 3" is what the table
   calls it. Past sessions stay on the page and fade rather than disappearing:
   where a campaign has been is as much the story as where it's going. */

.session-list { list-style: none; margin: 0 0 26px; padding: 0; max-width: 34rem; counter-reset: none; }
.session-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding: 10px 0;
  border-top: 1px solid var(--rule);
}
.session-row:last-child { border-bottom: 1px solid var(--rule); }
.session-no {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brick);
}
.session-when { font-size: 14px; }
.session-time { color: var(--muted); margin-left: 8px; }
.session-past { color: var(--muted); }
.session-past .session-no { color: var(--muted); }

/* ---- notifications ---- */

/* The header bell. It stands in the nav at all times for a signed-in
   reader — this started as a count that appeared only when something was
   waiting, which made the section itself flicker in and out of the
   navigation.

   It's the one icon in a nav of words, so it's drawn the way the games
   shelves are drawn: 16px box, no fill, currentColor stroke at 1.4, round
   joins. That keeps it quiet enough to sit beside uppercase 12px type
   without becoming the loudest thing in the row. */
.nav-bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  align-self: center;
  padding: 0;
  color: var(--ink);
  border-bottom: none !important;
}
.nav-bell svg {
  display: block;
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.nav-bell:hover { color: var(--brick); }

/* The count sits on the bell rather than beside it, so the pair reads as one
   control. The ring is the header's own paper, which is what lets it overlap
   the drawing without muddling it. */
.nav-bell-count {
  position: absolute;
  top: -5px;
  left: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 15px;
  height: 15px;
  padding: 0 4px;
  background: var(--brick);
  color: var(--white);
  border-radius: 8px;
  font-size: 10px;
  line-height: 1;
  letter-spacing: 0;
  box-shadow: 0 0 0 2px var(--paper);
}
.nav-bell:hover .nav-bell-count { background: var(--brick-2); }

.notice-list { list-style: none; margin: 18px 0 0; padding: 0; }
.notice {
  padding: 13px 0 14px;
  border-bottom: 1px solid var(--line);
}
.notice:last-child { border-bottom: none; }

/* What was new when the page opened. A rule in the margin rather than a
   filled row: the list is a record, and yesterday's notices shouldn't look
   like they've been switched off. */
.notice-new {
  border-left: 2px solid var(--brick);
  padding-left: 12px;
  margin-left: -14px;
}

.notice-line { margin: 0; font-size: 13px; color: var(--muted); }
.notice-title { margin: 3px 0 0; }
.notice-title a {
  font-family: var(--display);
  font-weight: 700;
  font-size: 18px;
  color: var(--ink);
  text-decoration: none;
}
.notice-title a:hover { color: var(--brick); }
.notice-excerpt { margin: 5px 0 0; font-size: 14px; color: var(--ink-2); }
.notice-when {
  margin: 6px 0 0;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Photos from a session — the half of the after-record members write.
   The strip on the night's page and the wall at /photos share the tile;
   what differs is how many columns they get. */
.event-shots { margin: 30px 0; }
.shots-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 12px;
}
.shots-empty { margin: 0; font-size: 14px; color: var(--muted); }

.shot-grid,
.shot-wall {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 14px;
}
.shot-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.shot-wall { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }

/* Previews are one shape, whatever shape the picture is. A wall of mixed
   portrait and landscape reads as a pile rather than a set, and the ragged
   bottom edge drags every caption out of line with its neighbours. The crop
   is centred and the full view is one click away, uncropped, so nothing is
   actually lost — the tile is a way in, not the photograph. 4:3 because a
   phone's landscape shot survives it and its portrait shot still shows a
   person's face. */
.shot img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--paper-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
}
.shot a { display: block; }
.shot-open:hover img { border-color: var(--brick); }
.shot-by {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--muted);
}
.shot-by a { color: inherit; }
.shot-by a:hover { color: var(--brick); }
.shot-where { font-weight: 700; }
.shot-when { font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; }
.shot-caption { color: var(--ink); }

.photos-page > h1 { margin-bottom: 4px; }
.photos-intro { margin: 0 0 26px; color: var(--muted); }

/* A photo in the feed. The rail is narrow, so the picture runs its full
   width and keeps its own shape. */
.feed-shot { display: block; margin: 8px 0 0; }
.feed-shot img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--paper-2);
}
.feed-caption { margin: 6px 0 0; font-size: 13px; }


/* ---- one photo, full ---- */

.photo-page { max-width: 1100px; margin: 0 auto; }
.photo-bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 14px;
}
.photo-count {
  margin: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The picture and its two arrows on one row. The arrows keep their column at
   every width, so the picture doesn't shift sideways as you step through a
   set of mixed shapes. */
.photo-stage {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) 44px;
  align-items: center;
  gap: 8px;
}
.photo-full {
  display: block;
  /* The frame hugs the picture rather than the column: filling the width and
     letterboxing a portrait shot draws a big empty box around the thing you
     came to look at. Tall pictures are held to the window instead of running
     off it — the whole promise of this page is seeing the picture, and one
     that needs scrolling to finish isn't that. */
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 78vh;
  margin: 0 auto;
  background: var(--paper-2);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
}
.photo-step {
  display: grid;
  place-items: center;
  height: 44px;
  font-size: 28px;
  line-height: 1;
  color: var(--muted);
  text-decoration: none;
  border-radius: var(--radius);
}
a.photo-step:hover { color: var(--brick); background: var(--paper-2); }
.photo-step-off { opacity: 0.25; }

.photo-foot { margin-top: 14px; }
.photo-caption { margin: 0 0 6px; font-size: 15px; }
.photo-by {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}
.photo-by a { color: inherit; }
.photo-by a:hover { color: var(--brick); }
.photo-by .inline-form { display: inline-flex; }
.photo-when { font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; }
.photo-x { color: var(--muted); }
.photo-x:hover { color: var(--brick); }

/* The talk under the picture. It is the feed's conversation about this photo,
   so it wears the feed's chips and the feed's comment line — the same remark
   should not look like two different things depending on which page you meet
   it on. What changes is the setting: a reading column instead of the full
   1100px, because a sentence that wide is one nobody finishes. */
.photo-talk {
  max-width: 640px;
  margin-top: 20px;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
/* In the feed the field waits behind the speech bubble, because a rail of
   open boxes reads as a form rather than as a feed. Here there is one thing
   on the page and somebody opened it on purpose, so the box is just open. */
.photo-talk .feed-comment-form { display: flex; margin-top: 10px; }
.photo-talk .feed-comment { margin-top: 6px; font-size: 14px; }
/* The + sits in a row of chips rather than in a floating bar, so it is drawn
   as one: the same hairline and wash, at the size of the emoji beside it. */
.photo-talk .act-more {
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--paper-2);
  padding: 2px 8px;
  height: auto;
  font-size: 14px;
  line-height: 1.3;
}
.photo-talk .act-more:hover { border-color: var(--line-2); background: var(--paper-2); }
/* Anchored left here: the picker hangs off a + at the start of the row, and
   a sheet pinned to its right edge would open off the side of the page. */
.photo-talk .react-sheet { left: -4px; right: auto; }
.talk-note {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--muted);
}
.talk-note a { color: inherit; }
.talk-note a:hover { color: var(--brick); }

@media (max-width: 640px) {
  /* The arrows drop under the picture rather than squeezing it. */
  .photo-stage { grid-template-columns: 1fr 1fr; gap: 10px; }
  .photo-full { grid-column: 1 / -1; max-height: 60vh; }
}

/* ---- captions, the step after an upload ---- */

.captions-card { max-width: 640px; }
.caption-list { list-style: none; margin: 18px 0 22px; padding: 0; }
.caption-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 0;
  border-top: 1px solid var(--rule);
}
.caption-row:last-child { border-bottom: 1px solid var(--rule); }
.caption-thumb {
  flex: none;
  width: 96px;
  /* height:auto is load-bearing: the img carries its real height as an
     attribute, and without this the attribute wins over aspect-ratio and the
     thumbnail renders at full size. */
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--paper-2);
}
.caption-field { flex: 1 1 auto; margin: 0; min-width: 0; }
.caption-label {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 4px;
}
.caption-field input { width: 100%; }
/* "optional" is a note beside the label, not part of it. */
.field-optional { text-transform: none; letter-spacing: 0; opacity: 0.7; }

/* ---- an event's dates ---- */

/* One row per date. Deliberately plainer than the old night rows: the event
   above has already said what this is, so a date only has to say when it is,
   what it's called if that differs, and where to sign up when this date
   registers on its own. */
.date-list { list-style: none; margin: 0 0 30px; padding: 0; }
.date-row {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  padding: 12px 0;
  border-bottom: 1px solid var(--rule);
}
.date-row:first-child { border-top: 1px solid var(--rule); }
.date-when { flex: 0 0 auto; min-width: 12rem; }
.date-line { font-weight: 700; }
.date-sub {
  margin-left: 8px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
}
.date-what { flex: 1 1 12rem; min-width: 0; }
.date-name { font-weight: 700; margin-right: 8px; }
.date-note { color: var(--muted); }
.date-reg {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}
.date-reg-soon { color: var(--brick); }

/* ---- what a past date left behind ---- */

.record-list { list-style: none; margin: 0 0 30px; padding: 0; }
/* A date that left nothing behind is one line; one that did grows. */
.record-row { padding: 12px 0; border-bottom: 1px solid var(--rule); }
.record-row:first-child { border-top: 1px solid var(--rule); }
.record-when {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  font-weight: 700;
}
.record-row > :not(:first-child) { margin-top: 10px; }
.record-name { font-weight: 400; color: var(--muted); }
.record-came {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Tighter than the night's own strip used to be: this is one date among
   several on a page, not the whole subject of one. */
.record-shots { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); margin-top: 10px; }
.record-add { margin: 10px 0 0; }
.date-run {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--brick);
}
/* Staff working links on a date's row. Same idea as the strip that used to
   sit in the night's rail: the person on shift needs the portal page and the
   edit screen for one date without leaving the page they're reading. */
.date-staff {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--muted);
}
.date-staff:hover { color: var(--brick); }
/* A note is meant to be one line, and the form caps it at 200 characters —
   but the column has held far longer text before, so the row clamps rather
   than trusting the input. */
.date-note {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.record-note { font-weight: 400; color: var(--muted); }
/* Staff working links on a date, kept as one group so a wrap moves all of
   them rather than orphaning two. */
.date-tools {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: baseline;
  gap: 12px;
}
.shots-actions { display: inline-flex; align-items: center; gap: 8px; }
/* On the event's own gallery each picture says what it is; the caption leads
   and the byline sits under it. */
.shot-caption-line { margin: 6px 0 0; font-size: 13px; }

/* ---- viewing as a player ---- */

/* The band a staff viewer gets while the site is showing them what everyone
   else sees. Dark and full-bleed, above the header rather than inside it:
   it isn't part of the page, it's the thing holding the page at arm's
   length. It doesn't follow the scroll — the calendar's month header already
   owns the top of the homepage, and two bands fighting over the same inch
   is worse than a short trip back up. */
.as-player-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 8px 16px;
  background: var(--ink);
  color: var(--paper);
  font-size: 13px;
}
.as-player-bar p { margin: 0; }
.as-player-bar form { display: flex; }
/* The × is the way out, so it gets a border to be a target rather than a
   glyph. Wood rather than brick on hover: brick on ink is the pairing this
   design already refuses. */
.as-player-bar button {
  padding: 1px 8px 3px;
  border: 1px solid rgba(242, 239, 230, 0.4);
  border-radius: var(--radius);
  background: none;
  color: var(--paper);
  font: inherit;
  font-size: 15px;
  line-height: 1.1;
  cursor: pointer;
}
.as-player-bar button:hover { border-color: var(--wood); color: var(--wood); }

/* ---- sign-ups: the list a date keeps here ----
   Draws in two places: the attend card of a single-date event, and as the
   rows of a multi-date event's picker. Reuses the seat meter and the
   attendee chips, which never left the sheet — the list came back, and the
   clothes still fit. */
.signup-block { max-width: 46ch; }
.signup-count {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  margin: 0 0 10px;
}
.signup-full .signup-count { color: var(--brick-2); }
.signup-block .seat-meter { margin: 0 0 14px; }
.signup-act { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 0; }
.signup-act .attend-state { margin: 0; }
.signup-act .attend-fine { margin: 0; }
.signup-login { font-size: 14px; color: var(--muted); margin: 0; }
.signup-list { margin-top: 14px; }
.signup-list .avatar { width: 22px; height: 22px; font-size: 10px; }
.signup-list li a, .signup-list li { display: inline-flex; align-items: center; gap: 6px; }
/* The second step, for somebody who holds a seat: one sentence, one link. */
.signup-step { font-size: 14px; color: var(--muted); margin: 0; max-width: 46ch; }
.signup-step a { color: var(--brick); font-weight: 700; text-decoration: none; }
.signup-step a:hover { text-decoration: underline; }

/* ---- the picker: a Dates list you tick through ----
   One form in a card, after Tableland's: four lanes on every row — box ·
   when · who · badge — so the eye has a line to ride down. Ticking a box
   picks the date; unticking a held one stages a leave; the button under
   the list applies the lot. A held row locks in ink. */
.picker-card {
  background: var(--white);
  border: 1px solid var(--line);
  padding: 18px 20px 16px;
  margin: 0 0 30px;
}
.picker-title {
  font-family: var(--display);
  font-weight: 700;
  font-size: 24px;
  letter-spacing: -0.01em;
  margin: 0 0 12px;
}
.picker-card .date-list { margin: 0; }
.slist { border-top: 1px solid var(--rule); }
.date-row.srow {
  display: grid;
  align-items: center;
  grid-template-columns: 2.2rem minmax(0, 1fr) 12rem 7rem;
  column-gap: 10px;
  padding: 10px 10px 10px 6px;
  border-left: 3px solid transparent;
  border-bottom: 1px solid var(--rule);
  transition: background 0.12s, border-color 0.12s;
}
.date-row.srow:first-child { border-top: none; }
.date-row.srow .date-when { grid-column: 2; min-width: 0; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; cursor: pointer; }
.date-row.srow-static .date-when { cursor: default; }
.date-row.srow .date-what { grid-column: 2; }
.date-row.srow .date-tools, .date-row.srow .date-reg, .date-row.srow .sstep { grid-column: 2 / -1; }
.date-row.srow .sbox-lane { grid-column: 1; grid-row: 1 / span 2; align-self: start; padding-top: 2px; }
.date-row.srow .swho { grid-column: 3; grid-row: 1; }
.date-row.srow .sright { grid-column: 4; grid-row: 1; }
.srow input[type="checkbox"] { position: absolute; opacity: 0; pointer-events: none; width: 0; height: 0; }
.inline-form { display: inline; margin: 0; }
.sbox {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 7px; box-sizing: border-box;
  border: 2px solid var(--line-2); background: var(--white);
  color: transparent; font: inherit; font-size: 15px; font-weight: 700; line-height: 1;
  padding: 0; margin: 0; cursor: pointer; text-decoration: none;
  transition: background 0.12s, border-color 0.12s, transform 0.12s;
}
.sbox:hover { border-color: var(--ink); }
.sbox::after { content: "✓"; }
.sbox-static { border-style: dashed; cursor: default; background: transparent; }
.sbox-static:hover { border-color: var(--line-2); }
.sbox-static::after { content: ""; }
/* A fresh tick: brick, and the row warms up. */
.srow:has(input[name="sessions"]:checked) { background: var(--brick-wash); border-left-color: var(--brick); }
.srow:has(input[name="sessions"]:checked) .sbox { background: var(--brick); border-color: var(--brick); color: var(--paper); transform: scale(1.06); }
/* A seat you hold: ink. Unticking it stages a leave — the row goes quiet
   and says so, and nothing happens until the button. */
.srow-held:has(input[name="keep"]:checked) { background: rgba(43, 33, 21, 0.04); border-left-color: var(--ink); }
.srow-held:has(input[name="keep"]:checked) .sbox { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.srow-wait:has(input[name="keep"]:checked) { background: var(--brick-wash); border-left-color: var(--brick); }
.srow-wait:has(input[name="keep"]:checked) .sbox { background: var(--brick-wash); border-color: var(--brick); color: var(--brick-2); }
.srow-held:has(input[name="keep"]:not(:checked)) { opacity: 0.6; }
.srow-held:has(input[name="keep"]:not(:checked)) .date-line { text-decoration: line-through; }
.srow-static { opacity: 0.85; }
.swho { display: flex; align-items: center; gap: 8px; min-width: 0; }
.sfaces { display: inline-flex; padding-left: 6px; }
.sfaces .avatar {
  width: 24px; height: 24px; margin-left: -6px; font-size: 10px;
  border: 2px solid var(--paper); background: var(--paper);
}
.sfaces .avatar-blank { background: var(--concrete); color: var(--white); }
.sgoing { font-size: 13px; font-weight: 700; white-space: nowrap; }
.sgoing-empty { font-weight: 400; font-style: italic; color: var(--muted); }
.sright { display: flex; justify-content: flex-start; gap: 6px; }
.sbadge {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  white-space: nowrap; border-radius: 999px; padding: 2px 8px;
  color: var(--muted); border: 1px solid var(--line-2);
}
.sbadge-in { color: var(--ink); border-color: var(--ink); }
.sbadge-wait, .sbadge-left { color: var(--brick-2); border-color: var(--brick); }
.sbadge-full { color: var(--muted); }
/* The staged badges: shown only while the box disagrees with the record. */
.sbadge-joining, .sbadge-leaving { display: none; color: var(--brick-2); border-color: var(--brick); background: var(--brick-wash); }
.srow:has(input[name="sessions"]:checked) .sbadge-joining { display: inline; }
.srow:has(input[name="sessions"]:checked) .sbadge-full,
.srow:has(input[name="sessions"]:checked) .sbadge-left { display: none; }
.srow-held:has(input[name="keep"]:not(:checked)) .sbadge-leaving { display: inline; }
.srow-held:has(input[name="keep"]:not(:checked)) .sbadge-in,
.srow-held:has(input[name="keep"]:not(:checked)) .sbadge-wait { display: none; }
/* The second step on a held row: one line, one plain button. */
.sstep { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.sstep-note { font-size: 13px; color: var(--muted); }
.sstep-btn { padding: 6px 12px; font-size: 13px; }
/* The Companion step has a sentence to read, so it stacks: words, then the
   button. The code is the part somebody copies, so it wears mono and ink. */
.sstep-companion { flex-direction: column; align-items: flex-start; gap: 8px; }
.sstep-companion .sstep-note { max-width: 52ch; }
.sstep-code {
  font-family: var(--mono); font-weight: 700; color: var(--ink);
  letter-spacing: 0.08em; background: var(--paper-2); padding: 1px 6px; border-radius: 3px;
  user-select: all;
}
.signup-block .sstep-btn { margin-top: 4px; }
.pick-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 16px; }
.pick-actions .button-primary:disabled { opacity: 0.45; cursor: default; }
.pick-actions-note { font-size: 13px; color: var(--muted); max-width: 46ch; }
.held-line { margin: 12px 0 0; font-size: 14px; color: var(--muted); }
.held-line a { color: var(--brick); font-weight: 700; text-decoration: none; margin-right: 6px; }
.held-line a:hover { text-decoration: underline; }
@media (max-width: 700px) {
  .date-row.srow { grid-template-columns: 2.2rem minmax(0, 1fr) auto; }
  .date-row.srow .swho { grid-column: 2; grid-row: 3; }
  .date-row.srow .sright { grid-column: 3; grid-row: 1; }
  .date-row.srow .sbox-lane { grid-row: 1 / span 3; }
  .picker-card { padding: 14px 14px 12px; }
}
/* The date lane is a label so the whole line ticks the box, but it is a
   line of the list, not a form field: the form's small-caps label dress
   does not apply here. */
.picker-card label.date-when, .picker-card label.sbox {
  display: inline-flex; font-size: inherit; font-weight: inherit; text-transform: none;
  letter-spacing: normal; color: inherit; margin: 0;
}
.picker-card label.date-when { display: flex; }
.date-row.srow .date-what { grid-column: 2 / -1; }
/* The reset above gave the box its ink back; an empty box shows nothing. */
.picker-card label.sbox { color: transparent; }
.srow:has(input[name="sessions"]:checked) label.sbox { color: var(--paper); }
.srow-held:has(input[name="keep"]:checked) label.sbox { color: var(--paper); }
.srow-wait:has(input[name="keep"]:checked) label.sbox { color: var(--brick-2); }


/* ---- regulars: who keeps showing up to a game ----
   The chips are the followers strip's, without the faces: these people
   come from the portal and have no picture here, so the chip pads evenly
   instead of leaving a hole where the avatar would sit. */
.game-regulars { margin: 0 0 26px; }
.game-regulars .section-label { margin-top: 0; }
.regulars-chips li { padding: 4px 12px; }
.regulars-rule {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--muted);
}

/* The full list: a ranked column, handle first, the count and the last
   night in the mono the rest of the site uses for facts. */
.regulars-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 34rem;
}
.regulars-list li {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 16px;
  align-items: baseline;
  padding: 8px 0;
  border-bottom: 1px solid var(--paper-2);
}
.regular-handle { font-weight: 600; }
.regular-nights, .regular-last {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}
.regulars-list-quiet .regular-handle { font-weight: 400; color: var(--ink-2); }

/* ---- who's going: the roster sheet a picker row opens ----
   A native dialog, so Escape and focus come free. The list is the same
   avatar-and-name row the site uses everywhere; each public player is a
   link to their profile, a private one reads as "A player" with no face,
   exactly as in the strip that opened it. */
.swho-btn {
  font: inherit; color: inherit; background: none; border: none;
  margin: 0; padding: 0; text-align: inherit; cursor: pointer;
  display: flex; align-items: center; gap: 8px; min-width: 0;
}
.swho-btn:hover .sgoing { text-decoration: underline; }
.swho-btn:focus-visible { outline: 2px solid var(--brick); outline-offset: 3px; border-radius: 6px; }
.roster-dialog {
  width: min(420px, calc(100vw - 32px));
  max-height: 80vh;
  padding: 0;
  border: 1px solid var(--line);
  background: var(--white);
  color: var(--ink);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.18);
  overflow: hidden;
  display: none;
}
.roster-dialog[open] { display: flex; flex-direction: column; }
.roster-dialog::backdrop { background: rgba(26, 24, 21, 0.45); }
.roster-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--rule);
}
.roster-kicker {
  margin: 0 0 4px;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--muted);
}
.roster-when { margin: 0; font-family: var(--display); font-weight: 700; font-size: 20px; line-height: 1.2; }
.roster-time { font-family: var(--mono); font-weight: 400; font-size: 12px; color: var(--muted); margin-left: 6px; }
.roster-title { display: block; font-size: 15px; font-weight: 600; margin-top: 2px; }
.roster-seats { margin: 6px 0 0; font-size: 13px; color: var(--muted); }
/* A dialog hands focus to its first button when it opens, and the browser
   draws its focus ring on top of the button's own border — two rings, and
   the × sitting low inside them. The ring is for keyboards only; the ×
   is a centred glyph, not a line of text. */
.roster-close {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 0 2px; margin: 0;
  border: 1px solid var(--line-2); background: var(--white); color: var(--muted);
  font: inherit; font-size: 22px; line-height: 1; cursor: pointer;
}
.roster-close:hover { color: var(--brick); border-color: var(--brick); }
.roster-close:focus { outline: none; }
.roster-close:focus-visible { box-shadow: 0 0 0 2px var(--paper), 0 0 0 4px var(--brick); }
.roster-list { list-style: none; margin: 0; padding: 6px 0; overflow-y: auto; }
.roster-list li a, .roster-list li.roster-private {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 18px; text-decoration: none; color: inherit;
}
.roster-list li a:hover { background: var(--paper-2); color: var(--brick); }
.roster-list .avatar { width: 30px; height: 30px; font-size: 12px; }
.roster-name { flex: 1; font-weight: 600; }
.roster-go { color: var(--muted); }
.roster-list li a:hover .roster-go { color: var(--brick); }
.roster-private { color: var(--muted); }
.roster-foot {
  margin: 0; padding: 10px 18px 14px;
  border-top: 1px solid var(--rule);
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted);
}
