/* =====================================================================
   Membership Platform — base stylesheet
   Brand colours are injected as CSS custom properties by the layout,
   read from the `organisation` table. Nothing here hard-codes a brand.
   ===================================================================== */

:root {
  --brand:        #0B6E4F;
  --brand-2:      #123C69;
  --accent:       #F0A202;

  --ink:          #10151c;
  --ink-soft:     #48566a;
  --ink-faint:    #7c8798;
  --line:         #e3e8ef;
  --line-soft:    #eef1f6;
  --bg:           #ffffff;
  --bg-soft:      #f6f8fb;
  --bg-sunken:    #eef2f7;

  --ok:           #0f7b4f;
  --ok-bg:        #e7f6ee;
  --warn:         #9a6200;
  --warn-bg:      #fdf3e0;
  --bad:          #b3261e;
  --bad-bg:       #fdecea;
  --info:         #1a5fb4;
  --info-bg:      #e8f0fe;

  --radius:       14px;
  --radius-sm:    9px;
  --shadow-sm:    0 1px 2px rgba(16,21,28,.06), 0 1px 3px rgba(16,21,28,.04);
  --shadow:       0 4px 12px rgba(16,21,28,.07), 0 2px 4px rgba(16,21,28,.04);
  --shadow-lg:    0 18px 40px rgba(16,21,28,.12), 0 4px 12px rgba(16,21,28,.06);

  --wrap:         1140px;
  --font:         -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                  "Helvetica Neue", "Noto Sans Malayalam", Arial, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }

/* The hidden attribute is how the JavaScript shows and hides things. Any
   component that sets display: flex or grid would otherwise outrank the
   browser's own [hidden] rule and stay on screen. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a   { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4 { line-height: 1.25; margin: 0 0 .5em; font-weight: 700; letter-spacing: -.015em; }
h1 { font-size: clamp(1.9rem, 4vw, 2.9rem); }
h2 { font-size: clamp(1.5rem, 3vw, 2.1rem); }
h3 { font-size: 1.2rem; }
p  { margin: 0 0 1rem; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: 1.25rem; }

.muted     { color: var(--ink-soft); }
.tiny      { font-size: .82rem; }
.small     { font-size: .9rem; }
.center    { text-align: center; }
.mt-0 { margin-top: 0 } .mb-0 { margin-bottom: 0 }
.stack > * + * { margin-top: 1rem; }

/* skip link ----------------------------------------------------------- */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--brand); color: #fff; padding: .7rem 1.1rem; border-radius: 0 0 var(--radius-sm) 0;
}
.skip:focus { left: 0; }

:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ---------------------------------------------------------------------
   Header
   --------------------------------------------------------------------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(255,255,255,.9);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
}
.site-header .bar {
  display: flex; align-items: center; gap: 1.25rem;
  min-height: 68px;
}
.brand { display: flex; align-items: center; gap: .75rem; text-decoration: none; color: inherit; }
.brand:hover { text-decoration: none; }
.brand .logo {
  width: 42px; height: 42px; border-radius: 10px; object-fit: contain;
  background: var(--bg-soft); padding: 3px; flex: none;
}
.brand .logo-fallback {
  width: 42px; height: 42px; border-radius: 10px; flex: none;
  display: grid; place-items: center;
  background: linear-gradient(135deg, var(--brand), var(--brand-2));
  color: #fff; font-weight: 800; font-size: 1rem; letter-spacing: .02em;
}
.brand .name  { font-weight: 700; font-size: 1.02rem; line-height: 1.2; display: block; }
.brand .tag   {
  font-size: .74rem; color: var(--ink-faint); line-height: 1.2; display: block;
  max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Below this width the nav needs the room more than the tagline does. */
@media (max-width: 1100px) and (min-width: 861px) { .brand .tag { display: none; } }

.nav { margin-left: auto; display: flex; align-items: center; gap: .35rem; }

/* :not(.btn) matters — a bare `.nav a` colour rule outranks `.btn-primary`
   on specificity and turns the Apply button's label dark-on-dark. */
.nav a:not(.btn) {
  padding: .5rem .8rem; border-radius: 8px; color: var(--ink-soft);
  font-size: .93rem; font-weight: 500; white-space: nowrap;
}
.nav a:not(.btn):hover { background: var(--bg-soft); color: var(--ink); text-decoration: none; }
.nav a:not(.btn).active { color: var(--brand); background: color-mix(in srgb, var(--brand) 9%, transparent); }
.nav .btn { margin-left: .4rem; }

.nav-toggle {
  margin-left: auto; display: none; background: none; border: 1px solid var(--line);
  border-radius: 8px; padding: .5rem .6rem; cursor: pointer; line-height: 0;
}
.nav-toggle span { display: block; width: 20px; height: 2px; background: var(--ink); margin: 4px 0; border-radius: 2px; }

@media (max-width: 860px) {
  .nav-toggle { display: block; }
  .nav {
    /*
     * `top: 100%`, not `68px`.
     *
     * 68px is the header's MINIMUM height, and on a phone it is regularly
     * not its real one: a long organisation name wraps to two lines, and
     * the panel then opened 20-odd pixels up inside the header, covering
     * the name it dropped from. Asking for the bottom of the header is
     * the same intent and is right whatever the name is.
     */
    position: absolute; inset: 100% 0 auto; flex-direction: column; align-items: stretch;
    background: #fff; border-bottom: 1px solid var(--line); padding: .6rem 1.25rem 1rem;
    gap: .15rem; display: none; box-shadow: var(--shadow);
    /* A site with a dozen sections has a dozen nav links. Below the fold
       they are unreachable on a phone, because the panel is as tall as it
       likes and the page behind it does not scroll. */
    max-height: calc(100dvh - 68px); overflow-y: auto; overscroll-behavior: contain;
  }
  .nav.open { display: flex; }
  .nav a:not(.btn) { padding: .7rem .6rem; }
  .nav .btn { margin-top: .4rem; text-align: center; }
}

/* ---------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .68rem 1.25rem; border-radius: 10px; border: 1px solid transparent;
  font: inherit; font-size: .95rem; font-weight: 600; cursor: pointer;
  transition: transform .06s ease, box-shadow .15s ease, background .15s ease;
  text-decoration: none; white-space: nowrap;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }

.btn-primary   { background: var(--brand); color: #fff; box-shadow: var(--shadow-sm); }
.btn-primary:hover { background: color-mix(in srgb, var(--brand) 88%, #000); color: #fff; }
.btn-accent    { background: var(--accent); color: #22180a; }
.btn-accent:hover { background: color-mix(in srgb, var(--accent) 88%, #000); }
.btn-outline   { background: #fff; border-color: var(--line); color: var(--ink); }
.btn-outline:hover { border-color: var(--brand); color: var(--brand); }
.btn-ghost     { background: transparent; color: var(--ink-soft); }
.btn-ghost:hover { background: var(--bg-soft); color: var(--ink); }
.btn-light     { background: rgba(255,255,255,.14); color: #fff; border-color: rgba(255,255,255,.3); }
.btn-light:hover { background: rgba(255,255,255,.24); color: #fff; }
.btn-block     { width: 100%; }
.btn-lg        { padding: .85rem 1.6rem; font-size: 1rem; }
.btn[disabled] { opacity: .55; cursor: not-allowed; }

/* ---------------------------------------------------------------------
   Hero
   --------------------------------------------------------------------- */
.hero {
  position: relative; overflow: hidden; color: #fff;
  background:
    radial-gradient(1100px 480px at 12% -10%, color-mix(in srgb, var(--accent) 28%, transparent), transparent 60%),
    linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%);
}
.hero::after {
  content: ""; position: absolute; inset: 0; opacity: .16; pointer-events: none;
  background-image:
    linear-gradient(rgba(255,255,255,.5) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.5) 1px, transparent 1px);
  background-size: 52px 52px;
  mask-image: radial-gradient(70% 70% at 70% 20%, #000, transparent);
}
.hero .wrap { position: relative; z-index: 1; padding-block: clamp(3.5rem, 8vw, 6rem); }
.hero-grid { display: grid; grid-template-columns: 1.25fr .85fr; gap: 3rem; align-items: center; }
@media (max-width: 900px) { .hero-grid { grid-template-columns: 1fr; gap: 2.25rem; } }

.hero h1 { color: #fff; margin-bottom: .6rem; }
.hero .lede { font-size: clamp(1.02rem, 1.6vw, 1.18rem); color: rgba(255,255,255,.9); max-width: 46ch; }
.hero .cta { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 1.75rem; }

.eyebrow {
  display: inline-flex; align-items: center; gap: .5rem;
  background: rgba(255,255,255,.15); border: 1px solid rgba(255,255,255,.25);
  padding: .3rem .8rem; border-radius: 999px;
  font-size: .78rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  margin-bottom: 1rem;
}
.eyebrow .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }

/* scheme summary card in the hero */
.scheme-card {
  background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.22);
  border-radius: var(--radius); padding: 1.5rem; backdrop-filter: blur(8px);
}
.scheme-card h3 { color: #fff; font-size: 1.05rem; margin-bottom: 1rem; }
.scheme-row {
  display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  padding: .6rem 0; border-bottom: 1px dashed rgba(255,255,255,.2);
}
.scheme-row:last-of-type { border-bottom: 0; }
.scheme-row dt { color: rgba(255,255,255,.75); font-size: .88rem; margin: 0; }
.scheme-row dd { margin: 0; font-weight: 700; font-size: 1.02rem; text-align: right; }

/* stat strip ---------------------------------------------------------- */
/* The counts strip.
   `repeat(3, 1fr)` was fine while three was the only number the template
   could produce. The strip is content now — an organisation adds as many
   figures as it has — so the track count comes from how many fit: four or
   five across on a desktop, two on a phone, wrapping onto a second row
   rather than squeezing eight tiles into one line. */
.stat-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: 1px; background: var(--line); border-block: 1px solid var(--line);
}
.stat-strip .stat {
  background: var(--bg); padding: 1.5rem 1.25rem; text-align: center;
  display: flex; flex-direction: column; justify-content: center;
}
a.stat { text-decoration: none; transition: background .15s ease; }
a.stat:hover { background: var(--bg-soft); }
.stat .stat-icon { font-size: 1.4rem; color: var(--brand); margin-bottom: .35rem; line-height: 1; }
.stat .num { font-size: clamp(1.6rem, 3.4vw, 2.2rem); font-weight: 800; color: var(--brand); line-height: 1.1; }
/* A prefix or suffix is an annotation on the figure, not part of it —
   "₹ 50 Lakh" should read as one number with units, not as three numbers. */
.stat .num .affix { font-size: .55em; font-weight: 700; opacity: .75; }
.stat .lbl { font-size: .8rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint); font-weight: 600; }
@media (max-width: 620px) { .stat-strip { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 380px) { .stat-strip { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------------
   Sections & cards
   --------------------------------------------------------------------- */
/* Positioned so a section can carry an overlay layer of free objects.
   Nothing inside a section was positioning against the page — the two
   that use `position: absolute` (.hero::after, the honeypot) already
   resolve against an ancestor of their own. */
section { padding-block: clamp(3rem, 6vw, 4.5rem); position: relative; }
section.tint { background: var(--bg-soft); }

.section-head { max-width: 62ch; margin-bottom: 2.25rem; }
.section-head.center { margin-inline: auto; text-align: center; }
.section-head .kicker {
  color: var(--brand); font-weight: 700; font-size: .8rem;
  letter-spacing: .1em; text-transform: uppercase; margin-bottom: .5rem;
}
.section-head p { color: var(--ink-soft); font-size: 1.03rem; margin-bottom: 0; }

.grid { display: grid; gap: 1.25rem; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }

.card {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.5rem; box-shadow: var(--shadow-sm);
}
.card.hoverable { transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease; }
.card.hoverable:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: color-mix(in srgb, var(--brand) 30%, var(--line)); }
.card h3 { margin-bottom: .4rem; }
.card p:last-child { margin-bottom: 0; }

.card-icon {
  width: 44px; height: 44px; border-radius: 11px; display: grid; place-items: center;
  background: color-mix(in srgb, var(--brand) 10%, transparent);
  color: var(--brand); font-size: 1.25rem; margin-bottom: 1rem;
}

/* requirement / benefit lists ----------------------------------------- */
.ticklist { list-style: none; margin: 0; padding: 0; }
.ticklist li { position: relative; padding-left: 2rem; margin-bottom: .8rem; color: var(--ink-soft); }
.ticklist li::before {
  content: ""; position: absolute; left: 0; top: .42em;
  width: 18px; height: 18px; border-radius: 50%;
  background: color-mix(in srgb, var(--ok) 14%, transparent);
}
.ticklist li::after {
  content: ""; position: absolute; left: 6px; top: .68em;
  width: 5px; height: 9px; border: solid var(--ok); border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.ticklist.on-dark li { color: rgba(255,255,255,.88); }

/* numbered steps ------------------------------------------------------ */
.steps { counter-reset: step; list-style: none; margin: 0; padding: 0; }
.steps li { counter-increment: step; position: relative; padding-left: 3.25rem; margin-bottom: 1.5rem; }
.steps li::before {
  content: counter(step); position: absolute; left: 0; top: -.1rem;
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  background: var(--brand); color: #fff; font-weight: 700; font-size: .95rem;
}
.steps li strong { display: block; }

/* rules of the scheme -------------------------------------------------
   Numbered like .steps, but each rule is a heading over a paragraph rather
   than a single line, and the paragraphs run long — a scheme's rules are
   the part people quote back at you, so they get room and a rule between
   them rather than being packed into cards. */
.rules { counter-reset: rule; list-style: none; margin: 0; padding: 0; }
.rules > li {
  counter-increment: rule; position: relative;
  padding: 0 0 1.5rem 3.25rem; margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--line);
}
.rules > li:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.rules > li::before {
  content: counter(rule); position: absolute; left: 0; top: 0;
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  background: var(--brand); color: #fff; font-weight: 700; font-size: .95rem;
}
.rules > li h3 { margin: .1rem 0 .5rem; font-size: 1.15rem; }
.rules > li p:last-child { margin-bottom: 0; }
@media (max-width: 640px) {
  .rules > li { padding-left: 2.75rem; }
  .rules > li::before { width: 28px; height: 28px; font-size: .85rem; }
}

/* committee ----------------------------------------------------------- */
.person { text-align: center; }
.person .avatar {
  width: 108px; height: 108px; border-radius: 50%; object-fit: cover;
  margin: 0 auto .9rem; border: 3px solid var(--bg); box-shadow: var(--shadow);
  background: var(--bg-sunken);
}
.person .avatar-fallback {
  width: 108px; height: 108px; border-radius: 50%; margin: 0 auto .9rem;
  display: grid; place-items: center; font-size: 2rem; font-weight: 700; color: #fff;
  background: linear-gradient(135deg, var(--brand), var(--brand-2));
  box-shadow: var(--shadow);
}
.person .pname { font-weight: 700; }
.person .pdesig { color: var(--brand); font-size: .88rem; font-weight: 600; }
.person .pplace { color: var(--ink-faint); font-size: .84rem; }

/* FAQ ----------------------------------------------------------------- */
details.faq {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 1rem 1.15rem; background: var(--bg); margin-bottom: .75rem;
}
details.faq[open] { border-color: color-mix(in srgb, var(--brand) 35%, var(--line)); }
details.faq summary { cursor: pointer; font-weight: 600; list-style: none; }
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::after { content: "+"; float: right; color: var(--brand); font-size: 1.25rem; line-height: 1; }
details.faq[open] summary::after { content: "\2212"; }
details.faq .answer { padding-top: .75rem; color: var(--ink-soft); }

/* CTA band ------------------------------------------------------------ */
.cta-band {
  background: linear-gradient(135deg, var(--brand-2), var(--brand));
  color: #fff; border-radius: var(--radius); padding: clamp(2rem, 4vw, 3rem);
  display: flex; justify-content: space-between; align-items: center; gap: 2rem; flex-wrap: wrap;
}
.cta-actions { display: flex; gap: .75rem; flex-wrap: wrap; }
.cta-band h2 { color: #fff; margin-bottom: .35rem; }
.cta-band p { color: rgba(255,255,255,.85); margin: 0; }

/* ---------------------------------------------------------------------
   Forms
   --------------------------------------------------------------------- */
.field { margin-bottom: 1.1rem; }
.field label { display: block; font-weight: 600; font-size: .9rem; margin-bottom: .38rem; }
.field .req { color: var(--bad); }
.field .hint { font-size: .82rem; color: var(--ink-faint); margin-top: .3rem; }

.input, .select, .textarea {
  width: 100%; padding: .7rem .85rem; font: inherit; font-size: .97rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg); color: var(--ink);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 16%, transparent);
}
.input.is-invalid, .select.is-invalid { border-color: var(--bad); }
.field .error { color: var(--bad); font-size: .84rem; margin-top: .3rem; font-weight: 500; }

.input-lg { padding: .9rem 1rem; font-size: 1.05rem; }

/* Who is signed in ------------------------------------------------------
   A face and a name read as one thing; two table cells would read as two. */
.wholine { display: flex; align-items: center; gap: .6rem; }
.wholine .avatar { flex: none; }
tr.is-you > td { background: color-mix(in srgb, var(--brand) 6%, transparent); }

/* Present to a screen reader, absent from the page. Clipped rather than
   display:none, which would take it out of the accessibility tree too. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Positioning a photograph ---------------------------------------------
   The frame does not move; the picture moves behind it. A rubber band you
   must place precisely with a fingertip is the version people give up on. */
.cropdialog {
  border: none; border-radius: var(--radius); padding: 0;
  background: var(--bg); color: var(--ink); max-width: 96vw;
  box-shadow: 0 24px 60px rgba(9, 20, 16, .35);
}
.cropdialog::backdrop { background: rgba(9, 20, 16, .55); }
.crop-shell { padding: 1.15rem 1.25rem 1.1rem; margin: 0; }
.crop-title { margin: 0 0 .2rem; font-size: 1.08rem; }
.crop-help  { margin: 0 0 .8rem; font-size: .86rem; color: var(--ink-soft); max-width: 46ch; }
.crop-stage {
  background: #0e1614; border-radius: var(--radius-sm); overflow: hidden;
  display: flex; justify-content: center;
}
/* touch-action, or a drag on a phone scrolls the page instead of moving
   the picture. */
.crop-canvas { display: block; touch-action: none; cursor: grab; }
.crop-canvas:active { cursor: grabbing; }
.crop-tools {
  display: flex; align-items: center; gap: .9rem; margin: .85rem 0 .2rem;
}
.crop-zoom { flex: 1; display: block; margin: 0; }
.crop-zoom input[type=range] { width: 100%; accent-color: var(--brand); }
.crop-actions {
  display: flex; justify-content: flex-end; gap: .6rem; margin-top: .9rem;
}
/* The preview takes the shape the field asks for, so what is on the page
   after cropping is what will be printed. */
.imagefield .preview.is-shaped { height: auto; }
.imagefield .preview.is-shaped img { width: 100%; height: 100%; object-fit: cover; }

/* The live note under a telephone field -------------------------------
   Empty until there is something to say, and it takes up no room while it
   is empty — a reserved blank strip under four boxes is four holes in the
   layout, and the fields would jump anyway when the note wraps to two
   lines. */
.field .fieldnote { font-size: .82rem; margin-top: .3rem; }
.field .fieldnote:empty { display: none; }
.field .fieldnote.is-good { color: var(--ok); }
.field .fieldnote.is-warn { color: var(--bad); font-weight: 500; }

/* The suggested number. A button, never applied on its own: guessing a
   country code and writing it in would be the one mistake worse than the
   missing code — a number that looks complete and rings somebody else. */
.field .suggest {
  display: inline-block; margin-top: .35rem;
  font: inherit; font-size: .82rem; font-weight: 500;
  padding: .22rem .55rem; cursor: pointer;
  color: var(--ink); background: var(--bg-soft);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.field .suggest:hover { border-color: var(--brand); color: var(--brand); }
.field .suggest:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* "Same as" — the tickbox that switches a field off ------------------
   It sits between the label and the box it controls, because that is the
   order the decision is made in: read what is being asked for, say whether
   it is the same as the one above, and only then type anything. */
.field .mirrorline {
  font-weight: 400; margin: 0 0 .45rem; font-size: .86rem;
  color: var(--ink-soft);
}
.field .mirrorline span { font-weight: 500; }

/* Switched off, not broken. Kept legible rather than faded to nothing:
   the number in it is the answer, and somebody proof-reading the form
   still has to be able to read it. */
.input.is-mirrored, .input:disabled {
  background: var(--bg-soft);
  color: var(--ink-faint);
  cursor: not-allowed;
}

/* ---------------------------------------------------------------------
   Alerts, badges, tables
   --------------------------------------------------------------------- */
.alert {
  padding: .85rem 1.1rem; border-radius: var(--radius-sm); margin-bottom: 1.1rem;
  border: 1px solid transparent; font-size: .94rem;
}
/* Reasons a form cannot be sent that have no field of their own to sit
   beside — see the top of the apply view for why that happens at all. */
.alert-list { margin: .5rem 0 .3rem; padding-left: 1.2rem; }
.alert-list li { margin: .2rem 0; font-weight: 500; }

/* Flash messages are plain strings that sometimes carry newlines and indented
   shell commands, so they keep their shape. Only flashes: applying this to
   every .alert would also preserve the source indentation of alerts written
   as HTML in a template, which reads as broken. */
.alert-flash { white-space: pre-wrap; }
.alert-success { background: var(--ok-bg);   color: var(--ok);   border-color: color-mix(in srgb, var(--ok) 25%, transparent); }
.alert-error   { background: var(--bad-bg);  color: var(--bad);  border-color: color-mix(in srgb, var(--bad) 25%, transparent); }
.alert-warning { background: var(--warn-bg); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); }
.alert-info    { background: var(--info-bg); color: var(--info); border-color: color-mix(in srgb, var(--info) 25%, transparent); }

.badge {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .22rem .65rem; border-radius: 999px;
  font-size: .78rem; font-weight: 700; letter-spacing: .02em;
}
.badge-ok   { background: var(--ok-bg);   color: var(--ok); }
.badge-bad  { background: var(--bad-bg);  color: var(--bad); }
.badge-warn { background: var(--warn-bg); color: var(--warn); }
.badge-mute { background: var(--bg-sunken); color: var(--ink-soft); }

.table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.table th, .table td { text-align: left; padding: .65rem .75rem; border-bottom: 1px solid var(--line-soft); }
.table th { font-size: .76rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); font-weight: 700; }
.table tbody tr:hover { background: var(--bg-soft); }

.kv { display: grid; grid-template-columns: minmax(9rem, auto) 1fr; gap: .55rem 1.25rem; }
.kv dt { color: var(--ink-faint); font-size: .86rem; }
.kv dd { margin: 0; font-weight: 600; }

/* ---------------------------------------------------------------------
   Footer
   --------------------------------------------------------------------- */
.site-footer { background: #0d1218; color: rgba(255,255,255,.68); padding-block: 3rem 1.5rem; margin-top: 0; }
.site-footer h4 { color: #fff; font-size: .82rem; text-transform: uppercase; letter-spacing: .1em; margin-bottom: 1rem; }
.site-footer a { color: rgba(255,255,255,.68); }
.site-footer a:hover { color: #fff; }
.footer-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1.2fr; gap: 2.5rem; }
@media (max-width: 860px) { .footer-grid { grid-template-columns: 1fr 1fr; gap: 2rem; } }
/*
 * Two link columns stay side by side on a phone.
 *
 * Stacking all four into one column was right when a footer link was 16
 * pixels tall. Once each one is a 44-pixel target — which is what makes
 * them usable — four stacked columns are most of a screen of scrolling
 * to reach a copyright line. The words and the address take the full
 * width because they are sentences; the two lists are short labels and
 * read perfectly well in halves.
 */
@media (max-width: 520px) {
  .footer-grid { grid-template-columns: 1fr 1fr; gap: 1.75rem 1.25rem; }
  .footer-grid > :first-child, .footer-grid > :last-child { grid-column: 1 / -1; }
}
.footer-links { list-style: none; margin: 0; padding: 0; }
.footer-links li { margin-bottom: .5rem; font-size: .92rem; }
.footer-about { max-width: 34ch; }
.footer-about p:last-child { margin-bottom: 0; }
/* The small print is one line beside the copyright, whether it was typed
   as bare words or wrapped in a paragraph by the editor. */
.footer-note p { display: inline; margin: 0; }
.footer-bottom {
  border-top: 1px solid rgba(255,255,255,.1); margin-top: 2.5rem; padding-top: 1.25rem;
  display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; font-size: .85rem;
}

/* A link column outside the footer — the same shape, site colours. */
.linklist { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.linklist li { font-size: .95rem; }

/* ---------------------------------------------------------------------
   Auth screens
   --------------------------------------------------------------------- */
.auth-wrap {
  min-height: 100vh; display: grid; place-items: center; padding: 2rem 1.25rem;
  background:
    radial-gradient(900px 420px at 15% 0%, color-mix(in srgb, var(--brand) 16%, transparent), transparent 62%),
    var(--bg-soft);
}
.auth-card {
  width: 100%; max-width: 430px; background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 2.25rem;
}
.auth-card .brand { justify-content: center; margin-bottom: 1.5rem; }
.auth-card h1 { font-size: 1.4rem; text-align: center; margin-bottom: .25rem; }
.auth-card .sub { text-align: center; color: var(--ink-soft); font-size: .93rem; margin-bottom: 1.75rem; }
.auth-foot { text-align: center; margin-top: 1.5rem; font-size: .88rem; color: var(--ink-faint); }

/* ---------------------------------------------------------------------
   Admin shell
   --------------------------------------------------------------------- */
/* The width of the sidebar, in one place. The rail state changes only this. */
:root { --nav-w: 252px; }
html[data-nav="rail"] { --nav-w: 68px; }

.admin { display: grid; grid-template-columns: var(--nav-w) 1fr; min-height: 100vh; }

/* Sticky, full-height, and scrolling on its own.
   `position: sticky` rather than `fixed` so the grid column still reserves
   the space — a fixed sidebar would sit on top of the content and every page
   would need a matching left margin that nothing enforces. The page scrolls
   under it; the sidebar scrolls only when its own list is taller than the
   window, and only its own list moves when it does. */
.sidebar {
  background: #0f1620; color: rgba(255,255,255,.72); padding: 1.1rem .85rem;
  display: flex; flex-direction: column; gap: 1.25rem;
  position: sticky; top: 0; height: 100vh; overflow-y: auto; overflow-x: hidden;
  scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.22) transparent;
  transition: none;   /* width is a grid change; animating it janks the page */
}
.sidebar::-webkit-scrollbar { width: 8px; }
.sidebar::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.18); border-radius: 4px;
}
.sidebar::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.3); }
.sidebar::-webkit-scrollbar-track { background: transparent; }

.sidebar .brand { color: #fff; padding: .35rem .4rem 0; }
.sidebar .brand .tag { color: rgba(255,255,255,.5); }
.sidebar nav { display: flex; flex-direction: column; gap: .1rem; }

/* --- groups --------------------------------------------------------- */
.navgroup > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center;
  user-select: none; border-radius: 6px;
}
.navgroup > summary::-webkit-details-marker { display: none; }
.navgroup > summary:hover { background: rgba(255,255,255,.05); }
.navgroup > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.sidebar .navlabel {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .12em;
  color: rgba(255,255,255,.36); padding: .8rem .7rem .35rem; font-weight: 700;
}
.navchev {
  margin-left: auto; opacity: .55; flex: none;
  transition: transform .18s ease;
}
.navgroup[open] > summary .navchev { transform: rotate(180deg); }
.navgroup-items { display: flex; flex-direction: column; gap: .1rem; }

.sidebar a {
  color: rgba(255,255,255,.74); padding: .55rem .7rem; border-radius: 8px;
  font-size: .92rem; display: flex; align-items: center; gap: .65rem;
  white-space: nowrap;
}
.sidebar a:hover { background: rgba(255,255,255,.07); color: #fff; text-decoration: none; }
.sidebar a.active { background: var(--brand); color: #fff; font-weight: 600; }
.sidebar a.disabled { opacity: .4; pointer-events: none; }
.sidebar .navicon { flex: none; opacity: .82; }
.sidebar a.active .navicon { opacity: 1; }
.sidebar .navtext { overflow: hidden; text-overflow: ellipsis; }
.sidebar .soon {
  margin-left: auto; font-size: .62rem; letter-spacing: .06em; text-transform: uppercase;
  background: rgba(255,255,255,.12); padding: .1rem .38rem; border-radius: 4px;
}

/* --- the collapsed rail ---------------------------------------------
   Text is hidden, never removed: a screen reader still reads the link's
   name, and the accessible name of a rail of unlabelled icons is otherwise
   nothing at all. */
html[data-nav="rail"] .sidebar { padding-inline: .55rem; align-items: stretch; }
html[data-nav="rail"] .sidebar .navtext,
html[data-nav="rail"] .sidebar .navlabel-text,
html[data-nav="rail"] .sidebar .navchev,
html[data-nav="rail"] .sidebar .brand-text,
html[data-nav="rail"] .sidebar .soon,
html[data-nav="rail"] .drawer-foot,
html[data-nav="rail"] .drawer-account { 
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
html[data-nav="rail"] .sidebar a {
  justify-content: center; padding: .6rem .4rem; position: relative;
}
html[data-nav="rail"] .sidebar .brand { justify-content: center; padding: .35rem 0 0; }
html[data-nav="rail"] .navgroup > summary { justify-content: center; padding: .55rem 0 .2rem; }
/* A hairline instead of the group heading, so the rail still has the shape
   of the list rather than becoming one undifferentiated column of icons. */
html[data-nav="rail"] .navgroup > summary::after {
  content: ""; display: block; width: 18px; height: 1px;
  background: rgba(255,255,255,.16);
}
html[data-nav="rail"] .navgroup > summary:hover { background: none; }
/* The rail cannot fold, so a group closed on the wide sidebar must not hide
   its icons here — there would be no way to reach them. */
html[data-nav="rail"] .navgroup-items { display: flex !important; }

/* The label, on hover. Built from the link's own text by the script, so it
   cannot disagree with what the link says. */
html[data-nav="rail"] .sidebar a[data-label]:hover::after,
html[data-nav="rail"] .sidebar a[data-label]:focus-visible::after {
  content: attr(data-label);
  position: absolute; left: calc(100% + 10px); top: 50%;
  transform: translateY(-50%);
  background: #1d2836; color: #fff; padding: .35rem .6rem; border-radius: 6px;
  font-size: .82rem; font-weight: 500; white-space: nowrap; z-index: 70;
  box-shadow: 0 6px 20px rgba(0,0,0,.4); pointer-events: none;
}

.railtoggle {
  display: none; background: none; border: 0; cursor: pointer; padding: .45rem;
  border-radius: 8px; color: var(--ink-soft); margin-left: -.25rem; flex: none;
}
html.js .railtoggle { display: inline-flex; }
.railtoggle:hover { background: var(--bg-sunken); color: var(--ink); }
html[data-nav="rail"] .railtoggle { color: var(--brand); }

.admin-main { background: var(--bg-soft); min-width: 0; }
.admin-top {
  background: #fff; border-bottom: 1px solid var(--line);
  padding: .9rem 1.75rem; display: flex; align-items: center; gap: 1rem;
  position: sticky; top: 0; z-index: 20;
}
.admin-top h1 { font-size: 1.15rem; margin: 0; }
.admin-user { margin-left: auto; display: flex; align-items: center; gap: .85rem; font-size: .9rem; }
.admin-user .who { text-align: right; line-height: 1.25; }
.admin-user .who strong { display: block; }
.admin-user .who span { color: var(--ink-faint); font-size: .8rem; }
.admin-body { padding: 1.75rem; }

/* the drawer, on desktop: not there at all ---------------------------- */
.drawer-toggle,
.drawer-scrim,
.drawer-button { display: none; }

.drawer-account { display: none; }
.drawer-foot { margin-top: auto; padding: .5rem .4rem; font-size: .85rem; }

/* ---------------------------------------------------------------------
   The admin panel on a phone
   ---------------------------------------------------------------------
   The sidebar used to become `flex-direction: row; overflow-x: auto` here —
   twenty links in a horizontally scrolling strip with their group headings
   hidden, so finding "Card Templates" meant dragging a black band sideways
   past nineteen other words. It was a layout, but it was not navigation.

   It is a drawer now: off-canvas, full height, the same list in the same
   order as on a desktop, opened by the button in the top bar. The toggle is
   a checkbox, so it works with scripting off.
   --------------------------------------------------------------------- */
@media (max-width: 900px) {

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

  .sidebar {
    position: fixed; inset-block: 0; left: 0; z-index: 60;
    width: min(82vw, 320px); padding: 1rem .85rem;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    transform: translateX(-100%);
    transition: transform .22s ease;
    box-shadow: 0 0 40px rgba(0,0,0,.35);
  }
  .drawer-toggle:checked ~ .sidebar { transform: none; }

  /* Dimmed backdrop. It is a <label>, so tapping anywhere outside the
     drawer closes it — the gesture everybody already expects. */
  .drawer-scrim {
    display: block; position: fixed; inset: 0; z-index: 50;
    background: rgba(8, 12, 18, .5);
    opacity: 0; pointer-events: none; transition: opacity .22s ease;
  }
  .drawer-toggle:checked ~ .drawer-scrim { opacity: 1; pointer-events: auto; }

  .drawer-button {
    display: grid; gap: 4px; padding: .55rem .5rem; margin-left: -.35rem;
    cursor: pointer; border-radius: 8px; align-content: center;
    min-width: 44px; min-height: 44px;   /* a thumb, not a mouse pointer */
  }
  .drawer-button span {
    display: block; width: 22px; height: 2px; border-radius: 2px;
    background: var(--ink); transition: transform .2s ease, opacity .2s ease;
  }
  .drawer-button:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
  .drawer-toggle:checked ~ .admin-main .drawer-button span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  .drawer-toggle:checked ~ .admin-main .drawer-button span:nth-child(2) { opacity: 0; }
  .drawer-toggle:checked ~ .admin-main .drawer-button span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

  /* Signing out and changing a password live in the drawer at this width;
     the top bar has room for a title and one button, and that is all. */
  .drawer-account {
    display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
    padding: .9rem .4rem .2rem; margin-top: auto;
    border-top: 1px solid rgba(255,255,255,.12);
  }
  .drawer-account .who { flex: 1 1 100%; line-height: 1.3; color: #fff; }
  .drawer-account .who span { display: block; color: rgba(255,255,255,.55); font-size: .8rem; }
  .drawer-account .btn-outline { background: transparent; color: #fff; border-color: rgba(255,255,255,.3); }
  .drawer-account .btn-ghost { color: rgba(255,255,255,.8); }
  .drawer-foot { margin-top: .8rem; }
  .drawer-foot a { color: rgba(255,255,255,.7); }

  .admin-top { padding: .7rem .9rem; gap: .6rem; }
  .admin-top h1 { font-size: 1.02rem; min-width: 0; overflow-wrap: anywhere; }
  .admin-user .who,
  .admin-user .btn-outline { display: none; }   /* both live in the drawer */

  .admin-body { padding: 1rem .9rem; }
}

.stat-card { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: 1.25rem; }
.stat-card .lbl { font-size: .76rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint); font-weight: 700; }
.stat-card .num { font-size: 2rem; font-weight: 800; line-height: 1.15; margin-top: .2rem; }
.stat-card .foot { font-size: .82rem; color: var(--ink-faint); }

.empty {
  border: 1px dashed var(--line); border-radius: var(--radius); padding: 2.5rem 1.5rem;
  text-align: center; color: var(--ink-faint); background: #fff;
}

/* error pages --------------------------------------------------------- */
.errpage { padding-block: clamp(4rem, 10vw, 7rem); text-align: center; }
.errpage .code { font-size: clamp(4rem, 14vw, 8rem); font-weight: 800; line-height: 1; color: var(--brand); opacity: .18; }
.errpage h1 { margin-top: -.4em; }

@media print {
  .site-header, .site-footer, .sidebar, .admin-top, .btn { display: none !important; }
  body { background: #fff; }
}

/* ---------------------------------------------------------------------
   Admin forms, toolbars and lists
   --------------------------------------------------------------------- */
.page-actions { display: flex; gap: .6rem; align-items: center; margin-left: auto; }
.toolbar { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.toolbar h2 { margin: 0; font-size: 1.1rem; }

/* The global `section { padding-block: 3–4.5rem }` rule is for public page
   sections; admin panels are also <section> elements and were inheriting it,
   which put an unexplained blank band above every panel heading. */
.panel {
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  margin-bottom: 1.25rem; padding-block: 0;
}
.panel > header {
  padding: 1rem 1.35rem; border-bottom: 1px solid var(--line-soft);
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
}
.panel > header > :last-child:not(:only-child) { margin-left: auto; }
.panel > header h2 { margin: 0; font-size: 1.02rem; }
.panel > header .hint { color: var(--ink-faint); font-size: .85rem; margin: 0; }
.panel .body { padding: 1.35rem; }
.panel .body > :last-child { margin-bottom: 0; }
.panel footer {
  padding: .9rem 1.35rem; border-top: 1px solid var(--line-soft);
  background: var(--bg-soft); border-radius: 0 0 var(--radius) var(--radius);
  display: flex; gap: .6rem; align-items: center;
}

.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1.5rem; }
.form-grid .span-2 { grid-column: 1 / -1; }
@media (max-width: 760px) { .form-grid { grid-template-columns: 1fr; } }

.colour-row { display: flex; gap: .6rem; align-items: center; }
.colour-row input[type=color] {
  width: 46px; height: 42px; padding: 2px; border: 1px solid var(--line);
  border-radius: var(--radius-sm); background: #fff; cursor: pointer;
}

.imagefield { display: flex; gap: 1rem; align-items: flex-start; }
.imagefield .preview {
  width: 92px; height: 92px; flex: none; border: 1px solid var(--line);
  border-radius: var(--radius-sm); background: var(--bg-sunken);
  display: grid; place-items: center; overflow: hidden;
}
.imagefield .preview img { width: 100%; height: 100%; object-fit: contain; }
.imagefield .preview .none { color: var(--ink-faint); font-size: .74rem; text-align: center; padding: .4rem; }
.imagefield .controls { flex: 1; min-width: 0; }

input[type=file] {
  font-size: .9rem; width: 100%;
}
input[type=file]::file-selector-button {
  font: inherit; font-size: .85rem; font-weight: 600; cursor: pointer;
  padding: .45rem .8rem; margin-right: .7rem;
  border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink);
}

.checkline { display: flex; align-items: center; gap: .55rem; margin-bottom: .7rem; font-size: .93rem; }
.checkline input[type=checkbox] { width: 17px; height: 17px; accent-color: var(--brand); }

.rowactions { display: flex; gap: .4rem; justify-content: flex-end; }
.rowactions .btn { padding: .35rem .7rem; font-size: .82rem; }

/* A <td> set to display:flex stops being a table cell: it no longer takes
   part in column sizing, so its buttons hang off the end of the row while
   the borders stop short. Cells keep table layout and lay their buttons out
   inline instead; the <div class="rowactions"> uses above are unaffected. */
td.rowactions { display: table-cell; text-align: right; white-space: nowrap; }
td.rowactions > * { vertical-align: middle; }
td.rowactions > * + * { margin-left: .4rem; }
/* A single-button <form> is the only way to POST from a row. Left as a block
   it puts every button on its own line. */
td.rowactions > form { display: inline-block; margin: 0; }
/* Backups ------------------------------------------------------------
   A copy-and-paste command wants to be obviously that: monospace, one
   line, scrolling rather than wrapping, because a wrapped path pasted
   into a scheduler is a path with a newline in it. */
.codeblock {
  background: var(--bg-sunken); border: 1px solid var(--line); border-radius: 6px;
  padding: .6rem .8rem; overflow-x: auto; margin: .4rem 0 1.2rem;
  font-size: .84rem; line-height: 1.5; white-space: pre;
}
.codeblock code { background: none; padding: 0; font-size: inherit; }

/* The restore panel is walled off on purpose: it is the only screen in
   the application that destroys something that cannot be typed back. */
.dangerpanel { border-color: color-mix(in srgb, var(--bad) 35%, var(--line)); }
.dangerpanel > header { background: var(--bad-bg); }
.dangerpanel > header h2 { color: var(--bad); }

/* "does nothing without …" on the role editor ------------------------
   Shown, never enforced. Loud only when the prerequisite is missing —
   a note that always shouts is a note nobody reads. */
.permneeds { margin-top: .2rem; font-size: .8rem; }
.permneeds.is-missing { color: var(--warn); font-weight: 500; }

.btn-danger { background: var(--bad-bg); color: var(--bad); border-color: transparent; }
.btn-danger:hover { background: var(--bad); color: #fff; }

.thumb-sm {
  width: 40px; height: 40px; border-radius: 8px; object-fit: cover; background: var(--bg-sunken);
}
.thumb-sm.placeholder {
  display: grid; place-items: center; color: #fff; font-weight: 700; font-size: .9rem;
  background: linear-gradient(135deg, var(--brand), var(--brand-2));
}

.drag-handle { cursor: grab; color: var(--ink-faint); user-select: none; }
/* A row the page draws somewhere of its own choosing. No grab cursor,
   because there is nothing to grab — see ContentService::DRAWN_INSIDE. */
.drag-fixed { color: var(--ink-faint); opacity: .45; user-select: none; cursor: help; }
tr.dragging { opacity: .4; }

/* a table whose rows sit inside one another ---------------------------
   Content sections and the items within them. The indent and the tick are
   the whole signal, so they have to survive a narrow screen: no nested
   tables, no absolute positioning, just padding on the first cell. */
.table-tree tr.is-item > td:nth-child(2) { padding-left: 2.25rem; }
.table-tree tr.is-item .tree-lead {
  color: var(--ink-faint); margin-left: -1.1rem; margin-right: .45rem;
}
.table-tree tr.is-additem > td {
  padding-top: .2rem; padding-bottom: .5rem;
  background: var(--bg-soft); border-top: 0;
}
.table-tree tr.is-additem .btn { padding-left: 2.25rem; }

.tabs { display: flex; gap: .25rem; border-bottom: 1px solid var(--line); margin-bottom: 1.25rem; flex-wrap: wrap; }
.tabs a {
  padding: .6rem 1rem; border-radius: 8px 8px 0 0; color: var(--ink-soft);
  font-size: .92rem; font-weight: 500; border: 1px solid transparent; border-bottom: none;
  margin-bottom: -1px;
}
.tabs a:hover { background: var(--bg-soft); text-decoration: none; }
.tabs a.active { background: #fff; border-color: var(--line); color: var(--brand); font-weight: 600; }

/* ---------------------------------------------------------------------
   Application form (public)
   --------------------------------------------------------------------- */
.apply-head { background: var(--bg-soft); border-bottom: 1px solid var(--line-soft); }
.apply-head .section-head { margin-bottom: 0; }
.apply-body { background: var(--bg-soft); padding-bottom: 4rem; }
.apply-body .wrap { max-width: 900px; }

/* The same form, inside the panel. It is already in a page with a heading,
   a sidebar and its own background, so it drops the public page's ground
   and its centred column and simply fills the width it is given. */
.apply-body.is-staff { background: none; padding-bottom: 1rem; }
.apply-body.is-staff .wrap-flush { max-width: 1000px; }
.apply-body.is-staff .stepbar { margin-top: 0; }

/* Honeypot: off-screen rather than display:none, which some bots skip. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.stepbar {
  list-style: none; margin: 0 0 1.5rem; padding: 0;
  display: flex; flex-wrap: wrap; gap: .4rem;
}
.stepbar li {
  display: flex; align-items: center; gap: .5rem;
  padding: .45rem .85rem; border-radius: 999px; background: #fff;
  border: 1px solid var(--line); font-size: .84rem; color: var(--ink-faint);
  cursor: pointer; user-select: none;
}
.stepbar li .n {
  width: 22px; height: 22px; flex: none; border-radius: 50%;
  display: grid; place-items: center; font-weight: 700; font-size: .76rem;
  background: var(--bg-sunken); color: var(--ink-soft);
}
.stepbar li.done { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
.stepbar li.done .n { background: var(--ok-bg); color: var(--ok); }
.stepbar li.current {
  color: var(--brand); border-color: var(--brand); font-weight: 600;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 12%, transparent);
}
.stepbar li.current .n { background: var(--brand); color: #fff; }
@media (max-width: 700px) { .stepbar li .t { display: none; } }

.applyform .step {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: #fff; padding: 1.75rem; margin: 0 0 1.25rem;
  box-shadow: var(--shadow-sm);
}
.applyform.js-stepped .step { margin-bottom: 0; }
/* The visible heading is a <div>, not the <legend>. A legend is painted on
   the fieldset's border and no amount of styling reliably drags it inside
   the card without breaking the grid underneath it — so the legend stays for
   screen readers and this carries the look. */
.step-legend { margin-bottom: 1.25rem; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.step-legend h2 { font-size: 1.4rem; margin-bottom: .25rem; }
.step-legend h2:focus { outline: none; }
.step-legend p { margin: 0; font-size: .92rem; }
.step-count {
  display: inline-block; font-size: .76rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand); margin-bottom: .35rem;
}
.step-nav {
  display: flex; gap: .7rem; justify-content: flex-end; flex-wrap: wrap;
  margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--line-soft);
}
.step-nav .js-prev { margin-right: auto; }

.field-heading { grid-column: 1 / -1; font-size: 1.02rem; margin: .6rem 0 .3rem; color: var(--ink-soft); }
.field-divider { grid-column: 1 / -1; border: 0; border-top: 1px solid var(--line-soft); margin: .6rem 0 1.1rem; }
.field.has-error label { color: var(--bad); }

/*
 * The field Continue stopped on.
 *
 * A red border alone is easy to scroll past on a form this long, and a
 * validation bubble is gone by the time somebody looks up. This keeps the
 * whole field marked until it is answered: a tinted panel, a rule down the
 * left edge, and the focus ring the cursor brings with it.
 */
.field.has-error {
  border-left: 3px solid var(--bad);
  padding-left: .7rem;
  margin-left: calc(-.7rem - 3px);
  background: var(--bad-bg);
  border-radius: 0 6px 6px 0;
}
.field.has-error .optiongroup legend,
.field.has-error > label { color: var(--bad); }
.field.has-error .input, .field.has-error .select { border-color: var(--bad); }
.js-step-error { color: var(--bad); font-size: .84rem; margin-top: .3rem; font-weight: 500; }

.optiongroup { border: 0; margin: 0 0 1.1rem; padding: 0; }
.optiongroup legend { font-weight: 600; font-size: .9rem; margin-bottom: .38rem; padding: 0; }
.optionlist { display: flex; flex-wrap: wrap; gap: .25rem 1.4rem; }
.optionlist .checkline { margin-bottom: .25rem; }

.suffixed { display: flex; align-items: stretch; }
.suffixed .input { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.suffixed .suffix {
  display: grid; place-items: center; padding: 0 .8rem; font-size: .9rem; font-weight: 600;
  color: var(--ink-soft); background: var(--bg-sunken);
  border: 1px solid var(--line); border-left: 0;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

/* ---- repeaters ---- */
.repeater { margin-top: .5rem; }
.repeat-row {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-soft); padding: 1.1rem 1.2rem .35rem; margin-bottom: 1rem;
}
.repeat-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: .8rem; padding-bottom: .6rem; border-bottom: 1px solid var(--line-soft);
}
.repeat-no { font-weight: 700; font-size: .88rem; color: var(--ink-soft); }
.btn-sm { padding: .32rem .7rem; font-size: .82rem; }
.repeat-row .input, .repeat-row .select { background: #fff; }

.share-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .7rem 1rem; margin-bottom: 1rem; border-radius: var(--radius-sm);
  background: var(--bg-sunken); font-size: .92rem;
}
.share-bar.is-good { background: var(--ok-bg);  color: var(--ok); }
.share-bar.is-bad  { background: var(--warn-bg); color: var(--warn); }

/* ---- document uploads ---- */
.doclist { display: grid; gap: .9rem; }
.docrow {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: .3rem 1.5rem;
  align-items: center; padding: .9rem 1rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-soft);
}
.docrow.has-error { border-color: var(--bad); }
.docrow .docmeta label { font-weight: 600; font-size: .92rem; display: block; }
.docrow .hint { font-size: .8rem; color: var(--ink-faint); }
.docrow .error { grid-column: 1 / -1; color: var(--bad); font-size: .84rem; font-weight: 500; }
@media (max-width: 700px) { .docrow { grid-template-columns: 1fr; } }

.declaration {
  background: var(--bg-soft); border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm); padding: 1.1rem 1.2rem; margin-bottom: 1.2rem;
  font-size: .92rem; color: var(--ink-soft);
}
.declaration p:last-child { margin-bottom: 0; }

/* ---- acknowledgement ---- */
.tick-badge {
  width: 68px; height: 68px; margin: 0 auto; border-radius: 50%;
  display: grid; place-items: center; font-size: 2.1rem; font-weight: 700;
  background: var(--ok-bg); color: var(--ok);
}
.refbox {
  margin: 1.5rem auto; padding: 1.1rem 1.4rem; max-width: 22rem;
  border: 2px dashed var(--brand); border-radius: var(--radius);
  background: color-mix(in srgb, var(--brand) 5%, transparent);
}
.refbox .lbl {
  display: block; font-size: .76rem; letter-spacing: .07em; text-transform: uppercase;
  font-weight: 700; color: var(--ink-faint);
}
.refbox .num { display: block; font-size: 1.55rem; letter-spacing: .04em; color: var(--brand); }
.whatnext { text-align: left; margin: 1.5rem auto 0; max-width: 32rem; font-size: .92rem; color: var(--ink-soft); }
.whatnext li { margin-bottom: .5rem; }

/* ---------------------------------------------------------------------
   Form builder (admin)
   --------------------------------------------------------------------- */
.fieldtable th, .fieldtable td { padding: .55rem .45rem; }
.fieldtable th:nth-child(2), .fieldtable td:nth-child(2) { padding-left: .75rem; }
.fieldtable th.flagcol, .fieldtable td.flagcol { width: 3.3rem; text-align: center; padding-inline: .15rem; }
.fieldtable th.flagcol { font-size: .68rem; letter-spacing: 0; }
.fieldtable .rowactions .btn { padding: .3rem .6rem; font-size: .8rem; }
.fieldtable tr.is-off td { opacity: .5; }
.fieldtable tr.is-off td:first-child, .fieldtable tr.is-off .rowactions { opacity: 1; }
.fieldtable .badge { font-size: .68rem; padding: .1rem .4rem; margin-left: .3rem; vertical-align: middle; }
.fieldtable code { font-size: .78rem; background: var(--bg-sunken); padding: .05rem .3rem; border-radius: 4px; }

.flagbtn {
  width: 28px; height: 28px; border-radius: 7px; cursor: pointer; font: inherit;
  font-size: .9rem; line-height: 1; border: 1px solid var(--line); background: #fff;
  color: var(--ink-faint); transition: background .12s ease, color .12s ease;
}
.flagbtn.on { background: var(--ok-bg); border-color: color-mix(in srgb, var(--ok) 30%, transparent); color: var(--ok); font-weight: 700; }
.flagbtn:hover { border-color: var(--brand); }
.flagbtn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

.reorder-save {
  display: flex; align-items: center; gap: .8rem; justify-content: flex-end;
  padding: .8rem 0 .2rem;
}

.flagline { align-items: flex-start; margin-bottom: .9rem; }
.flagline input[type=checkbox] { margin-top: .2rem; }
.flagline span strong { display: block; font-size: .93rem; }
.flagline span .tiny { display: block; line-height: 1.4; }

/* A wide table inside a panel scrolls rather than pushing past the card. */
.panel .body > form > .table, .panel .body > .table { width: 100%; }
.tablescroll { overflow-x: auto; margin: 0 -1.35rem; padding: 0 1.35rem; }
.tablescroll > .table { min-width: 44rem; }

/* ---------------------------------------------------------------------
   Applications inbox
   --------------------------------------------------------------------- */
.filterchips { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: 1rem; }
.filterchips .chip {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .4rem .85rem; border-radius: 999px; background: #fff;
  border: 1px solid var(--line); font-size: .87rem; color: var(--ink-soft);
}
.filterchips .chip:hover { border-color: var(--brand); text-decoration: none; }
.filterchips .chip span {
  font-size: .76rem; font-weight: 700; padding: .05rem .4rem; border-radius: 999px;
  background: var(--bg-sunken); color: var(--ink-soft);
}
.filterchips .chip.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.filterchips .chip.on span { background: rgba(255,255,255,.22); color: #fff; }

/* Which scheme the public site shows, on Schemes & Years. Same chip as the
   filter row, but the "on" one is a statement rather than a link — the others
   are buttons that change it. */
.chiprow { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.chiprow .chip {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .3rem .7rem; border-radius: 999px; font-size: .85rem;
  border: 1px solid var(--brand); background: var(--brand); color: #fff;
}
.chiprow .chip span {
  background: rgba(255,255,255,.22); color: #fff;
  padding: 0 .4rem; border-radius: 999px; font-size: .75rem;
}

.filterbar {
  display: flex; flex-wrap: wrap; gap: 0 1rem; align-items: flex-end;
  padding: 1rem 1.35rem; margin-bottom: 1.25rem;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
}
.filterbar .field { margin-bottom: .6rem; min-width: 10rem; flex: 1 1 10rem; }
.filterbar .field.filteractions { display: flex; gap: .5rem; flex: 0 0 auto; align-items: flex-end; }
.filterbar .field .hint { font-size: .78rem; color: var(--ink-faint); margin: .25rem 0 0; }

/* The column chooser ---------------------------------------------------
   Full width on its own line inside the filter bar, closed until the
   columns are not the usual set — the answer to "why does this report
   look different" should be visible without hunting for it. */
.colchooser { flex: 1 0 100%; margin: .3rem 0 .2rem; border-top: 1px solid var(--line); padding-top: .7rem; }
.colchooser > summary {
  cursor: pointer; font-weight: 600; font-size: .9rem; list-style: none;
  display: flex; align-items: center; gap: .5rem;
}
.colchooser > summary::-webkit-details-marker { display: none; }
.colchooser > summary::before { content: '▸'; color: var(--ink-faint); }
.colchooser[open] > summary::before { content: '▾'; }

.colgroups {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: .8rem; margin: .5rem 0 .2rem;
}
.colgroup {
  border: 1px solid var(--line); border-radius: 6px; padding: .5rem .7rem .6rem;
  margin: 0; min-width: 0;
}
.colgroup legend {
  font-size: .78rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .04em; color: var(--ink-muted); padding: 0 .3rem;
  display: flex; align-items: baseline; gap: .4rem;
}
.colgroup .tickline { display: flex; align-items: baseline; gap: .4rem; font-size: .86rem; margin: .18rem 0; }
.colgroup .tickline .tiny { margin-left: auto; }

/* A button that reads as a link: "all" and "none" are not actions worth
   a button's weight beside fifty-six tick-boxes. */
.linkbtn {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--brand); font-size: .74rem; text-decoration: underline;
  text-transform: none; letter-spacing: 0; font-weight: 500;
}
.linkbtn:hover { color: var(--ink); }

.pager {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .9rem 0 .2rem;
}
.btn.disabled, .btn[disabled] { opacity: .45; pointer-events: none; }

/* ---- review screen ---- */
.reviewgrid { display: grid; grid-template-columns: minmax(0, 1fr) 22rem; gap: 1.25rem; align-items: start; }
@media (max-width: 1100px) { .reviewgrid { grid-template-columns: 1fr; } }

.reviewhead { display: flex; gap: 1.25rem; align-items: flex-start; flex-wrap: wrap; }
.reviewphoto {
  width: 108px; height: 132px; flex: none; object-fit: cover;
  border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--bg-sunken);
}
.reviewphoto.placeholder {
  display: grid; place-items: center; color: var(--ink-faint); font-size: .78rem; text-align: center;
}
.reviewhead .kv { flex: 1; min-width: 16rem; margin: 0; }

.kv-2 { display: grid; grid-template-columns: auto 1fr auto 1fr; gap: .45rem 1rem; }
.kv-2 dt { grid-column: auto; }
@media (max-width: 800px) { .kv-2 { grid-template-columns: auto 1fr; } }

.remark {
  margin: 0; padding: .7rem 1rem; border-left: 3px solid var(--line);
  background: var(--bg-soft); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: .92rem; color: var(--ink-soft);
}

.doclinks { list-style: none; margin: 0; padding: 0; }
.doclinks li { padding: .5rem 0; border-bottom: 1px solid var(--line-soft); }
.doclinks li:last-child { border-bottom: 0; }
.doclinks .tiny { display: block; }

.timeline { list-style: none; margin: 0; padding: 0 0 0 1rem; border-left: 2px solid var(--line-soft); }
.timeline li { position: relative; padding: 0 0 1rem 1rem; }
.timeline li::before {
  content: ''; position: absolute; left: -1.42rem; top: .45rem;
  width: 9px; height: 9px; border-radius: 50%; background: var(--brand);
  box-shadow: 0 0 0 3px #fff;
}
.tl-head { display: flex; justify-content: space-between; gap: .6rem; align-items: baseline; }

/* ---------------------------------------------------------------------
   Member grid
   --------------------------------------------------------------------- */
.gridsearch { display: flex; gap: .5rem; align-items: center; }
.gridsearch .input { min-width: 18rem; }

.colpicker { position: relative; }
.colpicker > summary { list-style: none; cursor: pointer; }
.colpicker > summary::-webkit-details-marker { display: none; }
.colmenu {
  position: absolute; right: 0; top: calc(100% + .4rem); z-index: 30; width: 17rem;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: .9rem;
}
.colmenu-list { max-height: 22rem; overflow-y: auto; margin-bottom: .8rem; }
.colmenu-list .checkline { margin-bottom: .35rem; font-size: .88rem; }

.gridpanel > header { gap: 1rem; }
.perpage { display: flex; align-items: center; gap: .5rem; margin: 0 0 0 auto; }
.perpage .select { width: auto; padding: .35rem 1.6rem .35rem .6rem; font-size: .85rem; }

.gridscroll { overflow: auto; max-height: 68vh; }
.datagrid { font-size: .88rem; }
.datagrid thead th {
  position: sticky; top: 0; z-index: 2; background: var(--bg-soft);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.datagrid thead tr.filterrow th { top: 2.35rem; z-index: 1; padding: .3rem .45rem; }
.datagrid th.sortable a { color: var(--ink-soft); display: inline-flex; gap: .3rem; align-items: center; }
.datagrid th.sortable a:hover { color: var(--brand); text-decoration: none; }
.datagrid th.sorted a { color: var(--brand); }
.datagrid .sortmark { font-size: .62rem; }
.datagrid td { white-space: nowrap; }
.datagrid .stickyend { position: sticky; right: 0; background: #fff; }
.datagrid thead .stickyend { background: var(--bg-soft); }
.filterinput { padding: .35rem .5rem; font-size: .82rem; min-width: 6rem; }

/* ---------------------------------------------------------------------
   Member record
   --------------------------------------------------------------------- */
.memberhead-grid { display: flex; gap: 1.5rem; align-items: flex-start; flex-wrap: wrap; }
.memberphoto {
  width: 96px; height: 116px; flex: none; object-fit: cover;
  border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--bg-sunken);
}
.memberphoto.placeholder {
  display: grid; place-items: center; font-size: 2.2rem; font-weight: 700; color: #fff;
  background: linear-gradient(135deg, var(--brand), var(--brand-2));
}
.memberid { flex: 1; min-width: 14rem; }
.memberid h2 { margin-bottom: .2rem; }
.membermeta { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-top: .5rem; }
.memberquick { margin: 0; min-width: 15rem; }

/* ---------------------------------------------------------------------
   Enrolments, payments and the collection sheet
   --------------------------------------------------------------------- */
.statrow {
  display: grid; gap: 1rem; margin-bottom: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}
/* The card's three parts stack. They are spans so a card can be written on
   one line in a template; without this they run together as "MEMBERS1". */
.statrow .stat-card { padding: 1rem 1.1rem; }
.statrow .stat-card .lbl,
.statrow .stat-card .num,
.statrow .stat-card .foot { display: block; }
.statrow .stat-card .num { font-size: 1.5rem; }
.statrow .stat-card .foot { margin-top: .15rem; }
.panel .body > .statrow:last-child { margin-bottom: 0; }

.filterbar .field.grow { flex: 3 1 16rem; }

/* Money reads down the column, so it is right-aligned and lines up. */
.table th.num, .table td.num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }

.table tbody tr.is-selected { background: var(--brand-tint, #f2f6ff); }
.table tbody tr.is-void td { color: var(--ink-faint); }
.table tbody tr.is-void td:first-child strong { text-decoration: line-through; }

.bulkcount { font-variant-numeric: tabular-nums; opacity: .85; }

/* ---- the printed collection sheet ---- */
.sheet {
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.75rem 1.9rem;
}
.sheet-head {
  display: flex; gap: 2rem; flex-wrap: wrap; align-items: flex-start;
  justify-content: space-between; border-bottom: 2px solid var(--ink);
  padding-bottom: 1rem; margin-bottom: 1.25rem;
}
.sheet-head h1 { font-size: 1.4rem; margin: 0; }
.sheet-head p { margin: .15rem 0 0; }
.sheet-meta { display: grid; grid-template-columns: auto auto; gap: .2rem .9rem; margin: 0; font-size: .88rem; }
.sheet-meta dt { color: var(--ink-faint); font-weight: 600; }
.sheet-meta dd { margin: 0; font-weight: 600; }
.sheet-table tfoot th, .sheet-table tfoot td { border-top: 2px solid var(--ink); font-size: .95rem; }

.sheet-sign { display: flex; flex-wrap: wrap; gap: 2.5rem; margin-top: 3rem; }
.sheet-sign > div { flex: 1 1 12rem; }
.sheet-sign .line { display: block; border-bottom: 1px solid var(--ink); height: 2.2rem; margin-bottom: .35rem; }
.sheet-foot { margin-top: 1.5rem; }

/* The block below the signature ---------------------------------------
   Enrolments the system settled at approval. Real money on the books and
   no money in anybody's hand, so it is deliberately drawn as a different
   document: set back, headed in plain type, with its own subtotal that is
   never added to the one above. Somebody skim-reading a printed sheet must
   not be able to mistake this for cash they are holding. */
.sheet-aside {
  margin-top: 3rem; padding-top: 1.5rem;
  border-top: 1px dashed var(--line);
}
.sheet-aside h2 {
  font-size: 1rem; margin: 0 0 .35rem; letter-spacing: .01em;
}
.sheet-aside p { max-width: 74ch; }
.sheet-aside .sheet-table { margin-top: 1rem; }
/* Not the heavy rule the cash total gets: this subtotal is information,
   and it should not look like the figure somebody signs for. */
.sheet-aside .sheet-table tfoot th,
.sheet-aside .sheet-table tfoot td {
  border-top: 1px solid var(--line); font-weight: 600;
}

@media print {
  .noprint, .page-actions, .alert { display: none !important; }
  .sheet { border: 0; padding: 0; }
  .sheet-table { font-size: .85rem; }
  .sheet-table thead { display: table-header-group; }   /* repeat on every page */
  .sheet-table tr { break-inside: avoid; }
  .sheet-sign { break-inside: avoid; }
  /* Printed, not hidden — the coordinator asking "what happened to my
     members this week" is holding paper. Started on its own page so it can
     never be read as a continuation of the table above the signature. */
  .sheet-aside { break-before: page; }
}

/* The sheet's own toolbar sits on one line. `.input`/`.select` are 100% wide
   by default, which inside a flex row pushes every control onto its own. */
.sheetbar .select { width: auto; min-width: 15rem; }
.sheetbar .input[type="date"] { width: auto; min-width: 9.5rem; }
.table .nowrap { white-space: nowrap; }

/* ---------------------------------------------------------------------
   Cards
   --------------------------------------------------------------------- */
.tickcol { width: 2.2rem; text-align: center; }
.datagrid td.tickcol, .datagrid th.tickcol { padding-left: .5rem; padding-right: .5rem; }

.bulkbar {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  padding: .6rem 1.35rem; border-bottom: 1px solid var(--line); background: var(--bg-soft);
}

/* The template editor: element table on the left, live render on the right. */
.cardeditor { display: grid; grid-template-columns: minmax(0, 1fr) 22rem; gap: 1.25rem; align-items: start; }
@media (max-width: 1200px) { .cardeditor { grid-template-columns: 1fr; } }
.cardeditor-side { position: sticky; top: 1rem; }

/* The aspect ratio is set inline from the template's own page size — a
   landscape card must not be shown squeezed into a portrait box. The value
   here is only a fallback for a template with no size yet. */
.cardpreview {
  width: 100%; aspect-ratio: 0.63; border: 1px solid var(--line);
  border-radius: var(--radius-sm); background: var(--bg-sunken); display: block;
}

.eltable { font-size: .85rem; }
.eltable th { white-space: nowrap; font-size: .72rem; }
.eltable td { padding: .3rem .35rem; vertical-align: middle; }
.eltable .mm { width: 4.6rem; }
.eltable .colour { width: 5.4rem; }
.eltable select.filterinput { min-width: 6rem; }
.eltable .elhandle { white-space: nowrap; width: 1px; }
.eltable .elextra { display: flex; gap: .3rem; align-items: center; flex-wrap: wrap; min-width: 12rem; }
.eltable tr.is-removing td { opacity: .4; text-decoration: line-through; }

.iconbtn {
  border: 1px solid var(--line); background: #fff; border-radius: 6px;
  width: 1.5rem; height: 1.5rem; line-height: 1; cursor: pointer; color: var(--ink-soft);
  padding: 0; font-size: .78rem;
}
.iconbtn:hover { border-color: var(--brand); color: var(--brand); }

.tickline { display: inline-flex; align-items: center; gap: .35rem; font-size: .85rem; white-space: nowrap; }
.formactions { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.cardactions { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1.25rem; }
.dangerzone { margin-top: .5rem; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.cardpreview-fallback {
  height: 100%; display: grid; place-content: center; gap: .6rem;
  text-align: center; padding: 1.5rem; color: var(--ink-soft); font-size: .88rem;
}

/* ---------------------------------------------------------------------
   Member portal
   --------------------------------------------------------------------- */
body.portal main { padding-block: 1.75rem 3rem; }
.portalwrap { max-width: 900px; }
.portalwrap > .card { margin-bottom: 1.25rem; }
.navform { display: inline-flex; margin-left: .4rem; }

/* The one thing a member came to find out, in the first screenful. */
.coverpanel {
  display: flex; gap: 1.5rem; flex-wrap: wrap; align-items: center;
  justify-content: space-between;
  border-radius: var(--radius); padding: 1.5rem 1.6rem; margin-bottom: 1.25rem;
  border: 1px solid var(--line); background: #fff;
  border-left: 6px solid var(--ink-faint);
}
.coverpanel.cover-ok   { border-left-color: var(--ok);   background: var(--ok-bg); }
.coverpanel.cover-warn { border-left-color: var(--warn); background: var(--warn-bg); }
.coverpanel.cover-bad  { border-left-color: var(--bad);  background: var(--bad-bg); }

.coverlabel {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .08em;
  font-weight: 700; color: var(--ink-faint);
}
.coverpanel h1 { font-size: clamp(1.25rem, 3.5vw, 1.7rem); margin: .15rem 0 .35rem; }
.covermeta { margin: 0; color: var(--ink-soft); }

.coveridentity { display: flex; align-items: center; gap: .85rem; }
.coverphoto {
  width: 62px; height: 76px; object-fit: cover; border-radius: var(--radius-sm);
  border: 2px solid #fff; box-shadow: var(--shadow-sm);
}

.portalgrid {
  display: grid; gap: 1.25rem; margin-bottom: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.portalgrid .card { margin: 0; }
.portalgrid h2 { font-size: 1rem; margin-bottom: .6rem; }

.plainlist { list-style: none; margin: 0 0 .75rem; padding: 0; }
.plainlist li {
  display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap;
  padding: .3rem 0; border-bottom: 1px solid var(--line-soft);
}
.plainlist li:last-child { border-bottom: 0; }
.plainlist strong { margin-left: auto; }

.portalform .card { margin-bottom: 1.25rem; }

@media (max-width: 600px) {
  .coverpanel { flex-direction: column; align-items: flex-start; }
  .coveridentity { order: -1; }
}

/* ---- reports ---- */
.cardgrid {
  display: grid; gap: 1rem; margin-bottom: 1.25rem;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
}
.reportcard {
  /* A column, so "Run report" sits on the bottom edge of every card and the
     row of links stays level however long the descriptions are. */
  display: flex; flex-direction: column; background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.1rem 1.2rem; color: inherit;
  text-decoration: none; transition: border-color .12s, box-shadow .12s;
}
.reportcard:hover {
  border-color: var(--brand); text-decoration: none;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .06);
}
.reportcard h3 { font-size: 1rem; margin: 0 0 .35rem; }
.reportcard p { font-size: .88rem; color: var(--ink-soft); margin: 0 0 .7rem; flex: 1 1 auto; }
.reportcard-go { font-size: .82rem; font-weight: 600; color: var(--brand); }

.reportnote {
  font-size: .85rem; color: var(--ink-soft); margin: 0 0 .9rem;
  padding: .5rem .75rem; background: var(--bg-sunken);
  border-left: 3px solid var(--line); border-radius: var(--radius-sm);
}
/* A report can be very wide, so its table scrolls rather than squeezing. */
/* A register reads as a grid, so cells do not wrap: the table gets wider and
   .tablescroll scrolls it, rather than a name folding over three lines. */
/* "showing the first 200" ---------------------------------------------
   Loud enough that nobody mistakes a preview for the whole report, quiet
   enough that it is not an error. */
.rowcap {
  margin: .7rem 0 0; padding: .55rem .75rem; border-radius: 6px;
  background: var(--bg-sunken); border: 1px solid var(--line);
  font-size: .86rem; color: var(--ink-muted);
}
.rowcap strong { color: var(--ink); }

.reporttable { font-size: .86rem; }
.reporttable th, .reporttable td { white-space: nowrap; }
.reporttable th, .reporttable td { padding: .42rem .6rem; }
.reporttable tfoot td { font-weight: 700; border-top: 2px solid var(--line); }
.tablescroll > .reporttable { min-width: 100%; }

.btn.is-disabled { opacity: .45; cursor: not-allowed; }

/* ---- notifications ---- */
.msgpeek { margin-top: .3rem; }
.msgpeek > summary {
  cursor: pointer; font-size: .78rem; color: var(--ink-faint);
  list-style: none; display: inline-block;
}
.msgpeek > summary::-webkit-details-marker { display: none; }
.msgpeek > summary::before { content: '▸ '; }
.msgpeek[open] > summary::before { content: '▾ '; }
.msgpeek > summary:hover { color: var(--brand); }
.msgpeek pre {
  margin: .4rem 0 0; padding: .55rem .7rem; max-width: 44rem;
  background: var(--bg-sunken); border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm); font-size: .78rem; line-height: 1.45;
  white-space: pre-wrap; word-break: break-word;
}
.errline {
  font-size: .78rem; color: var(--bad); margin-top: .25rem;
  max-width: 26rem; word-break: break-word;
}

/* ---- two-stage review ---- */
.stagechips { align-items: center; margin-top: -.4rem; }
.stagechips .chiplabel {
  font-size: .76rem; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-faint); font-weight: 700; margin-right: .2rem;
}

/* Which desk it is on, and what the last one said. */
.stageline { list-style: none; margin: 0; padding: 0; }
.stageline li {
  position: relative; padding: 0 0 .9rem 1.4rem;
  border-left: 2px solid var(--line);
}
.stageline li:last-child { padding-bottom: 0; border-left-color: transparent; }
.stageline li::before {
  content: ''; position: absolute; left: -7px; top: .25rem;
  width: 12px; height: 12px; border-radius: 50%;
  background: #fff; border: 2px solid var(--line);
}
.stageline li.done::before { background: var(--ok); border-color: var(--ok); }
.stageline li.on::before   { background: var(--brand); border-color: var(--brand); }
.stageline li.done { border-left-color: var(--ok); }
.stageline strong { display: block; font-size: .93rem; }
.stageline blockquote {
  margin: .4rem 0 0; padding: .45rem .7rem; font-size: .84rem;
  background: var(--bg-sunken); border-radius: var(--radius-sm);
  color: var(--ink-soft);
}

/* ---- import screen ---- */
.readonlyvalue {
  margin: 0; padding: .45rem 0; font-weight: 600; word-break: break-word;
}
.subhead {
  font-size: .95rem; margin: 1.4rem 0 .4rem;
  padding-top: 1rem; border-top: 1px solid var(--line-soft);
}
.panel .body > .subhead:first-child { margin-top: 0; border-top: 0; padding-top: 0; }

/* ---- users and roles ---- */
.credentials { display: grid; gap: .4rem; }
.credentials code {
  font-size: 1.25rem; letter-spacing: .08em; font-weight: 700;
  background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: .5rem .8rem;
  display: inline-block; justify-self: start; user-select: all;
}
.credentials span { font-size: .85rem; }

.table tr.is-off td { opacity: .55; }

.rolegrid {
  display: grid; gap: .7rem;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
}
.rolecard {
  display: flex; gap: .6rem; align-items: flex-start; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .8rem .9rem; background: #fff;
}
.rolecard:hover { border-color: var(--brand); }
.rolecard input { margin-top: .2rem; width: 17px; height: 17px; accent-color: var(--brand); }
.rolecard > span { display: block; }
.rolecard strong { display: block; font-size: .93rem; }
.rolecard .muted { display: block; line-height: 1.35; margin: .15rem 0; }
.rolecard .rolemeta {
  display: block; font-size: .76rem; color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .04em; margin-top: .3rem;
}
.rolecard:has(input:checked) { border-color: var(--brand); background: #fafdfb; }

/* The exception editor is long by nature, so it collapses by module. */
.permgroup { border-bottom: 1px solid var(--line-soft); }
.permgroup > summary {
  cursor: pointer; padding: .6rem .1rem; font-weight: 600; font-size: .92rem;
}
.permgroup > summary:hover { color: var(--brand); }
.permtable { font-size: .86rem; margin-bottom: .8rem; }
.permtable th:not(:first-child), .permtable td:not(:first-child) {
  text-align: center; width: 5.5rem;
}
.permtable input[type=radio] { width: 16px; height: 16px; accent-color: var(--brand); }
.permtable input[type=radio]:disabled { opacity: .3; }

/* The row you are already looking at, inside a list of its own siblings. */
.table tr.is-current { background: var(--bg-sunken); }
.table tr.is-current td:first-child { box-shadow: inset 3px 0 0 var(--brand); }

/* A table row opened for editing: the cells give way to one form. */
.table tr.is-editing > td { background: var(--bg-sunken); padding: .9rem 1rem; }
.rowedit {
  display: flex; gap: .7rem; align-items: flex-end; flex-wrap: wrap; margin: 0;
}
.rowedit .field { margin: 0; flex: 1; min-width: 9rem; }
.rowedit .field label { font-size: .78rem; color: var(--ink-faint); }

/* A search box that sits in a panel header rather than above the table. */
.inline-search { display: flex; gap: .5rem; margin: 0 0 0 auto; }
.inline-search .input { min-width: 15rem; }

/* The geography search: one box, one scope, one button. */
.geosearch { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; margin: 0; }
.geosearch .input { flex: 1; min-width: 16rem; }
.geosearch .select { width: auto; min-width: 10rem; }
.geosearch-bar { margin: 0 0 1.2rem; }

/* =====================================================================
   Searchable dropdowns
   ---------------------------------------------------------------------
   A <select> long enough to be a nuisance keeps its job — it is still the
   form control, still submitted, still validated — and gains a text box in
   front of it that filters the options. See searchable-select.js.

   The native select is hidden rather than removed: with JavaScript off none
   of this exists and the page is a plain dropdown, which is the whole point
   of doing it this way.
   ===================================================================== */
.ss { position: relative; }

/* Kept operable and out of the way. Not display:none and not
   visibility:hidden — the element still has to be focusable and settable by
   the browser's own form restoration. */
.ss-native {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 0; pointer-events: none;
}

.ss-input { position: relative; z-index: 1; cursor: text; }
.ss-input::placeholder { color: var(--ink-faint); }

/* A caret, so it still reads as a dropdown rather than a text field. */
.ss::after {
  content: ''; position: absolute; right: .85rem; top: 50%;
  width: .5rem; height: .5rem; margin-top: -.35rem;
  border-right: 2px solid var(--ink-faint);
  border-bottom: 2px solid var(--ink-faint);
  transform: rotate(45deg); pointer-events: none; z-index: 2;
}

.ss-list {
  position: absolute; z-index: 40; top: calc(100% + 2px); left: 0; right: 0;
  max-height: 17rem; overflow-y: auto;
  margin: 0; padding: .25rem; list-style: none;
  background: var(--bg-card, #fff);
  border: 1px solid var(--line, #d8dce3);
  border-radius: 10px;
  box-shadow: 0 12px 28px rgba(16, 24, 40, .14);
  -webkit-overflow-scrolling: touch;
}

.ss-option {
  padding: .55rem .7rem;
  border-radius: 7px;
  cursor: pointer;
  font-size: .95rem;
  line-height: 1.35;
}
.ss-option:hover,
.ss-option.is-active { background: var(--bg-sunken, #eef1f5); }
.ss-option[aria-selected="true"] { font-weight: 600; color: var(--brand); }

.ss-note {
  position: absolute; z-index: 40; top: calc(100% + 2px); left: 0; right: 0;
  padding: .5rem .7rem;
  font-size: .82rem; color: var(--ink-faint);
  background: var(--bg-card, #fff);
  border: 1px solid var(--line, #d8dce3);
  border-radius: 10px;
}
.ss-list:not([hidden]) + .ss-note {
  position: static; border: 0; padding: .35rem .2rem 0; background: none;
}

/* On a phone the list is the point, so give it more of the screen and
   fingers something to hit. */
@media (max-width: 640px) {
  .ss-list { max-height: 60vh; }
  .ss-option { padding: .75rem .7rem; font-size: 1rem; }
}

@media print {
  .ss-list, .ss-note, .ss::after { display: none; }
}

/* =====================================================================
   Phones — the rest of the app
   =====================================================================
   The drawer above fixes navigation. These rules fix what is left, and
   they are mostly about one thing: nothing may make the page wider than
   the screen. A single un-scrollable table is enough to stretch the
   layout viewport, and when that happens the browser zooms the WHOLE page
   out to fit — every heading, every label, every button on it, including
   the ones that were fine. That is why the admin panel looked shrunken
   rather than merely wide.
   ===================================================================== */
@media (max-width: 900px) {

  /* A grid or flex child will happily grow past its parent unless told
     not to; this is the rule that keeps a wide table inside its panel
     instead of pushing the page out. */
  .admin-body,
  .admin-body > *,
  .panel,
  .panel > .body { min-width: 0; }

  /* Data grids stay wide on purpose — a members list is a spreadsheet and
     squashing it to 390px would make every column unreadable. So it
     scrolls sideways inside its panel, with momentum, and the page does
     not move with it. */
  .panel > .body { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .table { min-width: max-content; }

  /* Toolbars: the actions drop under the text rather than being pushed
     off the right-hand edge. */
  .toolbar { gap: .6rem; }
  .page-actions { margin-left: 0; width: 100%; flex-wrap: wrap; }
  .page-actions .btn { flex: 1 1 auto; justify-content: center; }
  .panel > header { padding: .9rem 1rem; }
  .panel > header > :last-child:not(:only-child) { margin-left: 0; }
  .panel .body { padding: 1rem; }
  .panel footer { padding: .85rem 1rem; flex-wrap: wrap; }

  .rowactions { justify-content: flex-start; flex-wrap: wrap; }

  /* Touch targets. 44px is the size of a fingertip; anything under it is
     a button you press twice. */
  .btn { min-height: 44px; }
  .rowactions .btn,
  .btn-sm { min-height: 38px; }

  /* iOS zooms the page in when a focused field is under 16px, and never
     zooms back out. Every form in this application is filled in on a
     phone at least once. */
  .input, .select, .textarea,
  input[type=text], input[type=email], input[type=tel],
  input[type=number], input[type=password], input[type=date],
  input[type=search], select, textarea { font-size: 16px; }

  .filterbar { flex-direction: column; align-items: stretch; }
  .filterbar .field { min-width: 0; }

  /* The grid search box asks for 18rem, which with its button beside it is
     wider than a phone — and a form that is 10px too wide widens the page,
     which zooms all of it out. It takes the row it is on instead. */
  .toolbar > form { width: 100%; }
  .gridsearch { width: 100%; flex-wrap: wrap; }
  .gridsearch .input { min-width: 0; flex: 1 1 10rem; }

  /* The drawer is open: the page behind it must not scroll under it. */
  body:has(.drawer-toggle:checked) { overflow: hidden; }
}

@media (max-width: 560px) {
  .admin-body { padding: .85rem .7rem; }
  .stat-card { padding: 1rem; }
  .stat-card .num { font-size: 1.6rem; }

  /* Buttons that sat side by side now stack, full width, in the order
     they are read. */
  .panel footer .btn,
  .form-actions .btn { flex: 1 1 100%; }
}

/* ---------------------------------------------------------------------
   Live card preview
   ---------------------------------------------------------------------
   A sketch of the card, drawn from the form by card-preview.js and redrawn
   as the numbers change. Everything inside is positioned absolutely in
   pixels the script works out from millimetres, so nothing here may
   introduce a layout of its own. */
.cardlive-stage { margin-bottom: .6rem; }
.cardlive {
  position: relative; width: 100%; background: #fff; overflow: hidden;
  border: 1px solid var(--line); border-radius: 6px;
  box-shadow: var(--shadow-sm);
}
.cardlive-el { position: absolute; line-height: 1.15; overflow: hidden; }
.cardlive-text { text-overflow: ellipsis; }
.cardlive-image img { width: 100%; height: 100%; object-fit: contain; display: block; }
.cardlive-el.is-placeholder {
  display: grid; place-items: center; background: var(--bg-sunken);
  color: var(--ink-faint); font-size: .55rem; letter-spacing: .04em;
  text-transform: uppercase; border: 1px dashed var(--line);
}
.facetabs { display: flex; gap: .3rem; margin-bottom: .6rem; }
.facetabs .btn.is-on { background: var(--brand); color: #fff; }

/* what the card will leave off ----------------------------------------
   The renderer drops an element rather than printing half of it. Doing
   that in silence is what turned "the tagline is missing from the card"
   into a question with no answer on the screen. */
.cardomissions {
  margin: .6rem 0; padding: .5rem .65rem; border-radius: 6px;
  background: var(--bg-sunken); border: 1px solid var(--line);
}
.cardomit { margin: .3rem 0 0; padding-left: 1.1rem; color: var(--ink-muted); }
.cardomit li { margin-bottom: .15rem; }

/* the member's Cards tab ----------------------------------------------
   The record beside a drawing of the card it describes. On a narrow
   screen the drawing goes under it rather than shrinking to a stamp. */
.cardtab { display: grid; grid-template-columns: minmax(0, 1fr) 20rem; gap: 1.25rem; align-items: start; }
@media (max-width: 1100px) { .cardtab { grid-template-columns: 1fr; } }
.cardpreview-panel .cardlive-stage { margin-bottom: 0; }

/* the "enrolment is closed" notice on the banner ----------------------
   Sits where the Apply button would be, so the absence of the button has
   something beside it saying why. On the dark hero, so it borrows the
   light-on-dark treatment the buttons there use. */
.notice-closed {
  background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.28);
  border-radius: var(--radius); padding: .9rem 1.1rem; margin-bottom: 1.25rem;
  max-width: 46ch; color: rgba(255,255,255,.92);
}
.notice-closed strong { display: block; margin-bottom: .15rem; }
.notice-closed span,
.notice-closed div { font-size: .9rem; color: rgba(255,255,255,.75); }
.eyebrow.is-closed .dot { background: var(--bad); }


/* ---------------------------------------------------------------------
   Renewal, in the member portal
   --------------------------------------------------------------------- */

/* The call to renew on the dashboard. Text on the left, the button on the
   right where there is room; stacked when there is not. */
.renewcall { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center;
             justify-content: space-between; }
.renewcall > div { flex: 1 1 20rem; min-width: 0; }

.renewsummary .kv { margin: 0; }
.confirmlist { margin: 0; }
.confirmlist dt { color: var(--muted); font-size: .85rem; }
.confirmlist dd { margin: 0 0 .6rem; font-weight: 600; }

/* "Something needs correcting" — a <details> element rather than a button
   with a script behind it, because the content-security policy forbids
   inline script and a renewal must work with none at all. */
.correctbox > summary {
  cursor: pointer; font-weight: 600; padding: .25rem 0;
  list-style: none;
}
.correctbox > summary::-webkit-details-marker { display: none; }
.correctbox > summary::before {
  content: '\25B8'; display: inline-block; width: 1.1em;
  transition: transform .12s ease; color: var(--brand);
}
.correctbox[open] > summary::before { transform: rotate(90deg); }
.correctbox > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.correctbox h3 { margin: 1.5rem 0 .5rem; font-size: 1rem; }


/* ---------------------------------------------------------------------
   The years a member has been in — public status page and the portal
   --------------------------------------------------------------------- */
.historyhead { margin: 0 0 .75rem; font-size: 1rem; display: flex;
               flex-wrap: wrap; gap: .5rem; align-items: baseline; }

.coverhistory { list-style: none; margin: 0; padding: 0; }
.coverhistory li {
  display: grid;
  grid-template-columns: minmax(9rem, 1.4fr) minmax(0, 1.2fr) auto;
  gap: .5rem 1rem; align-items: center;
  padding: .55rem 0 .55rem .85rem;
  border-bottom: 1px solid var(--line-soft);
  border-left: 3px solid transparent;
}
.coverhistory li:last-child { border-bottom: 0; }
.coverhistory .hy { min-width: 0; }
.coverhistory .hd { color: var(--muted); font-size: .85rem; }
.coverhistory .hs { justify-self: end; }

/* The year that is live now, and the years that were not paid for, are the
   two a reader is looking for. */
.coverhistory li.cover-active { border-left-color: var(--brand); }
.coverhistory li.cover-none   { border-left-color: var(--warn, #C77700); }

/* On a phone the dates go under the year rather than squeezing all three
   columns into 320px. */
@media (max-width: 560px) {
  .coverhistory li { grid-template-columns: 1fr auto; }
  .coverhistory .hd { grid-column: 1 / -1; }
}

/* ---------------------------------------------------------------------
   Section layouts
   
   A section on the landing page carries a layout, chosen in the admin
   panel, and these are the shapes it can take. Everything here is
   content-driven: an organisation adds a partners row or a gallery
   without anybody touching this file again.
   --------------------------------------------------------------------- */

/* the words directly under a section heading, before its items */
.section-lede {
  color: var(--ink-soft); max-width: 70ch; margin: 0 auto 1.75rem; text-align: center;
}
.wrap-narrow { max-width: 820px; }

/* a card that leads with a picture */
.card-img {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: calc(var(--radius) - 4px); margin-bottom: 1rem;
  background: var(--bg-sunken);
}

/* =====================================================================
   A card's logo, four ways
   =====================================================================

   One <img class="card-logo"> in the markup and a class on the card that
   decides where it lands. Forking the markup instead would be two code
   paths that drift, and the day they drift is the day a card renders two
   pictures.

   `contain`, never `cover`: these are logos. A crest cropped to fill a
   square is a crest with its edges cut off, and an organisation notices
   that about its own logo before it notices anything else on the page.
   --------------------------------------------------------------------- */

.card-logo {
  background: transparent;
  object-fit: contain;
}

/* The default: at the left, with the heading and the words flowing round
   and under it. `shape-margin` keeps the text off the logo's edge, and
   the float is cleared by the card's own overflow so a short paragraph
   cannot leave the next card's border riding up beside it. */
.card.logo-wrap { overflow: hidden; }
.card.logo-wrap .card-logo {
  float: inline-start;
  width: 72px; height: 72px;
  margin: .15rem 1rem .6rem 0;
  shape-margin: .8rem;
}
/* Malayalam headings run long, and a 72px logo beside a two-line heading
   leaves a notch of white under it. Letting the heading itself wrap round
   the logo — rather than clearing it — is what closes that up. */
.card.logo-wrap h3 { margin-bottom: .4rem; overflow: visible; }

/* Centred above everything. Evenest when the headings are long. */
.card.logo-above { text-align: center; }
.card.logo-above .card-logo {
  width: 84px; height: 84px; margin: 0 auto 1rem;
}
.card.logo-above .card-icon { margin-inline: auto; }

/* Its own column. Nothing wraps underneath, so the lines stay even. */
.card.logo-side {
  display: grid;
  grid-template-columns: 84px 1fr;
  gap: 0 1.1rem;
  align-items: start;
}
.card.logo-side .card-logo { width: 84px; height: 84px; grid-row: 1 / span 99; }
.card.logo-side .card-icon { grid-column: 2; }
.card.logo-side h3,
.card.logo-side .muted,
.card.logo-side .more,
.card.logo-side p { grid-column: 2; }

/* Across the top, cropped — a photograph rather than a logo. */
.card.logo-wide .card-logo {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: calc(var(--radius) - 4px); margin-bottom: 1rem;
  background: var(--bg-sunken);
}

/* On a phone a floated 72px logo leaves about fourteen characters of
   Malayalam per line beside it, which is unreadable. Everything stacks. */
@media (max-width: 480px) {
  .card.logo-wrap .card-logo,
  .card.logo-side .card-logo {
    float: none; width: 64px; height: 64px; margin: 0 0 .75rem;
  }
  .card.logo-side { display: block; }
}

/* =====================================================================
   Per-section typography and rhythm
   =====================================================================

   Set from the content screen, one key each, and each key is a class.
   A section that has chosen nothing carries none of these, which is why
   the page is unchanged until somebody decides otherwise.
   --------------------------------------------------------------------- */

/* =====================================================================
   Type, per part of a section
   =====================================================================

   Four parts — Title, Subtitle, Heading, Body — each with a size, an
   emphasis, a letter case and a typeface.

   Two classes per choice, and the pair matters:

     `.lt-head-size`        the choice was made — switches the rule on
     `.ltv-head-size-xl`    what it was — sets the custom property

   Without the marker, the consumer rule would have to apply to every
   section and carry a copy of the default value; the day the default
   changed in the base stylesheet and not in the copy, every section
   nobody had touched would quietly move. This way a section with no
   choices carries neither class and is drawn exactly as before.

   The body selector is the one that was wrong before. It named
   `.section-lede`, `.card` and friends — and the sections this page
   actually has write their body as `<div class="muted">`, which was in
   none of those lists. The setting saved, the class appeared, and no
   element matched it.

   Then it was wrong a second way, on the banner. That band is built out
   of different elements — an `<h1>`, a `<p class="lede">` for the
   subtitle, a `<div class="lede">` for the words — so Title and Body
   matched nothing there and the body rule's bare `p` caught the
   SUBTITLE instead. Hence `.t-title`, `.t-sub`, `.t-body`: a band names
   its own parts in the markup rather than being guessed at from here,
   The exclusion is `.t-sub`, not `.lede`. Excluding the class was the
   obvious first move and the wrong one: the banner's BODY is also a
   `.lede`, so it took the body out along with the subtitle and the
   control went back to doing nothing. What must be excluded is the part
   that is a subtitle, which is the thing the markup now says.
   --------------------------------------------------------------------- */

/* ---- the consumer rules: one per part, per property ---- */
.lt-head-size :is(.section-head h2, .t-title) { font-size: var(--ty-head-size); }
.lt-head-emph :is(.section-head h2, .t-title) { font-weight: var(--ty-head-weight); font-style: var(--ty-head-style); }
.lt-head-case :is(.section-head h2, .t-title) { text-transform: var(--ty-head-case); }
.lt-head-font :is(.section-head h2, .t-title) { font-family: var(--ty-head-font); }
.lt-sub-size :is(.section-head .kicker, .t-sub) { font-size: var(--ty-sub-size); }
.lt-sub-emph :is(.section-head .kicker, .t-sub) { font-weight: var(--ty-sub-weight); font-style: var(--ty-sub-style); }
.lt-sub-case :is(.section-head .kicker, .t-sub) { text-transform: var(--ty-sub-case); }
.lt-sub-font :is(.section-head .kicker, .t-sub) { font-family: var(--ty-sub-font); }
.lt-item-size :is(h3, .faq summary, .pname, .t-item) { font-size: var(--ty-item-size); }
.lt-item-emph :is(h3, .faq summary, .pname, .t-item) { font-weight: var(--ty-item-weight); font-style: var(--ty-item-style); }
.lt-item-case :is(h3, .faq summary, .pname, .t-item) { text-transform: var(--ty-item-case); }
.lt-item-font :is(h3, .faq summary, .pname, .t-item) { font-family: var(--ty-item-font); }
.lt-text-size :is(.section-lede, .muted, .answer, .lbl, .t-body, p, li):not(.tiny):not(.small):not(.kicker):not(.t-sub):not(.t-title) { font-size: var(--ty-text-size); }
.lt-text-emph :is(.section-lede, .muted, .answer, .lbl, .t-body, p, li):not(.tiny):not(.small):not(.kicker):not(.t-sub):not(.t-title) { font-weight: var(--ty-text-weight); font-style: var(--ty-text-style); }
.lt-text-case :is(.section-lede, .muted, .answer, .lbl, .t-body, p, li):not(.tiny):not(.small):not(.kicker):not(.t-sub):not(.t-title) { text-transform: var(--ty-text-case); }
.lt-text-font :is(.section-lede, .muted, .answer, .lbl, .t-body, p, li):not(.tiny):not(.small):not(.kicker):not(.t-sub):not(.t-title) { font-family: var(--ty-text-font); }

/* ---- the values ---- */
.ltv-head-size-sm { --ty-head-size: clamp(1.15rem, 2vw, 1.4rem); }
.ltv-head-size-md { --ty-head-size: clamp(1.5rem, 3vw, 2.1rem); }
.ltv-head-size-lg { --ty-head-size: clamp(1.9rem, 4vw, 2.6rem); }
.ltv-head-size-xl { --ty-head-size: clamp(2.3rem, 5.5vw, 3.4rem); }
.ltv-sub-size-sm { --ty-sub-size: .7rem; }
.ltv-sub-size-md { --ty-sub-size: .8rem; }
.ltv-sub-size-lg { --ty-sub-size: 1rem; }
.ltv-sub-size-xl { --ty-sub-size: 1.25rem; }
.ltv-item-size-sm { --ty-item-size: 1rem; }
.ltv-item-size-md { --ty-item-size: 1.2rem; }
.ltv-item-size-lg { --ty-item-size: 1.45rem; }
.ltv-item-size-xl { --ty-item-size: 1.75rem; }
.ltv-text-size-sm { --ty-text-size: .92rem; }
.ltv-text-size-md { --ty-text-size: 1rem; }
.ltv-text-size-lg { --ty-text-size: 1.1rem; }
.ltv-text-size-xl { --ty-text-size: 1.22rem; }

.ltv-head-emph-normal { --ty-head-weight: 400; --ty-head-style: normal; }
.ltv-head-emph-medium { --ty-head-weight: 600; --ty-head-style: normal; }
.ltv-head-emph-bold { --ty-head-weight: 700; --ty-head-style: normal; }
.ltv-head-emph-italic { --ty-head-weight: 400; --ty-head-style: italic; }
.ltv-head-emph-bolditalic { --ty-head-weight: 700; --ty-head-style: italic; }
.ltv-sub-emph-normal { --ty-sub-weight: 400; --ty-sub-style: normal; }
.ltv-sub-emph-medium { --ty-sub-weight: 600; --ty-sub-style: normal; }
.ltv-sub-emph-bold { --ty-sub-weight: 700; --ty-sub-style: normal; }
.ltv-sub-emph-italic { --ty-sub-weight: 400; --ty-sub-style: italic; }
.ltv-sub-emph-bolditalic { --ty-sub-weight: 700; --ty-sub-style: italic; }
.ltv-item-emph-normal { --ty-item-weight: 400; --ty-item-style: normal; }
.ltv-item-emph-medium { --ty-item-weight: 600; --ty-item-style: normal; }
.ltv-item-emph-bold { --ty-item-weight: 700; --ty-item-style: normal; }
.ltv-item-emph-italic { --ty-item-weight: 400; --ty-item-style: italic; }
.ltv-item-emph-bolditalic { --ty-item-weight: 700; --ty-item-style: italic; }
.ltv-text-emph-normal { --ty-text-weight: 400; --ty-text-style: normal; }
.ltv-text-emph-medium { --ty-text-weight: 600; --ty-text-style: normal; }
.ltv-text-emph-bold { --ty-text-weight: 700; --ty-text-style: normal; }
.ltv-text-emph-italic { --ty-text-weight: 400; --ty-text-style: italic; }
.ltv-text-emph-bolditalic { --ty-text-weight: 700; --ty-text-style: italic; }

.ltv-head-case-none { --ty-head-case: none; }
.ltv-head-case-upper { --ty-head-case: uppercase; }
.ltv-head-case-lower { --ty-head-case: lowercase; }
.ltv-head-case-caps { --ty-head-case: capitalize; }
.ltv-sub-case-none { --ty-sub-case: none; }
.ltv-sub-case-upper { --ty-sub-case: uppercase; }
.ltv-sub-case-lower { --ty-sub-case: lowercase; }
.ltv-sub-case-caps { --ty-sub-case: capitalize; }
.ltv-item-case-none { --ty-item-case: none; }
.ltv-item-case-upper { --ty-item-case: uppercase; }
.ltv-item-case-lower { --ty-item-case: lowercase; }
.ltv-item-case-caps { --ty-item-case: capitalize; }
.ltv-text-case-none { --ty-text-case: none; }
.ltv-text-case-upper { --ty-text-case: uppercase; }
.ltv-text-case-lower { --ty-text-case: lowercase; }
.ltv-text-case-caps { --ty-text-case: capitalize; }

.ltv-head-font-sans { --ty-head-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans Malayalam", Arial, sans-serif; }
.ltv-head-font-serif { --ty-head-font: Georgia, Cambria, "Times New Roman", "Noto Serif Malayalam", "Rachana", serif; }
.ltv-head-font-malayalam { --ty-head-font: "Noto Sans Malayalam", "Manjari", "Meera", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.ltv-head-font-classic { --ty-head-font: "Rachana", "Meera", "Noto Serif Malayalam", Georgia, "Times New Roman", serif; }
.ltv-head-font-condensed { --ty-head-font: "Roboto Condensed", "Arial Narrow", "Segoe UI", "Noto Sans Malayalam", sans-serif; }
.ltv-sub-font-sans { --ty-sub-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans Malayalam", Arial, sans-serif; }
.ltv-sub-font-serif { --ty-sub-font: Georgia, Cambria, "Times New Roman", "Noto Serif Malayalam", "Rachana", serif; }
.ltv-sub-font-malayalam { --ty-sub-font: "Noto Sans Malayalam", "Manjari", "Meera", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.ltv-sub-font-classic { --ty-sub-font: "Rachana", "Meera", "Noto Serif Malayalam", Georgia, "Times New Roman", serif; }
.ltv-sub-font-condensed { --ty-sub-font: "Roboto Condensed", "Arial Narrow", "Segoe UI", "Noto Sans Malayalam", sans-serif; }
.ltv-item-font-sans { --ty-item-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans Malayalam", Arial, sans-serif; }
.ltv-item-font-serif { --ty-item-font: Georgia, Cambria, "Times New Roman", "Noto Serif Malayalam", "Rachana", serif; }
.ltv-item-font-malayalam { --ty-item-font: "Noto Sans Malayalam", "Manjari", "Meera", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.ltv-item-font-classic { --ty-item-font: "Rachana", "Meera", "Noto Serif Malayalam", Georgia, "Times New Roman", serif; }
.ltv-item-font-condensed { --ty-item-font: "Roboto Condensed", "Arial Narrow", "Segoe UI", "Noto Sans Malayalam", sans-serif; }
.ltv-text-font-sans { --ty-text-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans Malayalam", Arial, sans-serif; }
.ltv-text-font-serif { --ty-text-font: Georgia, Cambria, "Times New Roman", "Noto Serif Malayalam", "Rachana", serif; }
.ltv-text-font-malayalam { --ty-text-font: "Noto Sans Malayalam", "Manjari", "Meera", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.ltv-text-font-classic { --ty-text-font: "Rachana", "Meera", "Noto Serif Malayalam", Georgia, "Times New Roman", serif; }
.ltv-text-font-condensed { --ty-text-font: "Roboto Condensed", "Arial Narrow", "Segoe UI", "Noto Sans Malayalam", sans-serif; }

/* The body alignment has the same reach as the body type, and for the
   same reason: `.muted` is where these sections put their words. */
.ls-talign-left   :is(.section-lede, .muted, .card, .obj, .answer, .t-body) { text-align: left; }
.ls-talign-center :is(.section-lede, .muted, .card, .obj, .answer, .t-body) { text-align: center; }
.ls-talign-right  :is(.section-lede, .muted, .card, .obj, .answer, .t-body) { text-align: right; }


.ls-halign-left   .section-head { text-align: left;   margin-inline: 0; }
.ls-halign-center .section-head { text-align: center; margin-inline: auto; }
.ls-halign-right  .section-head { text-align: right;  margin-inline: 0 0 0 auto; }


/* Families, never files: `font-src 'self'` refuses a web font, so these
   are what is already on the device. Every stack ends in a Malayalam
   face, because half this page is Malayalam and a stack that stops at
   sans-serif leaves it to whatever the browser finds. */
.ls-font-sans {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", "Noto Sans Malayalam", Arial, sans-serif;
}
.ls-font-serif {
  font-family: Georgia, Cambria, "Times New Roman",
               "Noto Serif Malayalam", "Rachana", serif;
}
.ls-font-malayalam {
  font-family: "Noto Sans Malayalam", "Manjari", "Meera",
               -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.ls-font-classic {
  font-family: "Rachana", "Meera", "Noto Serif Malayalam",
               Georgia, "Times New Roman", serif;
}
.ls-font-condensed {
  font-family: "Roboto Condensed", "Arial Narrow", "Segoe UI",
               "Noto Sans Malayalam", sans-serif;
  letter-spacing: -.01em;
}

.ls-space-tight  { padding-block: clamp(1.5rem, 3vw, 2.25rem); }
.ls-space-normal { padding-block: clamp(3rem, 6vw, 4.5rem); }
.ls-space-loose  { padding-block: clamp(4.5rem, 9vw, 7rem); }

.wrap-wide { max-width: 1400px; }
.wrap-full { max-width: none; padding-inline: clamp(.75rem, 3vw, 2.5rem); }

/* =====================================================================
   Free objects, on a twelve-column grid
   =====================================================================

   `grid-auto-rows: min-content` rather than a fixed row height: an
   object's height comes from what is in it, so a paragraph that grows
   pushes its row down instead of spilling over the object below it. A
   pixel canvas cannot do that, which is the other reason this is a grid.
   --------------------------------------------------------------------- */

.objgrid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: min-content;
  gap: 1.25rem;
  align-items: start;
}
.obj { min-width: 0; }                 /* or a long word widens its column */
.obj img { width: 100%; border-radius: var(--radius-sm); }
.obj-spacer { min-height: 1px; }
.obj h3 { margin-bottom: .4rem; }
.obj.is-coloured {
  padding: 1.25rem; border-radius: var(--radius);
  background: var(--card-bg); color: var(--card-ink);
}
.obj.is-coloured h3 { color: var(--card-head); }

/* Below the phone breakpoint the grid is one column and the objects fall
   in reading order — row, then column. This is the line that makes free
   placement survive a phone, and it is why placement is columns and rows
   rather than pixels. */
/* Objects placed ON a section rather than in one.
   -------------------------------------------------------------------
   An absolute layer across the whole band, on the same twelve columns
   the page uses, so an object can land in the banner's empty right-hand
   column or as a badge over a photograph.

   `pointer-events: none` on the layer and `auto` on the objects: the
   layer covers the whole section, and without this it would swallow
   every click meant for the words underneath it. */
.objoverlay {
  position: absolute; inset: 0; z-index: 3;
  pointer-events: none;
  display: grid; align-content: stretch;
  padding-inline: 1.25rem;
}
.objoverlay > .objgrid {
  width: 100%; max-width: var(--wrap); margin-inline: auto; height: 100%;
  align-content: stretch;
}
.objoverlay .obj { pointer-events: auto; }

/* Where an overlaid object sits in the band's height. */
.objoverlay .obj-start  { align-self: start; }
.objoverlay .obj-center { align-self: center; }
.objoverlay .obj-end    { align-self: end; }

.objbelow { margin-top: 1.5rem; }

/* The column beside About. Its cards come from the same renderer as
   every other card grid, so they are one per row here rather than the
   three that grid would give them in a full-width band. */
.aboutaside .grid { grid-template-columns: 1fr; }
.aboutaside .card { background: var(--bg-soft); }

@media (max-width: 720px) {
  .objgrid { grid-template-columns: 1fr; }
  .objgrid > .obj { grid-column: 1 / -1 !important; grid-row: auto !important; }

  /*
   * An overlay stops overlaying.
   *
   * At 360 pixels an object on top of a paragraph hides the paragraph,
   * and the reader has no way to move it out of the way. It becomes a
   * normal block under the section instead, in reading order — which is
   * the same bargain the grid makes everywhere else: placement is what
   * you asked for where there is room, and legible where there is not.
   */
  .objoverlay {
    /*
     * It keeps the page's gutter when it stops overlaying.
     *
     * `padding-inline: 0` was right for an overlay — it is stretched over
     * a band that already has its own `.wrap`, and an overlay with a
     * gutter of its own would be inset twice. Once it becomes an ordinary
     * block it is no longer inside anything, and a logo placed in the
     * banner ran from screen edge to screen edge with the band's bottom
     * edge cutting it off: full-bleed and clipped, which reads as the
     * picture being broken rather than placed.
     *
     * The same 1.25rem every `.wrap` uses, and the bottom room the band's
     * own padding would have given it.
     */
    position: static;
    padding: 0 1.25rem clamp(2.5rem, 7vw, 4rem);
    margin-top: 1.5rem;
    width: 100%; max-width: var(--wrap); margin-inline: auto;
  }
  .objoverlay > .objgrid { height: auto; }

  /* A picture is placed, not blown up. An object given five of twelve
     columns on a desktop is not asking to be 390 pixels wide on a phone,
     and a logo at full screen width dwarfs the heading above it. */
  .objoverlay .obj-image img { max-width: min(100%, 20rem); margin-inline: auto; }
  .objoverlay .obj-center, .objoverlay .obj-image { text-align: center; }
}

/* =====================================================================
   The canvas, on the admin side
   =====================================================================

   The same twelve columns at the same proportions as the page, because a
   placing tool that is a picture of the layout rather than the layout is
   a tool that lies to you at the last moment.
   --------------------------------------------------------------------- */

.canvas {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  padding: .75rem;
}

.canvas-ruler {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: .5rem;
  margin-bottom: .5rem;
}
.canvas-ruler span {
  text-align: center; font-size: .7rem; font-variant-numeric: tabular-nums;
  color: var(--ink-faint); background: var(--bg);
  border: 1px dashed var(--line-soft); border-radius: 5px; padding: .1rem 0;
}

.canvas-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: minmax(74px, min-content);
  gap: .5rem;
  min-height: 160px;
}

.cobj {
  position: relative;
  background: var(--bg);
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius-sm);
  padding: .5rem .8rem .5rem .6rem;
  cursor: grab;
  min-width: 0;
  overflow: hidden;
  /* The pointer handler calls preventDefault, but a touch device scrolls
     the page first and asks questions later without this. */
  touch-action: none;
  user-select: none;
}
.cobj:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.cobj.is-dragging { cursor: grabbing; box-shadow: var(--shadow-lg); opacity: .92; z-index: 2; }
.cobj.is-off { opacity: .55; border-left-style: dotted; }

.cobj-image  { border-left-color: var(--accent, #7c3aed); }
.cobj-button { border-left-color: var(--ok, #0b6e4f); }
.cobj-spacer { border-left-color: var(--line); background: repeating-linear-gradient(
    45deg, var(--bg), var(--bg) 6px, var(--bg-sunken) 6px, var(--bg-sunken) 12px); }

.cobj-head {
  display: flex; justify-content: space-between; align-items: baseline; gap: .5rem;
  font-size: .68rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-faint);
}
.cobj-edit { font-size: .7rem; text-transform: none; letter-spacing: 0; }
.cobj-body {
  font-size: .86rem; line-height: 1.35; margin: .15rem 0 .3rem;
  overflow: hidden; text-overflow: ellipsis;
}
.cobj-body img { max-height: 46px; width: auto; object-fit: contain; }
.cobj-where {
  font-size: .68rem; color: var(--ink-faint); font-variant-numeric: tabular-nums;
}

/* Which band an object is on, and where on it. */
.cobj-move { display: flex; gap: .3rem; flex-wrap: wrap; margin: .15rem 0 .3rem; }
.cobj-host {
  font-size: .7rem; padding: .1rem .3rem; max-width: 100%;
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--bg); color: var(--ink-soft);
  cursor: pointer;
}
.cobj-host:hover { border-color: var(--brand); }

/* Width, as two buttons rather than only an edge to find. */
.cobj-width {
  position: absolute; right: 18px; bottom: .35rem;
  display: flex; gap: .2rem;
}
.cobj-w {
  width: 22px; height: 22px; padding: 0; line-height: 1;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--ink-soft);
  font-size: .95rem; cursor: pointer;
}
.cobj-w:hover { border-color: var(--brand); color: var(--brand); }
.cobj-w:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* The right edge, for widening. A real button so it is reachable by tab
   and announced; the arrow keys do the same job. */
.cobj-grip {
  position: absolute; top: 0; right: 0; width: 14px; height: 100%;
  border: 0; padding: 0; background: transparent; cursor: ew-resize;
}
.cobj-grip::after {
  content: ""; position: absolute; top: 50%; right: 4px;
  width: 3px; height: 26px; margin-top: -13px; border-radius: 2px;
  background: var(--line); transition: background .12s ease;
}
.cobj:hover .cobj-grip::after { background: var(--brand); }
.cobj-grip:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/* Under about a tablet the editor is not a placing tool any more — twelve
   columns across 500px is 40px a column. It becomes a list, and the
   numbers on each object's own form are how you place things there. */
@media (max-width: 760px) {
  .canvas-ruler { display: none; }
  .canvas-grid { grid-template-columns: 1fr; grid-auto-rows: min-content; }
  .canvas-grid > .cobj { grid-column: 1 / -1 !important; grid-row: auto !important; cursor: default; }
  .cobj-grip { display: none; }
}

/* checklist items that carry a paragraph as well as a line */
.ticklist-wide { max-width: 70ch; margin-inline: auto; }
.ticklist-wide li { margin-bottom: 1.1rem; }
.ticklist-wide strong { color: var(--ink); display: block; }

/* questions, when a whole section is them */
.faq-list { max-width: 820px; margin-inline: auto; }

/* partners, insurers, sponsors.
   Greyed back until hovered so a row of mismatched logos reads as one
   band rather than as the loudest thing on the page. */
.logo-row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 1.5rem 2.5rem;
}
.logo-row .logo { display: grid; place-items: center; }
.logo-row img {
  max-height: 56px; max-width: 190px; width: auto; object-fit: contain;
  filter: grayscale(1); opacity: .65; transition: filter .2s ease, opacity .2s ease;
}
.logo-row a.logo:hover img, .logo-row .logo:hover img { filter: none; opacity: 1; }

/* pictures with captions */
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr)); gap: 1.25rem; }
.gallery figure { margin: 0; }
.gallery img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--radius); background: var(--bg-sunken); box-shadow: var(--shadow-sm);
}
.gallery figcaption { margin-top: .6rem; font-size: .9rem; color: var(--ink-soft); text-align: center; }

/* full-width rows, picture and words, alternating sides */
.feature-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: 2.5rem;
  align-items: center; margin-bottom: 3rem;
}
.feature-row:last-child { margin-bottom: 0; }
.feature-row.flip .feature-img { order: 2; }
.feature-img img {
  width: 100%; border-radius: var(--radius); object-fit: cover;
  box-shadow: var(--shadow); background: var(--bg-sunken);
}
.feature-text h3 { font-size: 1.4rem; margin-bottom: .6rem; }
@media (max-width: 780px) {
  .feature-row { grid-template-columns: 1fr; gap: 1.25rem; margin-bottom: 2.25rem; }
  .feature-row.flip .feature-img { order: 0; }
}

/* label-and-figure rows, as a card on its own */
.valuecard {
  max-width: 560px; margin-inline: auto;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); box-shadow: var(--shadow-sm); overflow: hidden;
}
.valuecard .valuerow {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1.5rem; padding: .9rem 1.25rem; border-bottom: 1px solid var(--line);
}
.valuecard .valuerow:last-child { border-bottom: 0; }
.valuecard .lbl { color: var(--ink-soft); font-size: .92rem; }
.valuecard .val { font-weight: 700; text-align: right; }

/* ---------------------------------------------------------------------
   Items with a detail

   A card that opens something has to look like it does, and it has to say
   so on a phone too — where there is no hover at all. So the cue is always
   in the layout, and hover only strengthens it.
   --------------------------------------------------------------------- */
a.is-openable, a.card.is-openable { text-decoration: none; color: inherit; display: block; }
a.card.is-openable { cursor: pointer; }

.card.is-openable { transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease; }
.card.is-openable:hover,
.card.is-openable:focus-visible {
  transform: translateY(-3px);
  box-shadow: var(--shadow);
  border-color: color-mix(in srgb, var(--brand) 35%, var(--line));
}

/* The cue. Always rendered, so a touch device sees it; it just brightens
   and slides on hover rather than appearing out of nowhere. */
.more {
  display: inline-block; margin-top: .85rem;
  font-size: .85rem; font-weight: 700; color: var(--brand);
  text-decoration: none; transition: transform .16s ease, opacity .16s ease;
  opacity: .75;
}
.card.is-openable:hover .more,
.card.is-openable:focus-visible .more { opacity: 1; transform: translateX(3px); }
a.more:hover { opacity: 1; text-decoration: underline; }

.stat.is-openable .more { margin-top: .4rem; font-size: .72rem; }
.stat.is-openable:hover .more { opacity: 1; }

.gallery figure.is-openable img { transition: transform .2s ease, box-shadow .2s ease; }
.gallery figure.is-openable a:hover img,
.gallery figure.is-openable a:focus-visible img { transform: scale(1.02); box-shadow: var(--shadow); }

.is-loading { opacity: .6; cursor: progress; }

/* ---------------------------------------------------------------------
   The detail panel

   A native <dialog>: the backdrop, Escape, focus trapping and focus return
   are the browser's, not ours. With the script absent this element is never
   created and the card's link navigates to the same content as a page.
   --------------------------------------------------------------------- */
.detail-dialog {
  width: min(720px, calc(100vw - 2rem));
  max-height: min(86vh, 900px);
  padding: 0; border: 0; border-radius: var(--radius);
  background: var(--bg); color: var(--ink);
  box-shadow: 0 24px 60px rgba(0,0,0,.28);
  overflow: hidden;
}
.detail-dialog::backdrop { background: rgba(15, 30, 25, .55); }
.detail-dialog .detail-body { padding: 2rem clamp(1.25rem, 4vw, 2.5rem); overflow-y: auto; max-height: min(86vh, 900px); }
.detail-close {
  position: absolute; top: .5rem; right: .65rem; z-index: 2;
  width: 2.25rem; height: 2.25rem; border: 0; border-radius: 50%;
  background: var(--bg-soft); color: var(--ink-soft);
  font-size: 1.5rem; line-height: 1; cursor: pointer;
}
.detail-close:hover { background: var(--bg-sunken); color: var(--ink); }

/* The page behind must not scroll while the panel is open — on a phone,
   scrolling the page under a modal is how people lose their place. */
.has-dialog, .has-dialog body { overflow: hidden; }

/* ---------------------------------------------------------------------
   The detail itself, shared by the panel and the standalone page
   --------------------------------------------------------------------- */
.itemdetail > header { margin-bottom: 1.25rem; }
.itemdetail h2 { font-size: clamp(1.4rem, 3vw, 1.9rem); margin: .35rem 0 0; }
.itemdetail .lede { color: var(--ink-soft); font-size: 1.02rem; }
.itemdetail .detail-img {
  width: 100%; border-radius: var(--radius); margin-bottom: 1.25rem;
  object-fit: cover; background: var(--bg-sunken);
}
.itemdetail .stack > :last-child { margin-bottom: 0; }

/* Tables are allowed in a detail body, and a pasted one arrives unstyled. */
.itemdetail table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: .95rem; }
.itemdetail th, .itemdetail td { padding: .6rem .75rem; border: 1px solid var(--line); text-align: left; }
.itemdetail th { background: var(--bg-soft); font-weight: 700; }
.itemdetail hr { border: 0; border-top: 1px solid var(--line); margin: 1.5rem 0; }

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

   Every value here comes from a custom property set on the element by the
   server, computed from the one colour the office chose. Nothing in this
   file picks a colour; it only decides where the chosen one goes.

   A card without those properties is untouched, so switching colour off for
   a section returns it exactly to how it looked before any of this existed.
   --------------------------------------------------------------------- */
.card.is-coloured,
.stat.is-coloured,
.feature-row.is-coloured,
.rules > li.is-coloured {
  background: var(--card-bg);
  color: var(--card-ink);
  border-color: var(--card-border);
}

.card.is-coloured h3,
.stat.is-coloured .num,
.feature-row.is-coloured h3,
.rules > li.is-coloured h3 { color: var(--card-head); }

/* The muted body inside a coloured card cannot keep using the page's grey —
   on a solid card that is grey on colour, which is the one combination the
   contrast maths cannot rescue because it was never asked about. */
.card.is-coloured .muted,
.stat.is-coloured .lbl,
.feature-row.is-coloured .muted,
.rules > li.is-coloured .muted { color: var(--card-soft); }

.card.is-coloured .card-icon,
.stat.is-coloured .stat-icon,
.card.is-coloured .more,
.rules > li.is-coloured .more { color: var(--card-accent); }

.card.is-coloured a:not(.btn),
.feature-row.is-coloured a:not(.btn) { color: var(--card-head); }

/* The soft tint gets a coloured edge. It is what stops a row of pale cards
   reading as one wide pale block. */
.card.card-tint {
  border-left: 4px solid var(--card-accent);
}
.card.card-tint:hover, .card.card-tint:focus-visible {
  border-color: var(--card-border);
  border-left-color: var(--card-accent);
}

.card.card-solid {
  border-color: var(--card-border);
  box-shadow: 0 6px 18px color-mix(in srgb, var(--card-bg) 45%, transparent);
}
.card.card-solid:hover, .card.card-solid:focus-visible {
  box-shadow: 0 12px 28px color-mix(in srgb, var(--card-bg) 55%, transparent);
  border-color: var(--card-border);
}
/* A solid card's icon sits on the colour, so it needs a plate behind it or
   it disappears into the card at a glance. */
.card.card-solid .card-icon {
  background: color-mix(in srgb, var(--card-ink) 14%, transparent);
  border-radius: 10px;
}

/* The white variant keeps the page's own card treatment and colours only
   the words — the least loud of the three, and the safest for a section of
   long paragraphs. */
.card.card-plain { background: #fff; }

/* Numbered lists: the disc takes the colour rather than the row, so a page
   of rules stays a page of rules. */
.rules > li.is-coloured::before { background: var(--card-accent); }

/* A stat tile is small, so a solid one is the only style with enough room to
   read as coloured at all — the tint reads as a faint stripe, which is fine
   and deliberate. */
.stat.card-tint { border-left: 3px solid var(--card-accent); }

/* ---------------------------------------------------------------------
   The card colour picker
   --------------------------------------------------------------------- */
.colourfield { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }

.colourinput {
  width: 3.25rem; height: 2.5rem; padding: 2px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 8px; background: var(--bg);
}

.swatches { display: flex; gap: .4rem; flex-wrap: wrap; }
.swatch {
  width: 1.75rem; height: 1.75rem; border-radius: 50%; cursor: pointer;
  border: 2px solid transparent; box-shadow: 0 0 0 1px rgba(0,0,0,.12) inset;
  padding: 0;
}
.swatch:hover, .swatch:focus-visible {
  border-color: var(--ink); outline: none; transform: scale(1.1);
}

.cardpreview { margin-top: 1rem; max-width: 22rem; }
/* Dimmed, not hidden, while the colour is being chosen automatically — the
   card still shows the shape, and hiding it would make the panel jump. */
.cardpreview.is-auto [data-preview-card] { opacity: .5; }
.cardpreview .card { pointer-events: none; }
.cardpreview p { margin: .5rem 0 0; }

/* ---------------------------------------------------------------------
   User avatar
   --------------------------------------------------------------------- */
.avatar {
  border-radius: 50%; object-fit: cover; flex: none;
  display: inline-grid; place-items: center;
  font-weight: 700; letter-spacing: .01em; line-height: 1;
  background: var(--bg-sunken);
}
.avatar-sm { width: 30px; height: 30px; font-size: .72rem; }
.avatar-md { width: 36px; height: 36px; font-size: .82rem; }
.avatar-lg { width: 84px; height: 84px; font-size: 1.8rem; }
.avatar-initials { user-select: none; }

/* The account block in the top bar now leads with the picture. */
.admin-user .avatar { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--line); }
.drawer-account .who { display: flex; align-items: center; gap: .6rem; }
.drawer-account .who strong { display: block; }

/* The avatar field on the user form. */
.avatarfield { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.avatarfield .avatar-lg { box-shadow: 0 0 0 3px var(--line); }

/* An explanation that has to be read before the panel under it ---------
   `.notice` was written into three templates and never given a rule, so
   the sentence that says why a screen is read-only rendered as body text
   next to the thing it was explaining. It is one style, used by the
   member-edit warning, the service-area warning, and the sealed
   superadmin account. */
.notice {
  border: 1px solid var(--line); border-left-width: 4px;
  border-radius: var(--radius); padding: .85rem 1.1rem;
  margin: 0 0 1.25rem; font-size: .93rem; line-height: 1.5;
  background: #fff;
}
.notice-warn {
  background: var(--warn-bg); color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 30%, transparent);
  border-left-color: var(--warn);
}

/* The guidance above a form ------------------------------------------
   A panel of advice sits between somebody and the field they came to
   change, so it is measured in lines, not in words. Two decisions:

   The measure is wider here than in prose. These panels were capped at
   70ch on a screen twice that wide, which turned four sentences into
   nine lines down the left-hand side and left the rest of the row empty.
   A hundred and ten characters is near the top of what is comfortable
   for a short notice, and is about half the height for the same text —
   which is the whole point on a panel that stands between somebody and
   the field they came to change.

   The reasoning folds away. What somebody must know before typing stays
   visible; why it is so goes behind a disclosure triangle, which is a
   browser feature and needs no script — the policy forbids inline ones.
   Open it and it stays open until the page is left, which is the right
   behaviour for something read once and then known. */
.editnote > .tight { max-width: 110ch; }
.editnote > .tight:last-child { margin-bottom: 0; }
.editnote .notice.tight { padding: .7rem 1rem; margin-bottom: .75rem; }
.editnote p.tight { margin: 0 0 .75rem; }

.editnote details { margin-top: .4rem; }
.editnote details > summary {
  cursor: pointer; font-weight: 600; font-size: .88rem;
  width: max-content; padding: .1rem 0;
  opacity: .85;
}
.editnote details > summary:hover { opacity: 1; }
.editnote details[open] > summary { margin-bottom: .35rem; }
.editnote details p { margin: 0 0 .5rem; }
.editnote details p:last-child { margin-bottom: 0; }

/* A fieldset used only to disable everything inside it, not to group or
   label anything. Browsers give one a border, margin and padding by
   default, which would draw a box round a form that already has panels. */
fieldset.bare { border: 0; margin: 0; padding: 0; min-width: 0; }
fieldset.bare :disabled { cursor: not-allowed; }

/* The workflow, drawn as the sequence it is ---------------------------
   "Which of these steps am I changing" is the question somebody opens the
   workflow panel with, and a column of checkboxes does not answer it. The
   line rewrites itself from the settings, so ticking a box shows you the
   route you just built. */
.workflowsteps {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .5rem;
  margin: 0 0 1.4rem; padding: .85rem 1rem;
  background: var(--line-soft); border-radius: var(--radius);
  font-size: .9rem;
}
.wfstep {
  background: #fff; border: 1px solid var(--line); border-radius: 999px;
  padding: .3rem .8rem; white-space: nowrap;
}
.wfstep em { font-style: normal; font-weight: 700; color: var(--brand); }
.wfarrow { color: #9aa3b0; font-weight: 700; }
hr.rule { border: 0; border-top: 1px solid var(--line-soft); margin: 1.5rem 0; }

/* Committee tabs, on the admin screen and the public page ------------
   Links with a query string rather than a script: each committee gets a
   URL worth sending, the back button works, and the page survives the
   CSP that forbids inline scripts. */
.committabs {
  display: flex; flex-wrap: wrap; gap: .5rem;
  margin: 0 0 1.5rem; justify-content: center;
  /* `align-items` and `align-self`, both deliberate. The admin content
     area stretches its children, so without these the nav grew to the
     height of the column and every pill became a 700-pixel arch. */
  align-items: center; align-self: start;
}
/* `committabs-admin`, not `.committabs.admin`.
   The first version used a bare `admin` modifier, which also matches the
   page-level `.admin` layout — `display: grid; grid-template-columns:
   240px 1fr; min-height: 100vh` — so the tab strip became a two-column
   grid the height of the viewport. A modifier named after a layout class
   is a collision waiting for the one page that has both. */
.committabs-admin { justify-content: flex-start; margin-bottom: 1rem; }
.committab {
  display: inline-flex; align-items: center; gap: .5rem;
  align-self: center; flex: 0 0 auto;
  padding: .55rem 1rem; border-radius: 999px;
  border: 1px solid var(--line); background: #fff;
  color: inherit; text-decoration: none; font-weight: 600; font-size: .93rem;
}
.committab:hover { border-color: var(--brand); }
.committab.is-on {
  background: var(--brand); border-color: var(--brand); color: #fff;
}
.committab-n {
  font-size: .78rem; font-weight: 700; opacity: .7;
  padding: .05rem .45rem; border-radius: 999px;
  background: color-mix(in srgb, currentColor 14%, transparent);
}

/* The four-part type table on the content form. Compact selects, because
   sixteen of them stacked as ordinary fields is a screen nobody reads. */
.typetable th, .typetable td { padding: .35rem .4rem; vertical-align: middle; }
.typetable thead th { font-size: .74rem; text-transform: uppercase; letter-spacing: .05em; }
/* The row label is a name, not a column header: the table's base rule
   uppercases every th, which turned the one-line description under each
   part into shouting. */
.typetable tbody th {
  font-weight: 600; text-align: left; text-transform: none;
  letter-spacing: 0; font-size: .9rem; color: var(--ink);
}
.typetable tbody th .tiny {
  font-weight: 400; text-transform: none; letter-spacing: 0;
  margin-top: .15rem; max-width: 26ch;
}
.typetable select { font-size: .82rem; padding: .3rem .4rem; width: 100%; min-width: 7.5rem; }

/* ---------------------------------------------------------------------
   The photo gallery, drawn like a file explorer
   ---------------------------------------------------------------------

   Three panes and three view modes, and every one of them is a class on
   a container rather than a widget. The layout switch is a link that
   changes `.g-icons` to `.g-tiles`, and CSS grid does the rest — which
   is why the whole module works under `script-src 'self'` with no
   script of its own at all.

   The three views are Windows' own, because the people who will use
   this have been choosing between them for twenty years:

     icons    a wall of pictures, the name underneath. Browsing.
     tiles    a picture beside its name, date and size. Skimming.
     details  a table, one row per photograph. Looking for one thing.

   One markup, three layouts: every item carries a thumbnail, a name, a
   date, a size, a folder and its tags, and each view hides what it does
   not want. That keeps the PHP loop to one shape — the alternative is
   three templates that drift apart the first time a column is added.
   --------------------------------------------------------------------- */

.gwrap {
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
}
@media (max-width: 900px) {
  /* On a phone the tree goes above the contents rather than beside
     them, and is capped so it cannot push the photographs off screen. */
  .gwrap { grid-template-columns: minmax(0, 1fr); }
  .gtree { max-height: 14rem; overflow: auto; }
}

/* ---- the folder tree ---- */
.gtree {
  background: var(--surface, #fff);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .4rem 0 .6rem;
  position: sticky; top: 1rem;
}
.gtree-head {
  font-size: .74rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); padding: .3rem .9rem .5rem;
}
.gtree ul { list-style: none; margin: 0; padding: 0; }
/* Indentation is the tree. A branch one level in is one step right, and
   the line down the left is what makes six of them readable at a glance. */
.gtree ul ul {
  margin-left: .85rem;
  border-left: 1px solid var(--line-soft, var(--line));
}
.gtree a {
  display: flex; align-items: center; gap: .45rem;
  padding: .3rem .9rem; font-size: .88rem;
  color: var(--ink); text-decoration: none;
}
.gtree a:hover { background: color-mix(in srgb, var(--brand) 7%, transparent); }
.gtree a.here {
  background: color-mix(in srgb, var(--brand) 13%, transparent);
  font-weight: 600;
}
.gt-icon {
  width: 1rem; height: .8rem; flex: none; border-radius: 2px;
  background: color-mix(in srgb, var(--brand) 38%, #d7c98b);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.5);
}
.gt-icon.home { border-radius: 50%; background: var(--brand); }
.gt-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gt-count { font-size: .74rem; color: var(--muted); }
.gt-public { color: var(--brand); font-weight: 700; }

/* ---- breadcrumb and toolbar ---- */
.gcrumb {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  font-size: .9rem; margin-bottom: .5rem;
}
.gcrumb a { text-decoration: none; }
.gcrumb a:hover { text-decoration: underline; }
.gcrumb span[aria-hidden] { color: var(--muted); }
.gdesc { margin: 0 0 .75rem; max-width: 100ch; }
.gcount { margin: 0 0 .75rem; }

.gbar {
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
  margin-bottom: 1rem;
}
.gbar .input { flex: 1 1 14rem; min-width: 0; }
.gbar .gbar-narrow { flex: 0 1 11rem; }
.gbar .gbar-date   { flex: 0 1 9.5rem; }

.gviews { display: inline-flex; margin-left: auto; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.gview {
  padding: .4rem .7rem; font-size: .8rem; text-decoration: none;
  color: var(--ink); background: var(--surface, #fff);
  border-right: 1px solid var(--line);
}
.gview:last-child { border-right: 0; }
.gview.on { background: var(--brand); color: #fff; font-weight: 600; }

/* ---- sub-folders ---- */
.gfolders {
  display: grid; gap: .75rem; margin-bottom: 1.25rem;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
.gfolder {
  display: block; text-decoration: none; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .5rem; background: var(--surface, #fff);
}
.gfolder:hover { border-color: var(--brand); }
.gf-thumb {
  display: block; aspect-ratio: 4 / 3; border-radius: calc(var(--radius) - 2px);
  overflow: hidden; background: color-mix(in srgb, var(--brand) 10%, #e8e4d8);
  margin-bottom: .45rem;
}
.gf-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* An empty folder gets the manila-folder shape rather than a blank grey
   rectangle, which reads as a picture that failed to load. */
.gf-thumb.empty {
  background:
    linear-gradient(transparent 38%, color-mix(in srgb, var(--brand) 22%, #d7c98b) 38%),
    linear-gradient(to right, color-mix(in srgb, var(--brand) 22%, #d7c98b) 42%, transparent 42%);
}
.gf-name { display: block; font-weight: 600; font-size: .9rem; }
.gf-meta { display: block; font-size: .76rem; color: var(--muted); margin-top: .1rem; }

/* ---- the photographs: one markup, three layouts ---- */
.gitems { display: grid; gap: .75rem; }
.gitem { position: relative; min-width: 0; }
.gitem .gthumb { display: block; overflow: hidden; background: #f1efe9; }
.gitem .gthumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.gitem .gname { color: var(--ink); text-decoration: none; min-width: 0; }
.gitem .gname:hover { text-decoration: underline; }

/* The tick-box sits on the picture and only shows on hover or when it is
   already ticked, so a wall of photographs is photographs and not a wall
   of empty squares. `:focus-within` keeps it reachable from the keyboard. */
.gpick {
  position: absolute; top: .3rem; left: .3rem; z-index: 2;
  background: rgba(255,255,255,.9); border-radius: 4px;
  padding: .1rem .2rem; line-height: 1; opacity: 0;
  transition: opacity .12s ease;
}
.gitem:hover .gpick,
.gitem:focus-within .gpick,
.gpick:has(input:checked) { opacity: 1; }
.gitem:has(.gpick input:checked) .gthumb { outline: 3px solid var(--brand); outline-offset: -3px; }

/* Large icons: a wall of pictures, the name underneath. */
.g-icons { grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); }
.g-icons .gthumb { aspect-ratio: 1; border-radius: var(--radius); }
.g-icons .gname {
  display: block; margin-top: .35rem; font-size: .82rem; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.g-icons .gtaken, .g-icons .gsize, .g-icons .gfold, .g-icons .gtags { display: none; }

/* Tiles: the picture beside what it is. */
.g-tiles { grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); }
.g-tiles .gitem {
  display: grid; grid-template-columns: 5rem minmax(0, 1fr);
  gap: .6rem; align-items: center;
  border: 1px solid var(--line); border-radius: var(--radius); padding: .45rem;
  background: var(--surface, #fff);
}
.g-tiles .gthumb { aspect-ratio: 1; border-radius: calc(var(--radius) - 2px); grid-row: span 3; }
.g-tiles .gname { font-weight: 600; font-size: .86rem; }
.g-tiles .gtaken, .g-tiles .gsize { font-size: .76rem; color: var(--muted); }
.g-tiles .gsize::before { content: '· '; }
.g-tiles .gtaken, .g-tiles .gsize { display: inline; }
.g-tiles .gfold, .g-tiles .gtags { display: none; }

/* Details: a table, made of grid so the markup stays the same. */
.g-details { gap: 0; }
.gdet-head, .g-details .gitem {
  display: grid;
  grid-template-columns: 2rem 3rem minmax(8rem, 2fr) 7rem 5rem minmax(6rem, 1fr) minmax(6rem, 1fr);
  align-items: center; gap: .6rem;
  padding: .3rem .4rem; border-bottom: 1px solid var(--line-soft, var(--line));
}
.gdet-head {
  font-size: .74rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); border-bottom-width: 2px;
}
/* The head has no tick-box column of its own; the first cell is the
   spacer that lines its labels up with the rows underneath. */
.gdet-head span:first-child { grid-column: span 2; }
.g-details .gpick { position: static; opacity: 1; background: none; padding: 0; }
.g-details .gthumb { width: 3rem; height: 2.25rem; border-radius: 3px; }
.g-details .gname, .g-details .gtaken, .g-details .gsize,
.g-details .gfold, .g-details .gtags {
  display: block; font-size: .84rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.g-details .gtaken, .g-details .gsize, .g-details .gtags { color: var(--muted); }
@media (max-width: 760px) {
  /* Six columns do not fit on a phone. Details falls back to tiles
     rather than scrolling sideways, because a table you have to drag is
     not a table anybody reads. */
  .gdet-head { display: none; }
  .g-details .gitem {
    grid-template-columns: 3.5rem minmax(0, 1fr);
    grid-template-areas: 'pick name' 'thumb meta';
    row-gap: .15rem;
  }
  .g-details .gpick  { grid-area: pick; }
  .g-details .gthumb { grid-area: thumb; width: 3.5rem; height: 2.6rem; }
  .g-details .gname  { grid-area: name; font-weight: 600; }
  .g-details .gfold, .g-details .gtags { display: none; }
}

/* ---- the bar of things to do with what is ticked ---- */
.gbulk {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  margin-top: 1rem; padding: .7rem .8rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: color-mix(in srgb, var(--brand) 4%, transparent);
}
.gbulk-act { display: inline-flex; gap: .35rem; align-items: center; }
.gbulk-act .input { width: auto; min-width: 9rem; font-size: .85rem; padding: .35rem .5rem; }

.gupload { margin-top: 1.5rem; }

/* ---- one photograph ---- */
.gphoto {
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 1.25rem; align-items: start;
}
@media (max-width: 1000px) { .gphoto { grid-template-columns: minmax(0, 1fr); } }

.gphoto-stage {
  margin: 0; background: #1b1b1b; border-radius: var(--radius);
  padding: .75rem; text-align: center;
}
.gphoto-stage img {
  max-width: 100%; max-height: 74vh; height: auto;
  border-radius: calc(var(--radius) - 3px);
}
.gphoto-stage figcaption {
  color: rgba(255,255,255,.85); font-size: .85rem; margin-top: .6rem;
}
.gphoto-side .panel { margin-bottom: 1rem; }

.glist { list-style: none; margin: 0 0 .75rem; padding: 0; }
.glist li { padding: .2rem 0; border-bottom: 1px solid var(--line-soft, var(--line)); }
.glist li:last-child { border-bottom: 0; }

.gfind { display: flex; gap: .4rem; margin-bottom: .6rem; }
.gfind .input { flex: 1; min-width: 0; }

.gpeople { max-height: 16rem; overflow: auto; margin-bottom: .6rem; }
.gperson {
  display: flex; gap: .5rem; align-items: baseline;
  padding: .22rem .1rem; font-size: .88rem;
}
.gperson:hover { background: color-mix(in srgb, var(--brand) 6%, transparent); }

/* A multi-line tick-box explanation. `.tickline` is one line and nowrap,
   which is right beside a short label and wrong under a paragraph. */
.gcheck {
  display: flex; gap: .55rem; align-items: start;
  margin-top: .8rem; max-width: 100ch; font-size: .9rem;
}
.gcheck input { margin-top: .25rem; flex: none; }

/* =====================================================================
   The public gallery
   =====================================================================
   Same pictures as the admin side, none of the machinery — and a
   different job. Inside, somebody is looking for one photograph among
   four thousand, so the screen is a filing cabinet. Out here nobody is
   looking for anything: they are being shown what the organisation did
   last year, and the pictures have to carry it.

   So the album is a card made of its own cover photograph with the name
   written over it, rather than a thumbnail with a caption underneath.
   The second is what a file listing looks like; the first is what a
   gallery looks like.
   ===================================================================== */

.galbums {
  display: grid; gap: 1.25rem; justify-content: center;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 22rem));
}

.galbum {
  position: relative; display: block; overflow: hidden;
  aspect-ratio: 4 / 3; border-radius: var(--radius);
  text-decoration: none; color: #fff;
  box-shadow: var(--shadow-sm);
  /* Behind the photograph, so an album whose cover is still loading —
     or missing — is a brand-coloured tile rather than a grey hole. */
  background: linear-gradient(140deg,
    color-mix(in srgb, var(--brand) 88%, #000), var(--brand-2));
  transition: transform .25s ease, box-shadow .25s ease;
}
.galbum:hover, .galbum:focus-visible { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.galbum:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

.galbum-thumb { position: absolute; inset: 0; display: block; }
.galbum-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .6s cubic-bezier(.2, .7, .3, 1);
}
.galbum:hover .galbum-thumb img { transform: scale(1.06); }

/* The scrim, and the reason writing on photographs usually looks
   amateur: without it the title is black-on-sky or white-on-sand
   depending on the picture, and unreadable on half of them. A fixed
   gradient makes the bottom third dark whatever was photographed. */
.galbum-thumb::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top,
    rgba(8, 12, 18, .88) 0%, rgba(8, 12, 18, .48) 34%, rgba(8, 12, 18, 0) 66%);
}

.galbum-body { position: absolute; inset: auto 0 0 0; padding: 1rem 1.05rem; }
.galbum-name {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  font-weight: 700; font-size: 1.02rem; line-height: 1.32;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .45);
}
.galbum-when {
  display: block; margin-top: .22rem; font-size: .82rem;
  color: rgba(255, 255, 255, .84); text-shadow: 0 1px 2px rgba(0, 0, 0, .4);
}
.galbum-count {
  position: absolute; top: .72rem; right: .72rem;
  padding: .24rem .6rem; border-radius: 999px;
  font-size: .72rem; font-weight: 600; letter-spacing: .01em;
  color: #fff; background: rgba(8, 12, 18, .5);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}

/* ---------------------------------------------------------------------
   One album's photographs

   `auto-fit` rather than `auto-fill`, with a ceiling on the track: an
   album of two photographs was drawing two small squares against the
   left edge of a 1,140px page with nothing beside them, which reads as
   a page that failed to load. auto-fit collapses the empty tracks so
   `justify-content: center` can do its job, and the ceiling stops two
   pictures stretching to half a metre each.
   --------------------------------------------------------------------- */
.gpublic {
  display: grid; gap: 1rem; justify-content: center;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 17rem));
}

.gphoto {
  position: relative; display: block; overflow: hidden;
  aspect-ratio: 4 / 3; border-radius: var(--radius-sm);
  background: var(--bg-sunken); box-shadow: var(--shadow-sm);
  transition: transform .2s ease, box-shadow .2s ease;
}
.gphoto:hover, .gphoto:focus-visible { transform: translateY(-3px); box-shadow: var(--shadow); }
.gphoto:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.gphoto img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .6s cubic-bezier(.2, .7, .3, 1);
}
.gphoto:hover img { transform: scale(1.05); }

.gphoto-cap {
  position: absolute; inset: auto 0 0 0; padding: .9rem .7rem .55rem;
  font-size: .78rem; line-height: 1.35; color: #fff;
  background: linear-gradient(to top, rgba(8, 12, 18, .85), rgba(8, 12, 18, 0));
  opacity: 0; transform: translateY(8px);
  transition: opacity .2s ease, transform .2s ease;
}
.gphoto:hover .gphoto-cap, .gphoto:focus-visible .gphoto-cap { opacity: 1; transform: none; }

/* A phone has no hover, so a caption that only appears on hover is a
   caption nobody on a phone ever sees. */
@media (hover: none) {
  .gphoto-cap { opacity: 1; transform: none; }
}

/* The album's own heading block: where it sits, what it is, how big. */
.gback {
  display: inline-flex; align-items: center; gap: .4rem;
  /* It sits where the kicker usually does, and the kicker is set in
     small caps — which turns a back link into shouting. */
  text-transform: none; letter-spacing: normal;
  padding: .32rem .8rem .32rem .6rem; border-radius: 999px;
  background: var(--bg-soft); border: 1px solid var(--line);
  color: var(--ink-soft); font-size: .84rem; font-weight: 600;
  text-decoration: none;
}
.gback:hover { background: var(--bg-sunken); color: var(--ink); }
.gfacts {
  display: flex; flex-wrap: wrap; gap: .5rem .9rem;
  justify-content: center; align-items: center;
  margin-top: .55rem; color: var(--ink-soft); font-size: .92rem;
}
.gfacts .dot { width: 4px; height: 4px; border-radius: 50%; background: var(--ink-faint); }

/* ---------------------------------------------------------------------
   The lightbox

   Built by gallery-lightbox.js and absent without it: every picture is
   a plain link to its own file, so with no script — or an old browser
   with no <dialog> — clicking one still opens the photograph. The
   lightbox is the better version of that, not the only version.
   --------------------------------------------------------------------- */
.glightbox {
  width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh;
  margin: 0; padding: 0; border: 0; overflow: hidden;
  /* The dialog fills the viewport, so its own background is what
     darkens the page. ::backdrop as well, for the sliver of screen a
     rounding error leaves on some zoom levels — but not only
     ::backdrop, which some browsers decline to paint under a
     full-bleed dialog. */
  background: rgba(8, 12, 18, .94);
}
.glightbox::backdrop { background: rgba(8, 12, 18, .94); }
.glightbox-stage {
  position: absolute; inset: 0; display: grid;
  grid-template-rows: auto auto; gap: .9rem;
  align-content: center; justify-items: center;
  padding: 3.5rem 1rem 1rem;
}
.glightbox img {
  max-width: min(100%, 1400px); max-height: calc(100dvh - 11rem);
  object-fit: contain; border-radius: var(--radius-sm);
  box-shadow: 0 30px 80px rgba(0, 0, 0, .5);
}
.glightbox-cap {
  margin: 0; text-align: center; color: rgba(255, 255, 255, .9);
  font-size: .92rem; max-width: 70ch;
}
.glightbox-cap .n { display: block; margin-top: .25rem; font-size: .8rem; color: rgba(255, 255, 255, .55); }

.glightbox button {
  position: absolute; display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .22); background: rgba(255, 255, 255, .1);
  color: #fff; font-size: 1.1rem; line-height: 1; cursor: pointer;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: background .15s ease, transform .15s ease;
}
.glightbox button:hover { background: rgba(255, 255, 255, .2); }
.glightbox button:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.glightbox .gl-close { top: 1rem; right: 1rem; }
.glightbox .gl-prev  { left: 1rem;  top: 50%; transform: translateY(-50%); }
.glightbox .gl-next  { right: 1rem; top: 50%; transform: translateY(-50%); }
.glightbox .gl-prev:hover, .glightbox .gl-next:hover { transform: translateY(-50%) scale(1.06); }

@media (max-width: 640px) {
  .glightbox .gl-prev, .glightbox .gl-next { top: auto; bottom: 1rem; transform: none; }
  .glightbox .gl-prev:hover, .glightbox .gl-next:hover { transform: scale(1.06); }
  .glightbox-stage { padding: 3.5rem .5rem 4.5rem; }
}

/* Somebody who has asked their machine to stop moving things gets no
   zoom, no lift and no slide — the pictures are the point, not the
   transitions. */
@media (prefers-reduced-motion: reduce) {
  .galbum, .galbum-thumb img, .gphoto, .gphoto img, .gphoto-cap, .glightbox button {
    transition: none !important;
  }
  .galbum:hover .galbum-thumb img, .gphoto:hover img { transform: none; }
}

/* =====================================================================
   Phones
   =====================================================================

   One block, at the end, for the public site at phone width. It is here
   rather than scattered beside each component on purpose: "does the home
   page work on a phone" is a question about the page, not about fourteen
   components, and answering it in fourteen places is how the answer
   drifts apart again.

   Three rules run through all of it:

     * **Nothing is half a screen wide.** A button sized to its own text
       leaves a ragged right edge and three different targets; at this
       width one column of full-width controls is both tidier and easier
       to hit.
     * **Anything you tap is at least 44px tall.** Footer links were 16,
       FAQ headings 26. Both were links you could see and could not
       reliably press.
     * **Nothing touches the screen edge.** The page keeps its 1.25rem
       gutter everywhere, including the places that opt out of `.wrap`.
   ===================================================================== */

@media (max-width: 640px) {

  /* ---- Banner ------------------------------------------------------ */
  /* The top of the page is the one place where a phone's whole screen is
     the whole impression, so it loses some of the desktop's air rather
     than pushing the first real sentence below the fold. */
  .hero .wrap { padding-block: clamp(2.25rem, 9vw, 3.5rem); }
  .hero .lede { max-width: none; }

  /* One column of full-width buttons. `.btn` is nowrap everywhere else,
     which is right for a toolbar and wrong here — "Applications currently
     closed" does not fit on one line at 360px and was being clipped. */
  .hero .cta { display: grid; grid-template-columns: 1fr; gap: .6rem; }
  .hero .cta .btn { width: 100%; white-space: normal; }

  .scheme-card { padding: 1.25rem; }
  /* Label and figure side by side until they will not go, then stacked —
     rather than the label crushing to two words per line. */
  .scheme-row { flex-wrap: wrap; gap: .15rem 1rem; }
  .scheme-row dd { text-align: left; }

  .notice-closed { padding: 1rem 1.1rem; }

  /* ---- Counts strip ------------------------------------------------ */
  /* Two across, and an odd one out takes the whole row instead of sitting
     beside an empty grey cell — which read as a missing figure. */
  .stat-strip .stat { padding: 1.15rem 1rem; }
  .stat-strip .stat:last-child:nth-child(odd) { grid-column: 1 / -1; }

  /* ---- Sections and cards ------------------------------------------ */
  section { padding-block: clamp(2.5rem, 9vw, 3.5rem); }
  .section-head { margin-bottom: 1.5rem; }
  .card { padding: 1.25rem; }
  /* An e-mail address or a phone number inside a card is the one thing
     on that card anybody taps, and it was the height of its own text. */
  .card p > a[href^="mailto:"], .card p > a[href^="tel:"] {
    display: inline-flex; align-items: center; min-height: 44px;
  }

  /* ---- Committee ---------------------------------------------------- */
  /* Two across. One 108px portrait per row turned a committee of twelve
     into a page and a half of scrolling with nothing else on it. */
  .people-grid { grid-template-columns: repeat(2, 1fr); gap: 1.25rem; }
  .person .avatar, .person .avatar-fallback { width: 84px; height: 84px; }
  .person .avatar-fallback { font-size: 1.5rem; }
  .person .pname { font-size: .95rem; }

  /* ---- FAQ ---------------------------------------------------------- */
  details.faq { position: relative; padding: .35rem 1.15rem; }
  details.faq summary { padding: .55rem 1.75rem .55rem 0; min-height: 44px; display: flex; align-items: center; }
  details.faq summary::after { position: absolute; right: 1.15rem; top: 1.15rem; float: none; }
  details.faq .answer { padding-bottom: .75rem; }

  /* ---- Apply band --------------------------------------------------- */
  .cta-band { text-align: center; padding: 1.75rem 1.25rem; }
  .cta-actions { display: grid; grid-template-columns: 1fr; gap: .6rem; width: 100%; }
  .cta-band .btn { width: 100%; white-space: normal; }

  /* ---- Footer ------------------------------------------------------- */
  /* A link you can see and cannot press is not a link. These were 16px
     tall — the height of the words and nothing else.
     The padding goes on the `a`, so the whole 44px is the target rather
     than 44px of list item with a 16px link somewhere inside it. */
  .footer-links li { margin-bottom: 0; }
  .footer-links li a { display: flex; align-items: center; min-height: 44px; }
  .footer-links li { padding-block: .35rem; }
  .footer-bottom { margin-top: 2rem; }
}

/* Between a phone and a tablet the committee still wants three per row
   rather than the two the generic grid rule gives it. */
@media (min-width: 641px) and (max-width: 900px) {
  .people-grid { grid-template-columns: repeat(3, 1fr); }
}
