/* ==================================================================
   MEMBER DASHBOARD (members.html, stage 3)

   Only the signed-in view. The sign-in card and the one-time-code step
   are untouched by everything here — every rule is scoped to either
   .dash-* or .member-portal.dash-mode, which the script adds when the
   dashboard opens and removes again on sign-out.

   Kept in its own file so style.css stays as it was; the existing
   .md-* rules there still do all the work for the profile table, the
   ID card and the notebook.
   ================================================================== */

/* ---------- the panel goes full width once signed in ----------
   The sign-in card is a two-column split: branding on the left, form on
   the right. A dashboard has no use for half its width being a logo, so
   in dash mode the panel becomes a single wide column. The branding
   side is only hidden — it returns the moment the member signs out. */
/* `minmax(0, 1fr)`, not `1fr`.

   This is the rule that let every other overflow below escape to the screen.

   A grid item's `min-width` defaults to `auto`, which on a grid item means
   "never smaller than my own min-content". A plain `1fr` track therefore has a
   floor nobody wrote down: whatever the widest un-shrinkable thing inside it
   happens to be. On a 360px phone the panel had 268px to live in and its
   min-content came to 342px, so the track grew to 342, the whole dashboard was
   laid out 74px wider than the screen, and everything in it — the Logout
   button, the profile line, the tiles — ran off the right-hand edge.

   `minmax(0, 1fr)` sets that floor to zero and lets the content wrap the way
   it was always written to. The individual floors that made min-content so
   wide are fixed at source too (see .dash-grid, .dash-tiles and .md-who), but
   this line is what stops the next one from reaching the viewport. */
.member-portal.dash-mode {
  grid-template-columns: minmax(0, 1fr);
  max-width: 1180px;
}
/* Same reasoning one level down: the form is the grid item, so it is the thing
   whose `min-width: auto` was being resolved against its contents. */
.member-portal.dash-mode .member-portal-form { min-width: 0; }
/* Signed in, the cream sign-in panel steps aside and the dashboard sits on
   the same navy glass as every other card on the site. The sign-in form keeps
   its cream (a deliberate light island; see atmosphere.css), so the switch
   is tied to dash-mode and reverses on sign-out. The gold tokens that
   atmosphere.css deepens for that cream come back to the site's own accent. */
:root .member-portal.dash-mode .member-portal-form {
  background: transparent;
  --c-gold: #22D3EE;
  --c-gold-deep: #0891B2;
  --gold: #22D3EE;
  --gold-dark: #67E8F9;
}
:root .member-portal.dash-mode .member-portal-form h3 { color: var(--surface-text); }
:root .member-portal.dash-mode .member-portal-form p { color: var(--surface-text-muted); }
:root .member-portal.dash-mode .member-portal-form label { color: var(--surface-text-muted); }
:root .member-portal.dash-mode .member-portal-form .btn-gold { color: #0b1f38; }
:root .member-portal.dash-mode .member-portal-form .btn-gold:hover { color: #fff; }
.member-portal.dash-mode .member-portal-side { display: none; }
.member-portal.dash-mode .member-portal-form { padding: 34px 30px; }

/* ---------- what a phone can actually spare sideways ----------
   Four boxes are nested here before any content is reached: <main>, the
   portal card, the form panel, and the view. Each was keeping a desktop's
   padding on a 375px screen, and together they were spending 172px of it —
   the month grid, the profile table and the message box were all being asked
   to lay themselves out in the ~200px that was left, which is why they came
   out clipped and stacked. Trimming the three inner ones (--fab-room covers
   the outermost, in style.css) returns about 60px to every view below. */
@media (max-width: 700px) {
  .member-portal.dash-mode .member-portal-form { padding: 22px 14px; }
  .dash-view { padding: 16px 12px; }
  .dash-card { padding: 16px 13px; }
}
@media (max-width: 400px) {
  .member-portal.dash-mode .member-portal-form { padding: 20px 10px; }
  .dash-view { padding: 14px 10px; }
  .dash-card { padding: 14px 11px; }
}

/* ---------- the card grid ---------- */
/* `minmax(min(268px, 100%), 1fr)`.

   `minmax(268px, 1fr)` reads as "at least 268px wide", and on a container
   narrower than 268px that is not a minimum — it is an instruction to overflow
   by the difference. The panel has about 224px of usable width on a 360px
   phone, so every one of these cards was 44px too wide before a word of
   content was in it.

   Wrapping the floor in `min(…, 100%)` says "268px, or the whole width if that
   is smaller", which changes nothing above 268px and stops the overflow below
   it. The same shape is used on .dash-tiles below. */
.dash-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(268px, 100%), 1fr));
  gap: 18px;
  margin-top: 24px;
}
/* Anything that needs the full run: profile, ID card, gallery, notebook. */
.dash-wide { grid-column: 1 / -1; }

/* ---------- one search box for the whole portal ----------
   Sits above the tiles and stays a single line at every width: the icon and
   the clear button are absolutely positioned inside the field rather than
   being flex siblings, so there is nothing to wrap onto a second row and no
   arrangement where the input is squeezed to a sliver — which is exactly how
   the chat compose row failed on a phone. Results hang below in a floating
   panel, so the tiles stay where they are instead of being pushed down the
   page as somebody types. */
.dash-search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 20px;
}
.dash-search {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  z-index: 30;                  /* the panel floats over the tiles below */
}
/* The refresh button beside the search box: a circle the height of the box,
   its arrow turning while the portal fetches everything again. */
.md-mustchange { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin: 14px 0; border-color: #e0b341; }
.md-mustchange strong { flex: 1 1 100%; }
.md-mustchange span { flex: 1 1 260px; }
.dash-forms { margin: 22px 0 10px; }
.dash-forms-title { color: var(--c-gold, #22D3EE); font-size: 1.05rem; margin: 0 0 10px; }
.dash-forms .placed-form { margin: 0 0 14px; }
.dash-refresh {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  background: var(--surface-input);
  color: var(--gold);
  font: inherit;
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease), color var(--t) var(--ease);
}
.dash-refresh:hover, .dash-refresh:focus-visible {
  border-color: var(--gold);
  background: var(--c-gold-veil-soft);
  outline: none;
}
.dash-refresh[disabled] { cursor: progress; opacity: .8; }
.dash-refresh-icon { display: inline-block; }
.dash-refresh.is-busy .dash-refresh-icon { animation: dashRefreshSpin 0.8s linear infinite; }
@keyframes dashRefreshSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .dash-refresh.is-busy .dash-refresh-icon { animation: none; opacity: .5; }
}

/* Download as one symbol. "Download ID Card" and "Download PDF" had grown
   into full-width buttons; a square with the arrow says the same. */
.btn.btn-icon-only {
  flex: 0 0 auto !important;
  width: 46px;
  height: 46px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.btn.btn-icon-only svg { width: 22px; height: 22px; fill: currentColor; }

/* The annual fee history: a button, and the table it opens. Every payment
   ever submitted, newest first, one row each - what it was for, how much,
   the references, when, and what happened to it. */
.annual-history { margin-top: 12px; }
.annual-history .md-table { margin-top: 0; min-width: 600px; }   /* scrolls sideways rather than squashing */
.annual-history .md-table th { width: auto; white-space: nowrap; }
.annual-history .md-table td { vertical-align: top; }
.annual-history .md-table td:nth-child(1),
.annual-history .md-table td:nth-child(3),
.annual-history .md-table td:nth-child(4) { white-space: nowrap; }
.annual-history .md-table small { color: var(--surface-text-muted); font-size: .78rem; }
.annual-history .pay-badge { white-space: nowrap; }
.annual-history-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.annual-history-reason { display: block; color: #FF8F8F; font-size: .8rem; margin-top: 3px; }
.dash-search-icon {
  position: absolute;
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.95rem;
  line-height: 1;
  pointer-events: none;         /* the icon must not swallow the click */
  opacity: .75;
}
.dash-search input {
  width: 100%;
  /* Room for the icon on the left and the clear button on the right. */
  padding: 11px 40px 11px 38px;
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--surface-text);
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  /* Safari draws its own magnifier and clear widget inside type=search. */
  -webkit-appearance: none;
  appearance: none;
}
.dash-search input::-webkit-search-decoration,
.dash-search input::-webkit-search-cancel-button { -webkit-appearance: none; }
.dash-search input::placeholder { color: var(--surface-text-muted); }
.dash-search input:focus {
  outline: none;
  border-color: var(--gold-dark);
  box-shadow: 0 0 0 3px rgba(216, 178, 94, 0.25);
}
.dash-search-clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px; height: 26px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--surface-input);
  color: var(--surface-text-muted);
  font-size: 0.72rem;
  line-height: 1;
  cursor: pointer;
}
.dash-search-clear:hover { background: var(--surface-line); }

.dash-search-results {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  /* Tall enough to be worth scrolling, short enough that the tiles behind it
     are still visible on a phone. */
  max-height: min(58vh, 420px);
  overflow-y: auto;
  padding: 6px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 12px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.18);
}
.dash-search-results[hidden] { display: none; }
.dash-search-group + .dash-search-group { margin-top: 4px; }
.dash-search-label {
  margin: 6px 8px 4px;
  color: var(--surface-text-muted);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.6px;
  text-transform: uppercase;
}
.dash-search-hit {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  background: none;
  border: none;
  border-radius: 9px;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.dash-search-hit:hover,
.dash-search-hit.is-active { background: var(--surface-input); }
.dash-search-hit-icon { font-size: 1.05rem; line-height: 1; flex-shrink: 0; }
.dash-search-hit-text { display: flex; flex-direction: column; min-width: 0; }
.dash-search-hit-text strong {
  color: var(--surface-text);
  font-size: 0.9rem;
  font-weight: 700;
  /* One line each, so a long notice title cannot turn a result into a
     paragraph and push the rest out of reach. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dash-search-hit-text small {
  color: var(--surface-text-muted);
  font-size: 0.76rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dash-search-empty { margin: 10px; color: var(--surface-text-muted); font-size: 0.86rem; }

@media (max-width: 560px) {
  .dash-search input { padding: 10px 36px 10px 34px; font-size: 0.92rem; }
  .dash-search-icon { left: 11px; font-size: 0.88rem; }
  .dash-search-hit { padding: 8px; }
}

/* ---------- tiles: the dashboard's front page ----------
   The dashboard used to render every card at once, which meant a very long
   page and every endpoint fetched on sign-in whether or not anyone looked at
   it. These tiles are the menu; each opens one view, and that view fetches its
   own data the first time it is opened. */
.dash-tiles {
  display: grid;
  /* See .dash-grid above for why the floor is wrapped in min(). */
  grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
  gap: 14px;
  margin-top: 24px;
}
.dash-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  text-align: left;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 14px;
  padding: 18px 16px;
  cursor: pointer;
  font: inherit;
  transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.dash-tile:hover,
.dash-tile:focus-visible {
  border-color: var(--gold);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .12);
  transform: translateY(-2px);
  outline: none;
}
.dash-tile-icon { font-size: 1.9rem; line-height: 1.1; }
.dash-tile-name { font-weight: 800; color: var(--surface-text); margin-top: 6px; }
.dash-tile-sub { font-size: .82rem; color: var(--surface-text-muted); }
.dash-tile-badge {
  position: absolute;
  top: 12px; right: 12px;
  background: var(--red, #b3261e);
  color: #fff;
  font-size: .62rem;
  font-weight: 800;
  letter-spacing: 1px;
  padding: 2px 7px;
  border-radius: 20px;
}

/* ---------- one view at a time ---------- */
.dash-view {
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 12px;
  padding: 20px 18px;
  margin-top: 24px;
  min-width: 0;
}
.dash-view[hidden] { display: none; }
.dash-view .md-heading { margin: 0 0 8px; padding-top: 0; border-top: none; }
.dash-back {
  background: none;
  border: 1px solid var(--surface-line);
  border-radius: 8px;
  color: var(--surface-text-muted);
  cursor: pointer;
  font: inherit;
  font-size: .85rem;
  padding: 6px 12px;
  margin-bottom: 14px;
}
.dash-back:hover { border-color: var(--gold); color: var(--gold-dark); }

/* The chat panel is already a card; inside a view it must not be a second one. */
.dash-view > .chat-panel { border: none; padding: 0; background: none; }

.dash-card {
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 12px;
  padding: 20px 18px;
  min-width: 0;                 /* lets long words wrap instead of stretching the column */
}
/* .md-heading is a section separator in the single-column layout — a top
   border and 30px of space above it. Inside a card it is a card title. */
.dash-card .md-heading {
  margin: 0 0 8px;
  padding-top: 0;
  border-top: none;
}

/* ---------- membership status ---------- */
.dash-status-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.dash-badge {
  display: inline-block;
  padding: 5px 14px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.6px;
  background: var(--surface-input);
  color: var(--surface-text-muted);
}
.dash-badge.is-active { background: rgba(34, 197, 94, 0.16); color: #4ADE80; }
.dash-badge.is-waiting { background: var(--surface-input); color: var(--c-gold); }
.dash-badge.is-blocked { background: rgba(239, 68, 68, 0.16); color: #FF8F8F; }
.dash-status-note { color: var(--surface-text-muted); font-size: 0.84rem; }
.md-table.dash-mini th, .md-table.dash-mini td { padding: 8px 12px; font-size: 0.88rem; }

/* ---------- the same tables on a phone ----------
   `.md-table th` is 45% wide, which is a sensible split on a laptop and an
   unusable one on a 300px column: "WhatsApp Number" got about 120px and broke
   over two lines, and the value beside it broke mid-word — an email address
   came out as five stacked fragments (see Profile Details / Membership
   Status). Below 560px each row becomes a block with the label on its own
   line above the value, so both get the full width and neither is hyphenated
   by the layout. Used by Profile Details, Membership Status and any other
   .md-table in the portal. */
@media (max-width: 560px) {
  .md-table, .md-table tbody, .md-table tr, .md-table th, .md-table td {
    display: block;
    width: auto;
  }
  .md-table tr {
    border-bottom: 1px solid var(--surface-line);
    padding: 4px 0 8px;
  }
  .md-table tr:last-child { border-bottom: none; }
  .md-table th {
    /* The label is a caption above the value now, not a column beside it. */
    width: auto;
    background: none;
    border-bottom: none;
    padding: 8px 2px 2px;
    font-size: 0.78rem;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    color: var(--surface-text-muted);
  }
  .md-table td {
    border-bottom: none;
    padding: 0 2px;
    font-size: 0.95rem;
    /* `break-word` only, never `break-all`: an address wraps between words,
       and the one thing that genuinely has no break opportunity — a long
       email — is handled by overflow-wrap instead of being chopped. */
    word-break: normal;
    overflow-wrap: anywhere;
  }
  .md-table.dash-mini th { padding: 8px 2px 2px; }
  .md-table.dash-mini td { padding: 0 2px; font-size: 0.9rem; }
}

/* ---------- QR code ---------- */
.dash-qr-wrap {
  display: flex;
  justify-content: center;
  padding: 12px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
}
.dash-qr-wrap img {
  width: 100%;
  max-width: 190px;
  height: auto;
  image-rendering: pixelated;   /* keeps the modules crisp when scaled up */
}

/* ---------- quick links ---------- */
.dash-links {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.dash-links a {
  display: block;
  padding: 10px 12px;
  border: 1px solid var(--surface-line);
  border-radius: 9px;
  background: var(--surface-input);
  color: var(--surface-text);
  font-size: 0.86rem;
  font-weight: 700;
  text-decoration: none;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.dash-links a:hover {
  background: var(--surface-input);
  border-color: var(--gold);
  transform: translateY(-1px);
}

/* ---------- profile edit / change password ---------- */
.dash-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 16px;
}
.dash-form {
  margin-top: 18px;
  padding: 18px 16px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
}
.dash-form-title {
  margin: 0 0 12px;
  color: var(--surface-text);
  font-size: 1rem;
}
.dash-form label {
  display: block;
  margin-bottom: 5px;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--surface-text-muted);
}
.dash-form input,
.dash-form select,
.dash-form textarea {
  width: 100%;
  padding: 11px 13px;
  margin-bottom: 12px;
  font-size: 0.96rem;
  font-family: inherit;
  background: var(--surface-input);
  color: var(--surface-text);
  border: 1px solid var(--surface-line);
  border-radius: 8px;
  resize: vertical;
}
.dash-form input:focus,
.dash-form select:focus,
.dash-form textarea:focus {
  border-color: var(--gold-dark);
  box-shadow: 0 0 0 3px rgba(216, 178, 94, 0.25);
  outline: none;
}
.dash-form-btns { display: flex; gap: 10px; flex-wrap: wrap; }

/* ---------- notices / events / donations lists ---------- */
.dash-list { display: flex; flex-direction: column; gap: 10px; }
.dash-item {
  padding: 11px 13px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-left: 3px solid var(--gold);
  border-radius: 8px;
}
.dash-item-title {
  display: block;
  color: var(--surface-text);
  font-weight: 800;
  font-size: 0.92rem;
}
.dash-item-meta {
  display: block;
  margin-top: 3px;
  color: var(--surface-text-muted);
  font-size: 0.78rem;
}
.dash-item-text {
  margin: 6px 0 0;
  color: var(--surface-text);
  font-size: 0.85rem;
  /* Two lines is enough for a preview; the full text is on its own page. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.dash-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--surface-input);
  color: var(--c-gold);
  font-size: 0.7rem;
  font-weight: 800;
}
.dash-amount { color: var(--gold-dark); font-weight: 800; }
.dash-verified { color: #4ADE80; font-weight: 800; }
.dash-unverified { color: var(--c-gold); font-weight: 800; }
.dash-total {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--surface-line);
  color: var(--surface-text);
  font-size: 0.88rem;
  font-weight: 800;
}
.dash-empty { color: var(--surface-text-muted); font-size: 0.86rem; margin: 0; }

/* =====================================================================
   NOTICES
   ---------------------------------------------------------------------
   The list clipped its text before: a fixed-height card with a hidden
   overflow, so a long title lost its end and a long description simply
   stopped. Nothing here has a height — the card is as tall as what is in
   it — and every text node wraps, including a single unbroken word,
   which is what used to push the whole panel sideways.
   ===================================================================== */
.notice-item {
  display: block;
  width: 100%;
  min-width: 0;
  text-align: left;
  font: inherit;
  cursor: pointer;
  padding: 13px 15px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-left: 3px solid var(--gold);
  border-radius: 8px;
  transition: border-color .12s ease, box-shadow .12s ease;
}
.notice-item:hover, .notice-item:focus-visible {
  border-color: var(--gold);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .10);
  outline: none;
}
.notice-item.is-unread { border-left-color: #1c62d6; background: var(--surface-input); }

/* The badge sits beside the title on its own line, never over it. */
.notice-item-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}
.notice-item-title {
  flex: 1;
  min-width: 0;
  color: var(--surface-text);
  font-weight: 800;
  font-size: 0.95rem;
  line-height: 1.35;
  /* The three that between them make any title wrap: normal whitespace, a
     break allowed inside a very long word, and a flex child that is permitted
     to be narrower than its content. */
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.notice-item-meta {
  display: block;
  margin-top: 4px;
  color: var(--surface-text-muted);
  font-size: 0.78rem;
  overflow-wrap: anywhere;
}
/* Two lines of preview in the list — the whole thing is one tap away, and
   this is the only place any clamping happens. */
.notice-item-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 6px;
  color: var(--surface-text);
  font-size: 0.86rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

/* The blue NEW badge. Blue rather than the red the message badges use, so the
   two mean different things at a glance. */
.notice-new-badge {
  flex-shrink: 0;
  align-self: flex-start;
  padding: 2px 9px;
  border-radius: 999px;
  background: #1c62d6;
  color: #fff;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 1px;
}
.dash-tile-badge.is-notice { background: #1c62d6; }

/* The full notice. No clamping, no fixed height, and long words wrap. */
.notice-full {
  min-width: 0;
  padding: 16px 16px 18px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
}
.notice-full-title {
  margin: 4px 0 6px;
  color: var(--surface-text);
  font-size: 1.15rem;
  line-height: 1.35;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.notice-full-meta { margin: 0 0 12px; color: var(--surface-text-muted); font-size: 0.82rem; }
.notice-full-body {
  color: var(--surface-text);
  font-size: 0.94rem;
  line-height: 1.65;
  /* The line breaks the admin typed are kept; a long word still wraps rather
     than running off the side of the card. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.notice-full-details { margin: 16px 0 0; }
.notice-detail-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 0;
  border-top: 1px solid var(--surface-input);
}
.notice-detail-row dt {
  flex: 0 0 130px;
  color: var(--surface-text-muted);
  font-size: 0.8rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.notice-detail-row dd {
  flex: 1;
  min-width: 0;
  margin: 0;
  color: var(--surface-text);
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

/* The dashboard's own latest-notice card. */
.md-notice-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.md-notice-body {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
.md-notice-body #md-notice {
  display: block;
  font-weight: 700;
  line-height: 1.4;
  white-space: normal;
  overflow-wrap: anywhere;
}
.md-notice-meta { display: block; margin-top: 3px; font-size: 0.78rem; opacity: 0.8; }

@media (max-width: 560px) {
  .notice-detail-row dt { flex-basis: 100%; }
}

/* =====================================================================
   UPCOMING EVENTS
   ---------------------------------------------------------------------
   Cards rather than one-line items, because an event now carries a time,
   a venue and a description. Nothing here has a fixed height and every
   text node wraps — a long title moves onto a second line instead of
   being cut, and a long unbroken word wraps instead of widening the
   panel. The image keeps its own aspect ratio inside a fixed frame.
   ===================================================================== */
.event-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 14px;
}
/* Scoped to the dashboard's own list, and it has to be.
   `.event-card` is also a component on the public home page, where style.css
   gives it `width: 420px; flex-shrink: 0` for a horizontal carousel. This page
   loads both stylesheets, so those two declarations were landing on the
   dashboard's cards as well — a 420px card inside a 280px grid cell, refusing
   to shrink, spilling 140px past the panel and over whatever sat beside it.
   Nothing here had overridden `width`, so the later stylesheet did not win it.
   Naming the container both raises specificity above the carousel rule and
   keeps this out of the carousel's way. */
.event-list .event-card {
  display: flex;
  flex-direction: column;
  width: auto;
  flex-shrink: 1;
  min-width: 0;
  max-width: 100%;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  overflow: hidden;
}
.event-card-media {
  /* A fixed frame with object-fit inside it: every card is the same height
     whatever shape the photo is, and no photo is squashed to get there. */
  aspect-ratio: 16 / 9;
  background: var(--surface-input);
}
.event-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.event-card-body { padding: 13px 15px 15px; min-width: 0; }
.event-card-title {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  font-weight: 800;
  font-size: 1rem;
  line-height: 1.35;
  color: var(--surface-text);
  cursor: pointer;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.event-card-title:hover { color: var(--gold-dark); text-decoration: underline; }
.event-card-meta {
  margin: 5px 0 0;
  color: var(--surface-text-muted);
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}
.event-card-text {
  margin: 8px 0 0;
  color: var(--surface-text);
  font-size: 0.86rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
  /* Three lines in the card; the whole description is on the full view. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.event-card-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 12px;
}
.event-card-actions .btn { padding: 7px 14px; font-size: 0.82rem; }
.event-card-actions .btn.is-done { opacity: 0.85; cursor: default; }

.event-full {
  min-width: 0;
  padding: 16px 16px 18px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
}
.event-full-media { border-radius: 10px; overflow: hidden; margin-bottom: 14px; }
.event-full-media img { width: 100%; height: auto; display: block; }
.event-full-title {
  margin: 0 0 10px;
  color: var(--surface-text);
  font-size: 1.15rem;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.event-full-facts { margin: 0 0 14px; }
.event-full-text {
  color: var(--surface-text);
  font-size: 0.94rem;
  line-height: 1.65;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.participate-panel { margin-top: 18px; }

/* ---------- My Donations: the two cards ---------- */
.donate-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin-top: 8px;
}
.donate-card { align-items: flex-start; }
.donate-history { margin-top: 8px; }
.donate-more-wrap { display: flex; justify-content: center; margin-top: 14px; }

@media (max-width: 560px) {
  .event-list { grid-template-columns: 1fr; }
  .event-card-actions .btn { flex: 1 1 100%; }
}
.dash-more {
  display: inline-block;
  margin-top: 14px;
  color: var(--c-gold);
  font-size: 0.84rem;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.dash-more:hover { color: var(--gold-dark); }

/* ---------- identity-first landing ----------
   The dashboard now opens showing only the member's photo and membership ID.
   Everything personal sits behind this button, so a portal opened in a room
   with other people in it does not put an address and date of birth on screen
   until the member asks for it. */
.md-identity {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 1;
  /* No floor on a phone — a flat 200px is one a 320px screen cannot honour,
     and an unhonourable minimum is an overflow. It is restored below the
     stacking breakpoint, where it still does its real job: pushing the session
     row onto a line of its own before the name gets squeezed. See .md-who in
     style.css for why this is a media query and not a min(). */
  min-width: 0;
  padding: 10px 14px 10px 10px;
  background: none;
  border: 1px solid transparent;
  border-radius: 14px;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.md-identity:hover { background: var(--surface-input); border-color: var(--surface-line); }
.md-identity:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }
/* No `min-width` here. It carried one briefly and it was a trap: this selector
   is (0,2,0) and the rule that restores the 170px floor above the phone
   breakpoint is a plain `.md-who` at (0,1,0), so a `min-width: 0` on this line
   silently beat it at every width. The base value lives on `.md-who` in
   style.css, where the media query can reach it. */
.md-identity .md-who { display: flex; flex-direction: column; gap: 2px; }
.md-identity .md-who strong { color: var(--surface-text); font-size: 1.45rem; line-height: 1.2; }

.md-idline { color: var(--surface-text-muted); font-size: 0.92rem; }
.md-idline b { color: var(--surface-text); }

/* The photo moves above the name instead of beside it.

   Measured rather than guessed: "BNSCA/MBR/000002" is one unbreakable run
   163px wide. Beside an avatar on a 360px screen the text column gets 117px,
   so the ID broke mid-token — "BNSCA/MBR/0 / 00002" — which is readable and
   looks like a fault. Shrinking the type enough to fit 163px into 117px would
   have meant about 10px glyphs, which is worse than a wrap.

   Stacked, the column gets the panel's full width and the ID sits on one line.

   460px, and the number is arithmetic rather than taste. Beside the avatar the
   text column gets roughly `viewport - 262px` — 92px of page gutter, 44px of
   panel padding, an 86px photo, and the gaps. Setting that against the ID's
   163px means the row only works from about 445px up, so every phone below
   that stacks. 412px and 430px, which are the two commonest Android widths,
   sit just the wrong side of a 400px guess and were still wrapping.

   This block sits *after* the `.md-idline` rule above deliberately. A media
   query adds no specificity, so a plain `.md-idline` declared later would
   simply win — which is exactly what happened when this was written above it,
   and why the font size below appeared to do nothing. */
@media (max-width: 460px) {
  .md-identity {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    padding-left: 4px;
  }
  .md-identity .md-avatar,
  .md-identity #md-avatar { width: 60px; height: 60px; }
  .md-identity .md-who strong { font-size: 1.15rem; }
  /* 0.8rem puts the 163px identifier inside the ~147px this column has on a
     320px screen with room to spare, rather than landing 2px over and
     wrapping. */
  .md-idline { font-size: 0.8rem; }
}

/* Above the stack, the row again — and with it the minimum that makes the row
   behave.

   300px, not the 200px it used to be, and the number is what this box actually
   needs rather than a round figure: an 86px photo, a 16px gap, 24px of its own
   padding and a 163px membership ID come to 289px. At 200px the row still
   counted as "fitting", so the session counter stayed alongside — and the name
   and ID, given about 104px between them, spilled out of the button and under
   the Session pill.

   Asking for what it needs means the row breaks honestly: when the panel
   cannot give this box 300px *and* fit the counter beside it, the counter
   drops to its own line and this box gets the full width. */
@media (min-width: 461px) {
  .md-identity { min-width: 300px; }
}
.md-tap {
  margin-top: 3px;
  color: var(--gold-dark);
  font-size: 0.8rem;
  font-weight: 700;
  transition: transform 0.2s ease;
}
@media (prefers-reduced-motion: reduce) {
  .md-identity, .md-tap { transition: none; }
}

/* ---------- the session counter ----------
   Sits against Logout because the two answer the same question: how much
   longer this session has, and how to end it now. Tabular figures so the
   digits do not jitter as they count down. */
.md-session {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
  flex-wrap: wrap;
}
.md-timer {
  display: inline-block;
  padding: 6px 12px;
  border: 1px solid var(--surface-line);
  border-radius: 999px;
  background: var(--surface-input);
  color: var(--surface-text-muted);
  font-size: 0.86rem;
  font-weight: 800;
  letter-spacing: 0.4px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* The final minute. Colour and weight only — nothing moves, so it reads as a
   warning rather than as something demanding to be clicked. */
.md-timer.is-urgent {
  background: rgba(239, 68, 68, 0.16);
  border-color: rgba(239, 68, 68, 0.45);
  color: #FF8F8F;
}

@media (max-width: 560px) {
  .md-session { width: 100%; justify-content: space-between; }
}

/* The counter and Logout, on a phone.

   Together they wanted 288px — a 137px counter that may not wrap and a 141px
   button that refuses to shrink — in a column about 224px wide. They did not
   wrap onto two lines, because neither was allowed to give: the row simply
   reported 288px as its minimum and the panel grew to fit, which is how
   "LOGOUT ✕" ended up half off the side of the screen.

   Trimmed rather than stacked. Two short controls side by side is what this
   row is, and putting the timer on a line of its own to make room for a button
   that says the same thing in fewer pixels would be the wrong trade. */
@media (max-width: 460px) {
  .md-timer {
    padding: 5px 9px;
    font-size: 0.78rem;
    letter-spacing: 0;
  }
  .md-logout {
    padding: 8px 12px;
    font-size: 0.78rem;
    letter-spacing: 0.3px;
    /* It may now shrink if it still has to. Below this width the alternative
       is not a narrower button, it is a button somewhere off the screen. */
    flex-shrink: 1;
    min-width: 0;
  }
}

/* ---------- "Still there?" ----------
   The prompt in the last seconds of the session. Cream and gold like the rest
   of the dashboard, and so the same in both themes — this card is deliberately
   light whichever way the site's switch is thrown, and a navy dialog over a
   cream dashboard would read as belonging to a different site.

   A real <dialog>, so the backdrop, the focus trap and the centring are the
   browser's job rather than a stack of z-indexes here. */
.session-warn {
  width: min(420px, calc(100vw - 32px));
  padding: 26px 24px 22px;
  border: 1px solid var(--gold-dark);
  border-radius: 14px;
  background: var(--surface-input);
  color: var(--surface-text);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
  text-align: center;
}
.session-warn::backdrop {
  background: rgba(4, 20, 38, 0.62);
}
.session-warn-title {
  margin: 0 0 10px;
  font-size: 1.2rem;
  color: var(--c-gold);
}
.session-warn-text {
  margin: 0 0 14px;
  color: var(--surface-text-muted);
  font-size: 0.94rem;
  line-height: 1.6;
}
/* The number that actually counts down. Tabular figures for the same reason as
   the timer above: at one digit a second, a proportional font makes the whole
   line twitch. */
.session-warn-count {
  margin: 0 0 18px;
  font-weight: 800;
  color: #FF8F8F;
  font-variant-numeric: tabular-nums;
}
.session-warn-btns {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}
/* On a phone the two answers go full width and stack, so neither is a small
   target beside the other — this is a decision taken in a few seconds. */
@media (max-width: 460px) {
  .session-warn-btns { flex-direction: column; }
  .session-warn-btns .btn { width: 100%; }
}

/* ---------- monthly fees ---------- */
.fee-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}
.fee-stat {
  padding: 10px 13px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-left: 3px solid var(--gold);
  border-radius: 8px;
}
.fee-stat span { display: block; color: var(--surface-text-muted); font-size: 0.74rem; font-weight: 800;
                 letter-spacing: 0.4px; text-transform: uppercase; }
.fee-stat strong { display: block; margin-top: 2px; color: var(--surface-text); font-size: 1.15rem; }

/* The month picker. Each month is a label wrapping a real checkbox, so it
   stays keyboard-operable and screen readers announce it as a checkbox. */
/* Which year the month list is showing. Only appears when there is more
   than one year to switch between. */
.fee-yearbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin: 14px 0 4px;
}
/* The year is a dropdown now that the list runs decades ahead, but it should
   still read as the label it replaced, not as a form field. */
.fee-yearbar select {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--gold-dark);
  font: inherit;
  font-size: 1.05rem;
  font-weight: 800;
  text-align: center;
  text-align-last: center;
  padding: 4px 10px;
  margin: 0;
  min-width: 92px;
  cursor: pointer;
}
.fee-yearbar select:hover { border-color: var(--gold); }
.fee-yearbar select:focus { outline: none; border-color: var(--gold-dark); }
.fee-yearbar select option { color: var(--surface-text); background: var(--surface-input); }
.fee-yearbtn {
  width: 32px; height: 32px;
  border: 1px solid var(--surface-line);
  border-radius: 8px;
  background: var(--surface-input);
  color: var(--surface-text-muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.fee-yearbtn:hover:not(:disabled) { border-color: var(--gold); color: var(--gold-dark); }
.fee-yearbtn:disabled { opacity: .35; cursor: default; }

/* The year itself: a number the member steps or types, in place of the
   fifty-one-option dropdown that used to be here. Styled to read as the label
   it replaced rather than as a form field — see the note in members.html. */
.fee-yearinput {
  width: 6.5ch;                 /* four digits and the spinner's elbow room */
  padding: 4px 6px;
  margin: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--gold-dark);
  font: inherit;
  font-size: 1.05rem;
  font-weight: 800;
  text-align: center;
  -moz-appearance: textfield;   /* Firefox draws its own spinner otherwise */
}
/* The spinners are redundant next to the ‹ › buttons and make the box too
   narrow to read the year in. */
/* Reads as a label, not a field, in dash-mode too (style.css paints every
   signed-in input with the field navy; this one stays bare). */
:root .member-portal.dash-mode .member-portal-form input.fee-yearinput {
  background: transparent;
  color: var(--gold-dark);
  border-color: transparent;
}
:root .member-portal.dash-mode .member-portal-form input.fee-yearinput:hover { border-color: var(--gold); }
:root .member-portal.dash-mode .member-portal-form input.fee-yearinput:focus {
  border-color: var(--gold-dark);
  background: var(--surface-input);
}
.fee-yearinput::-webkit-outer-spin-button,
.fee-yearinput::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.fee-yearinput:hover { border-color: var(--gold); }
.fee-yearinput:focus {
  outline: none;
  border-color: var(--gold-dark);
  background: var(--surface-input);
  box-shadow: 0 0 0 3px rgba(216, 178, 94, 0.25);
}
/* "Back to this year", shown only when the member has wandered off it. */
.fee-yearnow {
  padding: 4px 10px;
  border: 1px solid var(--surface-line);
  border-radius: 999px;
  background: var(--surface-input);
  color: var(--surface-text-muted);
  font: inherit;
  font-size: 0.74rem;
  font-weight: 700;
  cursor: pointer;
}
.fee-yearnow:hover { border-color: var(--gold); color: var(--gold-dark); }
.fee-yearnow[hidden] { display: none; }

.fee-months {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  gap: 9px;
  margin-bottom: 16px;
}
.fee-month {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 11px 13px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 9px;
  cursor: pointer;
  transition: border-color 0.16s ease, background 0.16s ease;
}
.fee-month:hover { border-color: var(--gold); background: var(--surface-input); }
.fee-month input { accent-color: var(--gold-dark); width: 17px; height: 17px; flex-shrink: 0; }
.fee-month-name { color: var(--surface-text); font-size: 0.88rem; font-weight: 700; }
.fee-month-tag { display: block; margin-top: 1px; font-size: 0.72rem; font-weight: 800; }

/* A month that is settled or waiting cannot be chosen again. */
.fee-month.is-locked { cursor: not-allowed; background: var(--surface-input); border-style: dashed; }
.fee-month.is-locked:hover { border-color: var(--surface-line); background: var(--surface-input); }
.fee-month.is-paid .fee-month-tag { color: #4ADE80; }
.fee-month.is-pending .fee-month-tag { color: var(--c-gold); }
.fee-month.is-rejected .fee-month-tag { color: #FF8F8F; }
.fee-month.is-selected { border-color: var(--gold-dark); background: var(--surface-input); }

/* ---------- a month the club sent back ----------
   Deliberately not `.is-locked`: this month *is* payable, and dressing it in
   the dashed, greyed, not-allowed treatment would tell the member the opposite
   of what the club just emailed them. It is an ordinary tickable tile with a
   red left edge and the reason underneath, so it reads as "this needs doing
   again" rather than as "this is closed". */
.fee-month.is-rejected-open {
  border-color: rgba(239, 68, 68, 0.45);
  border-left: 3px solid #c0504d;
  background: rgba(239, 68, 68, 0.08);
}
.fee-month.is-rejected-open:hover { border-color: var(--gold); background: var(--surface-input); }
.fee-month.is-rejected-open .fee-month-tag { color: #FF8F8F; }
/* Kept visibly selected when ticked, so the red edge cannot be mistaken for
   "you cannot choose this one". */
.fee-month.is-rejected-open.is-selected {
  border-color: var(--gold-dark);
  border-left-color: #c0504d;
  background: var(--surface-input);
}
.fee-month-why {
  display: block;
  margin-top: 3px;
  color: #8a4a48;
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.35;
  /* The admin typed this; it can be a sentence or a paragraph. Wraps rather
     than stretching the tile, and long unbroken input cannot widen the grid. */
  overflow-wrap: anywhere;
}

.fee-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding-top: 14px;
  border-top: 1px solid var(--surface-line);
}
.fee-total span { display: block; color: var(--surface-text-muted); font-size: 0.82rem; }
.fee-total strong { color: var(--gold-dark); font-size: 1.5rem; }

.fee-pay-panel {
  margin-top: 18px;
  padding: 18px 16px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  text-align: center;
}
.fee-pay-months { color: var(--surface-text-muted); font-size: 0.86rem; margin: 0 0 12px; }
.fee-pay-note { color: var(--surface-text-muted); font-size: 0.82rem; margin: 12px 0 14px; }
.fee-upi {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  flex-wrap: wrap;
  margin-top: 12px;
}
.fee-upi span { color: var(--surface-text-muted); font-size: 0.74rem; font-weight: 800; text-transform: uppercase; }
.fee-upi code {
  padding: 5px 11px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 7px;
  color: var(--surface-text);
  font-size: 0.95rem;
  font-weight: 700;
  word-break: break-all;
}
.fee-copy {
  background: none; border: none; padding: 0;
  color: var(--gold-dark); font: inherit; font-size: 0.82rem; font-weight: 700;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.fee-pay-panel .dash-form-btns { justify-content: center; margin-top: 4px; }

@media (max-width: 700px) {
  /* `1fr 1fr` on a ~300px column gives each tile about 145px, and a tile is a
     17px checkbox + a gap + "September 2026" over two lines + the ✅ Paid tag
     under it. The text had nowhere to go, so the right-hand column ran past
     the card edge and the tags were clipped. min-width: 0 lets the label wrap
     inside the tile rather than setting the tile's floor, and the label gets
     the room back by dropping a step in size. */
  .fee-months { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  /* The padding, the gap, the box and the type are all a step down together
     because they are competing for the same ~120px column. The target is the
     longest label the picker can produce — "September" — fitting on one line:
     under the old sizes it had 62px and needed 76, so it broke as
     "Septemb / er 2026". */
  .fee-month { padding: 10px 8px; gap: 6px; }
  .fee-month input { width: 16px; height: 16px; }
  .fee-month > span { min-width: 0; }
  /* `break-word`, not `anywhere`: the name wraps between "September" and
     "2026", and a word is only split when one genuinely cannot fit. */
  .fee-month-name { font-size: 0.78rem; overflow-wrap: break-word; }
  .fee-month-tag { font-size: 0.68rem; }
  .fee-foot { flex-direction: column; align-items: stretch; }
  .fee-foot .btn { width: 100%; }
  /* Two stat cards side by side rather than one per row — the three of them
     stacked pushed the month grid most of a screen further down. */
  .fee-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .fee-stat { padding: 9px 11px; }
  .fee-stat strong { font-size: 1.05rem; }
}
/* Under about 340px two columns stop being readable at any font size. */
@media (max-width: 340px) {
  .fee-months { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) { .fee-month { transition: none; } }

/* ---------- annual function fee ---------- */
.annual-box {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 16px 18px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-left: 3px solid var(--gold);
  border-radius: 10px;
}
.annual-amount span { display: block; color: var(--surface-text-muted); font-size: 0.76rem;
                      font-weight: 800; letter-spacing: 0.4px; text-transform: uppercase; }
.annual-amount strong { display: block; margin: 2px 0; color: var(--gold-dark); font-size: 2rem; }
.annual-status { font-size: 0.82rem !important; font-weight: 800 !important;
                 letter-spacing: 0 !important; text-transform: none !important; }
.annual-status.is-paid { color: #4ADE80 !important; }
.annual-status.is-pending { color: var(--c-gold) !important; }
.annual-status.is-unpaid { color: var(--surface-text-muted) !important; }
.annual-status.is-rejected { color: #FF8F8F !important; }
.annual-note {
  margin: 12px 0 0;
  padding: 10px 13px;
  background: var(--surface-input);
  border-radius: 8px;
  color: var(--surface-text-muted);
  font-size: 0.86rem;
}
.annual-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.annual-actions .md-hint { margin: 0 !important; flex: 1 1 220px; }
.annual-history-wrap {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--surface-line);
}

/* ---------- one payment in a history list ----------
   Used by the Annual Function Fee history and by My Donation History. The
   status badge sits on the right of the same row, and the row grows to fit
   whatever is in it rather than clipping. */
.pay-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-left: 3px solid var(--gold);
  border-radius: 8px;
}
.pay-row-main { flex: 1 1 240px; min-width: 0; }
.pay-row-title {
  display: block;
  color: var(--surface-text);
  font-weight: 800;
  font-size: 0.93rem;
  overflow-wrap: anywhere;
}
.pay-row-meta {
  display: block;
  margin-top: 3px;
  color: var(--surface-text-muted);
  font-size: 0.78rem;
  overflow-wrap: anywhere;
}
.pay-row-reason {
  display: block;
  margin-top: 5px;
  color: #FF8F8F;
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}
.pay-badge {
  flex-shrink: 0;
  align-self: flex-start;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 800;
  white-space: nowrap;
  background: var(--surface-input);
  color: var(--surface-text-muted);
}
.pay-badge.is-pending { background: var(--surface-input); color: var(--c-gold); }
.pay-badge.is-paid { background: rgba(34, 197, 94, 0.16); color: #4ADE80; }
.pay-badge.is-rejected { background: rgba(239, 68, 68, 0.16); color: #FF8F8F; }

@media (max-width: 700px) {
  .annual-box { flex-direction: column; align-items: stretch; }
  .annual-box .btn { width: 100%; }
  .annual-actions .btn { width: 100%; }
}

/* ---------- chat: the two entry cards ---------- */
.chat-entry .md-heading { display: flex; align-items: center; gap: 8px; }
.chat-new {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  background: #d94b4b;
  color: #fff;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.8px;
  /* A quiet pulse so a waiting message is noticed without nagging. */
  animation: chatNew 1.8s ease-in-out infinite;
}
@keyframes chatNew {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* ---------- chat: the conversation panel ---------- */
.chat-panel { display: flex; flex-direction: column; }
.chat-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--surface-line);
}
.chat-back {
  background: none;
  border: 1px solid var(--surface-line);
  border-radius: 8px;
  width: 36px; height: 36px;
  color: var(--surface-text);
  font-size: 1.1rem;
  cursor: pointer;
  flex-shrink: 0;
}
.chat-back:hover { background: var(--surface-input); border-color: var(--gold); }
.chat-avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.chat-who { display: flex; flex-direction: column; min-width: 0; }
.chat-who strong { color: var(--surface-text); font-size: 1.05rem; }
.chat-who span { color: var(--surface-text-muted); font-size: 0.8rem; }

/* who to write to */
.chat-picker input[type="search"] {
  width: 100%;
  margin: 14px 0 10px;
  padding: 11px 13px;
  font-family: inherit;
  font-size: 0.95rem;
  background: var(--surface-input);
  color: var(--surface-text);
  border: 1px solid var(--surface-line);
  border-radius: 9px;
}
.chat-contacts { display: flex; flex-direction: column; gap: 8px; max-height: 420px; overflow-y: auto; }
.chat-contact {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.chat-contact:hover { border-color: var(--gold); background: var(--surface-input); }
.chat-contact img, .chat-contact .chat-initials {
  width: 42px; height: 42px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
}
.chat-initials {
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-input); color: var(--gold-dark); font-weight: 800;
}
.chat-contact-who { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.chat-contact-who strong { color: var(--surface-text); font-size: 0.95rem; }
.chat-contact-who span { color: var(--surface-text-muted); font-size: 0.78rem; }
.chat-unread {
  flex-shrink: 0;
  min-width: 22px; padding: 2px 7px;
  border-radius: 999px;
  background: #d94b4b; color: #fff;
  font-size: 0.72rem; font-weight: 800; text-align: center;
}

/* the messages themselves */
.chat-thread {
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: 430px;
  overflow-y: auto;
  padding: 16px 4px;
}
.chat-msg { max-width: 78%; align-self: flex-start; }
.chat-msg.is-mine { align-self: flex-end; }
.chat-bubble {
  padding: 9px 13px;
  background: #fff;
  border: 1px solid #e6d9b6;
  border-radius: 12px 12px 12px 3px;
  color: #1c1c1c;
  font-size: 0.94rem;
  /* Newlines are kept, and a long unbroken word wraps instead of stretching
     the panel sideways. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.chat-msg.is-mine .chat-bubble {
  background: #fdf1d6;
  border-color: var(--gold);
  border-radius: 12px 12px 3px 12px;
}
.chat-meta { display: block; margin-top: 3px; color: var(--surface-text-muted); font-size: 0.72rem; }
/* The bubbles are deliberately light in both themes, so the timestamp beside
   them keeps its warm grey on dark. On the light page that same grey measured
   4.04:1 against white — just under AA for text this small — so it is deepened
   here only. */
:root[data-theme="light"] .chat-meta { color: #6B6047; }
.chat-msg.is-mine .chat-meta { text-align: right; }
.chat-sender { color: var(--gold-dark); font-weight: 800; }
.chat-empty { margin: auto; color: var(--surface-text-muted); font-size: 0.88rem; text-align: center; }

/* ---------- edited, deleted, and the ⋮ menu ----------

   The bubble becomes the menu button's positioning context. It is the message
   that owns the action, and anchoring to the row instead would put the button
   in a different place on a one-line message than on a five-line one. */
.chat-msg { position: relative; }

/* Quiet on purpose: it says the message changed, it is not an announcement. */
.msg-edited { font-style: italic; opacity: 0.85; }

.chat-bubble.is-deleted {
  background: rgba(0, 0, 0, 0.04);
  border-style: dashed;
  border-color: #cfc3a4;
  /* 3.69:1 against the tinted bubble, which is under AA. Deepened for the
     light page only; on dark the tint sits over a dark panel and this reads
     correctly as it is. */
  color: #6f6650;
}
:root[data-theme="light"] .chat-bubble.is-deleted { color: #5A5240; }
.chat-msg.is-mine .chat-bubble.is-deleted {
  background: rgba(0, 0, 0, 0.04);
  border-color: #cfc3a4;
}

/* The button hangs outside the bubble so it never sits on the words — but on
   the INNER side, toward the middle of the thread.

   It was on the outer side, and that put it off the screen. Own messages are
   `align-self: flex-end`, so they sit flush against the right edge of
   `.chat-thread`; a control 22px further right lands 18px outside the box. The
   thread has `overflow-y: auto`, and CSS will not leave the other axis visible
   when one is clipped — so `overflow-x` clipped it away completely. The button
   was rendered, hover fired, and nothing was ever seen or clickable.

   The inner side always has room: a bubble is at most 78% of the thread, so
   there is a fifth of the width free on the side it is not anchored to. */
.msg-menu-btn {
  position: absolute;
  top: 2px;
  /* Default is for a message on the left (someone else's): menu on its right.
     Own messages flip it below. */
  right: -22px;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  background: none;
  border-radius: 50%;
  color: var(--surface-text-muted);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  /* Out of the way on a mouse, and revealed on hover or keyboard focus.
     Touch devices get it permanently — see the media query below. */
  opacity: 0;
  transition: opacity var(--t-fast, 0.15s) ease, background var(--t-fast, 0.15s) ease;
}
.chat-msg.is-mine .msg-menu-btn { right: auto; left: -22px; }
/* Out of sight for the mouse and the finger alike (29 Sep 2026): the club
   asked for WhatsApp's way - right-click a message on a computer, press and
   hold it on a phone - with no three dots to aim at. It is still there for
   the keyboard: Tab onto it and it shows. See setupMessageActions(). */
.msg-menu-btn { pointer-events: none; }
.msg-menu-btn:focus-visible { opacity: 1; pointer-events: auto; }
.msg-menu-btn:hover { background: rgba(212, 175, 55, 0.22); }

/* The popup itself (.msg-menu) is in style.css: the Admin Panel shows it too. */

/* ---------- editing in place ---------- */
.msg-edit { margin-top: 2px; }
.msg-edit-input {
  width: 100%;
  padding: 8px 11px;
  background: var(--surface-input);
  border: 1px solid var(--gold);
  border-radius: 12px;
  color: var(--surface-text);
  font: inherit;
  font-size: 0.94rem;
  line-height: 1.45;
  resize: none;
}
.msg-edit-input:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.msg-edit-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  margin-top: 5px;
}
.msg-edit-actions .btn { padding: 5px 13px; font-size: 0.8rem; }
.msg-edit-actions .btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ---------- attachments ----------
   Shared by Personal Messages, Message to Support and the admin panel's reply
   box. Nothing here is a security boundary: the type, the size and the count
   are all decided again on the server from the file's own bytes. */

/* Visible to a screen reader, to nobody else. Used for the paperclip's label,
   which is an emoji on screen and needs words underneath it. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.chat-compose-wrap { padding-top: 12px; border-top: 1px solid var(--surface-line); }
.chat-compose-wrap .chat-compose { padding-top: 0; border-top: none; }

/* the paperclip */
.attach-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 42px; height: 42px;
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  background: var(--surface-input);
  font-size: 1.15rem;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.attach-btn:hover, .attach-btn:focus-within {
  border-color: var(--gold-dark);
  background: var(--surface-input);
}
.attach-btn input[type="file"] { display: none; }

/* what has been chosen but not sent yet */
.attach-tray {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.attach-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  padding: 6px 8px 6px 6px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
}
.attach-chip .attach-thumb {
  width: 38px; height: 38px;
  object-fit: cover;
  border-radius: 7px;
  flex-shrink: 0;
}
.attach-icon { font-size: 1.4rem; line-height: 1; flex-shrink: 0; }
.attach-meta { display: flex; flex-direction: column; min-width: 0; }
.attach-meta strong {
  color: var(--surface-text);
  font-size: .82rem;
  font-weight: 700;
  /* A long filename shortens rather than stretching the tray sideways. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 190px;
}
.attach-meta small { color: var(--surface-text-muted); font-size: .72rem; }
.attach-remove {
  flex-shrink: 0;
  width: 24px; height: 24px;
  border: none;
  border-radius: 50%;
  background: var(--surface-input);
  color: var(--surface-text-muted);
  font-size: .8rem;
  line-height: 1;
  cursor: pointer;
}
.attach-remove:hover { background: #d94b4b; color: #fff; }
.attach-hint { margin: 8px 0 0; color: #FF8F8F; font-size: .8rem; }

/* attachments on a message that has been sent */
.attach-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}
.attach-image {
  display: block;
  line-height: 0;
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  overflow: hidden;
  background: var(--surface-input);
}
.attach-image img {
  display: block;
  max-width: 190px;
  max-height: 190px;
  width: auto; height: auto;
  object-fit: cover;
}
.attach-file {
  display: flex;
  align-items: center;
  gap: 9px;
  max-width: 100%;
  padding: 8px 12px 8px 9px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  text-decoration: none;
}
.attach-file:hover { border-color: var(--gold); background: var(--surface-input); }
.chat-msg.is-mine .attach-list { justify-content: flex-end; }

/* writing a message */
.chat-compose {
  display: flex;
  gap: 10px;
  align-items: flex-end;
  padding-top: 12px;
  border-top: 1px solid var(--surface-line);
}
.chat-compose textarea {
  flex: 1;
  padding: 11px 13px;
  font-family: inherit;
  font-size: 0.96rem;
  line-height: 1.4;
  background: var(--surface-input);
  color: var(--surface-text);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  resize: none;
  max-height: 130px;
}
.chat-compose textarea:focus {
  border-color: var(--gold-dark);
  box-shadow: 0 0 0 3px rgba(216, 178, 94, 0.25);
  outline: none;
}
.chat-compose .btn { flex-shrink: 0; }

@media (max-width: 700px) {
  .chat-thread { height: 340px; }
  .chat-msg { max-width: 88%; }

  /* The row is paperclip + emoji + box + Send. On a phone those three fixed
     items came to about 210px of a 238px row, which left the textarea 28px
     wide — narrow enough that a message being typed scrolled out of sight one
     character at a time. Wrapping gives the box a line of its own and drops
     the buttons underneath, so the text is always readable while it is being
     written. `order: -1` keeps the box on top; the source order stays as it
     is so the tab order still reaches attach and emoji before Send. */
  .chat-compose { flex-wrap: wrap; align-items: stretch; }
  .chat-compose textarea {
    order: -1;
    /* flex-basis 100% is what forces the wrap; min-width overrides the
       `auto` floor a textarea takes from its `cols`, which is what let it be
       squeezed in the first place. */
    flex: 1 1 100%;
    width: 100%;
    min-width: 0;
    min-height: 46px;
  }
  /* Send takes whatever the two icon buttons leave, so the row stays full
     width instead of trailing off with a gap on the right. */
  .chat-compose .btn { flex: 1 1 auto; }
}
@media (prefers-reduced-motion: reduce) {
  .chat-new { animation: none; }
}

/* =====================================================================
   NOTEBOOK
   ---------------------------------------------------------------------
   A sheet of ruled A4 paper. The geometry below is fixed in pixels and
   the whole sheet is then scaled to fit whatever width it is given, for
   one reason: a page has to hold the same number of lines on a phone as
   on a laptop, or the same note would paginate differently depending on
   the device it was opened on.

   --nb-rule is the distance between two ruled lines and is also the
   textarea's line-height, which is what puts the writing on the lines
   rather than floating between them.
   ===================================================================== */
.nb-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
  margin-top: 8px;
}
.nb-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 14px;
  padding: 26px 18px;
  cursor: pointer;
  font: inherit;
  text-align: center;
  transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.nb-choice:hover, .nb-choice:focus-visible {
  border-color: var(--gold);
  box-shadow: 0 6px 18px rgba(0,0,0,.12);
  transform: translateY(-2px);
  outline: none;
}
.nb-choice-icon { font-size: 2.2rem; }
.nb-choice strong { color: var(--surface-text); font-size: 1.05rem; }
.nb-choice small { color: var(--surface-text-muted); }

.nb-subhead {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}
.nb-subhead .dash-back { margin-bottom: 0; }
.nb-subhead strong { color: var(--gold-dark); }

/* ---------- the sheet ---------- */
.nb-stage {
  /* The sheet is scaled from its top-left, so the stage has to be told how
     much room the scaled result actually needs; JS sets its width and height.
     On a screen too narrow to show a whole A4 page at a readable size the
     sheet stays legible and this box scrolls sideways, which keeps the
     sideways scrolling inside the paper instead of on the page. */
  position: relative;
  overflow: hidden;
  margin: 0 auto;
  -webkit-overflow-scrolling: touch;
}
.nb-stage[data-scrolls="yes"] { overflow-x: auto; overflow-y: hidden; }
.nb-sheet {
  --nb-rule: 32px;
  --nb-top: 104px;              /* the blank band the title sits in */
  --nb-left: 96px;              /* text starts right of the red margin */
  --nb-right: 48px;
  --nb-lines-count: 26;
  --nb-foot: 82px;              /* blank paper under the last rule */
  --nb-page-w: 720px;
  /* Variables, not literals, because the two fields are sized twice: here and
     again in the specificity-override block at the foot of this file. A phone
     changes one number and both follow. */
  --nb-body-size: 19px;
  --nb-title-size: 25px;
  --nb-title-top: 42px;

  /* Bengali first, because that is what most of this club writes in, then the
     scripts Windows and Android ship by default, then whatever the system has.
     No web font is downloaded — the page stays as light as it was, and a
     member writing in a script we did not think of still gets their own
     system's font rather than a row of boxes. */
  --nb-font: "Noto Sans Bengali", "Nirmala UI", Vrinda, "Shonar Bangla",
             "Noto Sans Devanagari", "Noto Sans", "Segoe UI", system-ui,
             -apple-system, sans-serif;

  position: absolute;
  top: 0; left: 0;
  transform-origin: top left;
  width: var(--nb-page-w);
  /* Was a flat 1018px — 720 x 1.414, A4. Same number, derived instead of
     typed, so the phone geometry below gets a page exactly as tall as its own
     rules need rather than a second magic constant to keep in step.
     104 + (32 x 26) + 82 = 1018. */
  height: calc(var(--nb-top) + (var(--nb-rule) * var(--nb-lines-count)) + var(--nb-foot));
  background: #fff;
  border: 1px solid #d8d8d8;
  border-radius: 3px;
  box-shadow: 0 10px 30px rgba(0,0,0,.18);
  overflow: hidden;
}

/* Bhagini Nivedita, faint, behind the writing. */
.nb-sheet::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--nb-watermark, none) center 55% / 62% auto no-repeat;
  opacity: .07;
  pointer-events: none;
}

.nb-hole {
  position: absolute;
  left: 30px;
  width: 22px; height: 22px;
  margin-top: -11px;
  border: 1px solid #cfcfcf;
  border-radius: 50%;
  background: var(--surface-input);
}
.nb-margin {
  position: absolute;
  top: 0; bottom: 0;
  left: calc(var(--nb-left) - 14px);
  /* 2px, not 1: the whole sheet is scaled down to fit the screen, and a 1px
     rule came out at 0.8 of a device pixel and vanished. */
  width: 2px;
  background: #eb9aa3;
}

.nb-title {
  position: absolute;
  top: var(--nb-title-top);
  left: var(--nb-left);
  right: var(--nb-right);
  width: auto;
  border: none;
  border-bottom: 1px solid #d9d9d9;
  background: transparent;
  font-family: var(--nb-font);
  font-size: var(--nb-title-size);
  font-weight: 700;
  color: #1c1c1c;
  padding: 0 0 6px;
  outline: none;
}
.nb-title::placeholder { color: #8f8f8f; font-weight: 400; }

.nb-lines {
  position: absolute;
  top: var(--nb-top);
  left: var(--nb-left);
  right: var(--nb-right);
  height: calc(var(--nb-rule) * var(--nb-lines-count));
  /* One blue rule at the foot of every line-height, so text sits on it. */
  background: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent calc(var(--nb-rule) - 1px),
    #b7c4e8 calc(var(--nb-rule) - 1px),
    #b7c4e8 var(--nb-rule)
  );
}

.nb-body {
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  resize: none;
  overflow: hidden;              /* a page never scrolls - it overflows onto the next */
  font-family: var(--nb-font);
  font-size: var(--nb-body-size);
  line-height: var(--nb-rule);   /* must equal the rule pitch */
  color: #0a0a0a;                /* deep black, as asked */
  caret-color: #0a0a0a;
  word-break: break-word;
}
.nb-body::placeholder { color: #9a9a9a; }

.nb-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 16px;
}
.nb-pager span { color: var(--surface-text-muted); font-size: .9rem; min-width: 110px; text-align: center; }
.nb-pager .btn { padding: 7px 14px; font-size: .82rem; }

.nb-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.nb-actions .btn { flex: 1 1 140px; }

/* ---------- the list ---------- */
.nb-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  padding: 14px 16px;
  margin-bottom: 10px;
}
/* min-width: 0 is what actually lets the title wrap. A flex child defaults to
   min-width:auto, which is its content's intrinsic width — so a long title
   made the column wider than the row instead of moving onto a second line, and
   the end of it was cut off. */
.nb-row-main { min-width: 0; flex: 1 1 240px; }
.nb-row-title {
  display: block;
  color: var(--surface-text);
  font-weight: 800;
  line-height: 1.35;
  /* The full title, on as many lines as it needs. Deliberately not an
     ellipsis: a note's own title is the one thing a member uses to find it
     again, so it is never shortened. */
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.nb-row-meta { display: block; color: var(--surface-text-muted); font-size: .8rem; overflow-wrap: anywhere; }
/* The buttons keep their own column and never sit over the title: the row is
   flex with wrap, and this stops them shrinking into it on a narrow screen. */
.nb-row-btns { flex: 0 0 auto; align-self: flex-start; }
.nb-row-preview {
  color: #57544c;
  font-size: .88rem;
  margin: 6px 0 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.nb-row-btns { display: flex; gap: 8px; flex-wrap: wrap; }
.nb-row-btns .btn { padding: 6px 12px; font-size: .78rem; }

/* ---------- the sheet on a phone ----------
   A 720px page in a 248px column is a scale of 0.34, which put the writing at
   about 6px and pushed the rest of the page off to the right behind a
   sideways scrollbar. The trade the original geometry was making — one fixed
   page size everywhere, so a note paginates identically on every device —
   stops being worth it at that size, because the page it keeps identical is
   one nobody can read or type into.

   So a phone gets its own page: narrower, fewer rules on it, and drawn at the
   width it will actually be read at, which is what lets the type stay at 15px
   instead of being scaled down. fitSheet() in member-portal.js sets
   --nb-page-w from the room measured on screen and leaves the scale at 1.

   The cost is the one the original comment named: a note written here breaks
   into pages at different points than the same note written on a laptop. The
   text is identical either way — only where the paper ends moves. */
@media (max-width: 700px) {
  .nb-sheet {
    --nb-rule: 26px;
    --nb-top: 62px;
    --nb-left: 40px;
    --nb-right: 14px;
    --nb-lines-count: 18;
    --nb-foot: 28px;
    --nb-body-size: 15px;
    --nb-title-size: 17px;
    --nb-title-top: 22px;
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(0,0,0,.14);
  }
  /* The punched holes and the red margin scale with the page, not with the
     laptop's 96px left band. */
  .nb-hole { left: 11px; width: 14px; height: 14px; margin-top: -7px; }
  .nb-margin { left: calc(var(--nb-left) - 9px); }
}

@media (max-width: 560px) {
  .nb-pager .btn { padding: 7px 10px; }
  .nb-actions .btn { flex-basis: 100%; }
  /* On a phone the buttons drop below the note rather than squeezing the title
     into a column two words wide. */
  .nb-row-btns { flex: 1 1 100%; }
  .nb-row-btns .btn { flex: 1 1 auto; }

  /* Prev / "Page 1 of 2" / Next did not fit on one line: the page counter
     reserved 110px of about 250px and the two buttons were squeezed until
     their labels were cut to "← PR" and "NE →". The counter takes a line of
     its own and the buttons share the one under it. */
  .nb-pager { flex-wrap: wrap; gap: 8px; }
  .nb-pager span { order: -1; flex: 1 1 100%; min-width: 0; }
  .nb-pager .btn { flex: 1 1 0; min-width: 0; white-space: nowrap; }
}

/* ---------------------------------------------------------------------
   The sheet's two fields must beat style.css's site-wide form styling.

   That styling is built for a dark navy page: inputs get a navy fill, a
   border, rounded corners and padding, and `input:focus, textarea:focus`
   repaints the fill. `.nb-body` alone loses to `textarea:focus` — one
   class each, but the focus rule also carries a pseudo-class — so the
   moment the member clicked into the paper it turned navy and the ruled
   lines disappeared behind it.

   Scoping under .nb-sheet wins both states outright. The padding reset
   matters as much as the colour: any padding here would push the text
   off the ruled lines it is supposed to sit on.
   --------------------------------------------------------------------- */
.nb-sheet .nb-title,
.nb-sheet .nb-title:focus,
.nb-sheet .nb-body,
.nb-sheet .nb-body:focus,
:root .member-portal.dash-mode .member-portal-form .nb-sheet input.nb-title,
:root .member-portal.dash-mode .member-portal-form .nb-sheet input.nb-title:focus,
:root .member-portal.dash-mode .member-portal-form .nb-sheet textarea.nb-body,
:root .member-portal.dash-mode .member-portal-form .nb-sheet textarea.nb-body:focus {
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  outline: none;
  color: #0a0a0a;
}
.nb-sheet .nb-body,
.nb-sheet .nb-body:focus {
  border: 0;
  padding: 0;
  margin: 0;
  font-family: var(--nb-font);
  font-size: var(--nb-body-size);
  line-height: var(--nb-rule);
}
.nb-sheet .nb-title,
.nb-sheet .nb-title:focus,
:root .member-portal.dash-mode .member-portal-form .nb-sheet input.nb-title,
:root .member-portal.dash-mode .member-portal-form .nb-sheet input.nb-title:focus {
  border: 0;
  border-bottom: 1px solid #d9d9d9;
  padding: 0 0 6px;
  margin: 0;
  font-family: var(--nb-font);
  font-size: var(--nb-title-size);
  font-weight: 700;
}

/* ---------- message an admin ----------
   The list of people in the club office, and the conversation with one of
   them. The thread itself reuses .chat-* from the member-to-member screens —
   same shape of view, so it would be a waste to style it twice. */
.am-list { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.am-person {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  padding: 12px 14px;
  cursor: pointer;
  font: inherit;
  transition: border-color .12s ease, box-shadow .12s ease;
}
.am-person:hover, .am-person:focus-visible {
  border-color: var(--gold);
  box-shadow: 0 4px 14px rgba(0,0,0,.10);
  outline: none;
}
.am-avatar {
  width: 42px; height: 42px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  color: var(--gold-dark);
  font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.am-person-who { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.am-person-who strong { color: var(--surface-text); }
.am-person-who span { color: var(--surface-text-muted); font-size: .8rem; }
.am-person .chat-unread { margin-left: auto; }

.am-thread-wrap .chat-thread { min-height: 220px; max-height: 420px; }
.am-thread-wrap .chat-head { margin-bottom: 4px; }
.am-thread-wrap .am-avatar { font-size: 1.3rem; }

/* ---- Support as questions (bnsca/support.py) ----
   Each question is marked off with its topic; the desk's automatic lines sit
   in the middle like a note; the topics are buttons under the desk's
   question, and the box below is locked until one is chosen. */
.am-issue-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 6px 0 2px;
  color: var(--surface-text-muted);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.am-issue-divider::before,
.am-issue-divider::after {
  content: "";
  flex: 1;
  border-top: 1px dashed var(--surface-line);
}
.am-issue-divider span {
  padding: 3px 12px;
  border-radius: 999px;
  border: 1px solid var(--surface-line);
  background: var(--surface-tint, rgba(255, 255, 255, 0.05));
  white-space: nowrap;
}
.chat-msg.is-auto { align-self: center; max-width: 90%; text-align: center; }
.chat-msg.is-auto .chat-bubble {
  background: #fff8e6;
  border: 1px dashed var(--gold);
  border-radius: 12px;
  font-size: 0.9rem;
  text-align: center;
}
.chat-msg.is-auto .chat-meta { text-align: center; }
.am-topics {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 8px;
  padding: 2px 4px 6px;
}
.am-topic {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 12px;
  border: 1px solid var(--gold);
  background: rgba(212, 175, 55, 0.1);
  color: var(--surface-text);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
}
.am-topic:hover:not(:disabled),
.am-topic:focus-visible { background: rgba(212, 175, 55, 0.26); transform: translateY(-1px); }
.am-topic:disabled { opacity: 0.55; cursor: default; }
.am-topic.is-picked { opacity: 1; background: var(--gold); color: #0A192F; }
.am-topic-icon { font-size: 1.2rem; flex: 0 0 auto; }
.chat-compose-wrap.is-locked textarea,
.chat-compose-wrap.is-locked .btn,
.chat-compose-wrap.is-locked .attach-btn { opacity: 0.5; cursor: not-allowed; }
.chat-compose-wrap.is-locked .attach-btn { pointer-events: none; }

/* Message to Support's three cards, and Resume Chat with nothing going on. */
.am-cards { margin-top: 14px; }
.am-none {
  margin: auto;
  max-width: 420px;
  text-align: center;
  color: var(--surface-text-muted);
  font-size: 0.9rem;
}
.am-none-actions { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 12px; }
.am-none-actions .btn { padding: 8px 16px; font-size: 0.84rem; }
#am-history { border-bottom: none; max-height: none; }

/* A chat the office has closed, and the way to the old ones. */
.am-closed {
  align-self: center;
  margin: 4px 0;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--surface-line);
  background: var(--surface-tint, rgba(255, 255, 255, 0.05));
  color: var(--surface-text-muted);
  font-size: 0.8rem;
  text-align: center;
}
.am-thread-wrap .chat-who { flex: 1 1 auto; }
.am-history-btn { margin-left: auto; padding: 6px 14px; font-size: 0.82rem; flex-shrink: 0; white-space: nowrap; }
.am-thread-wrap .chat-who strong,
.am-thread-wrap .chat-who span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* On a phone the button is the clock alone, so the name keeps its line. */
@media (max-width: 480px) {
  /* The page keeps a wide margin for the floating side tabs; the two chat
     screens give back their own padding so a message line is not a column
     of single words. */
  #view-adminmail.dash-view, #view-messages.dash-view { padding-left: 6px; padding-right: 6px; }
  .am-thread-wrap .am-avatar { display: none; }
  .am-history-btn { width: 40px; height: 40px; padding: 0; border-radius: 50%; font-size: 1.05rem; }
  .am-history-label { display: none; }
  .am-thread-wrap .chat-who span { font-size: 0.74rem; }
}
.am-history-btn.is-on { background: var(--gold); color: #0A192F; border-color: var(--gold); }
.am-history {
  max-height: 420px;
  overflow-y: auto;
  padding: 10px 2px;
  border-bottom: 1px solid var(--surface-line);
}
.am-history-month + .am-history-month { margin-top: 14px; }
.am-history-name {
  margin: 0 0 6px;
  color: var(--gold-dark);
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.am-history-name span { color: var(--surface-text-muted); font-weight: 600; text-transform: none; letter-spacing: 0; }
.am-history-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  margin-bottom: 6px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--surface-line);
  background: var(--surface-tint, rgba(255, 255, 255, 0.04));
  color: var(--surface-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.am-history-row:hover, .am-history-row:focus-visible, .am-history-row.is-on { border-color: var(--gold); }
.am-history-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.am-history-top strong { font-size: 0.9rem; }
.am-history-status {
  flex-shrink: 0;
  padding: 1px 9px;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  border: 1px solid var(--surface-line);
  color: var(--surface-text-muted);
}
.am-history-status.is-open { color: #1d7a43; border-color: rgba(46, 160, 90, 0.5); background: rgba(46, 160, 90, 0.12); }
.am-history-status.is-topic { color: #8a5a00; border-color: rgba(230, 160, 40, 0.5); background: rgba(230, 160, 40, 0.12); }
:root:not([data-theme="light"]) .am-history-status.is-open { color: #5fd08a; }
:root:not([data-theme="light"]) .am-history-status.is-topic { color: #f0b44c; }
.am-history-preview {
  font-size: 0.84rem;
  color: var(--surface-text);
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
.am-history-row small { color: var(--surface-text-muted); font-size: 0.74rem; }
.am-viewing {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 8px;
  padding: 8px 12px;
  border-radius: 10px;
  background: rgba(212, 175, 55, 0.12);
  border: 1px solid rgba(212, 175, 55, 0.45);
  color: var(--surface-text);
  font-size: 0.84rem;
}
.am-viewing .btn { padding: 5px 12px; font-size: 0.8rem; }
@media (max-width: 700px) {
  .am-topics { grid-template-columns: 1fr 1fr; }
  .am-topic { padding: 10px; font-size: 0.84rem; gap: 6px; }
}

/* The read-only history from before the shared Support desk existed. Set apart
   from the live conversation so it cannot be mistaken for somewhere to write. */
.am-earlier {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--surface-line);
}
.am-earlier .dash-form-title { margin-bottom: 4px; }
.am-old-thread { margin-top: 12px; }
.am-old-thread .chat-thread { max-height: 340px; }

/* ---------- proof of payment ----------
   Step two of paying a monthly fee. The Submit button beside it stays disabled
   until a file is chosen, so this block is the gate, not a decoration. */
.fee-proof {
  margin-top: 18px;
  padding: 16px;
  background: var(--surface-input);
  border: 1px dashed var(--gold-dark);
  border-radius: 10px;
}
.fee-proof .dash-form-title { margin-top: 0; }
.fee-proof input[type="file"] {
  width: 100%;
  padding: 10px;
  background: var(--surface-input);
  border: 1px solid var(--surface-line);
  border-radius: 8px;
  font-size: .9rem;
  color: var(--surface-text);
}
.fee-shot-preview {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
}
.fee-shot-preview img {
  width: 76px;
  height: 76px;
  object-fit: cover;
  border: 1px solid var(--gold-dark);
  border-radius: 8px;
}
.fee-shot-preview span { color: var(--surface-text-muted); font-size: .84rem; word-break: break-all; }

/* The club's own QR picture cannot carry the amount the way a generated one
   does, so the figure is said out loud beneath it. Warning-coloured rather than
   quiet: somebody about to send money needs to read this before they scan, not
   after. Both themes, because the member portal has both. */
.qr-type-amount {
  margin: 8px auto 0;
  max-width: 320px;
  padding: 8px 12px;
  background: rgba(217, 119, 6, 0.12);
  border: 1px solid rgba(217, 119, 6, 0.45);
  border-left-width: 3px;
  border-radius: 8px;
  color: #8a5a08;
  font-size: 0.82rem;
  line-height: 1.5;
  text-align: left;
}
.qr-type-amount[hidden] { display: none; }
:root[data-theme="dark"] .qr-type-amount,
:root:not([data-theme="light"]) .qr-type-amount {
  background: rgba(217, 119, 6, 0.16);
  border-color: rgba(240, 170, 60, 0.55);
  color: #F0C070;
}


/* ==========================================================================
   WHAT IS STILL TO PAY, AND THE RECORD OF WHAT HAS BEEN

   Two things the annual fee card has to say plainly once a member has paid
   part of their figure: the three numbers (asked, received, left), and a
   history that reads as a table rather than as a paragraph. */
.annual-standing {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin: 14px 0 4px;
  padding: 12px 14px;
  border: 1px solid var(--surface-line);
  border-radius: 12px;
  background: var(--surface-input);
}
.annual-standing > div { text-align: center; }
.annual-standing dt {
  margin: 0 0 3px;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--surface-text-muted);
}
.annual-standing dd { margin: 0; font-size: 1.05rem; font-weight: 800; color: var(--surface-text); }
.annual-standing .is-due dd { color: var(--gold-dark); }
.annual-standing[hidden] { display: none; }
.pay-how { color: var(--surface-text-muted); }

/* A record table: ruled like a ledger on a screen with room, and one
   labelled card per row on a phone - a payment history read on a phone is
   otherwise five values with nothing saying which is which. */
.md-table.is-record { border: 1px solid var(--surface-line); }
.md-table.is-record th, .md-table.is-record td { border: 1px solid var(--surface-line); vertical-align: top; }
.md-table.is-record th { width: auto; }
@media (max-width: 620px) {
  .annual-standing { grid-template-columns: 1fr; gap: 6px; }
  .annual-standing > div { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; text-align: left; }
  .annual-standing dt { margin: 0; }

  .annual-history .md-table.is-record { min-width: 0; border: 0; }
  .md-table.is-record thead { display: none; }
  .md-table.is-record, .md-table.is-record tbody,
  .md-table.is-record tr, .md-table.is-record td { display: block; width: 100%; }
  .md-table.is-record tr {
    margin-bottom: 12px;
    border: 1px solid var(--surface-line);
    border-radius: 12px;
    overflow: hidden;
    background: var(--surface-input);
  }
  .md-table.is-record td {
    display: flex;
    justify-content: space-between;
    gap: 14px;
    border: 0;
    border-bottom: 1px solid var(--surface-line);
  }
  .md-table.is-record tr td:last-child { border-bottom: 0; }
  .md-table.is-record td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--surface-text-muted);
  }
}


/* The months one monthly-fee payment covered, as chips in its row - green
   for approved, amber for waiting, red for one the club sent back. A
   payment can be several answers at once, and a single status word on the
   row could not say which month was which. */
.fee-hist-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.fee-hist-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 700;
  border: 1px solid var(--surface-line);
  color: var(--surface-text);
  background: var(--surface-input);
  white-space: nowrap;
}
.fee-hist-chip.is-ok { border-color: rgba(31, 157, 85, 0.5); color: #1f9d55; }
.fee-hist-chip.is-wait { border-color: rgba(216, 178, 94, 0.6); color: var(--gold-dark); }
.fee-hist-chip.is-bad { border-color: rgba(192, 57, 43, 0.5); color: #c0392b; }
