/* ==========================================================================
   Public site theme.

   Every colour below is now an *alias* onto the shared tokens in tokens.css,
   which carries the club's navy/gold/white identity. The names are unchanged,
   so the two thousand rules in this file that say `var(--gold)` or
   `var(--panel)` were not touched — they simply resolve to the reference
   palette now instead of the older, greener navy and softer gold.

   Keeping the aliases rather than find-and-replacing the whole file is
   deliberate: a colour rename across 2,200 lines is exactly the kind of edit
   that silently misses three rules and leaves two shades of gold on the page.
   ========================================================================== */
:root {
  /* The header sits over the page rather than on top of it — see
     `header.site-header` below — so this gradient is only what it falls back
     to once the page has been scrolled and it needs to be solid. */
  --header-bar: linear-gradient(90deg, var(--c-navy-deep) 0%, #0d2b4d 100%);

  /* Each of these points at a `--surface-*` token rather than at a colour, and
     that indirection is the entire theme switch: tokens.css redefines the four
     surfaces under `[data-theme="light"]`, and every rule in this file follows
     without being touched. */
  --navy: var(--surface-page);    /* page background */
  --panel: var(--surface-panel);  /* card / panel background */
  --panel-2: var(--surface-card); /* slightly raised panel */
  --gold: var(--c-gold);          /* gold accent (headings, borders, buttons) */
  --gold-dark: var(--c-gold-deep);
  --gold-light: var(--c-gold-light);
  --header-bg: var(--surface-panel);
  --ink: var(--surface-text);     /* main text */
  --muted: var(--surface-text-muted);
  --red: var(--c-error);          /* date / highlight */
  /* Red used as TEXT — an error line, a required marker. Its own token
     because these surfaces are near-navy here and near-white in the light
     panel, and no one red reads on both; see the note beside the light value
     in admin.css. Measured 6.02:1 on the dark tint. */
  --red-text: #e88b93;
  --success: var(--c-success);
  --warning: var(--c-warning);
  --border: var(--c-gold-deep);   /* gold border */
  --radius: 12px;
  --shadow: 0 4px 18px var(--surface-shadow);

  /* old names kept as aliases so nothing breaks */
  --maroon: var(--c-gold);
  --maroon-dark: var(--c-gold-deep);
  --cream: var(--surface-page);
  --card-bg: var(--surface-panel);
}

* { box-sizing: border-box; }

/* The browser hides [hidden] elements with a plain `display: none`, which any
   author rule outranks — `.btn { display: inline-block }` was enough to leave
   the step forms' Back/Submit buttons on screen while the script believed it
   had hidden them. Restore the attribute's meaning for every element. */
[hidden] { display: none !important; }

body {
  margin: 0;
  /* Montserrat, from tokens.css. Body, labels, forms and buttons all take it;
     only headings and the brand mark use Cinzel. */
  font-family: var(--font-body);
  background: var(--navy);
  color: var(--ink);
  line-height: var(--lh-body);
  /* Was a flat 19px, which is roughly 24px of visual height at 320px once
     Montserrat's larger x-height is accounted for. The clamp keeps a phone
     readable without a horizontal squeeze and still opens up on a desktop. */
  font-size: var(--fs-body);
}

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

/* Cinzel is a titling face with no lowercase design of its own and no italic,
   so it is confined to headings, the brand mark and the section rules. Letting
   it near a paragraph would be the single fastest way to make this site hard
   to read. */
h1, h2, h3, h4, .brand, .section-title, .panel-title, .page-hero h1,
.detail-title, .md-heading {
  font-family: var(--font-heading);
  font-weight: var(--weight-heading);
  letter-spacing: 0.02em;
}

/* The page title carries the page; 900 is the reference's own choice for it. */
.page-hero h1 { font-weight: var(--weight-heading-strong); }

/* The home hero is the one exception to the title scale.
   Every other hero carries a short page name — "GALLERY", "FAQ" — over a photo
   with nothing written on it, and --fs-hero is sized for that. The home hero
   carries the club's full 46-character name over the club's own printed
   poster, which already has that name on it in two languages. At the shared
   scale the two sat on top of each other and neither could be read.
   Two-thirds of the scale keeps the title clearly the page's heading while
   leaving the artwork legible underneath it. */
.page-hero.hero-full h1 {
  /* Raised from clamp(1.1rem, 0.72rem + 1.9vw, 2rem). The club's full name is
     the page's title and was set smaller than several headings further down it.
     Still well under --fs-hero, because this one sits over the poster's own
     artwork and has to share the space with it. */
  font-size: clamp(1.35rem, 0.85rem + 2.5vw, 2.6rem);
  letter-spacing: clamp(0.5px, 0.15vw, 2px);
  line-height: 1.18;
}

/* The club's name and the two lines under it, on the home banner.

   They were sized down to two-thirds so the printed poster behind them stayed
   legible — a reasonable call, but it went too far: the club's own name and
   address ended up the smallest type on its own front page, smaller than the
   body copy further down. They are the page's identity and should read like it.

   Bigger, and given the weight and spacing to carry the size: the name in the
   heading face at nearly twice the scale, the registration line in small caps
   so a short line of digits still reads as a line, and both lifted off the
   photograph by the same dark halo the page titles use. The poster underneath
   is still visible — the scrim does that work, not the type being small. */
.page-hero.hero-full h1 {
  font-size: clamp(1.5rem, 0.95rem + 2.9vw, 3.1rem);
  line-height: 1.12;
}
.page-hero.hero-full .sub {
  font-size: clamp(0.95rem, 0.78rem + 0.8vw, 1.35rem);
  font-weight: 800;
  letter-spacing: 0.08em;
  margin-top: 16px;
  text-transform: uppercase;
}
.page-hero.hero-full .sub-small {
  font-size: clamp(0.86rem, 0.72rem + 0.6vw, 1.1rem);
  font-weight: 600;
  color: #F2F5FA;
}

/* ---------- Header / Nav ----------

   The bar is transparent at the top of the page and takes the colour of
   whatever is behind it — the banner photograph on most pages, the page
   itself elsewhere. It becomes solid once the page has been scrolled, because
   a transparent bar over a paragraph of body text is unreadable.

   Three details make that work rather than merely look like it:

     * `backdrop-filter: blur()` frosts what is behind the bar instead of
       hiding it, so the navigation stays legible over a busy photograph
       without a panel being drawn over the picture;
     * a soft gradient from a dark tint at the top to nothing at the bottom
       carries the text where the blur alone is not enough — over a pale sky,
       say — and it costs no layout;
     * `.is-scrolled` is added by common.js past 24px. Only `background`,
       `box-shadow`, `border-color` and `backdrop-filter` change between the
       two states, so nothing moves as it crosses over and the page cannot
       shift under the reader.

   The bar keeps its height in both states — it is `position: sticky`, so the
   page has always allowed for it. */
header.site-header {
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.06));
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 100;
  box-shadow: none;
  border-bottom: 1px solid var(--c-gold-veil);
  transition: background var(--t) var(--ease), box-shadow var(--t) var(--ease),
              border-color var(--t) var(--ease), backdrop-filter var(--t) var(--ease);
}

/* Scrolled: the solid bar the site has always had. */
header.site-header.is-scrolled {
  background: var(--header-bar);
  box-shadow: var(--shadow);
  border-bottom: 2px solid var(--gold);
}

/* On a pale page a black scrim over white would read as a grey smear, so the
   light theme tints with the page's own navy at a much lower strength and
   leans on the blur instead. */
:root[data-theme="light"] header.site-header {
  background: linear-gradient(to bottom, rgba(10, 25, 47, 0.10), rgba(10, 25, 47, 0.02));
}

/* The mobile menu is drawn inside the bar. Open over a transparent header it
   would be unreadable, so opening it makes the bar solid whatever the scroll
   position is. */
header.site-header:has(nav.main-nav.open) {
  background: var(--header-bar);
  box-shadow: var(--shadow);
  border-bottom: 2px solid var(--gold);
}

/* `:has()` is not everywhere yet. common.js sets this class as well, so the
   same thing happens on a browser without it. */
header.site-header.is-menu-open {
  background: var(--header-bar);
  box-shadow: var(--shadow);
  border-bottom: 2px solid var(--gold);
}

/* Anything the header sits over must be allowed to run under it, or the
   transparency reveals the page's own background rather than the banner.

   The hero is what sits directly under the bar on every page that has one, so
   it is pulled up by exactly the header's height and given that height back as
   padding — the artwork now starts at the top of the viewport and the title
   still sits clear of the navigation.

   `--header-h` is measured and written by common.js, because the bar's height
   depends on the font, the logo and how far the navigation has wrapped, and
   none of those is a number a stylesheet can know. The `0px` fallback is what
   makes this safe before that runs and with JavaScript off: the hero simply
   sits below the bar, exactly as it always did. */
.page-hero {
  margin-top: calc(-1 * var(--header-h, 0px));
  padding-top: calc(30px + var(--header-h, 0px));
}

.nav-inner {
  /* Wider than the 1200px content column on purpose. The bar was centred on
     the same 1200px, which on a 1920px screen left 360px of empty black to the
     left of the logo before the mark itself began — a gap wide enough to read
     as a mistake. The bar now runs to 1360px with a normal gutter, so the logo
     sits near the edge of the screen without touching it. */
  max-width: var(--container-wide);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px var(--gutter);
  flex-wrap: wrap;
  gap: 8px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: clamp(1.05rem, 0.95rem + 0.4vw, 1.3rem);
  font-weight: var(--weight-heading-strong);
  letter-spacing: 0.12em;
  color: #fff;
  /* Pinned to the start of the bar and never allowed to be stretched by the
     flex row, so it stays hard left however wide the navigation grows. */
  margin-right: auto;
  flex: 0 0 auto;
}
.brand img {
  /* Was 38x38 with object-fit: cover, which crops a non-square logo — the
     club's mark is a circular medallion on a wider canvas and lost its edges.
     `contain` shows the whole of it inside the same box, so nothing is cut. */
  width: 42px; height: 42px;
  border-radius: 6px;
  object-fit: contain;
  flex: 0 0 auto;
}

nav.main-nav {
  display: flex;
  gap: 2px;
  flex-wrap: wrap;
  align-items: center;
}

/* ---------- Hamburger (phones + tablets only) ---------- */
.nav-toggle {
  display: none;              /* desktop: never shown */
  width: 44px;
  height: 44px;
  padding: 10px 9px;
  background: none;
  border: none;
  cursor: pointer;
  flex-direction: column;
  justify-content: space-between;
}
.nav-toggle span {
  display: block;
  height: 3px;
  width: 100%;
  border-radius: 2px;
  background: var(--gold);
  transition: transform 0.3s ease, opacity 0.2s ease;
}
/* the three bars fold into an X while the menu is open */
.nav-toggle.open span:nth-child(1) { transform: translateY(10px) rotate(45deg); }
.nav-toggle.open span:nth-child(2) { opacity: 0; }
.nav-toggle.open span:nth-child(3) { transform: translateY(-10px) rotate(-45deg); }

@media (max-width: 900px) {
  .nav-inner { flex-wrap: wrap; }
  /* auto margin pushes the button to the far right of the bar */
  .nav-toggle { display: flex; margin-left: auto; }

  nav.main-nav {
    flex-basis: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    transition: max-height 0.35s ease, opacity 0.25s ease;
  }
  nav.main-nav.open {
    max-height: 560px;
    opacity: 1;
    margin-top: 10px;
  }
  nav.main-nav a,
  nav.main-nav .dropdown > span {
    display: block;
    padding: 13px 6px;
    border-top: 1px solid rgba(216, 178, 94, 0.22);
  }
  /* The underline this used to switch off no longer exists — it was removed
     site-wide when the limelight took over marking the active item. Kept as a
     belt-and-braces guard: the mobile stack must never grow a bar under each
     item, whatever a future rule adds. */
  nav.main-nav a::after,
  nav.main-nav .dropdown > span::after { display: none; }

  /* Submenu sits inline in the stack instead of floating over the page.

     Every selector here is scoped to `nav.main-nav` on purpose. The base
     `.dropdown-menu` rule further down this file (position: absolute, its own
     background, padding and box-shadow) sits AFTER this media query at the
     same specificity, so it was winning and the mobile submenu was being
     positioned absolutely after all: it floated over Notice Board, Members and
     Contact Us, and its last item was pushed past the nav's `overflow: hidden`
     clip box — so tapping Activities appeared to show nothing and swallow the
     items underneath. Scoping raises these one class, so they win where they
     are without having to move the base rules. */
  nav.main-nav .dropdown-menu {
    position: static;
    min-width: 0;
    box-shadow: none;
    background: rgba(0, 0, 0, 0.28);
    padding: 0;
  }
  nav.main-nav .dropdown:hover .dropdown-menu { display: none; }
  nav.main-nav .dropdown.open .dropdown-menu { display: block; }
  nav.main-nav .dropdown-menu a { padding-left: 26px; }
}

nav.main-nav a, nav.main-nav .dropdown > span {
  padding: 8px 13px;
  font-size: 0.95rem;
  cursor: pointer;
  transition: color 0.2s;
  color: #e8e8e8;
}
nav.main-nav a:hover, nav.main-nav .dropdown:hover > span { color: var(--gold); }
nav.main-nav a.active { color: #fff; font-weight: 700; }

.dropdown { position: relative; }
.dropdown-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  background: #111;
  color: var(--ink);
  min-width: 190px;
  box-shadow: var(--shadow);
  overflow: hidden;
  padding: 6px 0;
}
.dropdown:hover .dropdown-menu,
.dropdown.open .dropdown-menu { display: block; }
.dropdown-menu a { display: block; padding: 10px 16px; color: #eee; }
.dropdown-menu a:hover { background: #1d1d1d; color: var(--gold); }

/* ---------- Page hero (photo banner + gold serif title) ---------- */
.page-hero {
  background:
    linear-gradient(rgba(5, 18, 36, 0.62), rgba(5, 18, 36, 0.62)),
    url('/images/banner.jpg') center/cover,
    linear-gradient(160deg, #0d3154, #071f38);
  text-align: center;
  padding: 95px max(20px, var(--fab-room));
  margin: 0 24px;
  border-bottom: 2px solid var(--gold);
  position: relative;
  overflow: hidden;
  min-height: 420px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Inner pages keep one fixed banner height whatever photo is uploaded, so the
   layout never jumps around between a tall photo and a wide one. */
.page-hero {
  height: 380px;
  min-height: 380px;
  padding: 30px max(20px, var(--fab-room));
}

/* Home only: adopt the poster's own proportions so all of the artwork shows.
   min-height still wins when the ratio would leave too little room for the
   logo and title sitting on top of it. */
.page-hero.hero-full { height: auto; }
.page-hero.hero-full.hero-sized { padding: 30px max(20px, var(--fab-room)); }

/* On a desktop screen the Home hero is exactly one screen tall - the first
   thing Chrome shows is the whole of it, and it is the same size whatever
   the width and however the poster loads (it used to grow once the poster's
   proportions were known, and jump). The poster is still shown whole
   (contain) inside that box, on the navy gradient; the title sits centred.
   Phones keep the poster's own height: a 16:9 poster in a tall portrait
   screen would be a thin strip in a big empty box. */
@media (min-width: 901px) {
  .page-hero.hero-full,
  .page-hero.hero-full.hero-sized {
    /* One screen minus the sticky bar above it, so nothing of the hero is
       below the fold. --header-h is measured by common.js. */
    height: calc(100vh - var(--header-h, 71px));
    height: calc(100svh - var(--header-h, 71px));
    min-height: 0 !important;
    max-height: calc(100svh - var(--header-h, 71px));
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    box-sizing: border-box;
  }
}

/* Sliding banner layers (home hero) */
.hero-slides { position: absolute; inset: 0; z-index: 0; }
.hero-slides .slide {
  position: absolute; inset: 0;
  background-size: cover;        /* fill the fixed banner box */
  background-repeat: no-repeat;
  background-position: center;
  opacity: 0;
  transition: opacity 1.4s ease;
}
/* the home poster is shown whole instead of filling */
.hero-full .hero-slides .slide { background-size: contain; }
.hero-slides .slide.active { opacity: 1; }
.hero-overlay { position: absolute; inset: 0; background: rgba(5, 18, 36, 0.62); z-index: 1; }
.page-hero > *:not(.hero-slides):not(.hero-overlay):not(.hero-decor) { position: relative; z-index: 2; }
.page-hero h1 {
  margin: 0;
  /* Was a flat 2.9rem with two breakpoint overrides underneath it. A single
     clamp replaces all three and, unlike them, has no width at which the title
     is momentarily too big — "COMMITTEE & MANAGEMENT" set in uppercase Cinzel
     at 2.3rem with 5px of tracking was 470px wide, which overflowed every
     phone between the 1024px and 700px rules. */
  font-size: var(--fs-hero);
  /* Tracking scales with the type for the same reason: 8px of letter-spacing
     on a 28px title is proportionally three times what it is on an 80px one. */
  letter-spacing: clamp(1px, 0.28vw + 0.5px, 8px);
  text-transform: uppercase;
  /* A long single-word page title must break rather than push the hero wider
     than the screen. */
  overflow-wrap: break-word;
}
.page-hero .gold-line {
  width: 140px;
  height: 2px;
  background: var(--gold);
  margin: 16px auto 0;
}
.page-hero .hero-logo {
  /* Was a flat 160px on a desktop and 120px on a phone. The medallion is the
     first thing on the page and it was reading as an icon rather than as the
     club's mark, so it is roughly a third larger and now scales continuously.

     The clamp is tuned to three real widths rather than to a single multiplier:
     132px at 320px, about 154px at 768px, and 216px from 1080px up. three-bg.js
     reads this element's offsetWidth to size the rotating coin, so changing it
     here changes both. */
  width: clamp(132px, 20vw, 216px);
  height: clamp(132px, 20vw, 216px);
  margin: 0 auto 20px;
  object-fit: cover;
  border-radius: 50%;
  border: 3px solid var(--gold);
  box-shadow: 0 0 34px rgba(216, 178, 94, 0.4);
}
.page-hero .sub {
  color: #fff;
  font-weight: 700;
  margin-top: clamp(12px, 1.4vw, 18px);
  letter-spacing: 0.06em;
  /* Larger than body copy, and clearly below the title — the registration
     number and the year are the second thing read on the page, not the fifth. */
  font-size: clamp(0.95rem, 0.85rem + 0.5vw, 1.25rem);
  line-height: 1.5;
  overflow-wrap: break-word;
}
.page-hero .sub-small {
  color: #e6e6e6;
  /* The address line is 52 characters with a star between each part, and 3px
     of tracking added another 150px to it. At 735px it ran past the hero's
     right edge — and because the hero is `overflow: hidden` it was not a
     scrollbar but a truncation: the postcode simply was not there. Tracking
     and size both scale now, and the line is allowed to wrap. */
  letter-spacing: clamp(0.5px, 0.3vw, 3px);
  /* Up from --fs-small. The address was the smallest text on the page and it
     is the one line a visitor most often needs to read off a phone. */
  font-size: clamp(0.85rem, 0.78rem + 0.35vw, 1.05rem);
  line-height: 1.6;
  margin-top: 8px;
  overflow-wrap: break-word;
}


/* ---------- The home hero, on a desktop only ----------
   The club asked for a larger medallion sitting higher up, and a larger title
   under it. Only from 1024px: the phone layout was right as it was, and the
   clamps below 1024px are untouched. */
@media (min-width: 1024px) {
  .page-hero .hero-logo,
  .page-hero .hero-logo-3d {
    width: clamp(216px, 17vw, 268px) !important;
    height: clamp(216px, 17vw, 268px) !important;
    /* Up the page. The hero pads itself by --header-h so the title clears the
       navigation; the medallion can sit in some of that space, because it is
       artwork rather than text and nothing has to be read over it. */
    margin-top: calc(-0.35 * var(--header-h, 0px));
    margin-bottom: clamp(10px, 1vw, 16px) !important;
  }
  .page-hero.hero-full h1 { font-size: clamp(2.6rem, 1.2rem + 2.6vw, 3.4rem); }
  .page-hero .sub { font-size: clamp(1.25rem, 0.9rem + 0.6vw, 1.5rem); }
  .page-hero .sub-small { font-size: clamp(1.05rem, 0.85rem + 0.35vw, 1.22rem); }
}

/* ---------- Layout ---------- */
/* 1100px was narrow for a 1440px or 1920px screen — the Executive Committee
   grid was left with a band of empty navy down each side wide enough to fit
   another two cards. 1200px is the shared container width from tokens.css, and
   the gutter is a clamp so a 320px phone keeps a real margin without a desktop
   being padded by 20px. */
main {
  max-width: var(--container);
  margin: 0 auto;
  /* The side padding is the larger of the page gutter and the room the two
     fixed tabs need. Below about 1340px the content column reaches far enough
     out that "Join Our Community" and "Donate Us" would otherwise sit on top of
     it — on the Contact page they covered the first card's heading outright. */
  padding: clamp(24px, 2.5vw, 44px) max(var(--gutter), var(--fab-room));
}

/* 1200px of content plus a tab either side plus a gutter. Above this there is
   already room and nothing is reserved. */
@media (max-width: 1340px) { :root { --fab-room: 64px; } }
/* This is charged to both edges, so on a 375px phone 46px was taking 92px —
   a quarter of the screen — before the portal card added its own padding and
   the view inside that added more. The two tabs are trimmed to match at the
   same breakpoint (see .join-fab / .donate-fab below), so nothing they cover
   changes; the content column just gets the difference back. */
/* The tab itself is 33-36px wide on a phone; 34px left the words touching
   it. 46px keeps a finger's breadth of air between the two. */
@media (max-width: 700px)  { :root { --fab-room: 46px; } }

/* Running text is capped at a comfortable measure independently of the
   container, so widening the container above did not turn every paragraph into
   a 1200px line nobody can track back from. Anything that opts out — a grid, a
   table, a card — is unaffected, because this only applies to bare paragraphs
   directly inside the page's own sections. */
main > p,
main > section > p:not([class]),
.policy-intro { max-width: var(--measure); }
main > .center > p,
main > section.center > p { margin-left: auto; margin-right: auto; }

.center { text-align: center; }

.eyebrow {
  color: #fff;
  letter-spacing: 4px;
  font-weight: 700;
  font-size: 0.95rem;
  text-transform: uppercase;
}

/* Inline emphasis anywhere in body copy reads as a deep, solid gold rather
   than the same weight as its surrounding sentence. */
main strong,
main b { font-weight: 800; }
main p strong:not([style]),
main li strong:not([style]) { color: #f0d089; }

.section-title {
  font-size: var(--fs-h2);
  color: var(--gold);
  margin: 8px 0 6px;
  letter-spacing: 0.03em;
}

/* ---------- Rich metallic gold headings ----------
   A top-lit gradient poured through the glyphs, the way the engraved
   "COMMITTEE & MANAGEMENT" title reads. Because the text itself is
   transparent, the glow has to come from filter: drop-shadow — a
   text-shadow would be clipped away with the fill. */
.section-title,
.panel-title,
.policy-card > h2 {
  background: linear-gradient(177deg,
    var(--banner-heading-top)  0%,
    var(--banner-heading-high) 30%,
    var(--banner-heading-mid)  62%,
    var(--banner-heading-deep) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  /* The drop-shadow does two jobs: it separates the letters from a bright
     photograph, and it is the only way to give transparent-filled text a
     shadow at all — a text-shadow is clipped away with the fill. Deepened a
     little now the gradient itself is brighter, so the title still has an edge
     against a pale banner. */
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.62))
          drop-shadow(0 0 18px rgba(212, 175, 55, 0.30));
}

/* ---------- The banner title ----------

   A solid gold with a shadow under it, not a clipped gradient.

   It was `background-clip: text` over an oversized gradient with a highlight
   animating across it. That is a lovely effect and it is also a fragile one:
   the fill is transparent, so anything that disturbs the clipped background —
   and on the compact banners something did — leaves the letters reading as
   dark bronze on dark navy rather than as gold. Swapping the same element to a
   flat fill made it legible on the spot, which is how the cause was pinned
   down rather than guessed at.

   The section and panel titles keep the gradient: they sit on panels further
   down the page, where it renders as intended and was never the problem.

   The shadow does what the drop-shadow filter used to — lifts the letters off
   a bright banner photograph — and being a real text-shadow it works on a
   solid fill, which a filter was only ever needed to work around. */
.page-hero h1 {
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  color: var(--banner-heading-color);
  -webkit-text-fill-color: var(--banner-heading-color);
  filter: none;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.55),
               0 0 22px rgba(212, 175, 55, 0.22);
}

/* ---------- The banner title's moving highlight ----------

   The shine is back, and it is a different construction from the one that had
   to be taken out.

   The old one lived on the title itself: the glyphs were filled with
   `transparent` and a moving gradient was clipped to them. When anything
   disturbed that clipped background the letters had no fill left to fall back
   on, so the title read as dark bronze on dark navy — a heading that had gone
   effectively invisible. That is why it was replaced by a flat fill and
   `animation: none`, and it is why simply putting the old rule back would put
   the same fault back with it.

   Here the two jobs are on two elements. The <h1> keeps its solid gold, always
   painted, never transparent — that is the legibility fix, untouched. A span
   sits exactly over it carrying nothing but the sweeping highlight, clipped to
   the same glyph shapes.

   The failure mode is what makes this safe rather than merely prettier: the
   worst this layer can do is fail to draw, and underneath it is a title that
   was already fully legible. It cannot take the fill away, because it is not
   the fill.

   `aria-hidden` and a real element rather than `content: attr(data-text)` on a
   pseudo — generated content is read aloud by several screen readers, and the
   page's own heading announced twice is a worse bug than a missing sheen.
   site.js builds it; see paintHeroSheen in common.js. */
.page-hero h1 .hero-sheen {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  pointer-events: none;
  /* Wider than the element so the band can start and finish off-screen; the
     animation slides the background, not the box. */
  background-image: linear-gradient(100deg,
    rgba(255, 255, 255, 0)    38%,
    rgba(255, 250, 226, 0.95) 50%,
    rgba(255, 255, 255, 0)    62%);
  background-size: 240% 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  /* No text-shadow: the title underneath already has one, and a second copy
     on the highlight would double it every time the band passed. */
  text-shadow: none;
  animation: heroSheen 5s ease-in-out infinite;
}
@keyframes heroSheen {
  /* A pause at each end rather than a band sweeping continuously — the club's
     banner is the first thing on the page and a light that never stops moving
     on it competes with the words for attention. */
  0%, 12%   { background-position: -120% 0; }
  60%, 100% { background-position: 260% 0; }
}

/* ---------- The banner text arrives ----------

   `main > section` has had a fade-up entrance for a long time, and .page-hero
   sits outside <main> — so every block on the page rose into place except the
   banner at the top of it, which simply appeared. The stagger is small and in
   the order the eye reads them. */
.page-hero > h1,
.page-hero > .gold-line,
.page-hero > .sub,
.page-hero > .sub-small {
  animation: riseIn 0.7s ease both;
}
.page-hero > .gold-line  { animation-delay: 0.10s; }
.page-hero > .sub        { animation-delay: 0.18s; }
.page-hero > .sub-small  { animation-delay: 0.26s; }
.section-sub { color: var(--muted); margin-bottom: 24px; }

/* ---------- Mission & Vision horizontal rail ---------- */
/* The top padding is doing a job. `overflow-x: auto` cannot leave the other
   axis visible — CSS turns `overflow-y` into `auto` too — so this rail clips
   vertically whether it wants to or not. The cards lift 9px on hover and grow
   with `scale(1.02)` (see `.card:hover`), and 6px of room above them was not
   enough: the top of the hovered card was being cut off.

   The extra padding is taken back out of the margin, so the cards sit in
   exactly the same place as before (20 + 6 = 4 + 22). Only the clip box moved. */
.mission-rail {
  display: flex;
  gap: 22px;
  margin-top: 4px;
  padding: 22px 4px 18px;
  /* Centred when the three cards fit, which is every desktop width — the rail
     is 1200px and the cards come to 1004px with their gaps, so left-aligning
     them left a visible 200px of empty rail on the right.

     `safe` is the important half. A plain `center` on a scroll container puts
     the overflow *both* sides when the content is wider than the rail, and the
     part that overflows to the left cannot be scrolled back to — the first
     card becomes unreachable on a phone. `safe center` falls back to `start`
     the moment the content overflows, so narrow screens keep the scrolling
     behaviour they have now. Browsers without it also fall back to `start`,
     which is exactly today's layout. */
  justify-content: safe center;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  cursor: grab;
  scrollbar-width: thin;
  scrollbar-color: var(--gold-dark) transparent;
}
.mission-rail.dragging { cursor: grabbing; scroll-snap-type: none; }
.mission-rail::-webkit-scrollbar { height: 8px; }
.mission-rail::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 8px; }
.mission-rail::-webkit-scrollbar-thumb {
  background: linear-gradient(90deg, var(--gold-dark), var(--gold));
  border-radius: 8px;
}
.mission-card {
  flex: 0 0 320px;
  scroll-snap-align: center;
  text-align: center;
  /* keep the drag gesture from turning into a text/image selection */
  user-select: none;
}
.mission-card img {
  height: 190px;
  width: 100%;
  object-fit: cover;
  border-radius: 8px;
  margin-bottom: 14px;
  pointer-events: none;
}
.mission-card h3 { color: var(--gold); margin: 4px 0 10px; }
.mission-card p { color: var(--muted); font-size: 0.93rem; margin: 0; }
/* Every card the same height, whatever is written in it: the title keeps
   to two lines and the text to four, and the card opens on a click to show
   the rest (loadMissionCards in site.js). */
.mission-card {
  height: 400px;
  display: flex;
  flex-direction: column;
  cursor: pointer;
}
.mission-card h3 {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.mission-card p {
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.mission-more {
  margin-top: auto;
  padding-top: 10px;
  font-size: 0.74rem;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--gold);
  opacity: 0.75;
}
.mission-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
/* The hint appears only on a card that has more than it can show. */
.mission-card .mission-more, .cs-card .mission-more { visibility: hidden; }
.mission-card.is-clipped .mission-more, .cs-card.is-clipped .mission-more { visibility: visible; }
@media (max-width: 700px) {
  .mission-card { flex-basis: 82%; height: 380px; }
  .mission-card img { height: 165px; }
}

/* ---------- A form's boxes: the standard shapes, and a paged form ---------- */
input.is-invalid, textarea.is-invalid, select.is-invalid {
  border-color: #FF8F8F !important;
  box-shadow: 0 0 0 2px rgba(255, 143, 143, 0.25);
}
p.rp-error, .rp-q .rp-error, .rp-error { margin: 6px 0 0 !important; color: #FF8F8F !important; font-size: 0.82rem !important; font-style: normal !important; }
/* A title or heading over the club's picture. */
.has-picture { position: relative; background-size: cover; background-position: center; border-radius: 12px; overflow: hidden; min-height: 150px; display: flex; align-items: center; justify-content: center; margin-bottom: 14px; }
.has-picture::before { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(4, 16, 30, 0.35), rgba(4, 16, 30, 0.72)); }
.has-picture .picture-head-inner { position: relative; z-index: 1; padding: 26px 20px; text-align: center; width: 100%; }
.has-picture .panel-title, .has-picture h3 { margin: 0; text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6); }
.has-picture.rp-section-head { min-height: 110px; }
.gform-page-head.has-picture { border-left: 0; padding: 0; background-color: transparent; }
.gform-page-head.has-picture p { color: var(--ink); }
.gform-overlay-head { margin-bottom: 14px; }
.gform-overlay-head:not(.has-picture) { display: none; }
.rp-q-picture { display: block; width: 100%; max-height: 360px; object-fit: cover; border-radius: 10px; margin-bottom: 10px; }
.placed-form-open { margin: 18px 0 4px; }
/* The form, opened as a page over the page it is placed on. */
.gform-overlay { position: fixed; inset: 0; z-index: 9000; display: flex; flex-direction: column; background: var(--c-navy, #0A192F); color: var(--ink); }
.gform-overlay-bar { display: flex; align-items: center; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--border); background: rgba(6, 20, 33, 0.92); backdrop-filter: blur(8px); }
.gform-overlay-title { font-family: var(--font-heading, inherit); color: var(--gold); font-size: 1.1rem; letter-spacing: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gform-overlay-scroll { flex: 1; overflow: auto; padding: 24px 16px 60px; -webkit-overflow-scrolling: touch; }
.gform-overlay-card { max-width: 820px; margin: 0 auto; }
body.gform-overlay-open { overflow: hidden; }
.form-page { max-width: 820px; margin: 30px auto; }
.form-page-back { margin: 0 0 14px; }
.form-page .placed-form-open { display: none; }
.gform-otp { margin-top: 18px; padding: 14px 16px; border: 1px solid var(--gold); border-radius: 12px; background: rgba(34, 211, 238, 0.07); }
.gform-otp-text { margin: 0 0 10px; color: var(--ink); }
.gform-otp-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.gform-otp-code { width: 160px; letter-spacing: 4px; font-size: 1.2rem; text-align: center; }
.rp-section-card { border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px 6px; margin: 10px 0 18px; background: rgba(255, 255, 255, 0.03); }
.rp-section-card .rp-section-head { margin-top: 0; }
.rp-pay { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; margin: 6px 0 10px; padding: 14px; border: 1px solid var(--border); border-radius: 12px; background: rgba(255, 255, 255, 0.04); }
.rp-pay-qr-box { width: 168px; min-height: 168px; display: flex; align-items: center; justify-content: center; background: #fff; border-radius: 10px; padding: 6px; flex-shrink: 0; }
.rp-pay-qr { width: 156px; height: 156px; display: block; }
.rp-pay-wait { color: #333; font-size: 0.8rem; text-align: center; }
.rp-pay-text { flex: 1 1 200px; display: flex; flex-direction: column; gap: 6px; }
.rp-pay-amount { color: var(--gold); font-size: 1.3rem; }
.rp-pay-to { color: var(--muted); font-size: 0.88rem; word-break: break-all; }
.rp-pay-link { align-self: flex-start; }
.rp-pay { flex-direction: column; }
.rp-pay-amount-row label { display: block; font-weight: 600; margin-bottom: 6px; }
.rp-pay-amount-in { display: flex; gap: 10px; flex-wrap: wrap; }
.rp-pay-amount-in input { flex: 1 1 160px; }
.rp-pay-ways { width: 100%; }
.rp-pay-tabs { display: flex; gap: 6px; margin-bottom: 12px; }
.rp-pay-tab { padding: 8px 14px; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: var(--ink); cursor: pointer; font: inherit; font-size: 0.85rem; }
.rp-pay-tab.is-on { border-color: var(--gold); color: var(--gold); background: rgba(34, 211, 238, 0.1); }
.rp-pay-way[data-way="qr"] { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.rp-pay-backup { font-size: 0.82rem; color: var(--gold); text-decoration: underline; }
.rp-pay-after { margin-top: 10px; }
.rp-pay-shot { margin-top: 8px; }
.rp-file { display: block; width: 100%; padding: 10px; border: 1px dashed var(--muted); border-radius: 8px; background: rgba(255, 255, 255, 0.04); color: var(--ink); }
.placed-form-open .btn { min-width: 200px; }
.gform-page-head { margin: 4px 0 18px; padding: 14px 16px; border-left: 3px solid var(--gold); background: rgba(34, 211, 238, 0.06); border-radius: 0 10px 10px 0; }
.gform-page-head h3 { margin: 0 0 4px; color: var(--gold); font-size: 1.15rem; }
.gform-page-head p { margin: 0; color: var(--muted); font-size: 0.92rem; }
.gform-nav { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-top: 22px; }
.gform-nav-left { flex: 0 0 auto; }
.gform-nav-right { flex: 0 0 auto; margin-left: auto; display: flex; gap: 10px; }
.gform-progress { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 0.85rem; flex: 1 1 160px; justify-content: center; }
.gform-progress-bar { display: inline-block; width: 140px; height: 6px; border-radius: 999px; background: rgba(255, 255, 255, 0.12); overflow: hidden; }
.gform-progress-bar > span { display: block; height: 100%; background: var(--gold); transition: width 0.3s; }
.link-btn { background: none; border: 0; padding: 0; margin-top: 8px; color: var(--gold); text-decoration: underline; cursor: pointer; font: inherit; }
@media (max-width: 600px) { .gform-progress { justify-content: flex-start; } }

/* A 'cards' section the club made: a grid, so many to a row, every card the
   same height (chosen size), the words clipped and "Tap to read" opening the
   card whole. One column on a phone whatever the setting. */
.cs-cards { display: grid; gap: 18px; margin: 10px auto 0; max-width: 1100px; }
.cs-cols-1 { grid-template-columns: 1fr; max-width: 720px; }
.cs-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cs-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cs-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.cs-card { display: flex; flex-direction: column; text-align: center; cursor: pointer; overflow: hidden; }
.cs-card img { width: 100%; object-fit: cover; border-radius: 8px; margin-bottom: 12px; flex-shrink: 0; }
.cs-card h3 { color: var(--gold); margin: 4px 0 8px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.cs-card p { color: var(--muted); font-size: 0.93rem; margin: 0; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; text-align: left; }
.cs-card-date { font-size: 0.78rem; color: var(--gold); letter-spacing: 1px; text-transform: uppercase; margin-bottom: 6px; }
.cs-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.cs-size-small  .cs-card { height: 300px; }  .cs-size-small  .cs-card img { height: 130px; }  .cs-size-small  .cs-card p { -webkit-line-clamp: 3; }
.cs-size-medium .cs-card { height: 400px; }  .cs-size-medium .cs-card img { height: 190px; }  .cs-size-medium .cs-card p { -webkit-line-clamp: 4; }
.cs-size-large  .cs-card { height: 520px; }  .cs-size-large  .cs-card img { height: 260px; }  .cs-size-large  .cs-card p { -webkit-line-clamp: 6; }
@media (max-width: 900px) { .cs-cols-3, .cs-cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .cs-cards { grid-template-columns: 1fr; } }

/* The opened card: the same card, full size, over the page. */
.mission-lightbox {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: rgba(3, 10, 20, 0.78);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.mission-lightbox[hidden] { display: none; }
.mission-lightbox-card {
  position: relative;
  width: min(720px, 100%);
  max-height: calc(100vh - 48px);
  overflow: auto;
  text-align: center;
  padding: 26px 26px 30px;
}
.mission-lightbox-card img {
  width: 100%; max-height: 52vh; object-fit: contain;
  border-radius: 10px; margin-bottom: 18px;
}
.mission-lightbox-card h3 { color: var(--gold); font-size: 1.5rem; margin: 0 0 12px; }
.mission-lightbox-card p { color: var(--ink); font-size: 1rem; line-height: 1.75; margin: 0; text-align: left; }
.mission-lightbox-close {
  position: absolute; top: 10px; right: 10px;
  width: 36px; height: 36px; border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.25); background: rgba(0,0,0,0.45); color: #fff;
  font-size: 1rem; cursor: pointer;
}
.mission-lightbox-close:hover { background: var(--gold); color: #0b1f38; }
body.mission-lightbox-open { overflow: hidden; }

/* ---------- Programme photos inside admin-made sections ----------
   These are event posters and camp photos, so they get a large frame and are
   shown whole rather than cropped into a small thumbnail. */
.section-photos {
  display: flex;
  flex-direction: column;
  gap: 26px;
  align-items: center;
}
.section-photo {
  margin: 0;
  width: 100%;
  max-width: 760px;
  border: 1px solid var(--gold-dark);
  border-radius: 12px;
  overflow: hidden;
  background: #061a2e;
  box-shadow: var(--shadow);
  transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}
.section-photo:hover {
  transform: translateY(-4px);
  border-color: var(--gold);
  box-shadow: 0 16px 36px rgba(0,0,0,0.5), 0 0 26px rgba(216,178,94,0.3);
}
.section-photo img {
  width: 100%;
  max-height: 560px;
  object-fit: contain;   /* never slice the poster */
  display: block;
}
.section-photo figcaption {
  padding: 12px 16px;
  color: var(--muted);
  text-align: center;
  font-size: 0.95rem;
  border-top: 1px solid rgba(216,178,94,0.22);
}

/* ---------- Gallery page: big sliding showcase ---------- */
/* ==========================================================================
   PHOTO SHOWCASE  (buildPhotoSlider in site.js)

   Used on Social Activities, Cultural Events, the Gallery and every custom
   section, so all of them behave the same way and none of them has its own
   near-copy of these rules.
   ========================================================================== */

.gshow-stage {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background: #061a2e;
  border: 1px solid var(--gold-dark);
  aspect-ratio: 16 / 9;
  /* The stage takes focus so the arrow keys work; the ring is the shared one
     from tokens.css, inset so it is not clipped by `overflow: hidden`. */
  outline-offset: -3px;
  touch-action: pan-y;      /* a vertical scroll still scrolls the page */
}

.gshow-slide {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  /* A slight zoom out as a photo leaves and in as it arrives: the crossfade
     alone read as a slideshow from a decade ago. Both properties are
     composited, so this costs nothing on a phone. */
  transform: scale(1.04);
  transition: opacity var(--t-slow) var(--ease), transform 900ms var(--ease);
  will-change: opacity, transform;
}
.gshow-slide.active { opacity: 1; transform: scale(1); }
.gshow-slide img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* The arrows fade in with the pointer, and are always present on a touch
   screen, which has no hover to reveal them. */
.gshow-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: var(--tap); height: var(--tap);
  border-radius: 50%;
  border: 1px solid var(--c-gold-veil);
  background: rgba(0, 31, 63, 0.72);
  backdrop-filter: blur(6px);
  color: var(--gold);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: background var(--t) var(--ease), color var(--t) var(--ease),
              opacity var(--t) var(--ease), transform var(--t) var(--ease);
}
.gshow:hover .gshow-nav,
.gshow:focus-within .gshow-nav { opacity: 1; }
@media (hover: none) { .gshow-nav { opacity: 1; } }
.gshow-nav:hover, .gshow-nav:focus-visible {
  background: var(--gold);
  color: var(--c-navy-deep);
  transform: translateY(-50%) scale(1.1);
}
.gshow-prev { left: 12px; }
.gshow-next { right: 12px; }

/* "3 / 12" in the corner, so the length of the set is obvious at a glance. */
.gshow-count {
  position: absolute;
  top: 12px; right: 12px;
  font-size: var(--fs-tiny);
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--gold);
  background: rgba(0, 31, 63, 0.72);
  backdrop-filter: blur(6px);
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius-pill);
  padding: 3px 10px;
  pointer-events: none;
}

/* The bar along the bottom of the stage fills as the next photo approaches.
   Driven by a CSS animation rather than a JavaScript timer so it stays smooth
   while the main thread is busy; site.js restarts it on every move and pauses
   it whenever the slider is held. */
.gshow-progress {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  background: rgba(212, 175, 55, 0.18);
  pointer-events: none;
}
.gshow-progress span {
  display: block;
  height: 100%;
  width: 100%;
  transform-origin: left center;
  background: linear-gradient(90deg, var(--gold), var(--c-gold-light));
  animation: gshowFill 5200ms linear infinite;
}
@keyframes gshowFill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
/* Nothing auto-advances under reduced motion, so a bar counting down to an
   advance that never comes would be a lie. */
@media (prefers-reduced-motion: reduce) {
  .gshow-progress { display: none; }
}

/* ---------- The caption block ----------
   Three fields, each hidden when it is empty: the short line, the optional
   date beside it, and the longer note underneath. */
.gshow-caption {
  margin-top: 14px;
  text-align: center;
  color: var(--muted);
  min-height: 1.4em;
}
.gshow-cap-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 6px 12px;
}
.gshow-cap-title {
  color: #fff;
  font-weight: 700;
  font-size: var(--fs-body);
  overflow-wrap: anywhere;
}
.gshow-cap-title:empty { display: none; }
.gshow-cap-date {
  color: var(--gold);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: 0.04em;
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius-pill);
  padding: 2px 10px;
  white-space: nowrap;
}
.gshow-cap-date:empty { display: none; }
.gshow-cap-details {
  margin: 8px auto 0;
  max-width: var(--measure);
  font-size: var(--fs-small);
  color: var(--muted);
  overflow-wrap: anywhere;
}

.gshow-dots { display: flex; gap: 9px; justify-content: center; margin-top: 14px; flex-wrap: wrap; }
.gshow-dot {
  /* An 11px dot inside a 28x28 button: the target is comfortable on a phone
     without the row of indicators becoming a row of buttons. */
  width: 28px; height: 28px;
  border: none;
  background: none;
  cursor: pointer;
  padding: 0;
  display: grid;
  place-items: center;
}
.gshow-dot::before {
  content: "";
  width: 10px; height: 10px;
  border-radius: 50%;
  border: 1px solid var(--gold-dark);
  background: transparent;
  transition: background var(--t) var(--ease), transform var(--t) var(--ease);
}
.gshow-dot.active::before { background: var(--gold); transform: scale(1.3); border-color: var(--gold); }
.gshow-dot:hover::before { background: rgba(212, 175, 55, 0.5); }

@media (max-width: 700px) {
  .gshow-nav { width: 40px; height: 40px; font-size: 1.3rem; }
  .gshow-prev { left: 8px; }
  .gshow-next { right: 8px; }
}

/* ---------- Gallery page: highlight cards ---------- */
.highlight-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 20px;
}
.highlight-card {
  margin: 0;
  border: 1px solid var(--gold-dark);
  border-radius: 12px;
  overflow: hidden;
  background: var(--panel-2);
  box-shadow: var(--shadow);
  transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}
.highlight-card:hover {
  transform: translateY(-5px);
  border-color: var(--gold);
  box-shadow: 0 14px 32px rgba(0,0,0,0.5), 0 0 24px rgba(216,178,94,0.3);
}
.highlight-card img { width: 100%; height: 165px; object-fit: cover; display: block; }
/* `--ink`, not `#fff`.

   The card behind it is `--panel-2`, which follows the theme — navy in the
   dark site, white in the light one — so a hard white caption disappeared
   completely with dark mode off. White on white, which is what the club saw on
   the gallery: the photographs with no titles under them. The token is white
   on the dark theme and dark navy on the light one, which is what this always
   meant to say. */
.highlight-card figcaption {
  padding: 13px 14px;
  color: var(--ink);
  font-weight: 700;
  font-size: 0.95rem;
  text-align: center;
}

@media (max-width: 700px) {
  .section-photo img { max-height: 420px; }
  .gshow-nav { width: 38px; height: 38px; font-size: 1.4rem; }
  .highlight-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; }
  .highlight-card img { height: 125px; }
}

/* ---------- Feedback & Suggestions portal (review.html) ---------- */
.review-portal { max-width: 780px; margin: 0 auto; }
.rp-card {
  background: linear-gradient(160deg, rgba(13,49,84,0.94), rgba(8,32,56,0.94));
  border: 1px solid var(--gold);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 26px 28px 28px;
  margin-bottom: 24px;
}
.rp-intro { border-top: 6px solid var(--gold); }
.rp-intro h2 {
  color: var(--gold);
  font-size: 1.6rem;
  margin: 0 0 14px;
  line-height: 1.35;
}
.rp-org { color: #fff; font-weight: 800; margin: 0 0 4px; }
.rp-meta { color: var(--muted); font-weight: 700; margin: 0 0 4px; }
.rp-tagline { color: var(--gold); font-weight: 700; letter-spacing: 1px; margin: 0 0 16px; }
.rp-card p { color: var(--ink); margin: 0 0 12px; }
.rp-required-note {
  color: var(--red);
  font-size: 0.88rem;
  margin: 18px 0 6px !important;
  padding-top: 14px;
  border-top: 1px solid rgba(216,178,94,0.22);
}

/* Purple-bar section headers, mirroring the original form's layout */
.rp-section-head {
  background: linear-gradient(90deg, var(--gold-dark), var(--gold));
  color: #14243c;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-size: 0.95rem;
  padding: 11px 18px;
  border-radius: 8px;
  margin: -6px 0 12px;
}
.rp-section-note { color: var(--muted) !important; font-style: italic; }

.rp-label {
  display: block;
  color: #fff;
  font-weight: 700;
  font-size: 0.98rem;
  margin: 20px 0 8px;
}
.rp-star { color: var(--red); }

.rp-rating { border: none; margin: 20px 0 0; padding: 0; }
.rp-rating legend { padding: 0; }
.rp-rating label {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  margin-bottom: 6px;
  border: 1px solid rgba(216,178,94,0.3);
  border-radius: 8px;
  color: var(--ink);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.rp-rating label:hover { background: rgba(216,178,94,0.12); border-color: var(--gold); }
.rp-rating input { width: auto; margin: 0; flex-shrink: 0; }
.rp-stars { color: #ffcf4d; letter-spacing: 1px; }
/* A choice group drawn from a form definition (the club's added questions and
   its own forms): the same rows as the rating list above. */
.rp-choices { margin-top: 6px; }
.rp-choices label {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  margin-bottom: 6px;
  border: 1px solid rgba(216,178,94,0.3);
  border-radius: 8px;
  color: var(--ink);
  cursor: pointer;
}
.rp-choices label:hover { background: rgba(216,178,94,0.12); border-color: var(--gold); }
.rp-choices input { width: auto; margin: 0; flex-shrink: 0; }
/* A form the club made, placed on a page. */
.placed-form-sub { color: var(--muted); margin: -8px 0 14px; }
.placed-form-body { max-width: 720px; margin: 0 auto; text-align: left; }
.placed-form-section { margin-top: 18px; }
.placed-form-consent { margin-top: 16px; }
.placed-form-actions { margin-top: 18px; text-align: center; }
.placed-form-done { margin-top: 12px; }

.rp-terms-title { color: var(--gold); font-size: 1.05rem; margin: 22px 0 10px; }
.rp-terms { color: var(--ink); padding-left: 22px; margin: 0 0 18px; font-style: italic; }
.rp-terms li { margin-bottom: 8px; }
.rp-terms li::marker { color: var(--gold); }

.rp-assist {
  border: 1px solid rgba(216,178,94,0.35);
  border-radius: 10px;
  background: rgba(4,20,38,0.5);
  padding: 16px 18px;
  margin-bottom: 20px;
}
.rp-assist h3 { color: #fff; margin: 0 0 10px; font-size: 1.05rem; }
.rp-assist-label { color: var(--gold); font-weight: 800; margin: 12px 0 4px !important; }
.rp-assist p { margin: 0 0 4px; }
.rp-assist a { color: #fff; font-weight: 700; }
.rp-assist a:hover { color: var(--gold); text-decoration: underline; }
.rp-break { word-break: break-all; }

.rp-agree {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  color: var(--ink);
  border: 1px solid rgba(216,178,94,0.35);
  border-radius: 10px;
  padding: 15px 16px;
  cursor: pointer;
}
.rp-agree input { width: auto; margin: 3px 0 0; flex-shrink: 0; }

/* Sticky title block + progress across the steps */
.rp-header { border-top: 6px solid var(--gold); }
.rp-header h2 {
  color: var(--gold);
  font-size: 1.5rem;
  margin: 0 0 10px;
  line-height: 1.35;
}
.rp-progress { display: flex; align-items: center; gap: 14px; margin-top: 6px; }
.rp-progress-bar {
  flex: 1;
  height: 6px;
  border-radius: 4px;
  background: rgba(255,255,255,0.12);
  overflow: hidden;
}
.rp-progress-bar span {
  display: block;
  height: 100%;
  width: 25%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--gold-dark), var(--gold));
  transition: width 0.4s ease;
}
.rp-progress-text {
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 700;
  white-space: nowrap;
}

/* Back / Next / Submit row */
.rp-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.rp-nav-left { display: flex; gap: 12px; flex-wrap: wrap; }
.rp-clear {
  background: none;
  border: none;
  color: var(--gold);
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  cursor: pointer;
  padding: 8px 4px;
}
.rp-clear:hover { text-decoration: underline; }
#rv-submit[disabled],
#mf-submit[disabled] { opacity: 0.6; pointer-events: none; }

/* ---- Registration portal extras (membership.html) ---- */
/* The "same as my mobile number" tick sits above the WhatsApp box, so it reads
   as an option for that field rather than a declaration of its own. */
.rp-same { margin-bottom: 14px; font-weight: 700; }
/* While the box is mirroring the mobile number it is read-only, and looks it. */
input.is-mirrored {
  opacity: 0.75;
  cursor: not-allowed;
  border-style: dashed;
}
.rp-upload-note { color: var(--muted) !important; font-size: 0.86rem; margin: 0 0 10px !important; }

/* ---- Email verification on the registration form ---- */
.rp-verify-row { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
.rp-verify-row input { flex: 1; min-width: 190px; margin-bottom: 0; }
.rp-verify-row .btn { flex-shrink: 0; white-space: nowrap; }
.rp-verify-note { margin-top: 18px; }
.rp-dev-code {
  margin-top: 10px;
  font-family: 'Courier New', monospace;
  font-size: 1.6rem;
  font-weight: 800;
  letter-spacing: 8px;
  color: var(--gold);
}
.rp-verified {
  color: #6fd6a6 !important;
  font-weight: 700;
  margin: 16px 0 0 !important;
}
@media (max-width: 700px) {
  .rp-verify-row { flex-direction: column; }
  .rp-verify-row .btn { width: 100%; text-align: center; }
}
.rp-photo-preview {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 14px;
  color: var(--ink);
  font-size: 0.88rem;
  word-break: break-all;
}
.rp-photo-preview img {
  width: 86px;
  height: 86px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--gold);
  flex-shrink: 0;
}

/* Each step fades in as you move through the form */
@keyframes rpStepIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}
.rp-step:not([hidden]) { animation: rpStepIn 0.35s ease both; }

.rp-thanks { max-width: 780px; margin: 0 auto; text-align: center; }
.rp-thanks h2 { color: var(--gold); margin: 0 0 12px; }
.rp-thanks .btn { margin-top: 16px; }

@media (max-width: 700px) {
  .rp-card { padding: 20px 16px 22px; }
  .rp-header h2 { font-size: 1.2rem; }
  .rp-nav { flex-direction: column-reverse; align-items: stretch; }
  .rp-nav-left { flex-direction: row; }
  .rp-nav-left .btn { flex: 1; text-align: center; }
}

/* ---------- Policy pages (Privacy / Terms) + FAQ ---------- */
.effective-date {
  display: inline-block;
  border: 1px solid var(--red);
  color: var(--red);
  border-radius: 8px;
  padding: 7px 18px;
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  margin-bottom: 22px;
}
.policy-intro {
  max-width: 900px;
  margin: 0 auto 34px;
  text-align: center;
  font-style: italic;
  color: var(--ink);
}
.policy-card {
  background: linear-gradient(160deg, rgba(13,49,84,0.92), rgba(8,32,56,0.92));
  border: 1px solid var(--gold);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 24px 28px;
  margin-bottom: 26px;
}
.policy-card h2 {
  color: var(--gold);
  font-size: 1.25rem;
  letter-spacing: 1px;
  margin: 0 0 6px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(216,178,94,0.28);
}
.policy-card p { color: var(--ink); margin: 14px 0 0; text-align: justify; }
.policy-card .policy-label {
  color: #f0d089;
  display: block;
  margin-bottom: 2px;
  font-weight: 800;
  letter-spacing: 0.3px;
}
.policy-card .policy-bullet { padding-left: 20px; position: relative; }
.policy-card .policy-bullet::before {
  content: '◆';
  position: absolute;
  left: 0;
  color: var(--gold);
  font-size: 0.7rem;
  top: 4px;
}
.policy-card .btn { margin-top: 18px; }

.faq-list { display: flex; flex-direction: column; gap: 14px; }
.faq-item {
  background: linear-gradient(160deg, rgba(13,49,84,0.92), rgba(8,32,56,0.92));
  border: 1px solid var(--gold-dark);
  border-radius: 12px;
  overflow: hidden;
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
.faq-item:hover { border-color: var(--gold); box-shadow: 0 0 24px rgba(216,178,94,0.25); }
.faq-item.open { border-color: var(--gold); box-shadow: 0 0 26px rgba(216,178,94,0.3); }
.faq-q {
  width: 100%;
  background: none;
  border: none;
  color: #fff;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  text-align: left;
  padding: 17px 20px;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
}
.faq-arrow { color: var(--gold); transition: transform 0.3s ease; flex-shrink: 0; }
.faq-item.open .faq-arrow { transform: rotate(180deg); }
.faq-a { display: none; padding: 0 22px 18px; }
.faq-item.open .faq-a { display: block; }
.faq-a p { color: var(--muted); margin: 0 0 10px; text-align: justify; }
.faq-a p:last-child { margin-bottom: 0; }
@media (max-width: 700px) {
  .policy-card { padding: 18px 16px; }
  .policy-card p { text-align: left; }
  .faq-q { font-size: 0.93rem; padding: 14px 15px; }
  .faq-a { padding: 0 16px 15px; }
  .faq-a p { text-align: left; }
}

/* Gold-bordered navy panel (like the site's big containers) */
.panel {
  background: var(--panel);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 26px;
  margin-bottom: 36px;
}
.panel-title {
  text-align: center;
  color: var(--gold);
  /* Uppercase Cinzel with 5px of tracking pushed "UPCOMING EVENTS" past a
     320px screen. Tracking now scales with the type, as it does on the hero. */
  letter-spacing: clamp(1px, 0.35vw, 5px);
  text-transform: uppercase;
  font-size: var(--fs-h3);
  margin: 0 0 22px;
  overflow-wrap: break-word;
}

.card {
  background: var(--panel);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px;
}

/* Golden glow on hover — applies to every card / photo / member / notice / event */
.card, .member-card, .gallery-item, .notice-item, .event-card, .panel {
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.card:hover, .member-card:hover, .gallery-item:hover, .notice-item:hover, .event-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 0 30px rgba(216, 178, 94, 0.45);
}

.grid { display: grid; gap: 20px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }

/* ---------- Notice ---------- */
.notice-item {
  background: var(--panel);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  padding: 18px 20px;
  margin-bottom: 16px;
  box-shadow: var(--shadow);
}
.notice-item .date { color: var(--red); font-size: 0.85rem; font-weight: 700; }
.notice-item h3 { margin: 4px 0 8px; color: #fff; }
.notice-item p { color: var(--muted); margin: 0; }

/* ---------- Member grid (big cards with large photos, like the club site) ---------- */
/* The member grid — Executive Committee on the Home page, and every group on
   the Committee page.

   Was a centred flex-wrap of fixed 268px cards, which had two faults at
   opposite ends of the range. On a 1440px or 1920px screen the last row was
   centred under the ones above it, so four cards sat in the middle with a
   ragged edge either side and a band of empty navy beyond. On a phone the
   cards were `width: 46%; min-width: 155px` — and below about 350px the
   minimum won, two cards no longer fitted the row, and the grid silently
   dropped to one per row.

   A grid fixes both, and fixes them by construction rather than by a number
   that happens to work: `auto-fill` with a minimum track packs as many equal
   columns as fit and leaves the last row aligned with the rest, and the
   explicit two-column rule below is what guarantees the phone case rather than
   hoping a percentage lands right. */
.member-grid {
  /* Flex, not grid, and the reason is the last row.

     A grid places every item in a fixed column, so a final row holding one or
     two cards sits hard against the left edge with a wide empty gap beside it
     — which is exactly what it was doing on a desktop. Flex with
     `justify-content: center` centres each row independently, so a full row
     fills the width and a short last row sits in the middle under it.

     The card width is fixed rather than `flex: 1`, because letting the last
     row grow to fill the container would make two leftover cards twice the
     size of every card above them. Same size everywhere, centred when there
     is room to spare. */
  --grid-gap: clamp(14px, 2vw, 26px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
  gap: var(--grid-gap);
}
.member-grid .member-card {
  flex: 0 0 auto;
  /* 248px, not 262px. Inside the Home page's `.panel` the usable width on a
     desktop is about 1084px, and four 262px cards with their gaps came to
     1126 — fourteen pixels too many, so the row broke to three and the tenth
     card sat alone. Four now fit with room to spare, at both 1440 and 1920. */
  width: clamp(200px, 22vw, 248px);
  /* Without this a long unbroken name forces the card wider than its width
     and pushes the whole row past the screen. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
  background: var(--panel-2);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  text-align: center;
}
/* Large rectangular photo filling the top of the card */
.member-grid .member-card img {
  width: 100%;
  height: 250px;
  border-radius: 0;
  border: none;
  border-bottom: 2px solid var(--gold);
  object-fit: cover;
  margin: 0;
}
.member-grid .member-card .avatar-fallback {
  width: 100%;
  height: 250px;
  border-radius: 0;
  border: none;
  border-bottom: 2px solid var(--gold);
  margin: 0;
  font-size: 3rem;
}
.member-grid .member-card .m-body {
  padding: 16px 14px 20px;
  /* Fills the rest of the card so every card in a row is the same height and
     the names line up, whatever length they are. */
  flex: 1 1 auto;
  min-width: 0;
}
.member-grid .member-card h4 {
  margin: 0 0 6px;
  font-size: var(--fs-h4);
  /* A long compound name or a hyphenated surname wraps rather than widening
     the card. `anywhere` because some of these names have no space to break
     at, and a name overflowing a card is worse than one broken mid-word. */
  overflow-wrap: anywhere;
}
.member-grid .member-card .role {
  font-size: var(--fs-small);
  overflow-wrap: anywhere;
}

/* Exactly two per row on a phone, stated rather than implied.
   `1fr 1fr` cannot fall to one column the way a min-width percentage can, and
   `minmax(0, 1fr)` is what lets a track shrink below its content's natural
   width instead of overflowing the page at 320px. */
/* Exactly two per row on a phone, stated as arithmetic rather than left to a
   min-width to work out: two cards plus one gap is the full row, so the width
   is half the row minus half the gap. At 320px that is exactly 139px each and
   292px together, which is the container to the pixel — there is no width at
   which a third card could fit or a second could fall off. */
@media (max-width: 700px) {
  .member-grid { --grid-gap: 14px; }
  .member-grid .member-card { width: calc(50% - (var(--grid-gap) / 2)); }
}

/* ---------- Committee page sections ---------- */
.committee-section { margin-bottom: 54px; }
.committee-section h2 {
  text-align: center;
  color: #fff;
  font-size: 1.65rem;
  margin: 0 0 26px;
  letter-spacing: 0.5px;
}

/* ---------- Member card ---------- */
.member-card {
  text-align: center;
  padding: 18px;
  background: var(--panel-2);
}
.member-card img {
  width: 100px; height: 100px;
  border-radius: 50%;
  object-fit: cover;
  margin: 0 auto 12px;
  border: 2px solid var(--gold);
}
.member-card .avatar-fallback {
  width: 100px; height: 100px;
  border-radius: 50%;
  margin: 0 auto 12px;
  background: var(--panel);
  color: var(--gold);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.6rem; font-weight: 700;
  border: 2px solid var(--gold);
}
.member-card h4 { margin: 4px 0; color: #fff; font-family: Georgia, serif; }
.member-card .role {
  color: var(--gold);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.member-card .contact { color: var(--muted); font-size: 0.85rem; margin-top: 6px; }

/* ---------- Gallery ---------- */
.gallery-item {
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  background: var(--panel);
  border: 1px solid var(--gold);
}
.gallery-item img { width: 100%; height: 180px; object-fit: cover; }
.gallery-item .caption { padding: 10px 12px; font-size: 0.9rem; color: var(--muted); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-block;
  background: transparent;
  color: var(--gold);
  border: 1px solid var(--gold);
  padding: 10px 22px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  transition: background 0.2s, color 0.2s;
}
.btn:hover { background: var(--gold); color: #0b1f38; }
.btn-gold { background: var(--gold); color: #0b1f38; }
.btn-gold:hover { background: var(--gold-dark); color: #fff; }
.btn-danger { background: #8f2f2f; color: #fff; border-color: #8f2f2f; }
.btn-danger:hover { background: #a83a3a; }
.btn-outline { background: transparent; color: var(--gold); border: 1px solid var(--gold); }

.quick-btns {
  display: flex;
  gap: 16px;
  justify-content: center;
  flex-wrap: wrap;
  /* A little further down the page than the section above it - the club
     wanted the row to sit closer to the footer than to the reviews. */
  margin: 70px 0 10px;
}

/* ---------- Notices marquee-style bar ---------- */
.notice-bar {
  background: linear-gradient(90deg, #caa14a, #e3c06a);
  color: #241a05;
  border-radius: 8px;
  padding: 18px 22px 18px 54px;
  position: relative;
  font-weight: 600;
  margin-bottom: 18px;
  box-shadow: var(--shadow);
}
.notice-bar::before {
  content: 'NOTICES';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 34px;
  background: #0b1f38;
  color: var(--gold);
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  display: flex; align-items: center; justify-content: center;
  font-size: 0.65rem;
  letter-spacing: 2px;
  border-radius: 0 8px 8px 0;
}

/* ---------- Forms ---------- */
input, textarea, select {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--gold-dark);
  border-radius: 6px;
  font-size: 0.95rem;
  margin-bottom: 12px;
  font-family: inherit;
  background: #0d2846;
  color: var(--ink);
}
input::placeholder, textarea::placeholder { color: #7d8fa5; }
label { font-weight: 600; font-size: 0.9rem; color: var(--muted); }

.empty-state { color: var(--muted); text-align: center; padding: 30px; }

/* ---------- Footer ---------- */
footer {
  border-top: 2px solid var(--gold);
  color: var(--muted);
  text-align: center;
  padding: 34px 20px 26px;
  margin-top: 50px;
  font-size: 0.9rem;
}
/* ==========================================================================
   SOCIAL ICONS

   One block, shared by the footer's circles and the Contact page's platform
   buttons, because they are the same four brands and having them animate
   differently on two pages was the whole complaint.

   What happens, in order:

     1. **Arrival.** Each icon zooms up from 60% with a slight rise and a
        blur that clears, staggered by 90ms so they land one after another
        rather than all at once. Runs once, on load.
     2. **Rest.** A slow, continuous float — a two-pixel rise and fall on a
        long cycle, each icon offset from the next so the row breathes
        instead of pulsing in lockstep.
     3. **Hover / focus.** The icon zooms in and takes its brand's real
        colour: Facebook blue, Instagram's gradient, YouTube red, WhatsApp
        green.

   The float only ever moves the icon *inside* its own box — `transform` on
   the inner SVG, never a width, a margin or a gap — so nothing around it can
   be nudged as it runs, and the row's layout is completely static. All three
   collapse under prefers-reduced-motion, through tokens.css.
   ========================================================================== */

.social-circles {
  display: flex;
  gap: 16px;
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: 26px;
}

.social-circles a {
  /* Was 62px. Smaller, as asked, and still comfortably over the 44px minimum
     touch target. */
  width: 48px;
  height: 48px;
  border: 2px solid var(--gold);
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--gold);
  position: relative;
  /* `background` is listed first because the brand rules below change it, and
     `transform`/`box-shadow` carry the zoom. Nothing here affects layout. */
  transition: background var(--t) var(--ease), color var(--t) var(--ease),
              border-color var(--t) var(--ease), transform var(--t) var(--ease),
              box-shadow var(--t) var(--ease);
  animation: socialArrive 620ms var(--ease) both;
}

/* The icon itself — an SVG for a network we have a logo for, a letter in a
   span for one we do not. Both, because the span was getting the size and the
   colour and not the float: "Other" sat perfectly still in a row of icons that
   were all gently rising and falling, which is the difference an admin sees
   without being able to name it. */
.social-circles a svg,
.social-circles a .social-initial {
  width: 22px;
  height: 22px;
  fill: currentColor;
  /* The resting float lives on the SVG rather than the anchor, so it cannot
     fight the anchor's own hover transform — the two would otherwise
     overwrite each other and the zoom would stutter as the loop came round. */
  animation: socialFloat 4.8s ease-in-out infinite;
  transition: transform var(--t) var(--ease);
}

/* Stagger, applied to both the arrival and the float so the row never falls
   into step with itself.

   `4n+1` and not `1`. These were four rules for four brands, written when the
   footer had exactly four links — so the fifth link an admin added, and every
   one after it, took the default delay of zero: they all arrived in the same
   instant and floated in lockstep, next to four that did not. The club could
   see that the new icon was not behaving like the others without being able to
   say why.

   Cycling through the same four delays keeps the effect identical for the
   original four and gives every link after them the same treatment, however
   many there are. Nothing here names a network. */
.social-circles a:nth-child(4n+1)      { animation-delay: 0ms; }
.social-circles a:nth-child(4n+2)      { animation-delay: 90ms; }
.social-circles a:nth-child(4n+3)      { animation-delay: 180ms; }
.social-circles a:nth-child(4n+4)      { animation-delay: 270ms; }
.social-circles a:nth-child(4n+1) :is(svg, .social-initial) { animation-delay: 0s; }
.social-circles a:nth-child(4n+2) :is(svg, .social-initial) { animation-delay: 0.6s; }
.social-circles a:nth-child(4n+3) :is(svg, .social-initial) { animation-delay: 1.2s; }
.social-circles a:nth-child(4n+4) :is(svg, .social-initial) { animation-delay: 1.8s; }

.social-circles a:hover,
.social-circles a:focus-visible {
  transform: scale(1.18) translateY(-4px);
}
.social-circles a:hover :is(svg, .social-initial),
.social-circles a:focus-visible :is(svg, .social-initial) {
  transform: scale(1.12);
  animation-play-state: paused;   /* the float stops while it is being pointed at */
}

@keyframes socialArrive {
  from { opacity: 0; transform: scale(0.6) translateY(14px); filter: blur(4px); }
  60%  { opacity: 1; transform: scale(1.08) translateY(-2px); filter: blur(0); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes socialFloat {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-2.5px) scale(1.04); }
}

@media (max-width: 700px) {
  .social-circles { gap: 12px; }
  .social-circles a { width: 44px; height: 44px; }
  .social-circles a :is(svg, .social-initial) { width: 20px; height: 20px; }
}

/* ---------- Brand colours ----------

   One hover rule for every social link there will ever be, and a variable per
   network that says what colour it lights up in.

   It used to be a hover block per network — four of them, naming Facebook,
   Instagram, YouTube and WhatsApp. Everything else an admin added, and
   "Other" in particular, had no block, so it kept the gold border and did
   nothing at all on hover while the four beside it lit up. `--brand` was even
   declared for those links and nothing read it.

   So the effect is generic and only the *colour* is per network. A network
   added tomorrow gets the full behaviour by having a `--brand`, and one nobody
   has a colour for falls back to the club's gold — which is what the club
   would have picked anyway.

   Applied to both the footer circles and the Contact page buttons, so the two
   cannot drift apart again. */
[data-brand] { --brand: var(--gold); }
[data-brand="facebook"]  { --brand: #1877F2; }
[data-brand="instagram"] { --brand: #d6249f; }
[data-brand="youtube"]   { --brand: #FF0000; }
[data-brand="whatsapp"]  { --brand: #25D366; }
[data-brand="telegram"]  { --brand: #229ED9; }
[data-brand="twitter"], [data-brand="x"] { --brand: #0f1419; }
[data-brand="linkedin"]  { --brand: #0A66C2; }
[data-brand="threads"]   { --brand: #0f1419; }

[data-brand]:hover, [data-brand]:focus-visible {
  background: var(--brand);
  border-color: var(--brand);
  /* White on the brand colour, unless the network sets its own ink - Snapchat
     is black on its yellow. Both come inline from socialBrandFor(). */
  color: var(--brand-ink, #fff);
  /* colour-mix rather than a hand-written rgba per network: the glow is the
     brand colour at the same opacity every one of them used. */
  box-shadow: 0 6px 22px color-mix(in srgb, var(--brand) 48%, transparent);
}

/* Instagram is the one network whose colour is not a colour. It keeps its
   gradient; the border and the glow still come from --brand. */
[data-brand="instagram"]:hover, [data-brand="instagram"]:focus-visible {
  background: radial-gradient(circle at 30% 107%,
              #fdf497 0%, #fd5949 45%, #d6249f 60%, #285AEB 90%);
}

/* The club's gold is light, so white text on it is the one case where the
   generic rule would be unreadable. */
[data-brand="other"]:hover, [data-brand="other"]:focus-visible {
  color: var(--c-navy, #071f38);
}
.footer-links {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: 22px;
}
.footer-links a {
  border: 1px solid var(--gold);
  border-radius: 6px;
  padding: 8px 16px;
  color: var(--gold);
  font-size: 0.8rem;
  letter-spacing: 2px;
  font-weight: 700;
}
.footer-links a:hover { background: var(--gold); color: #0b1f38; }
.footer-location {
  display: inline-block;
  border: 1px solid var(--gold-dark);
  border-radius: 8px;
  padding: 8px 18px;
  color: #ffdf9e;
  font-weight: 600;
  margin-bottom: 14px;
}
/* The credit line under the copyright. Quieter than the line above it: it is
   the last thing on the page and should read as a signature, not a claim. */
.footer-credit {
  margin-top: 6px;
  font-size: 0.78rem;
  letter-spacing: 0.6px;
  opacity: 0.75;
}

/* ---------- Contact ---------- */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 26px;
  align-items: start;
}
.contact-col { display: flex; flex-direction: column; gap: 26px; }

.contact-card {
  background: linear-gradient(160deg, rgba(13,49,84,0.92), rgba(8,32,56,0.92));
  border: 1px solid var(--gold);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 22px 24px 24px;
  transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}
.contact-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 36px rgba(0,0,0,0.5), 0 0 28px rgba(216,178,94,0.32);
}
.contact-card > h2 {
  color: var(--gold);
  font-size: 1.15rem;
  letter-spacing: 2px;
  text-transform: uppercase;
  margin: 0 0 6px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(216,178,94,0.28);
}
.contact-card p { color: var(--ink); margin: 12px 0 0; }

.contact-block { display: flex; gap: 12px; margin-top: 18px; }
.contact-block .cb-icon { font-size: 1.15rem; line-height: 1.5; flex-shrink: 0; }
.contact-block p { margin: 2px 0 0; color: var(--ink); }
.cb-label {
  color: var(--gold);
  font-weight: 800;
  font-size: 0.9rem;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.cb-strong { color: #fff; font-weight: 800; }
.cb-strong:hover { color: var(--gold); text-decoration: underline; }
.cb-break { word-break: break-all; }
.cb-closed { color: var(--red); font-weight: 800; }

.contact-card-alert { border-color: var(--red); }
.contact-card-alert > h2 { color: var(--red); border-bottom-color: rgba(255,107,107,0.3); }
.contact-card-alert p { color: var(--muted); }

.contact-list { margin: 14px 0 0; padding-left: 20px; color: var(--ink); }
.contact-list li { margin-bottom: 10px; }
.contact-list li::marker { color: var(--gold); }

/* ==========================================================================
   CONTACT US — "Follow Our Official Platforms"

   The club's original design: a 2×2 grid of labelled buttons, each carrying
   its network's real logo in its real colour. Restored after a spell as the
   footer's row of circles; see loadContactPage in site.js for why the two are
   deliberately different components.

   The links themselves are unchanged and still come from /api/settings —
   Admin Panel > Website Content > Home Page > Social Links. Nothing here
   hard-codes a URL.
   ========================================================================== */
.platform-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 16px;
}

.platform-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border: 1px solid var(--gold-dark);
  border-radius: var(--radius-sm);
  background: rgba(4, 20, 38, 0.5);
  color: var(--ink);
  font-weight: 700;
  font-size: 0.92rem;
  min-width: 0;
  overflow-wrap: anywhere;
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease),
              transform var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
/* A link the browser has seen before would otherwise be painted its default
   purple, which on navy is close to unreadable. */
.platform-btn:visited { color: var(--ink); }

.platform-btn:hover,
.platform-btn:focus-visible {
  background: rgba(212, 175, 55, 0.14);
  border-color: var(--gold);
  transform: translateY(-2px);
}

/* The mark sits on a white chip. Several of these logos are defined on white
   and lose their contrast on navy — WhatsApp's green and YouTube's red both go
   muddy against #002852. */
.platform-btn .pb-icon {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  background: #fff;
  border-radius: 6px;
  padding: 2px;
  transition: background var(--t) var(--ease);
}
.platform-btn .pb-icon svg {
  width: 100%;
  height: 100%;
  /* The fallback, for a network with no brand rule of its own. The four that
     have one take it from --brand, set once for every network. */
  fill: var(--brand, var(--gold));
  transition: transform var(--t) var(--ease), fill var(--t) var(--ease);
}
.platform-btn:hover .pb-icon svg,
.platform-btn:focus-visible .pb-icon svg { transform: scale(1.12); }

/* The mark takes its network's colour, and its network's colour is one
   variable — declared once, up beside the footer circles, and shared by both.
   These were four rules naming four networks, which is why a platform an admin
   added later sat there in gold while the four beside it were branded. */
.platform-btn .pb-icon svg { fill: var(--brand, var(--gold)); }

/* Hovered, the button takes the brand and the mark inverts to sit on it. One
   rule for every network there will ever be — see the note on [data-brand]
   above. */
.platform-btn:hover,
.platform-btn:focus-visible {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--brand) 40%, transparent);
}
.platform-btn:hover .pb-icon svg,
.platform-btn:focus-visible .pb-icon svg { fill: #fff; }

/* Instagram's mark is a gradient, which neither `fill` nor a single background
   colour can express. It is the one network that needs a rule of its own, and
   it needs exactly two lines of one. */
.platform-btn[data-brand="instagram"]:hover,
.platform-btn[data-brand="instagram"]:focus-visible {
  background: radial-gradient(circle at 30% 107%,
              #fdf497 0%, #fd5949 45%, #d6249f 60%, #285AEB 90%);
}

/* The club's gold is light, so white on it is the one unreadable case. */
.platform-btn[data-brand="other"]:hover,
.platform-btn[data-brand="other"]:focus-visible { color: var(--c-navy, #071f38); }
.platform-btn[data-brand="other"]:hover .pb-icon svg,
.platform-btn[data-brand="other"]:focus-visible .pb-icon svg {
  fill: var(--c-navy, #071f38);
}

:root[data-theme="light"] .platform-btn {
  background: #FFFFFF;
  border-color: var(--surface-line);
  color: var(--c-ink-dark);
}
:root[data-theme="light"] .platform-btn:visited { color: var(--c-ink-dark); }
:root[data-theme="light"] .platform-btn .pb-icon { background: #F3F6FB; }

/* One column on a phone: two labelled buttons side by side at 320px leaves
   about 130px each, which is not enough for "Instagram" beside its mark. */
@media (max-width: 700px) {
  .platform-grid { grid-template-columns: 1fr; }
}

/* Review card: gently pulsing frame so it reads as one big click target */
@keyframes reviewPulse {
  0%, 100% { box-shadow: var(--shadow), 0 0 0 rgba(216,178,94,0); }
  50%      { box-shadow: var(--shadow), 0 0 26px rgba(216,178,94,0.45); }
}
.review-card { text-align: center; animation: reviewPulse 3.2s ease-in-out infinite; }
.review-card.review-card-live { cursor: pointer; }
.review-card > h2 { border-bottom-color: rgba(216,178,94,0.28); }
.review-btn { margin-top: 18px; display: inline-block; }
.review-btn.is-disabled { opacity: 0.5; pointer-events: none; }

@media (max-width: 700px) {
  .contact-card { padding: 18px 16px 20px; }
}

/* ---------- Notice auto-scroller ---------- */
.notice-scroller {
  height: 132px;
  overflow: hidden;
  position: relative;
}
.notice-track { will-change: transform; }
.notice-track.animate { animation: scrollUp var(--scroll-time, 14s) linear infinite; }
/* Pauses for a mouse *and* for a keyboard: the lines are links now, so tabbing
   through them would otherwise mean chasing a moving target. */
.notice-scroller:hover .notice-track,
.notice-scroller:focus-within .notice-track { animation-play-state: paused; }

.notice-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 4px;
  font-weight: 600;
  /* The bar is gold, so a line on it is dark navy — in both the unvisited and
     the visited state, which is what .visited-safe is doing on each anchor. */
  color: var(--c-navy-deep);
  border-radius: 5px;
  min-width: 0;
  transition: background var(--t) var(--ease);
}
a.notice-line:hover,
a.notice-line:focus-visible { background: rgba(11, 31, 56, 0.12); }
a.notice-line, a.notice-line:visited { color: var(--c-navy-deep); }
.notice-line .nl-date { color: #7a1616; flex: 0 0 auto; font-size: var(--fs-small); }
/* The corner label after a Home-bar line's words, when the notice asks for
   it: a small pale box right where the words end, the words in the blue →
   green → lime gradient the site's NEW badge uses. */
.notice-line .nl-badge {
  display: inline-block; vertical-align: middle; margin-left: 8px;
  padding: 1px 7px; border-radius: 6px;
  background: rgba(255, 255, 255, 0.88); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
  font-size: 0.7rem; font-weight: 900; letter-spacing: 1px; text-transform: uppercase; white-space: nowrap; line-height: 1.5;
}
.notice-line .nl-badge b {
  background: linear-gradient(90deg, #1e6bff 0%, #12b5c9 35%, #19c37d 65%, #b6e300 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;
}

/* Two lines at most, whatever the notice is called.

   The strip is a fixed height and scrolls by translating exactly -50%, so a
   line that grows takes the loop with it. A long title had nothing stopping it
   from being eight lines tall, which is what made the bar on a phone a wall of
   one word per row. Clamped, the strip's geometry is the same for every
   notice; the whole title is on the notice's own page, which the line links
   to. */
.notice-line .nl-title {
  min-width: 0;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- the ticker on a phone ----------
   Measured on a 390px screen: the vertical NOTICES tab and the bar's own
   padding left the text 246px, and the date beside it left the *title* 170px —
   which is where "one word per line" came from. The date keeps its row and the
   title gets the full width under it. */
@media (max-width: 700px) {
  .notice-bar { padding: 14px 14px 14px 34px; }
  .notice-bar::before { width: 24px; font-size: 0.56rem; letter-spacing: 1.5px; }
  .notice-line {
    flex-wrap: wrap;
    gap: 2px 6px;
    padding: 8px 2px;
    align-items: center;
  }
  /* Its own row, the full width of the bar. */
  .notice-line .nl-title { flex: 1 1 100%; }
  /* Room for two of those taller lines rather than one and a sliver. */
  .notice-scroller { height: 150px; }
}
@keyframes scrollUp {
  from { transform: translateY(0); }
  to { transform: translateY(-50%); }
}

/* ---------- Notice Board page: full cards auto-scrolling in one frame ---------- */
.notice-board-frame {
  height: 640px;
  /* Was `hidden`, because the board scrolled by animating a transform and
     nothing was ever meant to scroll it. It is now driven by scrollTop, so the
     frame has to be a real scroll container. */
  overflow-y: auto;

  /* `auto`, deliberately — this used to say `smooth`, and that is what stopped
     the board scrolling by itself.

     The glide advances the board by writing `scrollTop += a fraction of a
     pixel` on every animation frame. With `scroll-behavior: smooth` the
     browser does not *move* on such a write; it begins an animation towards
     that target. The next frame arrives long before the animation finishes,
     reads a scrollTop that has barely changed, and starts another one from
     there. Sixty times a second the board is asked to glide somewhere it never
     reaches, so it sits still — which is exactly what the club reported, and
     why it looked like the server had hung rather than a stylesheet.

     Measured on the page: `scrollTop = 200` read back as 0 with `smooth`, and
     as 200 the moment it was forced to `auto`.

     Nothing is lost. Every *deliberate* movement — the arrow keys, Home, End —
     goes through `frame.scrollTo({ behavior: 'smooth' })` and asks for smooth
     by name, so those still animate; and the reduced-motion rule in tokens.css
     still turns them off for anyone who has asked for that. */
  scroll-behavior: auto;
  overscroll-behavior: contain;
  position: relative;
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
  padding: 4px 22px;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%);
}
@media (max-width: 700px) {
  .notice-board-frame { height: 520px; padding: 4px 14px; }
}

/* ---------- Notice board: the glide ----------

   The track holds one or two copies of the list. The clone exists only so the
   continuous scroll can wrap without a visible seam — see site.js.

   There is deliberately nothing drawn on the panel: no arrows, no pause
   button, no dots. The board scrolls itself, and the reader moves it with the
   wheel, a finger or the arrow keys — all of which work because the panel is a
   real scroll container rather than a strip being animated past a window. */
.notice-board-copy { display: block; }

/* The panel's own scrollbar is hidden so it reads as a framed board rather
   than a scrolling box. Purely visual: `overflow-y: auto` is untouched, so the
   wheel, the trackpad, a drag and the arrow keys all still move it, and it is
   still focusable and announced as a scrollable region. */
.notice-board-frame { scrollbar-width: none; }
.notice-board-frame::-webkit-scrollbar { width: 0; height: 0; }

/* ---------- One notice, in full ---------- */
.notice-detail {
  background: var(--panel);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: clamp(18px, 3vw, 34px);
  /* Extra room on the right for the pinned Donate Us tab, which is
     `position: fixed` at the middle of the viewport edge and sits over
     whatever is under it. Without this the notice's tag chip — pushed to the
     right of its row — was half hidden behind the button.

     Reserved only between 700px and 1500px. Below 700px the button moves to
     the bottom of the screen and is not beside the content at all; above
     1500px the 900px card is centred with hundreds of pixels of clear navy
     either side, so there is nothing to avoid. */
  max-width: 900px;
  margin: 0 auto;
}
@media (min-width: 700px) and (max-width: 1500px) {
  .notice-detail { padding-right: clamp(34px, 7vw, 92px); }
}
.notice-back { margin-bottom: 18px; }
.notice-detail-title {
  color: var(--gold);
  font-size: var(--fs-h2);
  margin: 12px 0 18px;
  overflow-wrap: break-word;
}
.notice-detail-body {
  color: var(--ink);
  /* A notice is often a pasted paragraph with a long unbroken reference or a
     URL in it; `anywhere` is what stops one of those widening the card past
     the screen on a 320px phone. */
  overflow-wrap: anywhere;
  max-width: var(--measure);
}
.notice-detail-foot { margin-top: 26px; }

.notice-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 22px;
}
.notice-action {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  max-width: 100%;
  overflow-wrap: anywhere;
}
.notice-action small { color: var(--muted); font-weight: 400; }

.notice-card-foot { margin-top: 16px; }

/* ---------- Links that must not go purple ----------
   A visited link takes the browser's default colour unless something says
   otherwise, and on navy that default is close to unreadable. Every link the
   site generates into a list — notices, ticker lines, search results — carries
   this class. */
.visited-safe,
.visited-safe:visited { color: inherit; }
a.btn.visited-safe,
a.btn.visited-safe:visited { color: var(--gold); }
a.btn-gold.visited-safe,
a.btn-gold.visited-safe:visited { color: var(--c-navy-deep); }

/* ---------- Compact hero (Notice Board / Contact Us — no banner photo) ---------- */
.page-hero.hero-compact {
  height: 200px;
  min-height: 200px;
  padding: 20px;
  overflow: hidden;
}
@media (max-width: 700px) {
  .page-hero.hero-compact { height: 168px; min-height: 168px; }
}
.hero-decor {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}
.hero-decor canvas { width: 100%; height: 100%; display: block; }

/* ---------- Upcoming events: one card at a time, sliding ----------
   initEventSlider in site.js: the track is every card side by side, the
   viewport shows one, and the track is moved a whole width at a time - by
   the timer every ten seconds, by a drag or swipe, by the arrows or dots. */
.event-strip { overflow: visible; }
.event-slider { position: relative; max-width: 780px; margin: 0 auto; outline: none; }
.event-slider:focus-visible { box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.35); border-radius: var(--radius); }
.event-viewport { overflow: hidden; touch-action: pan-y; cursor: grab; border-radius: var(--radius); }
.event-viewport:active { cursor: grabbing; }
.event-track { display: flex; width: 100%; transition: transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1); will-change: transform; }
.event-slide { flex: 0 0 100%; min-width: 0; display: flex; justify-content: center; }
.event-nav {
  position: absolute; top: 40%; z-index: 2;
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--gold); background: rgba(6, 26, 46, 0.78); color: var(--gold);
  font-size: 1.6rem; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.event-nav:hover, .event-nav:focus-visible { background: var(--gold); color: #061a2e; }
.event-nav-prev { left: -14px; }
.event-nav-next { right: -14px; }
.event-dots { display: flex; justify-content: center; gap: 8px; margin-top: 14px; }
.event-dot { width: 10px; height: 10px; border-radius: 50%; border: 1px solid var(--gold); background: transparent; padding: 0; cursor: pointer; }
.event-dot.is-on { background: var(--gold); }
@media (max-width: 720px) {
  .event-nav { width: 36px; height: 36px; font-size: 1.3rem; top: 32%; }
  .event-nav-prev { left: 4px; }
  .event-nav-next { right: 4px; }
}
.event-card {
  width: 100%;
  max-width: 780px;
  flex-shrink: 0;
  cursor: pointer;
  position: relative;
  background: var(--panel-2);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
}
/* 'contain' so event posters are never cropped — they carry printed details */
.event-card img {
  width: 100%;
  height: 360px;
  object-fit: contain;
  background: #061a2e;
  display: block;
}
/* The photo's caption, set in the Admin Panel, along the picture's foot. */
.event-card .event-photo-cap {
  position: absolute; left: 0; right: 0; top: 360px; transform: translateY(-100%);
  padding: 6px 14px; font-size: var(--fs-small); color: #fff;
  background: linear-gradient(180deg, rgba(6, 26, 46, 0), rgba(6, 26, 46, 0.85));
  text-align: center;
}
/* One size for every card, whatever was typed: the photo box is a fixed
   height and so is the block under it; the title is clipped to two lines,
   and the description is not on the card at all - the card's own click
   opens the whole event (openEventDialog). */
.event-card .event-info { height: 270px; overflow: hidden; justify-content: flex-start; }
/* Nothing in the block may shrink to make room - a fixed box with items
   that give way had the title collapsing to nothing on a busy card. */
.event-card .event-info > * { flex-shrink: 0; }
.event-card .event-title { line-height: 1.3; max-height: 2.6em; overflow: hidden; max-width: 100%; }
.event-card .event-venue { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; max-width: 100%; }
.event-card .event-more { color: var(--gold); font-size: var(--fs-tiny); letter-spacing: 0.4px; text-transform: uppercase; }
.event-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.event-card .event-info {
  padding: 18px 16px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
  min-width: 0;
}
.event-card .event-venue {
  color: var(--muted);
  font-size: var(--fs-small);
  overflow-wrap: anywhere;
}
/* The card's buttons, as one block at the bottom.

   `margin-top: auto` is on the block rather than on either button: with it on
   both, the free space would be split *between* them and the card would end
   with a gap down its middle. One auto margin, one group. */
.event-card .event-actions {
  margin-top: auto;
  padding-top: 10px;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  align-items: center;
  width: 100%;
  min-width: 0;
}

/* Sized down from the page's standard button so a card is not mostly button. */
.event-card .event-link,
.event-card .event-join {
  padding: 8px 18px;
  font-size: var(--fs-small);
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  overflow-wrap: anywhere;
}
.event-card .event-date { color: var(--red); font-weight: 700; font-size: 0.95rem; }
.event-card .event-title { color: #fff; font-weight: 700; margin-top: 6px; font-size: 1.18rem; }
.event-card .no-img {
  height: 360px;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel);
  color: var(--gold);
  font-size: 2.4rem;
}

/* ---------- Responsive ---------- */
@media (max-width: 1024px) {
  /* The hero title and the page padding are clamps now, so the two overrides
     that used to live here would only have fought them. What is left is the
     one thing genuinely specific to this width. */
  .page-hero { margin: 0 16px; }
}
@media (max-width: 700px) {
  .contact-grid { grid-template-columns: 1fr; }
  .page-hero { padding: 30px max(14px, var(--fab-room)); margin: 0 10px; height: 240px; min-height: 240px; }
  .page-hero.hero-full { height: auto; }

  /* Home poster on a phone. The artwork is much wider than it is tall, so at
     this width it only wants ~200px of height while the logo + title stack
     needs ~550px. Left alone, the poster sat as a short strip in the middle of
     a much taller hero with the medallion stranded above it in empty blue.
     Fix: the slide paints twice — a blown-up blurred copy that fills the whole
     hero, and the untouched poster centred on top of it at its real
     proportions. The medallion and title then sit over banner artwork the whole
     way down, exactly as they do on desktop, and none of the poster is cropped
     away. Setting the slide's own background-size to 0 stops it painting a
     third time; the image itself is still its computed background-image, which
     is what the two pseudo-elements inherit. */
  .page-hero.hero-full.hero-sized .hero-slides .slide {
    background-size: 0 0;
  }
  .page-hero.hero-full.hero-sized .hero-slides .slide::before,
  .page-hero.hero-full.hero-sized .hero-slides .slide::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image: inherit;
    background-repeat: no-repeat;
    background-position: center;
  }
  .page-hero.hero-full.hero-sized .hero-slides .slide::before {
    background-size: cover;
    /* scaled up so the blur doesn't fade out against the hero's edges */
    transform: scale(1.2);
    filter: blur(22px) saturate(1.15);
  }
  .page-hero.hero-full.hero-sized .hero-slides .slide::after {
    background-size: contain;
  }
  /* The sharp copy lands right where the title does, and the poster carries gold
     lettering of its own, so the phone needs a little more dimming than the
     desktop 0.62 to keep the two from fighting. */
  .page-hero.hero-full.hero-sized .hero-overlay {
    background: rgba(5, 18, 36, 0.72);
  }
  .section-title { font-size: 1.55rem; }
  .panel-title { font-size: 1.15rem; letter-spacing: 3px; }
  .nav-inner { gap: 6px; }
  .panel { padding: 18px; }
  .event-card { width: 100%; }
  .event-card img, .event-card .no-img { height: 220px; }
  .event-card .event-photo-cap { top: 220px; }
  /* On a phone the card is barely 250px wide and the buttons stack; the
     block grows to hold them rather than clipping the last one. */
  .event-card .event-info { height: auto; min-height: 270px; }
  .quick-btns .btn { width: 100%; text-align: center; }
}

/* The message that tells you what just happened - or what went wrong.

   It used to sit in the bottom-right corner in dark red on navy, which is
   where the eye is not and a colour the club could not read. It is now at
   the top, over the middle of the screen where the work is, and an error
   wears a plain red band with white text and a ⚠ in front of it: the two
   things that make a failure legible are contrast and position, and it had
   neither. */
.toast {
  position: fixed;
  top: 18px; left: 50%;
  transform: translateX(-50%);
  max-width: min(680px, calc(100vw - 32px));
  background: var(--gold);
  color: #0b1f38;
  padding: 13px 20px;
  border-radius: 10px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.35);
  display: none;
  z-index: 100000;          /* over every dialog: a dialog is where errors happen */
  font-weight: 700;
  line-height: 1.45;
  text-align: center;
}
.toast.show { display: block; animation: toast-drop .22s ease; }
@keyframes toast-drop { from { transform: translate(-50%, -14px); opacity: 0; } }
.toast.error {
  background: #c0392b;
  color: #fff;
  border: 1px solid #ff8d7e;
  box-shadow: 0 14px 34px rgba(192, 57, 43, 0.4);
  text-align: left;
}
.toast.error::before { content: "⚠ "; font-weight: 800; }

/* ---- One ↻ per card (refresh-buttons.js) ---------------------------------
   The symbol alone: a card head already carries a heading and a count, and
   a third worded control competes with them. */
.card-refresh {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--c-gold-veil, rgba(216, 178, 94, 0.45));
  background: transparent;
  color: var(--gold);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, transform .3s ease;
}
.card-refresh:hover { background: rgba(216, 178, 94, 0.16); border-color: var(--gold); }
.card-refresh:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.card-refresh:disabled { cursor: default; opacity: 0.75; }
.card-refresh.is-busy { animation: card-refresh-spin .7s linear infinite; }
@keyframes card-refresh-spin { to { transform: rotate(360deg); } }
/* Where there is no head to sit in, it sits in the card's own top corner. */
.has-floating-refresh { position: relative; }
.has-floating-refresh > .card-refresh { position: absolute; top: 12px; right: 12px; z-index: 3; }
/* The corner it sits in, and the room the card's own heading leaves for it. */
.has-floating-refresh > :is(h3, h4, .ov-panel-head) { padding-right: 44px; }

/* ==================================================================
   VISUAL POLISH LAYER — 3D depth, motion & shine
   Nothing above is removed. This only adds looks, not features.
   ================================================================== */

/* ---- Smooth scrolling; no page scrollbar ---- */
html { scroll-behavior: smooth; }

/* The page's own scrollbar is not drawn. It used to be an 11px bar with a
   gold gradient thumb, which became a bright cyan stripe down the right-hand
   edge when the accent turned cyan, and the club asked for it to go - on
   every page, the Admin Panel included (admin-atmosphere.css used to restyle
   the same bar; that is gone too). The page still scrolls exactly as before:
   wheel, touch, keys, and the browser's own drag on the hidden bar's edge.
   Scroll areas inside the page (a chat thread, the mission rail) keep their
   own, so a long list still shows there is more of it. */
html { scrollbar-width: none; }
html::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* ---- Soft glow blobs behind the page (depth) ---- */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(circle at 12% 18%, rgba(216,178,94,0.09), transparent 42%),
    radial-gradient(circle at 88% 72%, rgba(63,132,204,0.12), transparent 46%);
}
main, footer, .page-hero { position: relative; z-index: 0; }
header.site-header { z-index: 500; }
.dropdown-menu { z-index: 600; }

/* ---- Fade-up entrance for main blocks ---- */
@keyframes riseIn {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: translateY(0); }
}
main > section,
main > .panel,
main > div > .panel,
.committee-section,
.custom-section {
  animation: riseIn 0.7s ease both;
}

/* ---- Header: nav underline — REMOVED ----
   There used to be a gold bar under each nav item, drawn on `::after`, scaled
   in on hover and held at scaleX(1) while the item was active. It is gone: the
   active item is now marked by the limelight above it (see
   /css/limelight-nav.css), and a bar underneath as well made two indicators
   competing for the same job — a line under the word and a light over it.

   `position: relative` is kept. It is not part of the underline: `.dropdown`
   and the nav links rely on it elsewhere, and removing it would change how
   descendants position themselves.

   Nothing about the item's size, padding or text position changes by taking
   the bar away — it was absolutely positioned, so it never occupied space. */
nav.main-nav a, nav.main-nav .dropdown > span { position: relative; }

.brand img { transition: transform 0.5s ease; }
.brand:hover img { transform: rotate(8deg) scale(1.08); }

/* ---- Hero: gentle depth + floating logo ---- */
@keyframes floatSoft {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-9px); }
}
.page-hero .hero-logo { animation: floatSoft 4.5s ease-in-out infinite; }

/* ---- 3D rotating logo medallion (replaces the flat hero-logo image) ---- */
.hero-logo-3d {
  animation: floatSoft 4.5s ease-in-out infinite;
  filter: drop-shadow(0 0 24px rgba(216, 178, 94, 0.45));
  /* three-bg.js sets a fixed pixel size inline when it builds the coin, which
     is why this needs !important — and why it has to be restated at all. It
     also means the coin would otherwise keep its start-up size through every
     resize; matching the img's clamp keeps the two the same at every width. */
  width: clamp(132px, 20vw, 216px) !important;
  height: clamp(132px, 20vw, 216px) !important;
  margin: 0 auto clamp(16px, 2vw, 24px);
}
/* !important because this rule has already lost this argument once. three.js
   writes `width`/`height` inline onto its canvas unless setSize is called with
   updateStyle=false, and an inline style beats a plain rule — which left the
   canvas at its start-up pixel size inside a wrapper the clamp above had made
   wider, so the coin sat against the left edge instead of in the middle.
   three-bg.js now passes that flag; this makes the box the stylesheet's either
   way. */
.hero-logo-3d canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}
/* The flat logo <img> stays in the DOM (as the 3D coin's texture source) but
   must never be visible — !important so a later script resetting its inline
   style (e.g. applySiteLogo) can't accidentally show a duplicate underneath. */
.hero-logo.hero-logo-3d-source { display: none !important; }

/* ---- Fixed ambient particle background (Three.js) ---- */
#three-bg-canvas { display: block; }
@media (max-width: 700px) {
  .hero-logo-3d { filter: drop-shadow(0 0 14px rgba(216, 178, 94, 0.35)); }
}
/* ---- 3D lift for every card-like block ---- */
.card, .member-card, .gallery-item, .notice-item, .event-card,
.page-card, .sub-card, .thumb, .tick-row {
  transform-style: preserve-3d;
  transition: transform 0.35s cubic-bezier(.2,.7,.3,1),
              box-shadow 0.35s ease,
              border-color 0.35s ease;
}
.card:hover, .member-card:hover, .gallery-item:hover,
.notice-item:hover, .event-card:hover {
  transform: perspective(900px) translateY(-9px) rotateX(3deg) scale(1.02);
  box-shadow: 0 18px 42px rgba(0,0,0,0.5), 0 0 34px rgba(216,178,94,0.42);
  border-color: var(--gold);
}

/* Member photo zooms softly inside its frame */
.member-card { overflow: hidden; }
.member-card img { transition: transform 0.6s ease; }
.member-card:hover img { transform: scale(1.07); }

/* Gallery image zoom */
.gallery-item img { transition: transform 0.6s ease; }
.gallery-item:hover img { transform: scale(1.08); }

/* Event cards */
.event-card img { transition: transform 0.6s ease; }
.event-card:hover img { transform: scale(1.06); }

/* ---- Panels: glass finish + gold edge ---- */
.panel {
  background: linear-gradient(160deg, rgba(13,49,84,0.92), rgba(8,32,56,0.92));
  backdrop-filter: blur(3px);
  position: relative;
}
.panel-title { position: relative; display: block; }
.panel-title::after {
  content: '';
  display: block;
  width: 92px; height: 2px;
  margin: 12px auto 0;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}

/* The divider bar under a panel title must keep its own solid fill — the
   parent's background-clip:text would otherwise swallow it. */
.panel-title::after {
  -webkit-text-fill-color: initial;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}

/* ---- Buttons: shine sweep ---- */
.btn { position: relative; overflow: hidden; }
.btn::before {
  content: '';
  position: absolute;
  top: 0; left: -120%;
  width: 60%; height: 100%;
  background: linear-gradient(120deg, transparent, rgba(255,255,255,0.38), transparent);
  transform: skewX(-20deg);
  transition: left 0.55s ease;
}
.btn:hover::before { left: 130%; }
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

/* ---- Notice items: gold bar grows on hover ---- */
.notice-item { position: relative; overflow: hidden; }
.notice-item::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  background: var(--gold);
  transform: scaleY(0.25);
  transform-origin: top;
  transition: transform 0.4s ease;
}
.notice-item:hover::before { transform: scaleY(1); }

/* ---- Skip link (added by renderHeader) ----
   Off-screen until it is focused, then it drops into the top-left corner. It
   is the first thing in the tab order, so a keyboard user can jump straight
   past the navigation on any page. */
.skip-link {
  position: absolute;
  left: 12px;
  top: -60px;                 /* parked out of sight, still in the tab order */
  z-index: 200;
  padding: 10px 18px;
  background: var(--gold);
  color: #071f38;
  font-weight: 800;
  border-radius: 0 0 8px 8px;
  text-decoration: none;
  transition: top 0.18s ease;
}
.skip-link:focus { top: 0; }

/* ---- Buttons and links: a visible focus ring ----
   Inputs already get the gold ring below. Buttons and links were relying on
   whatever the browser draws by default, which the dark background can make
   almost impossible to see. :focus-visible so it appears for keyboard use and
   never on a mouse click. */
.btn:focus-visible,
a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---- Inputs: gold focus ring ---- */
input, textarea, select { transition: border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease; }
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(216,178,94,0.22);
  background: #103055;
}

/* ---- Footer links & location chip ---- */
.footer-links a { transition: transform 0.25s ease, background 0.25s ease, color 0.25s ease; }
.footer-links a:hover { transform: translateY(-3px); }
.footer-location { transition: box-shadow 0.3s ease; }
.footer-location:hover { box-shadow: 0 0 22px rgba(216,178,94,0.35); }

/* ==================================================================
   ADMIN PANEL POLISH (same classes, only looks)
   ================================================================== */
.page-card { position: relative; overflow: hidden; }
.page-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 0%, rgba(216,178,94,0.18), transparent 62%);
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}
.page-card:hover {
  transform: perspective(900px) translateY(-8px) rotateX(4deg) scale(1.03);
  box-shadow: 0 20px 44px rgba(0,0,0,0.55), 0 0 32px rgba(216,178,94,0.4);
}
.page-card:hover::after { opacity: 1; }
.page-card .icon { transition: transform 0.4s ease; display: inline-block; }
.page-card:hover .icon { transform: scale(1.18) translateY(-3px); }

.sub-card:hover { box-shadow: 0 12px 30px rgba(0,0,0,0.45), 0 0 22px rgba(216,178,94,0.22); }
.item-row { transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; }
.item-row:hover {
  transform: translateX(5px);
  border-color: var(--gold);
  box-shadow: 0 8px 22px rgba(0,0,0,0.4);
}
.thumb:hover { transform: translateY(-5px) scale(1.03); box-shadow: 0 0 22px rgba(216,178,94,0.35); }
.thumb img { transition: transform 0.5s ease; }
.thumb:hover img { transform: scale(1.07); }
.tick-row:hover { transform: translateY(-3px); }
.tab-btn { transition: transform 0.25s ease, box-shadow 0.25s ease; }
.tab-btn:hover { transform: translateY(-2px); }
.detail-title { text-shadow: 0 2px 12px rgba(216,178,94,0.25); }

/* ==================================================================
   Motion safety + mobile: keep it light
   ================================================================== */
@media (max-width: 700px) {
  body::before { display: none; }
  .card:hover, .member-card:hover, .gallery-item:hover,
  .notice-item:hover, .event-card:hover, .page-card:hover {
    transform: translateY(-4px);
  }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ==================================================================
   NOTICE CARDS — corner chips, tag, detail rows
   ================================================================== */
.notice-card {
  background: linear-gradient(165deg, #0b2b4a, #08203a);
  border: 1px solid var(--gold);
  border-radius: 14px;
  padding: 22px 24px 24px;
  margin-bottom: 22px;
  box-shadow: var(--shadow);
  transition: transform 0.35s cubic-bezier(.2,.7,.3,1), box-shadow 0.35s ease;
}
.notice-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 16px 38px rgba(0,0,0,0.5), 0 0 30px rgba(216,178,94,0.35);
}
.notice-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
/* The right-hand end of a card's top row: the link / file icons, then the
   tag at the very edge - whatever else the row carries. */
.notice-top-right { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }
.notice-top-right:empty { display: none; }
/* A detail row that opens somewhere - a link, an email, a phone - is a
   small card the visitor can see is for clicking, sized by its words:
   "Join now" is a short pill, a whole address a longer one. */
.nd-link {
  display: inline-flex; align-items: center; gap: 6px;
  max-width: 100%; padding: 6px 14px; border-radius: 999px;
  background: linear-gradient(90deg, rgba(34, 211, 238, 0.16), rgba(34, 211, 238, 0.06));
  border: 1px solid rgba(34, 211, 238, 0.55);
  color: var(--gold); font-weight: 700; font-size: 0.86rem; letter-spacing: 0.3px;
  text-decoration: none; word-break: break-all; line-height: 1.3;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
  transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.nd-link::after { content: '↗'; font-size: 0.8em; opacity: 0.85; }
.nd-link[href^="mailto:"]::after { content: '✉'; }
.nd-link[href^="tel:"]::after { content: '📞'; }
.nd-link:hover { transform: translateY(-1px); background: var(--gold); color: #0b1a2e; }
.nd-link:hover::after { opacity: 1; }
.notice-date-chip {
  background: #061a2e;
  border: 1px solid var(--gold-dark);
  color: #ffe6a8;
  font-weight: 700;
  font-size: 0.88rem;
  padding: 7px 14px;
  border-radius: 8px;
}
.notice-tag {
  background: linear-gradient(90deg, var(--gold), #f0d089);
  color: #16233a;
  font-weight: 800;
  font-size: 0.76rem;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  padding: 6px 15px;
  border-radius: 6px;
  box-shadow: 0 0 16px rgba(216,178,94,0.35);
}
.notice-heading {
  color: #fff;
  font-size: 1.35rem;
  margin: 0 0 10px;
}
.notice-desc { color: var(--ink); margin: 0; }

.notice-details {
  margin-top: 18px;
  border: 1px solid rgba(216,178,94,0.35);
  border-radius: 10px;
  overflow: hidden;
  background: rgba(4, 20, 38, 0.55);
}
.nd-row {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 12px;
  padding: 12px 18px;
  border-bottom: 1px solid rgba(216,178,94,0.18);
}
.nd-row:last-child { border-bottom: none; }
.nd-label { color: var(--gold); font-weight: 700; }
.nd-value { color: var(--ink); word-break: break-word; }
@media (max-width: 700px) {
  .nd-row { grid-template-columns: 1fr; gap: 2px; padding: 12px 14px; }
  .notice-heading { font-size: 1.12rem; }
  .notice-card { padding: 18px 16px 20px; }
}

/* ---- Home page: heading list that opens the notice ---- */
.notice-acc {
  border: 1px solid var(--gold-dark);
  border-radius: 10px;
  margin-bottom: 12px;
  overflow: hidden;
  background: var(--panel-2);
}
.notice-acc-head {
  width: 100%;
  background: transparent;
  border: none;
  color: #fff;
  font-family: inherit;
  font-size: 1.02rem;
  font-weight: 600;
  text-align: left;
  padding: 15px 18px;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  transition: background 0.25s ease, color 0.25s ease;
}
.notice-acc-head:hover { background: rgba(216,178,94,0.12); color: var(--gold); }
.na-right { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.na-date { color: var(--red); font-size: 0.82rem; font-weight: 700; }
.na-arrow { color: var(--gold); transition: transform 0.3s ease; }
.notice-acc.open .na-arrow { transform: rotate(180deg); }
.notice-acc-body { display: none; padding: 0 14px 4px; }
.notice-acc.open .notice-acc-body { display: block; }
.notice-acc-body .notice-card { margin-bottom: 14px; }
@media (max-width: 700px) {
  .notice-acc-head { font-size: 0.94rem; padding: 13px 14px; }
  .na-date { display: none; }
}

/* ==================================================================
   MEMBER CARDS — round photos, deep navy + soft gold (like the club site)
   ================================================================== */
.member-grid .member-card {
  background: linear-gradient(170deg, #0c2c4c, #071f38);
  border: 1px solid var(--gold-dark);
  border-radius: 14px;
  padding: 26px 16px 24px;
  overflow: visible;
}
.member-grid .member-card img {
  width: 150px;
  height: 150px;
  border-radius: 50%;
  border: 3px solid var(--gold);
  border-bottom: 3px solid var(--gold);
  object-fit: cover;
  margin: 0 auto 16px;
  box-shadow: 0 0 22px rgba(216,178,94,0.28);
}
.member-grid .member-card .avatar-fallback {
  width: 150px;
  height: 150px;
  border-radius: 50%;
  border: 3px solid var(--gold);
  margin: 0 auto 16px;
  font-size: 2.6rem;
  box-shadow: 0 0 22px rgba(216,178,94,0.28);
}
.member-grid .member-card .m-body { padding: 0; }
.member-grid .member-card h4 {
  color: #fff;
  font-size: 1.12rem;
  font-weight: 700;
  margin: 0 0 6px;
}
.member-grid .member-card .role {
  color: var(--gold);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.5px;
}
.member-grid .member-card:hover {
  border-color: var(--gold);
  box-shadow: 0 16px 36px rgba(0,0,0,0.5), 0 0 30px rgba(216,178,94,0.4);
}
.member-grid .member-card:hover img,
.member-grid .member-card:hover .avatar-fallback {
  transform: scale(1.05);
  box-shadow: 0 0 32px rgba(216,178,94,0.5);
}
.member-grid .member-card img,
.member-grid .member-card .avatar-fallback { transition: transform 0.45s ease, box-shadow 0.45s ease; }

@media (max-width: 700px) {
  /* Two cards to a row means each one is roughly half the screen minus the
     gutters and the gap. At 320px that is a 139px track and a 107px content
     box once the card's own padding is off it — so a fixed 115px medallion
     was 8px wider than the space it had, and every card on the narrowest
     phones bulged.

     The clamp sizes it from the viewport instead: it is the same 115px from
     about 360px upwards, and shrinks below that rather than overflowing. The
     padding comes in at the same time, which is where most of the room comes
     back from. */
  .member-grid .member-card { padding: 18px 10px 16px; }
  .member-grid .member-card img,
  .member-grid .member-card .avatar-fallback {
    width: clamp(86px, 32vw, 115px);
    height: clamp(86px, 32vw, 115px);
  }
}

/* ==================================================================
   MEMBER PORTAL — split light/dark sign-in card
   ================================================================== */
.member-portal {
  display: grid;
  grid-template-columns: 1fr 1fr;
  max-width: 1060px;
  margin: 0 auto 40px;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  border: 1px solid var(--gold);
  position: relative;
  z-index: 1;
}
.member-portal-side {
  background: linear-gradient(160deg, #0d3154, #071f38);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 58px 32px;
  text-align: center;
  position: relative;
  overflow: hidden;
}
.member-portal-side img { width: 165px; margin-bottom: 18px; border-radius: 50%; }
.member-portal-side h3 { color: var(--gold); letter-spacing: 4px; margin: 6px 0; font-size: 1.5rem; }
/* `--c-ink-light-mute`, not `--muted`.

   This side of the card is a literal navy gradient in *both* themes — that is
   what "split light/dark" above means — so text on it must not follow the
   theme's own muted ink. `--muted` resolves to the dark-on-pale slate with
   dark mode off, which put #475569 on #0d3154: 1.75:1, the club's report of
   headings they could not make out. The palette has a value for exactly this
   job — secondary text on a dark surface — and it is the same in both themes. */
.member-portal-side .portal-tagline {
  color: var(--c-ink-light-mute);
  letter-spacing: 2px;
  font-size: 0.85rem;
}
.member-portal-side .portal-reg {
  color: var(--c-ink-light-mute);
  font-size: 0.85rem;
  margin-top: 14px;
  line-height: 1.6;
}
.member-portal-form {
  background: #fdf6e8;
  padding: 58px 44px;
}
.member-portal-form h3 { color: #1c1c1c; margin-top: 0; font-size: 1.45rem; }
.member-portal-form p { color: #6b5d3f; font-size: 0.92rem; margin-top: -6px; }
.member-portal-form input { padding: 13px 14px; font-size: 1rem; }
.member-portal-form .btn { padding: 13px 22px; }
.member-portal-form label { color: #4a3f28; }
/* Amber "read me first" callout above the login fields */
.portal-note {
  background: #fdfae0;
  border: 1px solid #f0e3a8;
  border-left: 5px solid var(--gold);
  border-radius: 8px;
  padding: 14px 16px;
  margin: 18px 0 22px;
  color: #6b4f13;
  font-size: 0.88rem;
  line-height: 1.6;
}
.portal-note strong {
  display: block;
  color: #7a5a12;
  letter-spacing: 0.5px;
  margin-bottom: 4px;
}
/* The note is glass now (glass-cards.css) - a dark pane on the cream sign-in
   panel - so the amber-era ink above would vanish into it. Light ink, and the
   heading in the notice-board gold; the links inside keep the panel's gold. */
:root .member-portal-form .portal-note { color: #DCE8F2; }
:root .member-portal-form .portal-note strong { color: #F8D878; }
:root .member-portal-form .portal-note a,
:root .member-portal-form .portal-note button { color: #7DE9F7; }

/* "Forgot password?" — a plain text link, not a button, despite the <button> tag
   (it toggles the panel below, so a button is the correct accessible element). */
.portal-forgot-link {
  display: block;
  width: 100%;
  margin: 14px 0 0;
  padding: 0;
  background: none;
  border: none;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  color: #1c2b45;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color 0.2s ease;
}
.portal-forgot-link:hover { color: var(--gold-dark); }
.portal-forgot-panel { margin-top: 14px; margin-bottom: 0; }
.portal-forgot-contacts {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
}
.portal-forgot-contacts a {
  color: #7a5a12;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  word-break: break-word;
}
.portal-forgot-contacts a:hover { color: var(--gold-dark); }
.member-portal-form input {
  background: #fff;
  color: #1c1c1c;
  border: 1px solid #d8c9a0;
}
.member-portal-form input::placeholder { color: #9a8c6a; }
.member-portal-form input:focus {
  border-color: var(--gold-dark);
  box-shadow: 0 0 0 3px rgba(216,178,94,0.25);
  background: #fff;
}
/* Signed in (dash-mode), the panel is navy glass - see member-dashboard.css -
   so its fields take the site's own field colours rather than the sign-in
   panel's white. */
:root .member-portal.dash-mode .member-portal-form input,
:root .member-portal.dash-mode .member-portal-form input:focus,
:root .member-portal.dash-mode .member-portal-form textarea,
:root .member-portal.dash-mode .member-portal-form select {
  background: var(--surface-input);
  color: var(--surface-text);
  border-color: var(--surface-line);
}
:root .member-portal.dash-mode .member-portal-form input::placeholder,
:root .member-portal.dash-mode .member-portal-form textarea::placeholder { color: var(--surface-text-muted); }
:root .member-portal.dash-mode .member-portal-form input:focus,
:root .member-portal.dash-mode .member-portal-form textarea:focus {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px var(--c-gold-veil-soft);
}
@media (max-width: 700px) {
  .member-portal { grid-template-columns: 1fr; }
  .member-portal-side { padding: 32px 20px; }
  .member-portal-form { padding: 32px 22px; }
}

/* ---------- Member portal: one-time code stage ---------- */
.otp-input {
  text-align: center;
  /* Six digits at 1.9rem with 14px of tracking is about 290px of glyphs, which
     did not fit a 320px screen once the field's own padding and the panel's
     gutters were taken off it — the last digit sat under the border. Both the
     size and the tracking scale now, and the field is allowed to shrink. */
  font-size: clamp(1.3rem, 0.8rem + 2.2vw, 1.9rem) !important;
  font-weight: 800;
  letter-spacing: clamp(6px, 2.4vw, 14px);
  text-indent: clamp(6px, 2.4vw, 14px);   /* balances the trailing letter-space */
  font-family: var(--font-mono);
  min-width: 0;
  width: 100%;
}
.otp-actions { display: flex; flex-wrap: wrap; gap: 4px 24px; }
.otp-actions .portal-forgot-link { width: auto; }
.otp-expiry { color: #6b5d3f !important; font-size: 0.86rem !important; margin-top: 14px !important; }
.otp-dev-code {
  margin-top: 10px;
  font-family: 'Courier New', monospace;
  font-size: 1.7rem;
  font-weight: 800;
  letter-spacing: 8px;
  color: #7a5a12;
}

/* ---------- Member portal: forgot / reset password ----------
   Lives inside .portal-forgot-panel, which is an amber .portal-note — the
   inputs there need the same light-panel treatment the sign-in fields get. */
.fp-step { margin-top: 14px; }
.fp-step label {
  display: block;
  margin-bottom: 5px;
  font-size: 0.85rem;
  font-weight: 700;
  color: #4a3f28;
}
.fp-step input {
  width: 100%;
  padding: 12px 13px;
  margin-bottom: 12px;
  font-size: 1rem;
  background: #fff;
  color: #1c1c1c;
  border: 1px solid #d8c9a0;
  border-radius: 8px;
}
.fp-step input:focus {
  border-color: var(--gold-dark);
  box-shadow: 0 0 0 3px rgba(216, 178, 94, 0.25);
  outline: none;
}
.fp-btn { width: 100%; padding: 12px 20px; }
.fp-sent { color: #6b5d3f; font-size: 0.88rem; margin: 0 0 12px; }
.fp-done { color: #2f6b3a; font-weight: 700; font-size: 0.92rem; margin: 14px 0 0; }
.fp-fallback { color: #6b5d3f; font-size: 0.84rem; margin: 16px 0 0; }
.fp-dev { margin: 0 0 14px; }
/* The panel and each step inside it are glass now (glass-cards.css), so the
   amber-era inks above sit on navy: everything in here goes light, and the
   buttons take the site's accent rather than the cream panel's deepened gold. */
:root .member-portal-form .portal-forgot-panel .fp-step label,
:root .member-portal-form .portal-forgot-panel .fp-sent,
:root .member-portal-form .portal-forgot-panel .fp-fallback { color: #DCE8F2; }
:root .member-portal-form .portal-forgot-panel .fp-done { color: #4ADE80; }
:root .member-portal-form .portal-forgot-panel .fp-step input {
  background: var(--surface-input);
  color: var(--surface-text);
  border-color: var(--surface-line);
}
:root .member-portal-form .portal-forgot-panel .fp-step input::placeholder { color: var(--surface-text-muted); }
:root .member-portal-form .portal-forgot-panel .btn-gold { background: #22D3EE; color: #0b1f38; }
:root .member-portal-form .portal-forgot-panel .btn-gold:hover { background: #0891B2; color: #fff; }
:root .member-portal-form .portal-forgot-panel .btn-outline { color: #7DE9F7; border-color: #7DE9F7; }

/* ---------- Member portal: signed-in dashboard ---------- */
.md-top { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.md-avatar {
  width: 86px; height: 86px; flex-shrink: 0;
  border-radius: 50%; object-fit: cover;
  border: 3px solid var(--gold);
}
/* No minimum at all, and long tokens may break.

   This was `min-width: 170px`. A flat floor is not a floor on a phone, it is
   an overflow: the box sits next to an 86px avatar in a column about 184px
   wide on a 320px screen, so 170px could not be honoured and the excess pushed
   the whole dashboard off the side of the screen rather than wrapping.

   `min-width: 0` is the right answer rather than a smaller number, because
   `flex: 1` already sizes this box — the minimum was only ever there to stop
   it collapsing, and text inside it does that job on its own.

   `overflow-wrap: anywhere` is for the membership ID. "BNSCA/MBR/000002" is
   one unbreakable run to a browser, so on the narrowest screens it hung out of
   the panel no matter how the boxes around it were sized. It is allowed to
   break mid-token now, which is the correct trade for an identifier: an ID
   over two lines is readable, an ID half off the screen is not. */
/* `min(170px, 100%)`, not a flat 170px and not 0 either.

   The floor does a real job: `.md-top` wraps, so a minimum on this box is what
   pushes the session counter and Logout down to their own line when the row
   gets tight, instead of squeezing the member's name into a column too narrow
   to read. Dropping it to 0 fixed the phone and quietly broke the middle — at
   725px the name had 103px and wrapped for no reason.

   A flat 170px was the original fault: a minimum cannot be shrunk past, so on
   a phone the excess did not wrap, it pushed the whole dashboard off the side
   of the screen. `min(170px, 100%)` is 170px wherever there is room for 170px
   and never more than the space available, which is what "minimum" should
   have meant all along.

   `overflow-wrap: anywhere` is the backstop for the membership ID —
   "BNSCA/MBR/000002" is one unbreakable 163px run, and on a 320px screen it
   would otherwise hang out of the panel however the boxes around it are
   sized. */
.md-who {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}
/* There is deliberately no minimum width on this box at any screen size.

   It had one — 170px — and a minimum here cannot work, because this box does
   not control how much room it is given: `.md-identity` does, and that is
   itself being squeezed by the session counter beside it. At around 725px the
   parent could spare 104px, the 170px floor was honoured anyway, and the
   membership ID ran straight out of the button and underneath the Session
   pill. A floor that its own parent cannot fund is not a floor.

   The job that minimum was really doing — making the session counter drop to
   its own line before the name gets crushed — is done on `.md-identity` in
   member-dashboard.css, which is the box that actually sits on that row. */
.md-who h3 { margin: 0; font-size: 1.6rem; }
.md-who p { margin: 2px 0 0 !important; color: var(--surface-text-muted); }
.md-who strong { color: var(--surface-text); }
.md-logout { flex-shrink: 0; }
.md-notice { margin-top: 22px; }
.md-heading {
  color: var(--surface-text);
  font-size: 1.15rem;
  margin: 30px 0 6px;
  padding-top: 22px;
  border-top: 1px solid var(--surface-line);
}
.md-hint { color: var(--surface-text-muted) !important; font-size: 0.86rem !important; margin: 0 0 14px !important; }

.md-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.md-table th, .md-table td {
  text-align: left;
  padding: 11px 14px;
  border-bottom: 1px solid var(--surface-line);
  font-size: 0.93rem;
}
.md-table th { color: var(--surface-text-muted); font-weight: 800; width: 45%; background: var(--surface-input); }
.md-table td { color: var(--surface-text); word-break: break-word; }
.md-payment { color: var(--gold-dark) !important; font-weight: 800; }

.md-card-wrap { text-align: center; }
.md-card-wrap canvas {
  width: 100%;
  max-width: 330px;
  height: auto;
  margin: 0 auto;
  border-radius: 10px;
  box-shadow: 0 6px 22px rgba(0,0,0,0.22);
}
.md-full { width: 100%; margin-top: 16px; }

/* Download PDF / Download PNG. They share a row on anything wider than a phone
   and stack on a narrow one, so the labels never get squeezed to a few
   characters. `min-width: 0` is deliberately *not* set on the buttons: their
   content width is the floor that makes the row wrap instead of clipping. */
.md-card-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;      /* one download symbol, under the centred card */
  gap: 10px;
  margin-top: 16px;
}
/* An author rule that sets display beats the [hidden] attribute, which is only
   display:none in the UA stylesheet — without this the buttons would still be
   on screen for a member whose card failed to build. */
.md-card-actions[hidden] { display: none; }
.md-card-actions .btn { flex: 1 1 90px; }
/* Side by side, "Download PDF" gets about 119px of a phone's column and the
   label breaks over three lines inside an 82px-tall button. One per row keeps
   each label on one line. */
@media (max-width: 560px) {
  .md-card-actions .btn { flex: 1 1 100%; }
}
.md-card-note { color: var(--red) !important; margin-top: 14px !important; }


/* ---------- Standing "Donate" call-to-action (every page) ---------- */
.donate-fab {
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 900;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 16px 12px;
  border: 1px solid var(--gold);
  border-right: none;
  border-radius: 12px 0 0 12px;
  background: linear-gradient(160deg, #0d3154, #071f38);
  box-shadow: -4px 4px 18px rgba(0, 0, 0, 0.45);
  color: var(--gold);
  font-weight: 800;
  letter-spacing: 2px;
  font-size: 0.82rem;
  text-transform: uppercase;
  transition: background 0.25s ease, padding-right 0.25s ease;
}
.donate-fab:hover {
  background: linear-gradient(160deg, #14456f, #0a2947);
  padding-right: 18px;
}
.donate-fab-icon { font-size: 1.3rem; }
/* Stacked letters, so the tab stays narrow against the edge of the page */
.donate-fab-text { writing-mode: vertical-rl; text-orientation: mixed; }

/* ---- Catching the eye ----
   The tab sits still most of the time and then hops out from the edge every
   few seconds, while the gold halo breathes and the heart beats. Two separate
   rhythms, so it reads as alive rather than as one blinking thing. Note the
   keyframes have to carry translateY(-50%) too, or the tab would drop out of
   its vertically-centred position the moment the animation starts. */
@keyframes donateNudge {
  0%, 62%, 100% { transform: translateY(-50%) translateX(0) scale(1); }
  68%           { transform: translateY(-50%) translateX(-34px) scale(1.1); }
  74%           { transform: translateY(-50%) translateX(-2px) scale(1); }
  80%           { transform: translateY(-50%) translateX(-26px) scale(1.07); }
  86%           { transform: translateY(-50%) translateX(-2px) scale(1); }
  91%           { transform: translateY(-50%) translateX(-14px) scale(1.03); }
  96%           { transform: translateY(-50%) translateX(-1px) scale(1); }
}
@keyframes donateGlow {
  0%, 100% { box-shadow: -4px 4px 18px rgba(0, 0, 0, 0.45),
                         0 0 10px rgba(216, 178, 94, 0.25); }
  50%      { box-shadow: -4px 4px 18px rgba(0, 0, 0, 0.45),
                         0 0 34px rgba(216, 178, 94, 0.9); }
}
@keyframes donateHeartBeat {
  0%, 100% { transform: scale(1); }
  12%      { transform: scale(1.45); }
  24%      { transform: scale(1); }
  36%      { transform: scale(1.3); }
  48%      { transform: scale(1); }
}
.donate-fab {
  animation: donateNudge 3.4s ease-in-out infinite,
             donateGlow 1.9s ease-in-out infinite;
}
.donate-fab-icon {
  display: inline-block;                     /* transform needs a block box */
  animation: donateHeartBeat 2.2s ease-in-out infinite;
}
/* Hold still once it is being aimed at, so it can't dodge the cursor. */
.donate-fab:hover, .donate-fab:focus-visible { animation-play-state: paused; }

@media (max-width: 700px) {
  /* Stays a side tab on a phone too — just trimmed down so it takes as little
     of a narrow screen as possible. Sitting along the bottom covered content
     and read as part of the footer instead of as a button. */
  .donate-fab {
    padding: 14px 6px;
    gap: 3px;
    font-size: 0.68rem;
    letter-spacing: 1.5px;
    border-radius: 10px 0 0 10px;
  }
  .donate-fab:hover { padding-right: 10px; }
  /* See .join-fab-icon: the emoji sets the tab width, and the tab width is
     what every page pays for twice in `--fab-room`. */
  .donate-fab-icon { font-size: 0.85rem; }
}

/* Constant motion is a genuine problem for some people — honour the setting
   the browser already exposes and leave the tab completely still. */
@media (prefers-reduced-motion: reduce) {
  .donate-fab, .donate-fab-icon { animation: none !important; }
}

/* ---------- Donate page ---------- */
.dn-amount {
  font-size: 1.7rem !important;
  font-weight: 800;
  letter-spacing: 2px;
}
.dn-full { width: 100%; margin-top: 16px; }
.dn-small { font-size: 0.82rem !important; margin-top: 10px !important; }
/* "Paying to BNSCA · ₹1,000" — the same name and figure the donor is about to
   see on their UPI confirmation screen, so the two can be compared. */
.dn-payee {
  background: rgba(4, 20, 38, 0.5);
  border: 1px solid rgba(216, 178, 94, 0.35);
  border-radius: 10px;
  padding: 12px 16px;
  margin: 14px 0 4px;
  color: var(--ink);
  font-size: 0.95rem;
}
.dn-payee strong { color: var(--gold); font-size: 1.08rem; }
.dn-upi { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.dn-upi code {
  flex: 1;
  min-width: 200px;
  background: rgba(4, 20, 38, 0.6);
  border: 1px solid var(--gold-dark);
  border-radius: 8px;
  padding: 12px 14px;
  color: var(--gold);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 1px;
  user-select: all;
  word-break: break-all;
}
.dn-qr-wrap { text-align: center; margin-top: 14px; }
.dn-qr-wrap img {
  width: 100%;
  max-width: 260px;
  margin: 0 auto;
  border-radius: 10px;
  border: 6px solid #fff;
  background: #fff;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
}

/* Floating decorative icons on the portal's dark side */
.member-portal-side .deco-icon {
  position: absolute;
  font-size: 1.8rem;
  opacity: 0.3;
  animation: floatSoft 5s ease-in-out infinite;
  pointer-events: none;
}
@media (max-width: 700px) {
  .member-portal-side .deco-icon { display: none; }
}

/* ==========================================================================
   SITE SEARCH

   The field lives at the end of the navigation — after Contact Us on a
   desktop, at the bottom of the open menu on a phone — and the results drop
   out of the header as one panel across the full bar width.

   Markup is in renderHeader()/setupSiteSearch() in common.js.
   ========================================================================== */

/* The three tools: search, theme, notifications.

   They sit in the bar itself, not inside the collapsible navigation. Inside it
   they were only reachable by opening the hamburger, where they landed under
   the two fixed side tabs and were partly unclickable on a phone. `margin-left:
   auto` pushes the group to the end of the bar, ahead of the hamburger. */
.nav-tools {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  min-width: 0;
  flex: 0 0 auto;
}

.nav-search {
  position: relative;
  display: flex;
  align-items: center;
  flex: 0 1 210px;
  min-width: 0;
}

/* The two icon buttons. Same size, same shape, so they read as a pair beside
   the field rather than as two unrelated controls. */
.nav-tool {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--c-gold-veil);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  color: inherit;
  cursor: pointer;
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease),
              transform var(--t) var(--ease);
}
.nav-tool:hover { background: rgba(255, 255, 255, 0.16); border-color: var(--gold); }
.nav-tool:focus-visible {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.25);
}
.nav-tool:active { transform: scale(0.94); }
.nav-tool-icon { font-size: 1rem; line-height: 1; }

/* The unread badge. A word rather than a dot: a dot says "something", and the
   thing worth saying is "new". It sits on the bell's corner and is allowed to
   overhang it, so the icon underneath is never covered.

   The pulse runs three times and stops. A badge that pulses forever becomes
   part of the furniture and stops being noticed, which is the opposite of what
   it is for. */
.notif-new {
  position: absolute;
  top: -12px;
  right: -12px;
  padding: 0 2px;
  /* The NEW look, with no box round it: the words alone in the blue ->
     green -> lime gradient, floating over the bell like before, blinking
     while something is new. The element's own text sizes it and is
     invisible; ::after draws the same words (data-text) in the gradient. */
  background: none;
  color: transparent;
  font-size: 0.9rem;
  font-weight: 900;
  letter-spacing: 1px;
  text-transform: uppercase;
  line-height: 1.3;
  white-space: nowrap;
  animation: notifNewBlink 1.1s ease-in-out infinite;
}
.notif-new::after {
  content: attr(data-text);
  position: absolute; inset: 0;
  padding: inherit; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(90deg, #1e6bff 0%, #12b5c9 35%, #19c37d 65%, #b6e300 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  /* A faint glow so the letters read on the navy header - no box. */
  filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.55));
}
@keyframes notifNewBlink {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  .notif-new { animation: none; }
}

/* The "new" flag on a single row inside the panel, so opening it says which
   ones are new rather than only that something is. */
.notif-item-new {
  display: inline-block;
  margin-left: 6px;
  padding: 0 6px;
  border-radius: var(--radius-pill);
  background: var(--red, #c0392b);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 800;
  vertical-align: middle;
}
.notif-item.is-new { background: rgba(212, 175, 55, 0.07); }

/* The foot is now only the way out of the panel — the two actions moved up
   into `.notif-top`. */
.notif-foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px 12px;
  flex-wrap: wrap;
  padding: 8px 14px 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
/* The two bulk actions, directly under the title. Wraps rather than squeezing,
   because "Mark all as read" and "Clear all" together are wider than a phone. */
.notif-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  padding: 0 14px 10px;
}
/* With neither action applicable there is nothing to show, and the padding
   would leave a band of empty space under the title. */
.notif-actions:not(:has(.notif-act:not([hidden]))) { display: none; }
.notif-act {
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius-pill);
  background: none;
  color: var(--gold);
  font-size: var(--fs-small);
  font-weight: 700;
  padding: 5px 11px;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease);
}
.notif-act:hover { background: rgba(212, 175, 55, 0.12); border-color: var(--gold); }
/* Clearing is the destructive-feeling one of the two, so it reads as the
   quieter option rather than sitting level with "mark as read". */
.notif-act-clear { color: var(--muted); border-color: var(--surface-veil); }
.notif-act-clear:hover {
  color: var(--red, #c0392b);
  border-color: var(--red, #c0392b);
  background: rgba(192, 57, 43, 0.1);
}

/* The panel. Anchored under the header like the search results, and for the
   same reason: opening it must not move the page behind it. */
.notif-panel {
  position: absolute;
  top: 100%;
  right: var(--gutter);
  z-index: 130;
  width: min(360px, calc(100vw - (2 * var(--gutter))));
  /* Frosted rather than solid, so it takes the colour of whatever page it has
     been opened over — the same trick the header itself uses. The blur is what
     stops the text behind it showing through as text.

     Written as an explicit rgba per theme rather than color-mix() on the
     surface token: color-mix is not resolved by every browser this site has to
     work on, and the version that fell back was fully opaque, which is the one
     thing this rule exists to avoid. --notif-veil is set for both themes below.
   */
  background: var(--notif-veil, rgba(7, 23, 43, 0.82));
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  backdrop-filter: blur(16px) saturate(140%);
  border: 1px solid var(--c-gold-veil);
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  box-shadow: var(--shadow-lg);
}
:root { --notif-veil: rgba(7, 23, 43, 0.82); }
:root[data-theme="light"] { --notif-veil: rgba(255, 255, 255, 0.8); }
/* The panel is a column of three: a fixed head, a scrolling list, a fixed
   foot. `overflow-y: auto` used to be on this element, which meant the title
   and the two actions scrolled away with the notices — the reason the actions
   were unreachable on a long list. The scrolling now belongs to `.notif-body`
   alone, and `min-height: 0` is what allows that child to actually shrink
   inside a flex column instead of pushing the panel taller than its max. */
.notif-panel-inner {
  display: flex;
  flex-direction: column;
  max-height: min(70vh, 460px);
  overscroll-behavior: contain;
  padding: 4px 0 10px;
}
.notif-top {
  flex: 0 0 auto;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.notif-body {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.notif-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px 6px;
  color: var(--gold);
}
.notif-close {
  border: 0;
  background: none;
  color: inherit;
  font-size: 1.4rem;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
  opacity: 0.75;
}
.notif-close:hover { opacity: 1; }

.notif-item {
  display: block;
  padding: 11px 14px;
  color: var(--ink);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  transition: background var(--t) var(--ease);
}
.notif-item:visited { color: var(--ink); }
.notif-item:hover, .notif-item:focus-visible { background: rgba(212, 175, 55, 0.1); }
.notif-item-title { display: block; font-weight: 700; }
.notif-item-meta {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-small);
  color: var(--muted);
}
.notif-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  background: var(--c-gold-veil-soft);
  color: var(--gold);
  font-size: 0.72rem;
}
/* Two lines of the notice, so the panel is a list rather than a wall. */
.notif-item-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 4px;
  font-size: var(--fs-small);
  color: var(--muted);
}
.notif-empty { padding: 16px 14px; color: var(--muted); font-size: var(--fs-small); }
.notif-all { color: var(--gold); font-size: var(--fs-small); white-space: nowrap; }

@media (max-width: 900px) {
  /* There is no room for a text field beside the logo and the hamburger at
     390px, so the field collapses to its magnifier and drops down as a
     full-width row when that is pressed.

     Not `display: none`: the input has to stay focusable so the button can
     open it and focus it in the same gesture. */
  .nav-tools { gap: 5px; }
  .nav-search { flex: 0 0 auto; position: static; }
  /* `.nav-search .nav-search-input`, not `.nav-search-input`.

     The base rule further down this file sets `width: 100%`, and it is the
     later of the two at equal specificity — so it won, and the field was 100%
     of the header *starting at* `left: var(--gutter)`. That put its right edge
     a gutter's width past the screen on every page of the site: the 4px of
     horizontal scroll the club could feel but not see, because the thing
     causing it is invisible until the magnifier is pressed.

     One class more, so `right` is what decides the far edge, which is what
     this rule always meant. */
  .nav-search .nav-search-input {
    position: absolute;
    left: var(--gutter);
    right: var(--gutter);
    top: calc(100% + 6px);
    width: auto;
    opacity: 0;
    pointer-events: none;
    transform: translateY(-6px);
    transition: opacity var(--t) var(--ease), transform var(--t) var(--ease);
    z-index: 130;
  }
  .nav-search.is-open .nav-search-input { opacity: 1; pointer-events: auto; transform: none; }
  .nav-search .nav-search-icon {
    position: static;
    right: auto;
    width: 40px;
    height: 40px;
    border: 1px solid var(--c-gold-veil);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    color: var(--gold);
    display: grid;
    place-items: center;
    opacity: 1;
    padding: 0;
  }
  .nav-search.is-open .nav-search-icon { background: var(--gold); color: var(--c-navy-deep); }
  .nav-tool { width: 40px; height: 40px; }
  .brand img { width: 34px; height: 34px; }
}

/* Four controls and a wordmark do not fit 320px. The mark is what identifies
   the club, so the word is what goes. */
@media (max-width: 400px) {
  .brand .brand-name { display: none; }
  .nav-tools { gap: 3px; }
  .nav-tool, .nav-search .nav-search-icon { width: 36px; height: 36px; }
}

.nav-search-input {
  width: 100%;
  min-width: 0;
  height: 38px;
  padding: 0 34px 0 13px;
  font-size: var(--fs-small);
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius-pill);
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease);
}
.nav-search-input::placeholder { color: var(--c-ink-light-mute); opacity: 0.8; }
.nav-search-input:focus {
  background: rgba(255, 255, 255, 0.14);
  border-color: var(--gold);
  outline: none;
  box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.25);
}
/* Chrome draws its own clear button on type=search and it lands on top of the
   magnifier. The site's own affordance is Escape, which the field handles. */
.nav-search-input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* The magnifier is a real <button> now, because on a phone it is the only way
   to open the collapsed field. On a desktop the field is already open, so it
   is given back its old behaviour and never swallows a click meant for the
   input — see the `min-width: 901px` rule below. */
.nav-search-icon {
  position: absolute;
  right: 8px;
  border: 0;
  background: none;
  color: inherit;
  font-size: 0.85rem;
  line-height: 1;
  padding: 4px;
  opacity: 0.75;
  cursor: pointer;
}
@media (min-width: 901px) {
  .nav-search-icon { pointer-events: none; }
}

/* The results panel. Absolutely positioned under the header rather than
   pushing the page down, so opening it never moves the content behind it. */
.search-panel {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 120;
  background: var(--c-navy-deep);
  border-top: 1px solid var(--c-gold-veil);
  border-bottom: 2px solid var(--gold);
  box-shadow: var(--shadow-lg);
}
.search-panel-inner {
  max-width: var(--container-wide);
  margin: 0 auto;
  padding: 14px var(--gutter) 18px;
  /* Long result lists scroll inside the panel instead of running off the
     bottom of a phone screen with no way back to the field. */
  max-height: min(70vh, 520px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.search-status {
  color: var(--muted);
  font-size: var(--fs-small);
  padding: 2px 0 8px;
}

.search-results { list-style: none; margin: 0; padding: 0; }
.search-results li + li { border-top: 1px solid rgba(255, 255, 255, 0.07); }

.search-hit {
  display: grid;
  /* Two columns on a desktop — the type chip, then the title and excerpt —
     collapsing to one on a phone where 92px of chip is a quarter of the width. */
  grid-template-columns: 108px minmax(0, 1fr);
  gap: 4px 14px;
  align-items: baseline;
  padding: 11px 10px;
  border-radius: var(--radius-sm);
  /* A search result is a link and used to inherit the browser's purple visited
     colour, which on navy is almost unreadable. Set explicitly for both
     states — the same fix the notice links carry. */
  color: var(--ink);
  transition: background var(--t) var(--ease);
  min-width: 0;
}
.search-hit:visited { color: var(--ink); }
.search-hit:hover, .search-hit:focus-visible { background: rgba(212, 175, 55, 0.1); }

.search-hit-type {
  grid-row: span 2;
  font-size: var(--fs-tiny);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius-pill);
  padding: 3px 9px;
  text-align: center;
  align-self: start;
  overflow-wrap: break-word;
}
.search-hit-title {
  font-weight: 700;
  overflow-wrap: anywhere;
}
.search-hit-excerpt {
  grid-column: 2;
  color: var(--muted);
  font-size: var(--fs-small);
  overflow-wrap: anywhere;
}
.search-hit mark {
  /* The accent's own veil, not the old gold's: cyan type on a gold tint
     read as a muddy green box around every matched word. */
  background: var(--c-gold-veil, rgba(212, 175, 55, 0.32));
  color: #EAFDFF;
  border-radius: 3px;
  padding: 0 2px;
}
.search-results li[aria-selected="true"] .search-hit {
  background: rgba(212, 175, 55, 0.16);
  outline: 2px solid var(--gold);
  outline-offset: -2px;
}

@media (max-width: 900px) {
  /* The field is no longer inside the menu — see the collapsed-field rules
     above. What is left here is the results panel, whose type chip stacks
     above the title on a narrow screen. */
  .nav-search-input { height: var(--tap); }
  .search-hit { grid-template-columns: minmax(0, 1fr); }
  .search-hit-type { grid-row: auto; justify-self: start; }
  .search-hit-excerpt { grid-column: 1; }
}

/* ==========================================================================
   EDITABLE CONTENT — the shared classes the page blocks render into.

   These replace the inline `style="..."` attributes the same paragraphs used
   to carry. An inline style cannot be restyled per breakpoint and cannot be
   themed, which is why a 760px-wide paragraph stayed 760px wide on a 360px
   phone until its container clipped it.
   ========================================================================== */

/* The gold emphasis inside body copy. This is the only tag the `**mark**`
   renderer emits, and the only formatting an editor can apply. */
.hl { color: var(--c-gold-light); font-weight: 800; }
/* The 3-D colours an admin can give a marked phrase (admin-markup.js):
   a gradient clipped to the glyphs, lit from above, with a soft drop. */
.hl[class*="hl-3d-"] {
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 2px 1px rgba(0, 0, 0, 0.55));
}
.hl-3d-gold    { background-image: linear-gradient(180deg, #FFF3B0 0%, #F5D060 45%, #B8860B 55%, #FFE58A 100%); }
.hl-3d-cyan    { background-image: linear-gradient(180deg, #E0FCFF 0%, #67E8F9 45%, #0891B2 55%, #A5F3FC 100%); }
.hl-3d-silver  { background-image: linear-gradient(180deg, #FFFFFF 0%, #D9DEE6 45%, #8A93A3 55%, #F0F3F7 100%); }
.hl-3d-fire    { background-image: linear-gradient(180deg, #FFF0A0 0%, #FF9A3C 45%, #E0301E 55%, #FFD36E 100%); }
.hl-3d-rose    { background-image: linear-gradient(180deg, #FFE4F0 0%, #FF8CC6 45%, #C2185B 55%, #FFB3D9 100%); }
.hl-3d-emerald { background-image: linear-gradient(180deg, #E6FFF2 0%, #5EEAA0 45%, #0F7A45 55%, #A8F5C8 100%); }
.hl-3d-violet  { background-image: linear-gradient(180deg, #F1E8FF 0%, #B98CFF 45%, #6A2FC9 55%, #D6BFFF 100%); }
.hl-3d-rainbow { background-image: linear-gradient(90deg, #FF5F6D, #FFC371, #47E5BC, #4FACFE, #C471F5); }
/* Only ever created by the page for a section that had no such line. */
.made-eyebrow { margin-bottom: 6px; text-align: center; }
.made-lead { max-width: 820px; margin: 6px auto 20px; text-align: center; }

.home-block { margin-bottom: clamp(28px, 4vw, 46px); }

.lead-italic,
.lead-justify {
  max-width: var(--measure);
  margin: 14px auto 0;
  color: var(--ink);
}
.lead-italic { font-style: italic; }
/* Justified text on a narrow column opens rivers of white space between words;
   below the point where a line holds enough words to distribute, it is left
   ragged-right instead. */
.lead-justify { text-align: justify; }
@media (max-width: 700px) {
  .lead-justify { text-align: left; }
}

/* Screen-reader-only. Present on the site already in spirit — the skip link
   uses the same idea — but never named, so each new label reinvented 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;
}

/* ==========================================================================
   LIGHT THEME — the rules that could not follow the tokens

   Almost the whole site changes theme for free, because every rule is built
   out of `--navy`, `--panel`, `--ink` and friends, and those now point at the
   `--surface-*` tokens that tokens.css swaps.

   What is left is the handful of places that name a colour outright — a `#fff`
   heading, a `#061a2e` card, the `#0d2846` inside a form field. Each was
   written when there was only ever going to be one background, which was a
   perfectly reasonable thing to assume at the time. They are gathered here
   rather than edited in place for two reasons: the dark theme's rules stay
   exactly as they were and cannot be broken by this, and every light-theme
   decision sits in one block somebody can read end to end.

   Only surfaces and text are touched. No layout, no spacing, no size.
   ========================================================================== */

:root[data-theme="light"] {
  /* The bar is transparent over the page until it is scrolled; this is what it
     becomes once it needs to be solid. */
  --header-bar: linear-gradient(90deg, #FFFFFF 0%, #F2F5FA 100%);
}

/* ---- Header and navigation ---- */
:root[data-theme="light"] header.site-header,
:root[data-theme="light"] .brand,
:root[data-theme="light"] nav.main-nav a.active { color: var(--c-navy); }

:root[data-theme="light"] nav.main-nav a,
:root[data-theme="light"] nav.main-nav .dropdown > span { color: #33415C; }

:root[data-theme="light"] .dropdown-menu {
  background: #FFFFFF;
  border: 1px solid var(--surface-line);
  box-shadow: 0 10px 30px var(--surface-shadow);
}
:root[data-theme="light"] .dropdown-menu a { color: var(--c-ink-dark); }
:root[data-theme="light"] .dropdown-menu a:hover { background: var(--surface-tint); }

:root[data-theme="light"] .nav-search-input {
  background: #FFFFFF;
  color: var(--c-ink-dark);
  border-color: var(--surface-line);
}
:root[data-theme="light"] .nav-search-input::placeholder { color: #7C8AA5; }

:root[data-theme="light"] .search-panel {
  background: #FFFFFF;
  border-top-color: var(--surface-line);
}
:root[data-theme="light"] .search-hit mark {
  background: rgba(154, 122, 30, 0.22);
  color: #5F4A0E;
}
:root[data-theme="light"] .search-results li + li { border-top-color: var(--surface-line); }

/* ---- The two tool buttons, and the notification panel ----
   Same shapes, pale surfaces. A white-on-white pill would vanish, so these
   take the page's own navy at low strength rather than a white veil. */
:root[data-theme="light"] .nav-tool {
  background: rgba(10, 25, 47, 0.05);
  border-color: var(--surface-line);
  color: var(--c-navy);
}
:root[data-theme="light"] .nav-tool:hover {
  background: rgba(10, 25, 47, 0.10);
  border-color: var(--c-gold);
}
:root[data-theme="light"] .notif-new::after { filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.25)); }
:root[data-theme="light"] .notif-item.is-new { background: rgba(154, 122, 30, 0.08); }
:root[data-theme="light"] .notif-act { color: #7A5F13; border-color: #7A5F13; }
:root[data-theme="light"] .notif-act:hover { background: rgba(154, 122, 30, 0.12); }
:root[data-theme="light"] .notif-act-clear { color: #5A6478; border-color: var(--surface-line); }
:root[data-theme="light"] .notif-act-clear:hover { color: #B3261E; border-color: #B3261E; }
:root[data-theme="light"] .notif-all { color: #7A5F13; }

:root[data-theme="light"] .notif-panel {
  /* The veil, not a flat #FFFFFF — that was here first and, being the more
     specific selector, quietly won and made the panel opaque again. */
  background: var(--notif-veil);
  border-color: var(--surface-line);
}
:root[data-theme="light"] .notif-top,
:root[data-theme="light"] .notif-foot { border-color: var(--surface-line); }
:root[data-theme="light"] .notif-item {
  color: var(--c-ink-dark);
  border-bottom-color: var(--surface-line);
}
:root[data-theme="light"] .notif-item:visited { color: var(--c-ink-dark); }
:root[data-theme="light"] .notif-item:hover,
:root[data-theme="light"] .notif-item:focus-visible { background: var(--surface-tint); }

/* ---- The hero keeps its photograph ----
   A banner is a photograph with a dark scrim over it and white text on top;
   that is the same in both themes, because the thing behind the text is the
   picture, not the page. Only the *fallback* gradient — what shows before a
   banner has been uploaded — is lightened, so a club with no banner yet does
   not get one navy block on an otherwise pale page. */
:root[data-theme="light"] .page-hero {
  background:
    linear-gradient(rgba(5, 18, 36, 0.55), rgba(5, 18, 36, 0.55)),
    url("/images/banner.jpg") center/cover,
    linear-gradient(160deg, #1B3A5C, #0A192F);
}
/* The hero's own subtitles stay white: they sit on the photograph. */
:root[data-theme="light"] .page-hero .sub { color: #fff; }
:root[data-theme="light"] .page-hero .sub-small { color: #e9edf3; }

/* ---- Cards and surfaces that named a colour ---- */
:root[data-theme="light"] .gshow-stage,
:root[data-theme="light"] .event-card .no-img,


:root[data-theme="light"] .notice-date-chip {
  background: #EDF1F7;
  color: #4A3A0C;
}

:root[data-theme="light"] .member-grid .member-card {
  background: linear-gradient(170deg, #FFFFFF, #F3F6FB);
}
:root[data-theme="light"] .event-card,
:root[data-theme="light"] .highlight-card { background: #FFFFFF; }

:root[data-theme="light"] .notice-bar::before { background: var(--c-navy); }

/* ---- Text that was written as white ---- */
:root[data-theme="light"] .eyebrow,
:root[data-theme="light"] .committee-section h2,
:root[data-theme="light"] .notice-heading,
:root[data-theme="light"] .notice-item h3,
:root[data-theme="light"] .gshow-cap-title,
:root[data-theme="light"] .member-card h4,
:root[data-theme="light"] .member-grid .member-card h4,
:root[data-theme="light"] .event-card .event-title,
:root[data-theme="light"] .cb-strong { color: var(--c-ink-dark); }

/* ---- Form fields ---- */
:root[data-theme="light"] input,
:root[data-theme="light"] textarea,
:root[data-theme="light"] select {
  background: var(--surface-input);
  color: var(--c-ink-dark);
  border-color: var(--surface-line);
}
:root[data-theme="light"] input::placeholder,
:root[data-theme="light"] textarea::placeholder { color: #8695AD; }
:root[data-theme="light"] label { color: #4A5670; }

/* ---- Buttons ----
   A gold fill with white text on it works in both themes — gold is a
   background here, not a foreground, so it does not need the darkened value.
   The outline button is the one that changes: bright gold on white is
   unreadable, so it takes the deepened gold from tokens.css. */
:root[data-theme="light"] .btn {
  background: #FFFFFF;
  border-color: var(--surface-line);
  color: var(--c-gold);
}
:root[data-theme="light"] .btn:hover,
:root[data-theme="light"] .btn-gold {
  background: var(--c-gold-deep);
  color: #FFFFFF;
  border-color: var(--c-gold-deep);
}
:root[data-theme="light"] .btn-gold:hover { background: #7A5F13; color: #FFFFFF; }
:root[data-theme="light"] .footer-links a:hover { color: #FFFFFF; }
:root[data-theme="light"] .toast { color: #FFFFFF; background: var(--c-gold-deep); }

/* ---- Panels and the surfaces drawn with a translucent white ---- */
:root[data-theme="light"] .panel,
:root[data-theme="light"] .card,
:root[data-theme="light"] .contact-card,
:root[data-theme="light"] .policy-card,
:root[data-theme="light"] .faq-item,
:root[data-theme="light"] .notice-card,
:root[data-theme="light"] .notice-detail,
:root[data-theme="light"] .notice-board-frame {
  background: var(--surface-panel);
  box-shadow: 0 2px 10px var(--surface-shadow);
}
:root[data-theme="light"] .nd-row { border-color: var(--surface-line); }

/* The floating Donate tab stays a navy chip in both themes: it is a call to
   action that has to stand off the page, and on a pale page navy does that
   better than another white panel would. */
:root[data-theme="light"] .donate-fab {
  background: linear-gradient(160deg, #123A5C, #0A192F);
  color: var(--c-gold-light);
}

/* The gold headings are drawn by painting a gradient through transparent
   glyphs. On a pale page the light end of that gradient disappears, so the
   light theme uses one solid, deep gold instead. */
:root[data-theme="light"] .page-hero h1,
:root[data-theme="light"] .section-title,
:root[data-theme="light"] .panel-title {
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  -webkit-text-fill-color: currentColor;
  color: var(--c-gold);
  filter: none;
}
/* Except the hero's, which is over a photograph and stays bright. */
:root[data-theme="light"] .page-hero h1 { color: var(--c-gold-light); }

/* The Three.js backdrop is a field of pale particles drawn for a navy page.
   On white it is either invisible or dirt on the screen, so it is hidden. The
   scene is purely decorative; nothing depends on it. */
:root[data-theme="light"] #bg-canvas,
:root[data-theme="light"] .hero-decor { display: none; }

/* ==========================================================================
   LIGHT THEME — the readability pass

   Four places named a colour that only ever had to work on navy, and on the
   pale page they are somewhere between hard to read and invisible. Measured on
   the rendered page rather than guessed at:

     .footer-location   #FFDF9E on #F5F7FB  →  1.20 : 1
     .hl                #F0D089 on #F5F7FB  →  1.39 : 1
     footer links       #9A7A1E on #F5F7FB  →  3.78 : 1   (4.5 needed)
     .event-date        #EF4444 on #FFFFFF  →  3.76 : 1   (4.5 needed)

   Cream and gold are the club's colours *against navy*; on white they carry no
   weight at all. The text takes the deep blue-black the identity already uses
   instead, and the golds that stay gold are deepened until they clear 4.5:1.
   ========================================================================== */

/* The deep blue-black. Dark enough to read as text on the pale page, still
   navy rather than a flat black, so the site keeps its own colour. */
:root[data-theme="light"] {
  --ink-on-pale: #12233F;
}

/* ---- Footer ---- */
:root[data-theme="light"] #site-footer,
:root[data-theme="light"] #site-footer p { color: var(--ink-on-pale); }

:root[data-theme="light"] .footer-location {
  color: var(--ink-on-pale);
  border-color: var(--c-gold-deep);
  background: rgba(10, 25, 47, 0.03);
}
:root[data-theme="light"] .footer-credit { color: #47556E; }

/* Deepened so the ring of links reads as a link and still passes on white. */
:root[data-theme="light"] .footer-links a {
  color: #7A5F13;
  border-color: #7A5F13;
}
:root[data-theme="light"] .footer-links a:hover {
  background: #7A5F13;
  color: #FFFFFF;
}

/* ---- Body copy ---- */
/* The highlight inside the welcome paragraph. Bold navy carries the emphasis
   on a pale page; pale gold on pale grey carries none. */
:root[data-theme="light"] .hl { color: var(--ink-on-pale); }

/* ---- Cards ---- */
:root[data-theme="light"] .event-card .event-date { color: #B3261E; }

/* The gold on small text — a committee member's role, an outline button's
   label — one step deeper than the heading gold so it clears 4.5:1 rather
   than sitting just under it. */
:root[data-theme="light"] .member-card .role,
:root[data-theme="light"] .btn-outline { color: #7A5F13; }

/* ==========================================================================
   JOIN OUR COMMUNITY — the mirror of the Donate tab

   Everything about it is the Donate tab reflected: the same size, the same
   stacked lettering, the same nudge-glow-and-beat rhythm, pinned to the left
   edge and hopping out to the right. The colours are the club's greens rather
   than its navy, so the two tabs are a pair without being a repetition — one
   asks, one offers.
   ========================================================================== */
.join-fab {
  position: fixed;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 900;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 16px 12px;
  border: 1px solid var(--gold);
  border-left: none;
  border-radius: 0 12px 12px 0;
  background: linear-gradient(160deg, #0d3154, #071f38);
  box-shadow: 4px 4px 18px rgba(0, 0, 0, 0.45);
  color: var(--gold);
  font-weight: 800;
  letter-spacing: 2px;
  font-size: 0.82rem;
  text-transform: uppercase;
  transition: background 0.25s ease, padding-left 0.25s ease;
}
.join-fab:hover {
  background: linear-gradient(160deg, #14456f, #0a2947);
  padding-left: 18px;
}
.join-fab-icon { font-size: 1.3rem; }
/* Stacked letters, so the tab stays narrow against the edge of the page. `rl`
   with a 180° turn rather than `lr`, which browsers render inconsistently. */
.join-fab-text { writing-mode: vertical-rl; text-orientation: mixed; }

/* Mirrored: it leaves from the left, so every offset is positive. */
@keyframes joinNudge {
  0%, 62%, 100% { transform: translateY(-50%) translateX(0) scale(1); }
  68%           { transform: translateY(-50%) translateX(34px) scale(1.1); }
  74%           { transform: translateY(-50%) translateX(2px) scale(1); }
  80%           { transform: translateY(-50%) translateX(26px) scale(1.07); }
  86%           { transform: translateY(-50%) translateX(2px) scale(1); }
  91%           { transform: translateY(-50%) translateX(14px) scale(1.03); }
  96%           { transform: translateY(-50%) translateX(1px) scale(1); }
}
@keyframes joinGlow {
  0%, 100% { box-shadow: 4px 4px 18px rgba(0, 0, 0, 0.45),
                         0 0 10px rgba(216, 178, 94, 0.25); }
  50%      { box-shadow: 4px 4px 18px rgba(0, 0, 0, 0.45),
                         0 0 34px rgba(216, 178, 94, 0.9); }
}
/* The handshake leans rather than beats — a heart pulses, a greeting waves. */
@keyframes joinWave {
  0%, 100% { transform: rotate(0deg) scale(1); }
  15%      { transform: rotate(-14deg) scale(1.18); }
  30%      { transform: rotate(10deg) scale(1.1); }
  45%      { transform: rotate(-6deg) scale(1.05); }
  60%      { transform: rotate(0deg) scale(1); }
}
.join-fab {
  /* Offset from the Donate tab's rhythm so the two never hop together, which
     reads as a glitch rather than as two invitations. */
  animation: joinNudge 3.4s ease-in-out -1.7s infinite,
             joinGlow 1.9s ease-in-out -0.95s infinite;
}
.join-fab-icon {
  display: inline-block;
  animation: joinWave 2.6s ease-in-out infinite;
}
.join-fab:hover, .join-fab:focus-visible { animation-play-state: paused; }

@media (max-width: 700px) {
  .join-fab {
    padding: 14px 6px;
    gap: 3px;
    font-size: 0.68rem;
    letter-spacing: 1.5px;
    border-radius: 0 10px 10px 0;
  }
  .join-fab:hover { padding-left: 10px; }
  /* The emoji, not the lettering, was setting the tab's width — and the tab's
     width is what `--fab-room` reserves out of every page's side padding. See
     the note beside --fab-room for what that costs a phone. */
  .join-fab-icon { font-size: 0.85rem; }
}

/* On a phone the hop is a nod: the 34px leap that reads as a wave on a wide
   screen lands the tab on the first word of every line beside it. */
@keyframes joinNudgeSmall {
  0%, 62%, 100% { transform: translateY(-50%) translateX(0) scale(1); }
  68%           { transform: translateY(-50%) translateX(6px) scale(1.04); }
  74%           { transform: translateY(-50%) translateX(1px) scale(1); }
  80%           { transform: translateY(-50%) translateX(4px) scale(1.02); }
  86%, 96%      { transform: translateY(-50%) translateX(0) scale(1); }
}
@keyframes donateNudgeSmall {
  0%, 62%, 100% { transform: translateY(-50%) translateX(0) scale(1); }
  68%           { transform: translateY(-50%) translateX(-6px) scale(1.04); }
  74%           { transform: translateY(-50%) translateX(-1px) scale(1); }
  80%           { transform: translateY(-50%) translateX(-4px) scale(1.02); }
  86%, 96%      { transform: translateY(-50%) translateX(0) scale(1); }
}
@media (max-width: 700px) {
  .join-fab { animation-name: joinNudgeSmall, joinGlow; }
  .donate-fab { animation-name: donateNudgeSmall, donateGlow; }
}

@media (prefers-reduced-motion: reduce) {
  .join-fab, .join-fab-icon { animation: none !important; }
}

/* On the pale page the navy tab still reads, but its gold needs the deeper
   one to clear against its own fill. */
:root[data-theme="light"] .join-fab,
:root[data-theme="light"] .donate-fab { color: #F0D089; }

/* ==========================================================================
   OUR HONEST REVIEWS — the testimonials carousel

   A page of cards slides in from the right: three to a page on a desktop, two
   on a tablet, one on a phone. The number per page is decided in JavaScript
   (reviewsPerPage in site.js) and handed to the page as `--per-page`, so the
   grid here does not need a breakpoint of its own and the two can never
   disagree about how many are showing.
   ========================================================================== */
.reviews-panel { overflow: hidden; }

/* The Bengali subtitle that used to sit between the heading and the carousel
   is gone, on the club's instruction. Nothing replaces it: the panel title now
   runs straight into the cards, which is the spacing every other panel on the
   page already uses. */

/* The window. `overflow: hidden` is what turns the track into a carousel;
   everything outside the current page is clipped rather than scrollable, so
   there is one way to move it and the arrows, the dots, the keys and a swipe
   all drive the same thing. */
.review-viewport {
  overflow: hidden;
  border-radius: var(--radius);
  outline-offset: 4px;
}

.review-track {
  display: flex;
  /* Each page is exactly one viewport wide, so `translateX(-N * 100%)` lands
     on page N with no measurement in JavaScript. */
  will-change: transform;
  transition: transform var(--t-slow) var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .review-track { transition: none; }
}

.review-page {
  flex: 0 0 100%;
  display: grid;
  /* `--per-page` comes from site.js. A final page holding fewer reviews than
     the others still lays them out at the same width — `1fr` columns of the
     full count — so the last page's cards match the ones before them instead
     of stretching to fill the row. */
  grid-template-columns: repeat(var(--per-page, 3), minmax(0, 1fr));
  gap: 16px;
  padding: 6px 2px 14px;
  align-items: stretch;
}

.review-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  padding: 22px 20px 18px;
  background: var(--panel-2, var(--surface-card));
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  transition: border-color var(--t) var(--ease), transform var(--t) var(--ease);
}
.review-card:hover { border-color: var(--gold); transform: translateY(-3px); }

/* The opening quotation mark, set large and faint behind the text. */
.review-quote {
  position: absolute;
  top: 2px;
  right: 16px;
  font-size: 3.4rem;
  line-height: 1;
  color: var(--c-gold-veil);
  pointer-events: none;
}

.review-stars { color: var(--gold); letter-spacing: 2px; font-size: 1rem; }

/* ---------- The overall rating (loadReviewSummary in site.js) ----------
   The block a shop puts above its reviews: one big average with a star row,
   and a bar per star beside it. The star row is five outlines with a filled
   copy clipped over each, so 4.3 shows as four and a bit rather than
   rounding to five. */
.reviews-summary { margin: 0 auto 26px; max-width: 760px; }
.rating-overall {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 28px;
  padding: 20px 24px;
  border: 1px solid var(--c-gold-veil);
  border-radius: 16px;
  background: var(--panel);
}
.rating-score { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 150px; }
.rating-score strong { font-size: 2.6rem; line-height: 1; color: var(--gold); }
.rating-score > span { color: var(--muted); font-size: 0.82rem; }
.rating-word { color: var(--ink); font-weight: 700; font-size: 0.95rem; }
.rating-count { color: var(--muted); font-size: 0.8rem; }
.rating-stars { display: inline-flex; gap: 2px; font-size: 1.15rem; line-height: 1; }
.rating-star { position: relative; color: var(--c-gold-veil); }
.rating-star-on {
  position: absolute; left: 0; top: 0; overflow: hidden; white-space: nowrap;
  color: var(--gold);
}
.rating-bars { flex: 1 1 260px; list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.rating-bars li { display: flex; align-items: center; gap: 10px; }
.rating-bar-label { width: 28px; color: var(--muted); font-size: 0.82rem; }
.rating-bar {
  flex: 1 1 auto; height: 9px; border-radius: 99px; overflow: hidden;
  background: var(--c-gold-veil);
}
.rating-bar-fill { display: block; height: 100%; background: var(--gold); border-radius: 99px; }
.rating-bar-count { width: 30px; text-align: right; color: var(--muted); font-size: 0.82rem; }
@media (max-width: 560px) {
  .rating-overall { gap: 16px; padding: 16px; }
  .rating-score { min-width: 0; flex: 1 1 100%; }
}
.review-stars-off { color: var(--c-gold-veil); }

/* The two pieces of text the reviewer wrote.

   Clamped to one height on **every** page. The cards used to be as tall as
   whatever each person happened to write — one four lines deep beside one four
   screens deep — and a row of them went ragged. A fixed size is what the club
   asked for, here and on /reviews.html.

   Nothing is lost, and this is deliberately not the old bug of hiding
   `comments` altogether. There the words were unreachable; here every card
   that is cut off carries "View full review", which opens the whole of it. A
   card that fits gets no button, because there is nothing behind it. */
.review-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  /* One height for every card on the page, so a row never goes ragged and the
     carousel does not resize as it turns. */
  height: 8.4em;
  overflow: hidden;
  position: relative;
}
/* The text fades out rather than being cut mid-letter, which is what tells the
   reader there is more rather than looking like a rendering fault.

   Only on a card that *is* cut off. `is-clipped` is set by
   markClippedReviews() in site.js, which measures rather than guesses — a fade
   over the last line of a short review reads as text going missing. */
.review-card.is-clipped .review-body::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2.2em;
  background: linear-gradient(to bottom, transparent, var(--panel-2, var(--surface-card)));
  pointer-events: none;
}

/* The door out of the clamp. Full width and a real tap target: on a phone this
   is the only way to the rest of the review. */
.review-more {
  align-self: flex-start;
  margin-top: 2px;
  padding: 7px 0;
  border: 0;
  background: none;
  color: var(--gold);
  font-family: inherit;
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: color var(--t-fast) var(--ease);
}
.review-more:hover,
.review-more:focus-visible { color: var(--c-gold-light); text-decoration: underline; }

/* A card with nothing hidden behind it still reserves the button's line, so
   every card is the same height whether or not it has one. `visibility`, not
   `display: none`: the space has to stay. It is out of the tab order and out
   of the accessibility tree either way. */
.review-more.is-off { visibility: hidden; }
:root[data-theme="light"] .review-more { color: var(--c-gold-deep); }
:root[data-theme="light"] .review-more:hover,
:root[data-theme="light"] .review-more:focus-visible { color: var(--c-gold-deeper); }

@media (max-width: 560px) {
  /* Wider than the words, so a thumb does not have to find a short line of
     text. Still left-aligned with the review it belongs to. */
  .review-more { align-self: stretch; text-align: left; padding: 10px 0; }
}

/* ==========================================================================
   PARTICIPATE NOW  (joinDialogEl in site.js)

   The public form behind the button on every event card. Two fields, and the
   same <dialog> mechanics as the full-review one below — the browser's
   backdrop, focus trap and Escape rather than a stack of z-indexes here.
   ========================================================================== */
.join-modal {
  width: min(440px, calc(100vw - 32px));
  padding: 24px 22px 22px;
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius);
  background: var(--panel-2, var(--surface-card));
  color: var(--ink);
  box-shadow: var(--shadow-lg);
}
.join-modal::backdrop { background: rgba(4, 20, 38, 0.68); }

.join-modal-title { margin: 0 0 4px; font-size: var(--fs-h3); color: var(--gold); }
:root[data-theme="light"] .join-modal-title { color: var(--c-gold-deep); }

.join-modal-event {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: var(--fs-small);
  overflow-wrap: anywhere;
}
.join-label {
  display: block;
  margin-bottom: 4px;
  font-weight: 700;
  font-size: var(--fs-small);
  color: var(--ink);
}
.join-note {
  margin: 2px 0 16px;
  color: var(--muted);
  font-size: var(--fs-tiny);
  line-height: 1.5;
}
.join-btns { display: flex; gap: 10px; flex-wrap: wrap; }
.join-btns .btn { flex: 1 1 auto; }

/* On a phone the two answers stack full width, so neither is a small target
   beside the other. */
@media (max-width: 460px) {
  .join-btns { flex-direction: column; }
  .join-btns .btn { width: 100%; }
}

/* The people on the Participate form (eventRegistrationFormHtml): a team
   event draws one block per person. */
.evp-count, .evp-team { width: 100%; margin-bottom: 12px; }
.evp-person { margin: 0 0 14px; }
.evp-person + .evp-person { padding-top: 12px; border-top: 1px dashed var(--c-gold-veil, rgba(216,178,94,0.35)); }
.evp-person input, .evp-person select, .evp-person textarea { width: 100%; }
.evp-title { margin: 0 0 8px; color: var(--gold); font-size: var(--fs-small); letter-spacing: 1px; text-transform: uppercase; }
:root[data-theme="light"] .evp-title { color: var(--c-gold-deep); }

/* The event's own questions on the Participate form (eventQuestionsHtml). */
.evq-field { margin: 0 0 12px; }
.evq-field input, .evq-field select, .evq-field textarea { width: 100%; }
.evq-req { color: var(--red); font-weight: 700; }
.evq-choices { display: flex; flex-direction: column; gap: 6px; }
.evq-choice { display: flex; align-items: center; gap: 8px; font-size: var(--fs-small); cursor: pointer; }
.evq-choice input { width: auto; margin: 0; }

/* ==========================================================================
   THE FULL EVENT  (openEventDialog in site.js) - the review dialog's shape
   ========================================================================== */
.event-modal {
  width: min(760px, calc(100vw - 32px));
  max-height: min(88vh, 900px);
  padding: 0;
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius);
  background: var(--panel-2, var(--surface-card));
  color: var(--ink);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.event-modal::backdrop { background: rgba(4, 20, 38, 0.72); }
.event-modal .review-modal-close { z-index: 2; background: rgba(6, 26, 46, 0.6); color: #fff; }
.event-modal-body { max-height: min(88vh, 900px); overflow: auto; }
.event-modal-figure { margin: 0; background: #061a2e; }
.event-modal-figure img { display: block; width: 100%; max-height: 52vh; object-fit: contain; }
.event-modal-figure figcaption { padding: 8px 16px; text-align: center; color: var(--muted); font-size: var(--fs-small); }
.event-modal-text { padding: 18px 22px 22px; display: flex; flex-direction: column; gap: 8px; }
.event-modal-title { margin: 0; color: var(--gold); font-size: var(--fs-h3); }
.event-modal-desc { line-height: 1.6; white-space: normal; }
.event-modal .event-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
:root[data-theme="light"] .event-modal-title { color: var(--c-gold-deep); }

/* ==========================================================================
   THE FULL REVIEW  (openReviewDialog in site.js)

   A real <dialog>, appended to <body>. It cannot live inside the card: on the
   home page the cards sit in `.review-viewport`, which is `overflow: hidden`
   to make the carousel work, and anything opened in there would be clipped by
   the rule that makes the slider slide.
   ========================================================================== */
.review-modal {
  width: min(620px, calc(100vw - 32px));
  /* Never taller than the screen — the whole point is a review that did not
     fit, so the long ones scroll inside the dialog rather than off it. */
  max-height: min(80vh, 720px);
  padding: 0;
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius);
  background: var(--panel-2, var(--surface-card));
  color: var(--ink);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.review-modal::backdrop { background: rgba(4, 20, 38, 0.68); }

.review-modal-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.review-modal-close:hover,
.review-modal-close:focus-visible { background: var(--c-gold-veil-soft); color: var(--gold); }

.review-modal-head {
  display: flex;
  align-items: center;
  gap: 12px;
  /* Room on the right for the close button, so a long name cannot run under
     it. */
  padding: 22px 60px 12px 22px;
}
.review-modal #review-modal-stars { padding: 0 22px; }

/* The only part that scrolls. The name, the stars and the close button stay
   put while a long review moves under them. */
.review-modal-body {
  padding: 12px 22px 22px;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* The head and the stars are roughly 7.5rem of the dialog; the rest is this. */
  max-height: calc(min(80vh, 720px) - 7.5rem);
}
.review-modal-body .review-text,
.review-modal-body .review-note { line-height: 1.75; }
.review-modal-body .review-note { margin-top: 14px; }

@media (max-width: 560px) {
  .review-modal { width: calc(100vw - 20px); max-height: 88vh; }
  .review-modal-body { max-height: calc(88vh - 7.5rem); }
  .review-modal-head { padding: 20px 56px 10px 18px; }
  .review-modal #review-modal-stars { padding: 0 18px; }
  .review-modal-body { padding: 12px 18px 20px; }
}
.review-text {
  margin: 0;
  color: var(--ink);
  line-height: 1.6;
  overflow-wrap: anywhere;
}
/* "Any Additional Comments" — quieter than the main feedback, and set off by a
   gold rule so it reads as a second thought rather than a second paragraph. */
.review-note {
  margin: 0;
  padding-left: 12px;
  border-left: 2px solid var(--c-gold-veil);
  color: var(--muted);
  font-size: var(--fs-small);
  line-height: 1.6;
  overflow-wrap: anywhere;
}

/* This is a <footer> element, so the site-wide `footer` rule reaches it —
   `padding: 34px 20px 26px`, among other things meant for the page's own
   footer. Only `padding-top` was being overridden here, which left 20px of
   side padding inside every review card: the by-line was indented, and the
   name and date had 40px less room than the card actually had. On a phone
   that was the difference between "Social Activities · 29 Jul 2026" fitting
   and being cut. Set in full rather than on one edge. */
.review-by {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: auto;
  padding: 12px 0 0;
  border-top: 1px solid var(--c-gold-veil-soft);
}
.review-avatar {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--c-gold-veil-soft);
  color: var(--gold);
  font-weight: 800;
  font-size: 0.85rem;
}
/* Two lines, always exactly two.

   They used to wrap, and that was the last thing making "same size" cards
   different sizes: "Website · 08 Aug 2026" fits one line on a phone and
   "Social Activities · 29 Jul 2026" does not, so two cards whose text was
   clamped to the identical height still came out 22px apart.

   Truncating is the right trade here. This is the by-line under a review —
   the name and where it was filed — and the alternative is a grid that goes
   ragged on every narrow screen. The full name is in the dialog, where there
   is room for it. */
/* Takes the width the avatar leaves, rather than only as much as the words
   need. Without the `flex: 1` the by-line sat centred in a box of its own and
   truncated at around 150px on a phone — with most of the card empty beside
   it. `min-width: 0` is what lets a flex item shrink below its content and is
   what makes the ellipsis below possible at all. */
.review-who { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; text-align: left; }
.review-who strong,
.review-who span {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.review-who strong { color: var(--ink); }
.review-who span { color: var(--muted); font-size: var(--fs-small); }

/* Inside the dialog there is no row to keep square, so the name is allowed to
   be as long as it is. */
.review-modal-head .review-who strong,
.review-modal-head .review-who span {
  overflow: visible;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* ---------- The controls ----------
   Hidden. The club asked for the arrows and dots to go and for the automatic
   slide to stay — which it does: the timer, the swipe and the arrow keys are
   all untouched, only the furniture is gone. Kept in the markup rather than
   removed from it so a screen reader still has the labelled buttons to move
   between pages with. */
.review-nav {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 6px;
}
.review-arrow {
  width: var(--tap);
  height: var(--tap);
  border-radius: 50%;
  border: 1px solid var(--c-gold-veil);
  background: var(--panel-2, var(--surface-card));
  color: var(--gold);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background var(--t) var(--ease), color var(--t) var(--ease),
              transform var(--t) var(--ease);
}
.review-arrow:hover, .review-arrow:focus-visible {
  background: var(--gold);
  color: var(--c-navy-deep);
  transform: scale(1.08);
}

.review-dots { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; }
.review-dot {
  /* A 10px dot inside a 28px button, so the row is comfortable to tap without
     becoming a row of buttons. */
  width: 28px; height: 28px;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.review-dot::before {
  content: "";
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--c-gold-veil);
  transition: background var(--t) var(--ease), transform var(--t) var(--ease);
}
.review-dot.is-on::before { background: var(--gold); transform: scale(1.35); }
.review-dot:hover::before { background: var(--c-gold-veil); opacity: 0.8; }

/* The way to the full text of everything, under the carousel. */
.reviews-more { text-align: center; margin-top: 4px; }

:root[data-theme="light"] .review-card {
  background: #FFFFFF;
  border-color: var(--surface-line);
}
:root[data-theme="light"] .review-body::after {
  background: linear-gradient(to bottom, transparent, #FFFFFF);
}
:root[data-theme="light"] .review-arrow {
  background: #FFFFFF;
  border-color: var(--surface-line);
}
:root[data-theme="light"] .review-quote { color: rgba(154, 122, 30, 0.22); }
:root[data-theme="light"] .review-note { border-left-color: var(--surface-line); }

/* ==========================================================================
   LIGHT THEME — text that was written for a navy panel

   The light theme swaps the surfaces, but a page that names `#fff` or the
   cream `#F0D089` outright keeps naming it. On the pale page those are between
   1.07:1 and 1.39:1 — the registration portal's headings, every bold run in
   body copy, the notice and committee headings, the FAQ questions. Measured on
   the rendered pages, not guessed at.

   Everything here is a foreground only. No layout, no spacing, and nothing
   that sits on a photograph — the hero's own subtitles stay white further up
   this file, because a scrim is drawn behind them.
   ========================================================================== */

/* Body copy and headings that were light-on-navy. */
:root[data-theme="light"] .eyebrow,
:root[data-theme="light"] .faq-q,
:root[data-theme="light"] .notice-item h3,
:root[data-theme="light"] .notice-heading,
:root[data-theme="light"] .notice-acc-head,
:root[data-theme="light"] .committee-section h2,
:root[data-theme="light"] .member-card h4,
:root[data-theme="light"] .member-grid .member-card h4,
:root[data-theme="light"] .cb-strong,
:root[data-theme="light"] .event-card .event-title { color: var(--ink-on-pale); }

/* Every bold run inside body copy. This one rule is most of what the pages
   above were missing: `main p strong` is cream, and cream on #F5F7FB is
   1.39:1 — the emphasis disappeared exactly where the writing leaned on it. */
:root[data-theme="light"] main p strong:not([style]),
:root[data-theme="light"] main li strong:not([style]) { color: var(--ink-on-pale); }

/* The registration portal — the worst of it, and the page a new member meets
   first. White on white, all the way down. */
:root[data-theme="light"] .rp-org,
:root[data-theme="light"] .rp-label,
:root[data-theme="light"] .rp-assist h3,
:root[data-theme="light"] .rp-assist a { color: var(--ink-on-pale); }
:root[data-theme="light"] .rp-stars { color: #B07C00; }
/* `!important` here only because the base rule has one: `.rp-card p` is more
   specific than `.rp-verified`, so the pale green had to force itself past it,
   and a light override without the same weight loses to it however specific
   the selector is. Measured, that left "✅ Email verified" at 1.77:1 on the
   white card — the one line on the form that confirms the member may carry on,
   and the one nobody could read with dark mode off. */
:root[data-theme="light"] .rp-verified { color: #16794C !important; }
:root[data-theme="light"] .rp-star { color: #B3261E; }
:root[data-theme="light"] .rp-terms-title,
:root[data-theme="light"] .rp-clear { color: #7A5F13; }

:root[data-theme="light"] .policy-card .policy-label { color: #7A5F13; }
:root[data-theme="light"] .notice-date-chip { color: #5F4A0E; }

/* ==========================================================================
   HERO HEADINGS — gold over a photograph nobody chose

   The banner behind a page title is whatever the club uploaded: a night sky on
   one page, a marquee full of balloons on the next. Gold reads beautifully on
   the first and vanishes into the second, and no single colour fixes that
   because the background is not one colour.

   So the title carries its own contrast instead of borrowing the photo's. A
   dark halo sits directly behind the letterforms — tight enough not to read as
   a drop shadow, dark enough that the gold has something to be gold against —
   and the scrim over the photograph is deepened at the top where the title
   sits. Both themes, because a bright photo is a bright photo either way.
   ========================================================================== */
.page-hero h1 {
  text-shadow:
    0 1px 2px rgba(3, 12, 24, 0.85),
    0 2px 10px rgba(3, 12, 24, 0.65),
    0 0 26px rgba(3, 12, 24, 0.45);
}
.page-hero .sub,
.page-hero .sub-small { text-shadow: 0 1px 6px rgba(3, 12, 24, 0.8); }

/* The scrim itself is `.hero-overlay`, which already exists and is already
   layered correctly — photo at z-index 0, scrim at 1, words at 2. A second
   scrim was added here and then taken out again: it sat at the same level as
   the photo, so it did nothing, and the blanket `.page-hero > *` it needed
   overrode the careful `:not(.hero-slides):not(.hero-overlay)` above and
   flattened all three layers onto one. Deepening the one that is already there
   is the whole change.

   A little heavier than it was. 0.62 was chosen against the club's own darker
   banners; a marquee full of balloons is a much brighter photograph, and gold
   on it at that strength is barely a title. */
.hero-overlay { background: rgba(5, 18, 36, 0.7); }

/* The banner keeps the *bright* gold in both themes.

   The light theme deepens `--c-gold` to #7A5F13 so it can be read as text on a
   white page, which is right everywhere except here: the hero is dark in both
   themes, because the scrim above makes it dark. Measured through that scrim
   over a bright photograph —

     deep gold   #7A5F13   1.55 : 1     unreadable
     bright gold #D4AF37   4.45 : 1     reads

   and over a dark one, 2.88 against 8.29. So the one place the deep gold was
   meant to help is the one place it hurts. */
:root[data-theme="light"] .page-hero h1,
:root[data-theme="light"] .page-hero .gold-line,
:root[data-theme="light"] .hero-title-gold { color: #E8C25A; }
:root[data-theme="light"] .page-hero .gold-line { background: #E8C25A; }

/* ==========================================================================
   SHOW / HIDE PASSWORD — the eye

   The wrapper is inline-block and full width so it takes the place the input
   used to occupy exactly, whatever form it is in. The field gets padding on the
   right so the longest password never runs under the button.
   ========================================================================== */
.pw-wrap {
  position: relative;
  display: block;
  width: 100%;
}
.pw-wrap > input { width: 100%; padding-right: 44px; }

.pw-eye {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.65;
  transition: opacity var(--t) var(--ease), background var(--t) var(--ease);
}
.pw-eye:hover { opacity: 1; background: var(--surface-veil, rgba(255, 255, 255, 0.1)); }
.pw-eye:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
  opacity: 1;
}

/* ==========================================================================
   BROWSER AUTOFILL — the public site

   The bug: a saved Membership ID or password filled in by the browser turned
   the field white with the browser's own tint, and only snapped back to the
   site's styling once the reader typed in it.

   The cause was simply that these rules did not exist. `admin.css` has had
   them for a long time, which is why the Admin Panel never showed the problem;
   nothing equivalent was ever written for the public site, so the Member
   Portal's sign-in fields fell through to whatever Chrome paints by default.

   Why it cannot be fixed with `background`: a browser paints an autofilled
   field itself, in the UA layer, and ignores `background-color` outright. The
   only reliable override is an enormous inset `box-shadow`, which is drawn
   over the UA's fill, plus `-webkit-text-fill-color` for the text — `color`
   is ignored on an autofilled field for the same reason.

   `!important` is genuinely necessary here and is not used elsewhere in this
   file for decoration: the declarations being beaten are the browser's own.

   The absurd `transition: background-color 100000s` is the long-standing trick
   that stops Chrome repainting its colour a moment after the first paint.

   Autocomplete is NOT disabled anywhere. Saved credentials and password
   managers keep working exactly as they did; only the paint changes.

   The three blocks below are separate on purpose. A browser that does not
   recognise one selector discards the ENTIRE rule it appears in, so listing
   the standard `:autofill`, the `-webkit-` spelling and Chrome's internal one
   together would mean any browser missing one of them gets none of the fix.
   ========================================================================== */

/* ---- The Member Portal's sign-in panel ----
   A cream card with white fields, in both themes: it is a deliberately light
   panel inside a dark page, so it does not follow the theme. */
.member-portal-form input:-webkit-autofill,
.member-portal-form input:-webkit-autofill:hover,
.member-portal-form input:-webkit-autofill:focus,
.member-portal-form input:-webkit-autofill:active {
  -webkit-text-fill-color: #1c1c1c !important;
  -webkit-box-shadow: 0 0 0 1000px #fff inset !important;
  box-shadow: 0 0 0 1000px #fff inset !important;
  caret-color: #1c1c1c;
  border: 1px solid #d8c9a0;
  transition: background-color 100000s ease-in-out 0s;
}
.member-portal-form input:autofill {
  -webkit-text-fill-color: #1c1c1c !important;
  box-shadow: 0 0 0 1000px #fff inset !important;
  caret-color: #1c1c1c;
}
.member-portal-form input:-internal-autofill-selected {
  -webkit-text-fill-color: #1c1c1c !important;
  background-color: #fff !important;
  background-image: none !important;
}
/* The focus ring is a box-shadow on the normal field, and a box-shadow is
   exactly what the fill above uses — the two would overwrite each other and a
   focused, autofilled field would go white again. Both are stated together so
   the ring is drawn *outside* the 1000px inset fill. */
.member-portal-form input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px #fff inset, 0 0 0 3px rgba(216, 178, 94, 0.25) !important;
  box-shadow: 0 0 0 1000px #fff inset, 0 0 0 3px rgba(216, 178, 94, 0.25) !important;
  border-color: var(--gold-dark);
}

/* ---- Every other field on the public site ----
   These follow the theme, so the fill is taken from --surface-input and the
   text from the theme's ink. In dark mode that is the navy #0d2846 the fields
   already use; in light mode, white. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active,
textarea:-webkit-autofill,
select:-webkit-autofill {
  -webkit-text-fill-color: var(--ink) !important;
  -webkit-box-shadow: 0 0 0 1000px var(--surface-input) inset !important;
  box-shadow: 0 0 0 1000px var(--surface-input) inset !important;
  caret-color: var(--ink);
  border: 1px solid var(--gold-dark);
  transition: background-color 100000s ease-in-out 0s;
}
input:autofill,
textarea:autofill,
select:autofill {
  -webkit-text-fill-color: var(--ink) !important;
  box-shadow: 0 0 0 1000px var(--surface-input) inset !important;
  caret-color: var(--ink);
}
input:-internal-autofill-selected {
  -webkit-text-fill-color: var(--ink) !important;
  background-color: var(--surface-input) !important;
  background-image: none !important;
}
input:-webkit-autofill:focus,
textarea:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--surface-input) inset,
                      0 0 0 3px rgba(212, 175, 55, 0.22) !important;
  box-shadow: 0 0 0 1000px var(--surface-input) inset,
              0 0 0 3px rgba(212, 175, 55, 0.22) !important;
  border-color: var(--gold);
}

/* ==========================================================================
   /reviews.html — every published review

   A plain responsive list, not a second carousel: nobody wants to page through
   the thing they clicked "Read All Reviews" to read.

   Exactly the same card as the home page, clamp and all. This page used to
   lift the clamp, which is what made its cards different heights — a short
   review beside a very long one, and the row ragged between them. Now every
   card is one size and the long ones carry "View full review".

   `stretch`, not `start`: the cards in a row take the height of the tallest,
   so a name that wraps onto two lines in one card does not leave the one
   beside it short.
   ========================================================================== */
.all-reviews {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  gap: 18px;
  align-items: stretch;
}

/* ==========================================================================
   LIGHT THEME — the inset panels that kept a dark fill

   Several small panels are drawn with a translucent navy — `rgba(4,20,38,.5)`
   — rather than with a palette variable: the "Need Assistance?" block on the
   membership and review forms, the development-mode note, the notice details
   table, and the donate page's payee and UPI blocks.

   Because the fill is a literal, it stayed navy when the light theme turned
   the text on it dark navy as well. Measured on the rendered page that is
   1.18:1 — text and background the same colour, which is precisely the "form
   er vitore likha gulo bojha jache na" the club reported.

   Each becomes a pale inset panel here. The text is left alone: it already
   follows the theme, and it was only ever unreadable because of what was
   behind it.
   ========================================================================== */

:root[data-theme="light"] .rp-assist,
:root[data-theme="light"] .rp-verify-note,
:root[data-theme="light"] .notice-details,
:root[data-theme="light"] .dn-payee {
  background: #EEF2F9;
  border-color: var(--surface-line);
}

:root[data-theme="light"] .dn-upi code {
  background: #E7ECF5;
  color: var(--c-ink-dark);
}

/* The gold-on-navy headings inside those panels need the deeper gold once the
   panel is pale, or they are gold on near-white. */
:root[data-theme="light"] .rp-assist h3,
:root[data-theme="light"] .rp-assist-label,
:root[data-theme="light"] .dn-payee strong { color: var(--c-gold-deep); }

/* Links inside them are phone numbers and an email address — they must read as
   links, not as body copy that happens to be clickable. */
:root[data-theme="light"] .rp-assist a { color: var(--c-gold-deep); }

/* The member portal's cream sign-in panel is a deliberately light card inside
   a dark page and is correct in both themes — it is listed here only to say
   that it was checked and deliberately left alone. */

/* ==========================================================================
   EMOJI PICKER  (setupEmojiPicker in common.js)

   Sits beside the paperclip on every compose box: the member's personal
   messages, Message to Support, and the admin panel's Support reply.
   ========================================================================== */

.emoji-btn {
  border: 0;
  background: none;
  cursor: pointer;
  font-size: 1.15rem;
  line-height: 1;
  padding: 0;
}

/* The panel is now a small keyboard rather than a short list: a fixed head
   (search + skin tones), a tab strip, and one scrolling list under them. The
   scroll moved from the panel to `.emoji-list` for that reason — with the whole
   panel scrolling, the search box and the tabs would slide out of reach the
   moment anybody scrolled to Symbols.

   Docked, WhatsApp-style (29 Sep 2026): it used to float up over the thread
   from the 🙂 button. It now opens in the page's flow, full width, right under
   the typing box - on a phone it is where the keyboard would be, and
   setupEmojiPicker() in common.js puts the keyboard away while it is open. */
.emoji-panel {
  position: relative;
  width: 100%;
  flex: 1 0 100%;               /* inside a wrapping compose row: a line of its own */
  box-sizing: border-box;
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  padding: 10px 12px 12px;
  background: var(--panel, #001F3F);
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  animation: emoji-dock-in 0.18s ease-out;
}
.emoji-panel[hidden] { display: none; }
@keyframes emoji-dock-in {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .emoji-panel { animation: none; } }
.emoji-btn.is-open { filter: drop-shadow(0 0 6px rgba(212, 175, 55, 0.7)); }

.emoji-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.emoji-search {
  flex: 1 1 auto;
  min-width: 0;                 /* or the flex row refuses to shrink on a phone */
  padding: 7px 10px;
  font: inherit;
  font-size: var(--fs-small, 0.9rem);
  color: inherit;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--c-gold-veil);
  border-radius: var(--radius-sm);
}
.emoji-search:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }

.emoji-tones { display: flex; gap: 1px; flex: 0 0 auto; }
.emoji-tone {
  width: 22px;
  height: 26px;
  border: 0;
  padding: 0;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.emoji-tone:hover { opacity: 1; }
.emoji-tone.is-on { opacity: 1; background: rgba(212, 175, 55, 0.24); }

.emoji-tabs {
  display: flex;
  gap: 1px;
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--c-gold-veil);
  /* Ten tabs on a 320px phone do not fit; they scroll rather than wrap onto a
     second row that would push the emoji off the panel. */
  overflow-x: auto;
  scrollbar-width: none;
}
.emoji-tabs::-webkit-scrollbar { display: none; }
.emoji-tab {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border: 0;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.65;
  transition: opacity var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.emoji-tab:hover, .emoji-tab:focus-visible {
  opacity: 1;
  background: rgba(212, 175, 55, 0.18);
}

.emoji-list {
  height: 240px;
  max-height: 240px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.emoji-empty {
  margin: 18px 4px;
  font-size: var(--fs-small, 0.9rem);
  color: var(--muted);
  text-align: center;
}
.emoji-group + .emoji-group { margin-top: 10px; }
.emoji-group-name {
  margin: 0 0 4px;
  font-size: var(--fs-tiny);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
}
.emoji-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(38px, 1fr)); gap: 2px; }
.emoji-key {
  width: 100%;
  height: 38px;
  border: 0;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.emoji-key:hover, .emoji-key:focus-visible {
  background: rgba(212, 175, 55, 0.18);
  transform: scale(1.15);
}

/* The compose row is the panel's positioning context. Without this the panel
   would hang off <body> and land in the wrong place on a scrolled thread. */
.chat-compose { position: relative; }

/* ==========================================================================
   MESSAGE MENU - Copy, Edit, Delete (setupMessageActions in common.js)
   ========================================================================== */
/* The popup itself is fixed to the viewport and lives on <body>: the thread is
   a scrolling box and would clip it. See setupMessageActions() in common.js. */
.msg-menu {
  position: fixed;
  z-index: 5200;
  min-width: 190px;
  padding: 5px;
  background: var(--panel, #001F3F);
  border: 1px solid var(--c-gold-veil, rgba(212, 175, 55, 0.35));
  border-radius: 10px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.4);
}
.msg-menu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 12px;
  border: 0;
  background: none;
  border-radius: 7px;
  color: var(--ink, var(--surface-input));
  font: inherit;
  font-size: 0.88rem;
  text-align: left;
  cursor: pointer;
}
.msg-menu button:hover,
.msg-menu button:focus-visible { background: rgba(212, 175, 55, 0.18); }
/* Pale red, for a dark panel. On the light theme's white panel the same value
   measured 2.19:1 — a destructive action that cannot be read is worse than one
   that is not styled at all — so the light theme takes the deep red the confirm
   dialog already uses (6.54:1 on white). */
.msg-menu button.is-danger { color: #ff8f8f; }
.msg-menu button.is-danger:hover,
.msg-menu button.is-danger:focus-visible { background: rgba(255, 90, 90, 0.16); }
:root[data-theme="light"] .msg-menu button.is-danger { color: #B3261E; }
:root[data-theme="light"] .msg-menu button.is-danger:hover,
:root[data-theme="light"] .msg-menu button.is-danger:focus-visible {
  background: rgba(179, 38, 30, 0.10);
}

.msg-menu { animation: msg-menu-in 0.12s ease-out; transform-origin: top left; }
@keyframes msg-menu-in { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: none; } }
.msg-menu-icon { width: 1.3em; text-align: center; flex: 0 0 auto; }
:root[data-theme="light"] .msg-menu { background: #FFFFFF; border-color: var(--surface-line, #d9e1f0); }
:root[data-theme="light"] .msg-menu button { color: var(--c-ink-dark, #33415C); }

/* The message a menu is open on stands out, as WhatsApp's does; a press being
   held gives way a little so the finger knows it is working. */
[data-msg-id] > .chat-bubble, [data-msg-id] > .inbox-bubble {
  transition: transform 0.4s ease, box-shadow 0.15s ease;
}
[data-msg-id].is-pressing > .chat-bubble,
[data-msg-id].is-pressing > .inbox-bubble { transform: scale(0.97); }
[data-msg-id].is-menu-open > .chat-bubble,
[data-msg-id].is-menu-open > .inbox-bubble {
  box-shadow: 0 0 0 2px var(--gold, #D4AF37), 0 6px 18px rgba(0, 0, 0, 0.25);
}
/* On a touch screen, holding a message opens the menu rather than selecting
   its words (Copy is in the menu). */
@media (hover: none), (pointer: coarse) {
  [data-msg-id] > .chat-bubble, [data-msg-id] > .inbox-bubble {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
  }
}

/* ==========================================================================
   CONFIRM DIALOG

   The shared yes/no box behind confirmDialog() in common.js. Built to match the
   admin panel's rejection dialog — same backdrop, same box, same tokens — so
   the site has one dialog that looks like one dialog, rather than a second
   style appearing the first time somebody deletes a message.

   It exists because the alternative is confirm(), which cannot be styled,
   cannot put the word "Delete" on its own button, and on a phone opens at the
   top of the screen attached to the URL bar rather than to the message being
   deleted.
   ========================================================================== */
.ask-backdrop {
  position: fixed;
  inset: 0;
  z-index: 6000;                /* above the ⋮ menu, which is 5200 */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  background: rgba(4, 12, 24, 0.72);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
body.ask-open { overflow: hidden; }

.ask-box {
  width: min(400px, 100%);
  padding: 22px;
  background: var(--panel, #001F3F);
  border: 1px solid var(--gold);
  border-radius: 14px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
.ask-title { margin: 0 0 6px; color: var(--gold); font-size: 1.05rem; }
.ask-body {
  margin: 0 0 18px;
  color: var(--muted, #8b97ad);
  font-size: 0.88rem;
  line-height: 1.55;
}
.ask-actions { display: flex; gap: 8px; justify-content: flex-end; }
.ask-actions .btn { padding: 8px 18px; font-size: 0.85rem; }
/* Not a palette colour: this is the one button on the site whose whole job is
   to look like it will destroy something. */
.ask-actions .btn.danger {
  background: #b3261e;
  border-color: #b3261e;
  color: #fff;
}
.ask-actions .btn.danger:hover { background: #9b201a; border-color: #9b201a; }

:root[data-theme="light"] .ask-box {
  background: #FFFFFF;
  border-color: var(--surface-line);
}
:root[data-theme="light"] .ask-title { color: var(--c-navy, #0A192F); }
:root[data-theme="light"] .ask-body { color: var(--c-ink-dark, #33415C); }

@media (max-width: 480px) {
  .ask-actions { flex-direction: column-reverse; }
  .ask-actions .btn { width: 100%; }
}

:root[data-theme="light"] .emoji-panel {
  background: #FFFFFF;
  border-color: var(--surface-line);
}
:root[data-theme="light"] .emoji-search {
  background: var(--surface-tint, rgba(10, 25, 47, 0.04));
  border-color: var(--surface-line);
  color: var(--c-ink-dark);
}
:root[data-theme="light"] .emoji-tabs { border-bottom-color: var(--surface-line); }

/* On a phone the panel is the keyboard: about the height of one, under the
   box, with bigger keys for a thumb. */
@media (max-width: 700px) {
  .emoji-panel { padding: 8px 8px 10px; }
  .emoji-head { flex-wrap: wrap; }
  .emoji-search { flex-basis: 100%; }
  .emoji-list { height: 34vh; max-height: 34vh; }
  .emoji-row { grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); }
  .emoji-key { height: 42px; font-size: 1.35rem; }
}

/* ==========================================================================
   LIGHT THEME — the registration and donation form cards

   `.rp-card` is drawn with a literal navy gradient rather than with a palette
   variable, so it stayed navy when the light theme turned every label on it
   dark. Measured on the rendered page that is a dark-on-dark label sitting on
   a navy card in the middle of a white page — the "form er vitore likha gulo
   dekha jache na" the club reported, and the same shape of bug as the inset
   panels fixed earlier.

   The card becomes white here; the labels and help text already follow the
   theme and are left alone.
   ========================================================================== */

:root[data-theme="light"] .rp-card {
  background: #FFFFFF;
  border-color: var(--surface-line);
  box-shadow: 0 2px 10px var(--surface-shadow);
}
:root[data-theme="light"] .rp-intro { border-top-color: var(--c-gold-deep); }
:root[data-theme="light"] .rp-intro h2,
:root[data-theme="light"] .rp-card h3 { color: var(--c-gold-deep); }

/* The two panels that sit *inside* a card keep a tint, or every surface on the
   page is the same white and the grouping is lost. */
:root[data-theme="light"] .rp-assist,
:root[data-theme="light"] .rp-verify-note { background: #EEF2F9; }

/* The donate page's question cards are the same component under another name. */
:root[data-theme="light"] .dn-card {
  background: #FFFFFF;
  border-color: var(--surface-line);
}

/* ==========================================================================
   The carousel's short last page

   Four reviews at three to a page leaves one card alone in a three-column
   grid, hard against the left edge with two empty columns beside it — which
   is what the club saw when the carousel turned over.

   `--count` is written per page by site.js. Each card keeps the width it would
   have had in a full row, and the row is centred, so a page of one and a page
   of three hold cards of exactly the same size.
   ========================================================================== */
.review-page {
  grid-template-columns: repeat(
    var(--count, 3),
    minmax(0, calc((100% - (var(--per-page, 3) - 1) * 16px) / var(--per-page, 3)))
  );
  justify-content: center;
}

/* ==========================================================================
   ATTACHMENT VIEWER

   Looking at something somebody sent you, without leaving the conversation.
   Built by setupAttachmentViewer() in /js/common.js and shared by all four
   screens that render a message bubble: the member's chat, the member's
   Support thread, and the Admin Panel's two message views. It lives here
   rather than in member-dashboard.css because admin pages do not load that
   file — style.css is what those four have in common.

   Deliberately dark in both themes. A lightbox is a room with the lights off:
   the point is that the picture is the only lit thing on the screen, and a
   pale backdrop in light mode would compete with what is being looked at.
   ========================================================================== */
.attach-viewer {
  position: fixed;
  inset: 0;
  z-index: 4000;                 /* over the fixed header and both side tabs */
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  background: rgba(6, 14, 26, 0.92);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.attach-viewer[hidden] { display: none; }

/* Stops the page behind from scrolling under the overlay on a phone, where a
   drag on the backdrop would otherwise move the conversation out of sight. */
body.attach-viewer-open { overflow: hidden; }

.attach-viewer-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  flex-shrink: 0;
}
.attach-viewer-name {
  color: #f3e6c4;
  font-size: 0.95rem;
  font-weight: 700;
  /* A long filename shortens rather than pushing the buttons off the screen. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.attach-viewer-btns { display: flex; gap: 8px; flex-shrink: 0; }
.attach-viewer-btns .btn { padding: 8px 14px; font-size: 0.82rem; }

.attach-viewer-stage {
  flex: 1;
  min-height: 0;                 /* lets the image and the frame shrink to fit */
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Fits the room it is given in both directions, and never enlarges past its
   own pixels — a small photograph shown at 3x is not a better look at it. */
.attach-viewer-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 8px;
  background: #fff;
}
.attach-viewer-pdf {
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 8px;
  background: #fff;
}

@media (max-width: 560px) {
  .attach-viewer { padding: 10px; }
  .attach-viewer-name { flex: 1 1 100%; order: -1; }
  .attach-viewer-btns { flex: 1 1 100%; }
  .attach-viewer-btns .btn { flex: 1 1 auto; }
}

/* ==========================================================================
   NOTICE CARD — "there is more inside" chips

   A notice can carry a link and a file, and both used to be drawn only inside
   the opened notice. On a board of a dozen that meant the only way to find out
   was to open all twelve. These chips say so on the card itself.

   Deliberately not links: the whole card already opens the notice, and a
   second focusable thing inside a marquee that renders a duplicate copy would
   put every action in the tab order twice.
   ========================================================================== */
.notice-has {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  background: var(--c-gold-veil-soft);
  border: 1px solid var(--c-gold-veil);
  color: var(--gold);
  font-size: var(--fs-tiny);
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* A social network the icon set has never heard of.

   "Other" is a supported choice, not a fallback for a mistake — it is what
   stops the next network being a code change — so it gets a real circle with
   its initial in it rather than an empty one. Painted in the club's gold,
   since it has no brand colour of its own to borrow. */
.social-circles a[data-brand="other"],
.platform-btn[data-brand="other"] { --brand: var(--gold); }
.social-initial {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Sized by the rule the SVGs share, so the letter and a logo occupy exactly
     the same box and the row does not shift when one is added. */
  font-family: var(--font-heading);
  font-size: 0.92em;
  font-weight: 700;
  line-height: 1;
}

/* ==========================================================================
   RICH TEXT — what an admin's formatted section text renders as

   The tags are a closed set: p, h3, h4, ul, ol, li, strong, em, a. See
   renderRichText() in common.js — there is no sanitizer, because there is
   never any HTML in the input to sanitize.
   ========================================================================== */
.rich-text {
  max-width: 820px;
  margin: 0 auto 22px;
  color: var(--ink);
  line-height: 1.7;
}
.rich-text p { margin: 0 0 14px; }
.rich-text p:last-child { margin-bottom: 0; }
.rich-text .rt-heading {
  margin: 22px 0 10px;
  color: var(--gold);
  font-family: var(--font-heading);
  line-height: 1.35;
}
.rich-text h3.rt-heading { font-size: 1.18rem; }
.rich-text h4.rt-heading { font-size: 1.02rem; }
.rich-text .rt-heading:first-child { margin-top: 0; }

/* Lists read left-aligned even inside a centred section: a bulleted list
   whose bullets sit under different words is not a list anybody can scan. */
.rich-text ul,
.rich-text ol {
  margin: 0 0 14px;
  padding-left: 1.35em;
  text-align: left;
}
.rich-text li { margin-bottom: 6px; }
.rich-text li:last-child { margin-bottom: 0; }

.rich-text a {
  color: var(--gold);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.rich-text a:hover { color: var(--gold-light, var(--gold)); }
.rich-text em { font-style: italic; }

/* A centred block whose paragraphs are centred but whose lists are not. */
.rich-text.center { text-align: center; }
.rich-text.center ul,
.rich-text.center ol {
  display: inline-block;
  text-align: left;
}

/* A section's cover picture. Wide and shallow on purpose: it introduces the
   section rather than being one of its photographs, and a tall one would push
   the heading it belongs to off the screen. */
.cs-cover {
  margin: -6px 0 18px;
  border-radius: 12px;
  overflow: hidden;
  line-height: 0;
}
.cs-cover img {
  width: 100%;
  height: clamp(140px, 26vw, 280px);
  object-fit: cover;
  display: block;
}

/* Every page: a button whose action has just worked turns green for a
   moment and says so ("✅ Saved", "✅ Sent" - common.js apiFetch); one whose
   action is still out is dimmed and cannot be pressed twice. */
.is-saving { opacity: 0.75; cursor: progress !important; }
button.is-saved, .btn.is-saved, input.is-saved {
  background: #16a34a !important; background-image: none !important;
  border-color: #16a34a !important; color: #ffffff !important;
  transition: background-color .2s ease;
}

/* The message at the top of the screen after anything is done. On the
   website's dark pages the glass tint left it dark text on a see-through
   dark band - there, but unreadable. A success is solid green with white
   words and a failure solid red, on every page and every theme. */
html .toast.show.ok {
  background: #15803d !important; background-image: none !important;
  color: #ffffff !important; border: 1px solid #0f5f2c !important;
  backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
}
html .toast.show.error {
  background: #c0392b !important; background-image: none !important;
  color: #ffffff !important; border: 1px solid #8f271c !important;
  backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
}

/* A one-time code's minute (common.js startCodeTimer): the time left, then -
   once it has run out - a plain red line, and the screen's Resend lit up. */
.code-timer { margin: 8px 0 0; font-size: 0.9rem; font-weight: 600; color: var(--muted, #8b97ad); }
.code-timer.is-expired { color: #e05252; }
:root[data-theme="light"] .code-timer.is-expired { color: #b42318; }
.code-resend-now { outline: 2px solid #22c55e; outline-offset: 3px; border-radius: 8px;
  animation: code-resend-pulse 1.2s ease-in-out 3; }
@keyframes code-resend-pulse { 50% { outline-color: transparent; } }
@media (prefers-reduced-motion: reduce) { .code-resend-now { animation: none; } }

/* The message box keeps its place - centred at the top - on every page. The
   glass cards' "nothing on a card animates" rule (glass-cards.css) matches it
   too and cleared its centring transform, which put it half off the right
   edge of the screen. */
html .toast.show { transform: translateX(-50%) !important; left: 50% !important; position: fixed !important; }
