/* Split out of style.css alongside layout.css -- reusable data-display
   components (cards, chips, tables, status strips) as opposed to page
   chrome (layout.css) or base primitives/forms/buttons (style.css). */

/* Club logo / monogram placeholder (views/partials/club-logo.ejs) */
.club-logo {
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

/* Three sizes per design_handoff_clubhub_views' Assets section: 28px in
   a roster row, 34px on an index card, 40px in a club header. */
.club-logo-sm {
  width: 28px;
  height: 28px;
}

.club-logo-md {
  width: 34px;
  height: 34px;
}

.club-logo-lg {
  width: 40px;
  height: 40px;
}

.club-card-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.club-card-header h3 {
  margin: 0;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text-muted);
  padding: var(--space-4);
  text-align: center;
  border: 1.5px dashed var(--color-border-tile);
  border-radius: var(--radius-md);
}

.empty-state .icon {
  width: 20px;
  height: 20px;
  margin-right: 0;
  color: var(--accent-700);
}

.empty-state-hint {
  font-size: 0.9118rem;
  color: var(--color-text-faint);
  margin: 0;
}

.icon-empty {
  width: 3rem;
  height: 3rem;
  color: var(--color-border);
}

/* "Your clubs" row-cards (views/index.ejs, screen 2a) -- one row per
   membership. The club monogram carries clubs.theme_color now; the old
   .club-card's border-left-color stripe is retired (ten clubs of ten
   colors made a striped list -- per the design handoff, the monogram is
   where the color belongs). */
.club-card-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.club-row-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 15px 16px;
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  box-shadow: 0 0 0 1px var(--color-border-tile);
  /* Containing block for the stretched title link below. This is a <div>,
     so position: relative is honoured unambiguously -- the extra
     transform: translate(0) the roster/events rows need is only because
     those are <tr>s, where WebKit doesn't reliably treat position:
     relative as establishing one. */
  position: relative;
}

.club-row-card:hover {
  box-shadow: 0 0 0 1px var(--accent-700);
}

.club-row-card-body {
  min-width: 0;
}

.club-row-card-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 500;
  /* row title: 12.5px -> 20px per THEMES.md's mapping table, as rem (20/17) */
  font-size: 1.1765rem;
  margin-bottom: var(--space-1);
}

/* The whole card is clickable, but the card itself is NOT one big <a> --
   it can't be, without nesting an <a> inside the "Latest posts" links
   below, which browsers silently mis-parse (the adoption-agency algorithm
   implicitly closes the outer <a> the moment it hits the inner one,
   fragmenting the DOM -- caught by inspecting the actual rendered DOM,
   not by reading the markup, since the raw HTML source looks completely
   correct). Instead the club name stays the one real link and stretches
   over the card, with the post links raised above it -- the same
   technique .forum-topic-card already uses for the same reason. One link
   per card for assistive tech; the caret ends up under the overlay, so it
   becomes clickable for free. */
.club-row-card-title a {
  color: var(--color-text);
  text-decoration: none;
}

.club-row-card-title a::after {
  content: "";
  position: absolute;
  inset: 0;
}

.club-row-card-title a:hover {
  text-decoration: underline;
}

.club-row-card-line {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 0.9412rem;
  line-height: 1.4;
  color: var(--color-text-muted);
  margin: 2px 0 0;
}

.club-row-card-line .icon {
  width: 14px;
  height: 14px;
  color: var(--color-text-faint);
  flex-shrink: 0;
}

.club-row-card-line .club-row-card-value {
  color: var(--color-text-secondary);
}

.club-row-card-line a {
  color: var(--club-accent);
  text-decoration: none;
}

.club-row-card-line a:hover {
  text-decoration: underline;
}

.club-row-card-line.is-unpaid {
  color: var(--color-danger-hover);
}

.club-row-card-line.is-unpaid .icon {
  color: var(--color-danger);
}

/* Each recent post gets its own bulleted row instead of one crammed,
   comma-separated line -- reads better generally and wraps far more
   readably on mobile, per direct feedback. Indented (20px) to roughly
   align under the "Latest posts" label's text, not the icon. */
.club-row-card-post-list {
  list-style: disc;
  margin: 2px 0 0;
  padding-left: 20px;
  font-size: 0.9412rem;
  line-height: 1.5;
  color: var(--color-text-muted);
}

/* Raised above the title link's stretched overlay so each post stays its
   own real link rather than being swallowed by the card's click target. */
.club-row-card-post-list a {
  position: relative;
  z-index: 1;
  color: var(--club-accent);
  text-decoration: none;
}

.club-row-card-post-list a:hover {
  text-decoration: underline;
}

/* The caret is the only .icon that's a direct child of .club-row-card --
   the dues/calendar/note icons all sit nested inside .club-row-card-body's
   <p> tags -- so this scoped selector reaches it without the icon
   partials needing a parameterized class. */
.club-row-card > .icon {
  width: 16px;
  height: 16px;
  margin-right: 0;
  color: var(--color-text-faint);
  flex-shrink: 0;
}

.page-header-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.page-header-row h1 {
  letter-spacing: -0.02em;
}

.page-header-summary {
  font-size: 0.8824rem;
  color: var(--color-text-faint);
}

/* Chips (role/status/visibility labels) -- outline for a neutral/default
   state, tint for a "positive" emphasized state (admin, published, paid),
   danger for the one negative state (unpaid dues). */
.chip {
  display: inline-flex;
  align-items: center;
  font-size: 0.7647rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 2px 7px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.chip .icon {
  width: 10px;
  height: 10px;
  margin-right: 3px;
}

.chip-outline {
  box-shadow: inset 0 0 0 1px var(--color-border);
  color: var(--color-text-muted);
}

.chip-tint {
  background: var(--accent-900);
  color: var(--accent-300);
}

.chip-danger {
  background: var(--color-danger-surface);
  color: var(--color-danger-hover);
}

/* The Requests tab's pending-count badge -- the first tab in the app to
   carry one. Reuses .chip-danger's colors, just tighter/rounder so it
   reads as a counter next to a tab label rather than a status word. */
.tab-badge {
  border-radius: 9px;
  padding: 2px 7px;
  font-size: 0.7647rem;
}

/* Paid-dues chip reads lighter than a plain neutral outline chip (e.g. a
   post's visibility chip) -- same outline shape, brighter text. */
.chip-outline-light {
  box-shadow: inset 0 0 0 1px var(--color-border);
  color: var(--color-text-secondary);
}

/* A plain "member" role reads as muted text, not a chip -- only "admin"
   gets the tinted chip treatment (roster table, screen 4b). */
.role-plain {
  color: var(--color-text-muted);
}

/* Section kicker (small-caps label above a list/table -- "Upcoming
   events", "Posts · 3", "Roster · 11 members · 4 unpaid") */
.kicker {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-weight: 500;
  font-size: 0.7647rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--color-text-faint);
  margin: var(--space-4) 0 var(--space-2);
}

.kicker .icon {
  width: 14px;
  height: 14px;
  margin-right: 0;
}

.kicker-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.kicker-row .kicker {
  margin: var(--space-4) 0 var(--space-2);
}

/* Club description (club.ejs Overview tab) */
.club-description {
  font-size: 1.0294rem;
  line-height: 1.65;
  color: var(--color-text-muted);
  max-width: 60ch;
  text-wrap: pretty;
}

.viewing-as-admin {
  font-style: italic;
  color: var(--color-text-faint);
}

/* Two-column overview areas (Upcoming events / Past results) */
.overview-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 30px;
}
.overview-columns.overview-columns-single {
  grid-template-columns: 1fr;
}

/* Upcoming-event row: a date block, then title + time/location */
.event-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  padding: var(--space-2) 0;
}

/* Overview's upcoming-event row becomes a stretched link when the event
   has a public signup page -- the title is the one real <a> (matching
   this app's "title is the real link, not the whole row nested" rule),
   and ::after covers the row so the whole thing is clickable, same
   technique already used for Roster/Events-list rows elsewhere. */
.event-row-clickable {
  position: relative;
  border-radius: var(--radius-sm);
}

.event-row-clickable:hover {
  background: var(--color-surface-sunken);
}

.event-row-title a {
  color: var(--color-text);
  text-decoration: none;
}

.event-row-title a:hover {
  text-decoration: underline;
}

.event-row-title a::after {
  content: "";
  position: absolute;
  inset: 0;
}

.event-row-title .chip {
  margin-left: var(--space-1);
  position: relative;
}

.event-row-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 44px;
  padding-right: var(--space-3);
  box-shadow: inset -1px 0 0 var(--color-border);
  text-align: center;
}

.event-row-weekday {
  font-size: 0.7647rem;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.event-row-day {
  font-weight: 500;
  font-size: 1.2353rem;
  color: var(--color-text);
  line-height: 1.2;
}

.event-row-month {
  font-size: 0.7647rem;
  color: var(--color-text-faint);
}

.event-row-title {
  font-weight: 500;
  font-size: 1rem;
  color: var(--color-text);
}

.event-row-meta {
  font-size: 0.8824rem;
  color: var(--color-text-muted);
}

/* Past-result row: title + monospace date, then a score line */
.result-row {
  padding: var(--space-2) 0;
}

.result-row-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.result-row-title {
  font-weight: 500;
  font-size: 1rem;
  color: var(--color-text);
}

.result-row-date {
  font-family: ui-monospace, monospace;
  font-size: 0.7941rem;
  color: var(--color-text-faint);
  white-space: nowrap;
}

.result-row-score {
  font-size: 0.9412rem;
  color: var(--color-text-secondary);
  margin-top: 2px;
}

.result-row-score .score {
  font-family: ui-monospace, monospace;
  font-size: 1rem;
}

.result-row-score .score-winner {
  color: var(--accent-300);
}

.result-row-score .score-dash {
  color: var(--color-border-tile);
}

.result-row-none {
  font-size: 0.9412rem;
  color: var(--color-text-faint);
  margin-top: 2px;
}

/* Post rows (club.ejs Posts tab) -- same rounded-container/edge shell as
   the table components, just for <div> rows instead of a <table>. */
.post-list {
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  box-shadow: 0 0 0 1px var(--color-border-tile);
  overflow: hidden;
}

.post-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-3);
  padding: 13px 14px;
  box-shadow: inset 0 -1px 0 var(--border);
}

.post-row:last-child {
  box-shadow: none;
}

.post-row-title {
  font-weight: 500;
  font-size: 1.0588rem;
  color: var(--color-text);
  text-decoration: none;
}

.post-row-title:hover {
  text-decoration: underline;
}

.post-row-title.is-draft {
  color: var(--color-text-secondary);
}

/* Documents tab: the upload form's "Show on the public page" box. */
form.stacked label.document-public-check {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-weight: 400;
}

form.stacked label.document-public-check input {
  width: 22px;
  height: 22px;
  margin: 0;
  flex-shrink: 0;
}

/* Documents tab: Make public / members-only beside the delete button. */
.document-row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.post-row-chips {
  display: flex;
  gap: var(--space-1);
  margin-top: 2px;
}

@media (max-width: 640px) {
  .overview-columns {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }

  .post-row {
    grid-template-columns: 1fr;
  }
}

/* Status strip (club overview's role+dues summary, and similar combined
   inline-stat rows elsewhere) */
.status-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  padding: 11px 14px;
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  box-shadow: 0 0 0 1px var(--color-border-tile);
  font-size: 0.9412rem;
}

.status-strip-item {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-text-muted);
}

.status-strip-item strong {
  color: var(--accent-300);
  font-weight: 500;
}

.status-strip-item.is-danger strong {
  color: var(--color-danger-hover);
}

.status-strip-divider {
  width: 1px;
  height: 16px;
  background: var(--color-border);
}

.status-strip .btn-outline-accent {
  margin-left: auto;
}

/* Two-option segmented control (post/nav-link visibility pickers) */
.segmented {
  display: inline-flex;
  padding: 2px;
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  box-shadow: inset 0 0 0 1px var(--color-border-tile);
}

.segmented label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 9px;
  border-radius: 3px;
  font-size: 0.9118rem;
  color: var(--color-text-muted);
  cursor: pointer;
}

.segmented input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* The background/text-color shift alone is invisible in low-vision --
   --action-tint (what --accent-900 aliases to) equals --bg there, so a
   selected segment showed no visual difference at all beyond a subtle
   text-color shift. A real border works in every theme, not just as a
   low-vision carve-out, matching the app's existing preference for
   universal fixes over per-theme overrides. */
/* The radios are hidden, so the segment shows where keyboard focus is. */
.segmented label:has(input:focus-visible) {
  outline: 2px solid var(--action);
  outline-offset: 1px;
}

/* The post editor's options row: visibility and status as segmented
   controls, then a checkbox per app, on one line under the title (wraps on
   a phone). Inside form.stacked, whose labels are a grid, so the labels
   here are put back in line. */
.post-options {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}
form.stacked .post-options .segmented label {
  display: inline-flex;
  gap: 4px;
}
form.stacked .post-options .post-options-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.9118rem;
  color: var(--ink);
  cursor: pointer;
}
.post-options-sep {
  width: var(--border-w);
  align-self: stretch;
  min-height: 1.4rem;
  background: var(--border);
}
/* On a phone the app boxes wrap to their own line; the divider would be
   left hanging at the end of the first. */
@media (max-width: 720px) {
  .post-options-sep { display: none; }
}

.segmented label:has(input:checked) {
  background: var(--accent-900);
  color: var(--accent-300);
  box-shadow: inset 0 0 0 var(--border-w) var(--action);
}

/* Selectable checkbox/radio cards (design_handoff_admin_data_and_nav) --
   a title + description + optional trailing size/count, the whole row
   clickable via the wrapping <label>. Reused across the export/import
   screens for kind/club/destination/mode choices. */
.option-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) 15px;
  background: var(--bg);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
}

.option-card:has(input:checked) {
  background: var(--action-tint);
  border-color: var(--action);
}

.option-card.option-card-danger:has(input:checked) {
  background: var(--danger-tint);
  border-color: var(--danger);
}

.option-card input[type="checkbox"],
.option-card input[type="radio"] {
  width: 24px;
  height: 24px;
  flex: none;
  margin: 2px 0 0;
  accent-color: var(--action);
}

.option-card.option-card-danger input {
  accent-color: var(--danger);
}

.option-card-body {
  flex: 1;
  min-width: 0;
}

.option-card-title {
  display: block;
  font-weight: 700;
  font-size: 1.1176rem;
}

.option-card-desc {
  display: block;
  margin-top: 3px;
  font-size: 1rem;
  line-height: 1.45;
  color: var(--ink-muted);
}

.option-card-size {
  flex: none;
  font-weight: 700;
  font-size: 1rem;
  align-self: center;
}

.option-card-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--surface-2);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  margin-top: var(--space-2);
}

.option-card-chip {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: 8px 11px;
  background: var(--bg);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  font-size: 1rem;
  cursor: pointer;
}

.option-card-chip:has(input:checked) {
  border-color: var(--action);
  font-weight: 700;
}

.option-card-chip input {
  width: 21px;
  height: 21px;
  accent-color: var(--action);
  margin: 0;
}

/* Import running-steps list (design_handoff_admin_data_and_nav, 18c) --
   reuses .option-card's shape, just keyed by data-step-status instead of
   a checked input. */
[data-step-status] {
  border-color: var(--border);
  color: var(--ink-dim);
}

[data-step-status="current"] {
  border-color: var(--action);
  background: var(--action-tint);
  color: var(--ink);
}

[data-step-status="current"] .option-card-title::before {
  content: "\25B8  ";
  color: var(--action);
}

[data-step-status="done"] {
  border-color: var(--border-strong);
  color: var(--ink);
}

[data-step-status="done"] .option-card-title::before {
  content: "\2713  ";
  color: var(--action);
}

/* Tables -- shell per design_handoff_clubhub_views: a rounded, edged
   container (not per-row borders bleeding to the page edge), header row
   tinted darker than the body, body rows separated by a faint inset line
   with the last row clean. */
.table-scroll {
  overflow-x: auto;
  margin-bottom: var(--space-3);
  border-radius: var(--radius-md);
  box-shadow: 0 0 0 1px var(--color-border-tile);
}

.table-scroll table {
  min-width: 480px;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
  background: var(--color-surface-sunken);
}

th, td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  white-space: nowrap;
}

th {
  background: var(--color-bg);
  color: var(--color-text-faint);
  font-size: 0.7647rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  box-shadow: inset 0 -1px 0 var(--border);
}

td {
  color: var(--color-text-secondary);
  box-shadow: inset 0 -1px 0 var(--border);
}

tr:last-child td {
  box-shadow: none;
}

/* Some UA stylesheets give tr a higher-specificity `display: table-row`
   than the built-in `[hidden] { display: none }` rule, so hiding table
   rows via the hidden attribute needs an explicit assist. */
tr[hidden] {
  display: none;
}

/* Same issue, different cause: any author-stylesheet `display` rule
   (not just a UA one) always outranks the browser's built-in
   `[hidden] { display: none }`, regardless of selector specificity --
   that's a cascade-origin rule, not a specificity one. list-paginate.js
   hides items via [hidden], so anything it's used on needs this same
   explicit assist if it sets its own `display` (list-paginate.js is
   selector-agnostic on purpose; caught on .post-row specifically, via
   the Overview tab's "Recent posts" section not actually hiding rows
   despite the pager's page count being correct). */
[data-list-item][hidden] {
  display: none;
}

/* Inline-editable table cells (events table in club.ejs, nav-link tables)
   -- per screen 3c: invisible until interacted with, so a row full of
   editable fields doesn't look like a row full of form fields. Overrides
   the base input's own background/border (transparent instead of the
   sunken-surface fill), since these already sit inside a styled table row. */
/* input.cell-input (not just .cell-input) to match the base
   input[type="..."] rules' specificity -- a bare class selector loses to
   an element+attribute selector regardless of source order, which
   silently no-op'd this whole block until caught by checking computed
   style directly (it *looked* plausible from a screenshot alone). */
input.cell-input,
select.cell-input {
  width: 8rem;
  padding: 5px 6px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
}

input.cell-input:hover,
select.cell-input:hover {
  background: var(--color-bg);
  border-color: var(--color-border);
}

input.cell-input:focus,
select.cell-input:focus {
  background: var(--color-bg);
  border-color: var(--club-accent);
}

.cell-input-mono {
  font-family: ui-monospace, monospace;
  font-size: 0.9118rem;
}

.cell-input-narrow {
  width: 3.5rem;
}

input[type="datetime-local"].cell-input {
  width: 11rem;
}

/* data-table.js: search + pagination toolbar */
.table-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}

.table-search {
  font: inherit;
  color: var(--color-text);
  background: var(--color-bg);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
  min-width: 200px;
  flex: 1 1 200px;
  max-width: 320px;
}

/* Site admin -> People without a club: one person per row, with a club
   picker and Add under their details. */
.people-row {
  padding: var(--space-3) 0;
  border-top: var(--border-w) solid var(--border);
}
.people-add {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}
.people-add select {
  width: auto;
  max-width: 200px;
}

/* The post editor's heading, with a second Save button beside it. */
.post-form-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
}
.post-form-head h1 {
  margin-right: auto;
}

/* "Show [10 v]" rows-per-page choice beside a table's search (data-table.js). */
.table-page-size {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.85rem;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.table-page-size select {
  font-size: 0.85rem;
  padding: 4px 8px;
  width: auto;
}

.table-pager {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.85rem;
  color: var(--color-text-muted);
  white-space: nowrap;
}

/* Overview tab's "Recent posts" section (club.ejs) -- reuses .table-pager
   for the Prev/Next controls, just centered under a standalone list
   instead of sitting beside a search box in a table toolbar. */
.list-pager {
  justify-content: center;
  margin-top: var(--space-3);
}

/* Narrow left-aligned form pages (login/signup, screen 3a/3b -- also
   reused by admin-new-club, screen 3d, with its own wider max-width). */
.narrow-form-page {
  padding: 34px 30px 38px;
}

.narrow-form-page h1 {
  /* No font-size/weight override -- inherit the global h1 rule (2rem,
     THEMES.md's explicit 34px target) instead of the old, smaller
     auth-page-specific size that only existed relative to a smaller
     default h1. */
  margin: 0 0 var(--space-1);
}

.narrow-form-page .page-subtitle {
  font-size: 0.9412rem;
  color: var(--color-text-faint);
  margin: 0 0 var(--space-4);
  max-width: 46ch;
}

.auth-form {
  max-width: 330px;
}

/* admin-new-club.ejs (screen 3d) reuses the same narrow-form-page/field
   pattern as auth, just wider. */
.new-club-form {
  max-width: 390px;
}

.mono-input {
  font-family: ui-monospace, monospace;
}

.slug-preview-value {
  color: var(--accent-300);
}

.field-optional {
  font-weight: 400;
  color: var(--color-text-faint);
  text-transform: none;
  letter-spacing: normal;
}

.dues-input-row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.dues-prefix {
  color: var(--color-text-faint);
}

.dues-amount-input {
  width: 110px;
}

.auth-form button[type="submit"] {
  align-self: flex-start;
  font-size: 1rem;
  padding: 9px 17px;
}

.form-error {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  background: var(--color-danger-surface);
  box-shadow: 0 0 0 1px var(--color-danger-border);
  font-size: 0.9412rem;
  color: var(--danger);
  margin-bottom: var(--space-3);
  max-width: 330px;
}

.form-error .icon {
  color: var(--color-danger-hover);
  margin-right: 0;
  flex-shrink: 0;
}

/* Same shape as .form-error but for a positive/neutral notice (event
   signup's "you're confirmed" state, vs. the waitlisted/error states which
   keep the danger-toned .form-error look). */
.form-notice {
  background: var(--accent-tint);
  box-shadow: 0 0 0 1px var(--club-accent);
  color: var(--color-text);
}

.form-notice .icon {
  color: var(--club-accent);
}

.narrow-form-page .form-footer {
  font-size: 0.9412rem;
  color: var(--color-text-faint);
  margin-top: var(--space-3);
}

/* Site admin (views/admin-clubs.ejs, screen 3c) */
.admin-club-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin: var(--space-3) 0 var(--space-4);
}

.admin-club-card {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  box-shadow: 0 0 0 1px var(--color-border-tile);
  text-decoration: none;
  color: inherit;
}

.admin-club-card:hover {
  box-shadow: 0 0 0 1px var(--accent-700);
}

.admin-club-card-name {
  font-weight: 500;
  font-size: 1rem;
  color: var(--color-text);
}

.admin-club-card-count {
  font-size: 0.8824rem;
  color: var(--color-text-faint);
}

.admin-panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  margin-bottom: var(--space-4);
}

.admin-panel {
  padding: 16px 17px;
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  box-shadow: 0 0 0 1px var(--color-border-tile);
}

.admin-panel h3 {
  font-weight: 500;
  font-size: 1rem;
  margin: 0 0 var(--space-3);
}

/* Site admin's Themes panel -- one row per theme, a default-selecting
   radio on the left, either an enable checkbox or an "Always on" chip on
   the right. */
.theme-admin-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.theme-admin-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 1px var(--color-border);
}

.theme-admin-row label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

/* Logo upload slot (site branding panel, and club-admin logo panel once
   that's migrated) -- dashed box with an icon while empty, the real
   image once one's uploaded. */
.logo-slot-empty {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1.5px dashed var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-faint);
  font-size: 0.9412rem;
  margin-bottom: var(--space-2);
}

/* The uploaded browser-tab icon on Site settings, at a size you can
   actually see. The border shows its edges when the icon is transparent. */
.favicon-slot-image {
  display: block;
  width: 64px;
  height: 64px;
  margin-bottom: var(--space-3);
  border: var(--border-w) solid var(--border);
  border-radius: 4px;
}

.logo-slot-image {
  height: 40px;
  border-radius: var(--radius-sm);
  display: block;
  margin-bottom: var(--space-2);
}

@media (max-width: 640px) {
  .admin-club-grid {
    grid-template-columns: 1fr;
  }

  .admin-panels {
    grid-template-columns: 1fr;
  }
}

/* Activity log (views/admin-activity.ejs, screen 3e) */
.activity-time {
  font-family: ui-monospace, monospace;
  font-size: 0.8824rem;
  color: var(--color-text-faint);
  white-space: nowrap;
}

.activity-action-actor {
  font-weight: 500;
  color: var(--color-text);
}

.activity-action-phrase {
  font-size: 0.9412rem;
  line-height: 1.45;
  color: var(--color-text-secondary);
}

.activity-metadata {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: var(--space-1);
}

.activity-metadata-chip {
  font-family: ui-monospace, monospace;
  font-size: 0.7941rem;
  color: var(--color-text-muted);
  background: var(--surface-3);
  padding: 4px 6px;
  border-radius: var(--radius-sm);
}

.activity-club {
  color: var(--color-text-muted);
}

.activity-club-none {
  color: var(--color-text-faint);
}

.activity-target {
  font-family: ui-monospace, monospace;
  font-size: 0.8824rem;
  color: var(--color-text-faint);
  white-space: nowrap;
}

section.panel {
  background: var(--color-surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}

/* Email tab's summary panel (club.ejs) and the full inbox/compose/thread
   views (email-inbox.ejs, email-compose.ejs, email-thread.ejs). */
.email-summary-counts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-3) 0 var(--space-4);
}

.email-summary-count {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  box-shadow: 0 0 0 1px var(--color-border-tile);
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 0.9412rem;
}

.email-summary-count:hover {
  box-shadow: 0 0 0 1px var(--accent-700);
}

.email-summary-count strong {
  font-size: 1.2941rem;
  color: var(--color-text);
}

.email-summary-count.is-open strong { color: var(--accent-300); }
.email-summary-count.is-waiting strong { color: var(--color-text-secondary); }
.email-summary-count.is-closed strong { color: var(--color-text-faint); }

.email-status-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.email-status-filters a {
  font-size: 0.9118rem;
  padding: 5px 11px;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  text-decoration: none;
  box-shadow: inset 0 0 0 1px var(--color-border);
}

.email-status-filters a.is-active {
  color: var(--accent-300);
  background: var(--accent-900);
  box-shadow: none;
}

.email-thread-row {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: var(--space-3);
}

.email-thread-row-name {
  font-weight: 500;
  color: var(--color-text);
}

.email-thread-row-email {
  font-size: 0.8824rem;
  color: var(--color-text-faint);
}

.email-thread-row-subject {
  font-size: 0.9412rem;
  color: var(--color-text-secondary);
}

.email-thread-row-time {
  font-size: 0.8235rem;
  color: var(--color-text-faint);
  white-space: nowrap;
}

/* EMAIL-COMPOSE.md's To row (14a): each option is its own bordered
   card, not a bare radio + label floating in a plain row -- the
   selected option gets the accent tint/border treatment, and every
   option (not just the selected one) carries a real border, since a
   background/color-only difference is exactly the kind of thing that
   goes flat in Low-vision. */
#email-recipient-mode-row {
  flex-wrap: wrap;
  gap: var(--space-2);
}

#email-recipient-mode-row label {
  gap: var(--space-2);
  min-height: 44px;
  padding: 9px 13px;
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 var(--border-w) var(--border-strong);
}

#email-recipient-mode-row label:has(input:checked) {
  background: var(--accent-900);
  box-shadow: inset 0 0 0 var(--border-w) var(--action);
  font-weight: 700;
}

#email-recipient-mode-row input[type="radio"] {
  width: 21px;
  height: 21px;
  flex: none;
}

/* Recipient checkbox list (email-compose.ejs) */
.email-recipient-list {
  max-height: 280px;
  overflow-y: auto;
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  box-shadow: 0 0 0 1px var(--color-border-tile);
  padding: var(--space-2);
}

/* Needs !important: this row is a <label> inside <form class="stacked">,
   and `form.stacked label { display: grid }` (style.css, the app-wide
   single-column caption-above-input pattern) is higher specificity
   (form+class+label vs. this one class) and wins regardless -- grid's
   default auto-placement with no column template puts the checkbox and
   the text span each on their own row, exactly the "checkbox above the
   name" bug reported. Same class of cascade-override this session has
   already hit with the roster add-member panel and the Quill toolbar. */
.email-recipient-row {
  display: flex !important;
  align-items: center;
  gap: 12px !important;
  padding: 8px 13px;
  border-bottom: var(--border-w) solid var(--color-border-tile);
}

/* Needs !important too: .email-recipient-row above is `display: flex
   !important`, and !important always wins over a normal-weight rule
   regardless of specificity or source order -- so without this, a
   "hidden" row still computed to display: flex and never disappeared
   (the filter's row.hidden toggle correctly set the property, it just
   had no visible effect). Same class of bug this app has already hit
   with tr[hidden]/.tabs/.nav-sheet/the roster add-member panel. */
.email-recipient-row[hidden] {
  display: none !important;
}

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

/* Name + email sit on one line next to the checkbox (not stacked on two
   lines, and not plain siblings loose in the row either) -- a fixed-width
   checkbox column plus `flex: 1; min-width: 0` on this wrapper is what
   keeps every row's checkbox aligned at the same x-position regardless of
   how long a name or email is, without costing the row a second line of
   height. Truncates as a whole with an ellipsis rather than wrapping,
   since a stacked layout was the actual complaint (too tall a list). */
.email-recipient-row-text {
  display: flex;
  align-items: baseline;
  gap: 5px;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.email-recipient-name {
  font-weight: 700;
  flex-shrink: 0;
}

.email-recipient-email {
  color: var(--color-text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
}

.email-recipient-row input[type="checkbox"] {
  width: 24px;
  height: 24px;
  flex: none;
}

/* Club Admin's Forum/Documents enable toggles -- same scoped
   bigger-checkbox treatment as the email recipient list above, not a
   global checkbox-size change. */
input.settings-checkbox {
  width: 24px;
  height: 24px;
}

/* Compose email as a real modal on the inbox page, matching the one
   modal pattern this app already has (.feedback-modal in feedback.css) --
   same shape (radius, shadow, backdrop dimming), just sized for real
   content (the recipient chooser, Quill toolbar) instead of a short form,
   and scrollable internally rather than growing past the viewport. */
.compose-modal {
  width: min(720px, calc(100vw - var(--space-4) * 2));
  max-height: min(720px, calc(100vh - var(--space-4) * 2));
  overflow-y: auto;
  padding: var(--space-4);
  border: none;
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: inset 0 0 0 1px var(--color-border-tile), 0 20px 50px rgba(0, 0, 0, 0.5);
  color: var(--color-text);
}

.compose-modal::backdrop {
  background: rgba(10, 11, 18, 0.6);
}

.compose-modal .stacked {
  max-width: none;
}

/* The composer's newsletter-layout choice and its preview window
   (lib/newsletterEmail.js). The preview is a picture of an email, so the
   frame keeps the email's own light colours in every theme. */
.email-layout-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.email-layout-row .field-hint {
  flex-basis: 100%;
  margin: 0;
}

.compose-modal .email-layout-choice {
  display: flex !important;
  align-items: center;
  gap: var(--space-2);
  font-weight: 600;
}

.email-preview-modal {
  width: min(760px, calc(100vw - var(--space-4) * 2));
}

.email-preview-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.email-preview-head h2 {
  margin: 0;
}

#email-preview-frame {
  display: block;
  width: 100%;
  height: min(560px, 60vh);
  margin-top: var(--space-3);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  background: #f4f5f7;
}

/* Club logo zoom/position editor (public/js/logo-cropper.js), same modal
   shape as .compose-modal. The stage is the 400px output square scaled to
   fit; a checkerboard shows where the saved PNG will be transparent, and
   the ring dims everything the round logo will cut off. */
.logo-crop-modal {
  width: min(600px, calc(100vw - var(--space-4) * 2));
  max-height: calc(100vh - var(--space-4) * 2);
  overflow-y: auto;
  padding: var(--space-4);
  border: none;
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: inset 0 0 0 1px var(--color-border-tile), 0 20px 50px rgba(0, 0, 0, 0.5);
  color: var(--color-text);
}

.logo-crop-modal::backdrop {
  background: rgba(10, 11, 18, 0.6);
}

.logo-crop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-3);
}

.logo-crop-head h3 {
  margin: 0;
}

.logo-crop-body {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-start;
}

.logo-crop-stage {
  position: relative;
  width: min(320px, 100%);
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #e4e4e4 25%, transparent 25%, transparent 75%, #e4e4e4 75%),
    linear-gradient(45deg, #e4e4e4 25%, transparent 25%, transparent 75%, #e4e4e4 75%);
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.logo-crop-stage.is-dragging {
  cursor: grabbing;
}

.logo-crop-stage:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 2px;
}

.logo-crop-image {
  position: absolute;
  top: 0;
  left: 0;
  max-width: none;
  transform-origin: 0 0;
  pointer-events: none;
}

.logo-crop-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 9999px rgba(10, 11, 18, 0.55);
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: -1px;
  pointer-events: none;
}

.logo-crop-side {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1 1 180px;
  min-width: 0;
}

.logo-crop-zoom {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.logo-crop-zoom input {
  width: 100%;
}

.logo-crop-side [data-crop-reset] {
  align-self: flex-start;
}

.logo-crop-preview-label {
  display: block;
  margin-bottom: 6px;
  color: var(--color-text-muted);
  font-size: 0.85rem;
}

.logo-crop-preview-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Match how the logo is really shown: round in the app (club-logo.ejs),
   a rounded square on the public page. Neither has a colour behind an
   uploaded logo; the sunken tint here only makes the shape visible. */
.logo-crop-preview-circle {
  border-radius: 50%;
  background: var(--color-surface-sunken);
}

.logo-crop-preview-square {
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
}

.logo-crop-modal [data-crop-error][hidden] {
  display: none;
}

/* EMAIL-COMPOSE.md's From line -- a plain field-hint line was too easy to
   miss entirely (reported directly: "the from section is not showing"),
   so this gets the same real bordered-panel treatment the mockup gives
   it, not just muted text. */
.email-from-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 10px;
  padding: 11px 13px;
  background: var(--color-surface-sunken);
  border: var(--border-w) solid var(--color-border-tile);
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-3);
  font-size: 0.9412rem;
}

.email-from-line code {
  font-family: ui-monospace, Menlo, monospace;
}

/* EMAIL-COMPOSE.md's screens show a compact ~150px message body, not
   however tall an empty contenteditable happens to collapse to -- this
   page doesn't load post-blocks.css (that's the full post-editor's
   styling), so #editor had no min-height at all before this. */
#editor.quill-editor .ql-editor {
  min-height: 150px;
}

/* EMAIL-COMPOSE.md's chooser (Choose people... mode) */
.email-recipient-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.email-recipient-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.email-recipient-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.email-recipient-chip button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  line-height: 1;
  cursor: pointer;
  color: inherit;
}

/* Toolbar buttons default to Quill's own ~24px (.ql-snow .ql-toolbar
   button, quill.snow.css), under THEMES.md's floor -- scoped to
   .email-toolbar (this page only) rather than every Quill instance in the
   app, since EMAIL-COMPOSE.md's scope is compose-only. Needs !important:
   Quill's own selector is the same specificity as this one and its
   stylesheet loads after components.css in every view's <head>, so a
   plain same-specificity override loses the cascade tie-break regardless
   of what it says -- the exact same reason the Quill icon-color override
   elsewhere in this file already needs !important.
   float and display also need resetting, not just size: Quill's rule
   sets float:left, and a floated element's computed display gets
   "blockified" (inline-block -> block) regardless of what this rule
   says unless float is reset too -- confirmed directly, every toolbar
   button (including the ones revealed by the mobile "More" toggle) was
   stacking one-per-line at display:block until this was added. */
.email-toolbar .ql-formats button,
.email-toolbar .ql-formats .ql-picker-label {
  width: 44px !important;
  height: 44px !important;
  border: 2px solid var(--color-border-tile) !important;
  border-radius: var(--radius-sm) !important;
  float: none !important;
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
}

.email-attachment-row {
  padding: 6px 0;
  font-size: 0.9412rem;
  color: var(--color-text-faint);
}

.email-send-bar {
  background: var(--color-surface-sunken);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-top: var(--space-3);
}

/* "Your drafts" on an inbox page: unsent emails the composer kept. A
   bordered block so it reads as its own thing in every theme. */
.email-drafts {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
}

.email-drafts ul {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
}

.email-drafts li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: 6px 0;
}

.email-drafts li + li {
  border-top: 1px solid var(--border);
}

.email-drafts li a {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.email-drafts-saved {
  font-size: 0.8824rem;
  color: var(--ink-muted);
  margin-right: auto;
}

/* 14c: mobile collapses the toolbar to Bold/Italic/Bulleted-list/Link
   plus a "More" toggle for the rest -- nine controls at 48px each would
   never fit a 375px screen. Hidden (not the `hidden` attribute -- a plain
   class, so there's no [hidden]+display cascade fight to worry about) by
   default only under the mobile breakpoint; always visible above it, no
   toggle needed on desktop. */
/* Needs !important x2: Quill's own .ql-snow .ql-toolbar button rule is
   higher specificity (two classes + element vs. this one class) so it
   wins regardless of source order, AND it sets float:left -- a floated
   element's computed display gets "blockified" (inline-block -> block)
   regardless of what this rule says, unless float is reset too. Confirmed
   directly: without this, the toggle computed to display:block (visible
   on desktop when it shouldn't be, full-width instead of a small pill on
   mobile) even though no rule anywhere literally said `display: block`. */
.email-toolbar-more-toggle {
  display: none !important;
  float: none !important;
}

@media (max-width: 720px) {
  /* !important here too -- an !important rule always wins over a plain
     one regardless of media query/source order, so this has to outrank
     this file's own desktop !important rule above, not just Quill's. */
  .email-toolbar .ql-formats button,
  .email-toolbar .ql-formats .ql-picker-label {
    width: 48px !important;
    height: 48px !important;
  }

  .email-toolbar-more-toggle {
    display: inline-flex !important;
    float: none !important;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    padding: 0 12px;
    font: inherit;
    font-weight: 700;
    color: var(--color-text);
    background: var(--color-surface);
    border: 2px solid var(--color-border-tile);
    border-radius: var(--radius-sm);
    cursor: pointer;
  }

  #email-toolbar-more-group {
    display: none;
  }

  #email-toolbar-more-group.is-open {
    display: contents;
  }

  .email-send-bar button {
    width: 100%;
    height: 56px;
  }
}

/* Message timeline (email-thread.ejs) */
.email-message {
  padding: 14px 16px;
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  box-shadow: 0 0 0 1px var(--color-border-tile);
  margin-bottom: var(--space-3);
}

.email-message.is-inbound {
  box-shadow: 0 0 0 1px var(--accent-700);
}

.email-message-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  font-size: 0.9118rem;
  color: var(--color-text-muted);
}

.email-message-header strong {
  color: var(--color-text);
}

.email-message-body {
  font-size: 1.0294rem;
  line-height: 1.6;
  color: var(--color-text-secondary);
}

.email-message-body.is-plain {
  white-space: pre-wrap;
}

/* A wide email (a newsletter's card table, a long link) scrolls sideways
   inside its own box instead of running past the page (production feedback
   #15). */
.email-message-body {
  max-width: 100%;
  overflow-x: auto;
  overflow-wrap: anywhere;
}

/* Each message is a <details> fold (production feedback #16): the header is
   its summary, with the start of the message beside it while folded. */
details.email-message > summary {
  cursor: pointer;
  list-style: none;
}
details.email-message > summary::-webkit-details-marker {
  display: none;
}
details.email-message > summary::before {
  content: "\25B8";
  color: var(--color-text-muted);
}
details.email-message[open] > summary::before {
  content: "\25BE";
}
details.email-message:not([open]) > summary {
  margin-bottom: 0;
}
.email-message-snippet {
  flex: 1 1 100%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-text-secondary);
}
details.email-message[open] .email-message-snippet {
  display: none;
}
.email-fold-controls {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.email-fold-controls[hidden] {
  display: none;
}
/* Quoted earlier messages inside a reply, folded by email-thread-folds.js. */
.email-quote {
  margin-top: var(--space-2);
}
.email-quote > summary {
  cursor: pointer;
  font-size: 0.8824rem;
  color: var(--color-text-muted);
}
.email-quote-text {
  white-space: pre-wrap;
}

/* Words for a screen reader only (the inbox's "Unread:"). The same rule is
   in polls.css and newsletter.css for their own pages. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Unread threads in the inbox list (production feedback #17). */
tr.is-unread .email-thread-row-name,
tr.is-unread .email-thread-row-subject {
  font-weight: 700;
  color: var(--color-text);
}
.email-unread-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--action);
  vertical-align: middle;
}
.email-read-toggle button {
  font-size: 0.8235rem;
  padding: 4px 8px;
  white-space: nowrap;
}

.email-message-error {
  margin-top: var(--space-2);
  font-size: 0.8824rem;
  color: var(--color-danger-hover);
}

.email-attachment-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.email-attachment-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  box-shadow: inset 0 0 0 1px var(--color-border);
  font-size: 0.8824rem;
  color: var(--color-text-secondary);
  text-decoration: none;
}

.email-attachment-chip:hover {
  box-shadow: inset 0 0 0 1px var(--club-accent);
  color: var(--color-text);
}

@media (max-width: 600px) {
  th, td {
    white-space: normal;
  }

  .email-thread-row {
    grid-template-columns: 1fr;
  }
}

/* Quill's own vendored quill.snow.css (node_modules/quill/dist, see
   server.js's static mount) hardcodes its toolbar icons to a fixed dark
   gray (#444) -- fine against Quill's own assumed white toolbar, but a
   real contrast failure against a dark theme's near-black toolbar
   background (email-compose.ejs, email-thread.ejs, forum-thread.ejs,
   forum-thread-form.ejs all use Quill with no toolbar styling of their
   own). Re-point both SVG color properties Quill's icons use at the
   theme's own ink color. post-form.ejs/post.ejs are the one place this
   needs to NOT apply -- their editor is a deliberate light "island"
   regardless of theme (post-blocks.css), where Quill's own gray default
   already contrasts correctly; post-blocks.css re-overrides this back for
   its one page, scoped to its own #toolbar. */
/* !important, deliberately: quill.snow.css (vendored, node_modules/quill/dist)
   defines its own icon colors on .ql-snow .ql-stroke etc. -- same
   specificity as a plain descendant-selector override, and it loads
   after this file in every view's <head>, so a same-specificity rule
   here would lose the cascade tie-break regardless of what's written.
   This is overriding a third-party library's own hardcoded, unthemed
   default, not fighting this app's own CSS. */
.ql-toolbar .ql-stroke {
  stroke: var(--ink) !important;
}

.ql-toolbar .ql-fill {
  fill: var(--ink) !important;
}

.ql-toolbar button:hover .ql-stroke,
.ql-toolbar button.ql-active .ql-stroke,
.ql-toolbar .ql-picker-label:hover .ql-stroke {
  stroke: var(--action) !important;
}

.ql-toolbar button:hover .ql-fill,
.ql-toolbar button.ql-active .ql-fill,
.ql-toolbar .ql-picker-label:hover .ql-fill {
  fill: var(--action) !important;
}

/* Transient "Saved" confirmation for any public/js/partial-panel.js AJAX
   save -- fixed rather than inline so it doesn't depend on where within a
   panel the triggering form sits. Visual language matches
   .feedback-success-notice (feedback.css) but this one auto-dismisses
   itself rather than needing a manual close, since it's a quick
   confirmation, not a persistent banner. */
.partial-save-toast {
  position: fixed;
  top: 62px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  padding: 9px 16px;
  border-radius: var(--radius-md);
  background: var(--accent-900);
  color: var(--accent-300);
  font-size: 0.9412rem;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
  opacity: 0;
  transition: opacity 0.15s ease;
}

.partial-save-toast.is-visible {
  opacity: 1;
}

@media (max-width: 600px) {
  .partial-save-toast {
    top: 100px;
  }
}

/* Image drag-and-drop target highlight (public/js/quill-image-drop.js) */
.quill-editor.is-dragover {
  outline: var(--border-w) dashed var(--action);
  outline-offset: -4px;
}

/* Inline images (dropped, pasted or picked in a Quill editor) must never
   spill past the content column -- no rule capped them before. Block
   images (hero/grid/image+text) size themselves and are unaffected. */
.post-body > p img,
.ql-editor > p img,
.email-message-body img {
  max-width: 100%;
  height: auto;
}

/* Show/hide button on password boxes (public/js/password-toggle.js). */
.password-field {
  position: relative;
  display: block;
}
.password-field input {
  width: 100%;
  padding-right: 3rem;
}
.password-field .password-toggle {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  min-width: 0;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--ink);
  cursor: pointer;
}
.password-field .password-toggle .icon {
  width: 1.4em;
  height: 1.4em;
  color: inherit;
}
.password-field .password-toggle:hover {
  filter: none;
  background: transparent;
  color: var(--action);
}

/* An email waiting out the send delay (views/partials/email-pending.ejs). */
.email-pending {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  align-items: center;
  justify-content: space-between;
  border: var(--border-w) solid var(--action);
  background: var(--action-tint);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  margin: var(--space-3) 0;
}
.email-pending.is-failed {
  border-color: var(--danger);
  background: var(--surface);
  justify-content: flex-start;
}
.email-pending-text {
  min-width: 0;
  overflow-wrap: anywhere;
}
.email-pending-actions {
  display: flex;
  gap: var(--space-2);
}
.email-pending-actions form {
  margin: 0;
}
.email-pending-actions[hidden] {
  display: none;
}

/* ---- Member profiles (plans/member-profiles.md) ---- */
/* A member's round picture, with their initials underneath for when there's
   no picture or it can't be loaded. The border is a real one, so the circle
   reads in Low-vision too. */
.person-avatar {
  position: relative;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: var(--border-w) solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  font-weight: 600;
  overflow: hidden;
  vertical-align: middle;
}
.person-avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.person-avatar-sm { width: 28px; height: 28px; font-size: 0.65rem; }
.person-avatar-md { width: 40px; height: 40px; font-size: 0.8rem; }
.person-avatar-lg { width: 72px; height: 72px; font-size: 1.4rem; }
.person-avatar-xl { width: 96px; height: 96px; font-size: 1.8rem; }

.profile-page {
  max-width: 640px;
  margin: 0 auto;
  padding: var(--space-4) var(--space-3);
  display: grid;
  gap: var(--space-4);
}
.profile-page h1 { margin: 0; }
.profile-identity {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.profile-identity-text { flex: 1; min-width: 12rem; display: grid; gap: var(--space-1); }
.profile-identity-text p, .profile-identity-text form { margin: 0; }
.profile-name { margin: 0; font-size: 1.3rem; font-weight: 600; }
.profile-photo-form { margin-top: var(--space-3); flex-wrap: wrap; }
.profile-photo-form input[type="file"] { max-width: 100%; }
form.stacked.profile-about-form { max-width: none; }
.profile-about-form textarea { width: 100%; box-sizing: border-box; }
.person-card { display: grid; gap: var(--space-3); }
.person-about { margin: 0; white-space: pre-line; overflow-wrap: anywhere; }

/* A name with its picture, wherever someone is named beside what they wrote. */
.person-byline {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.person-byline a { color: inherit; font-weight: 600; }
.nav-menu-account > summary .person-avatar { margin-right: 2px; }
.post-status-line { flex-wrap: wrap; }
.post-byline { font-size: 0.85rem; }
.roster-row-link .person-avatar { margin-right: var(--space-2); }
.roster-detail-profile { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-2); }
.roster-detail-profile form { margin: 0; }
