html {
  font-size: 14px;
}

@media (min-width: 768px) {
  html {
    font-size: 16px;
  }
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

html {
  position: relative;
  min-height: 100%;
}

body {
  margin-bottom: 60px;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
  color: var(--bs-secondary-color);
  text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
  text-align: start;
}
/* Password show/hide toggle, injected by site.js.
   Muted by default so it reads as an affordance rather than an action, and only takes on
   colour once the password is actually visible. */
.password-reveal {
  color: #6c757d;
  border-color: var(--bs-border-color);
}

.password-reveal:hover {
  color: #212529;
}

.password-reveal[aria-pressed="true"] {
  color: #0d6efd;
}

/* The validation message follows the input group rather than sitting inside it. */
.input-group + .text-danger {
  display: block;
}

/* ---------------------------------------------------------------------------------------
   Space sidebar and page tree
   --------------------------------------------------------------------------------------- */
.space-sidebar {
  background: #f8f9fa;
  border: 1px solid #dfe1e6;
  border-radius: 8px;
  padding: 1rem;
}

@media (min-width: 992px) {
  .space-sidebar {
    position: sticky;
    top: 1rem;
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
  }
}

.tree-link {
  color: #42526e;
  font-size: 0.9rem;
  line-height: 1.35;
}

.tree-link:hover {
  background: #ebecf0;
  color: #091e42;
}

/* The current page, so people can see where they are in a deep tree. */
.tree-link.active {
  background: #e6effc;
  color: #0052cc;
  font-weight: 600;
}

.page-content {
  line-height: 1.6;
}

/* Sidebar collapse control. The header stays visible when the tree is hidden, so there is
   always something to click to bring it back. */
.space-sidebar-toggle {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: .4rem;
  width: 100%;
  text-align: left;
}

.space-sidebar-toggle:hover {
  color: #0052cc;
}

.toggle-caret {
  font-size: .8em;
  color: #6b778c;
}

/* A sticky sidebar that is collapsed should not still reserve the full column height. */
.space-sidebar:has(.space-sidebar-body[hidden]) {
  max-height: none;
}

/* ---------------------------------------------------------------------------------------
   Rich page content (sanitised HTML from the editor)
   --------------------------------------------------------------------------------------- */
.page-content img { max-width: 100%; height: auto; }

.page-content pre,
.page-content .ql-syntax,
.page-content .ql-code-block-container {
  background: #f4f5f7;
  padding: .75rem 1rem;
  border-radius: 4px;
  overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .9em;
}

.page-content blockquote {
  margin: 1rem 0;
  padding-left: 1rem;
  border-left: 3px solid #dfe1e6;
  color: #5e6c84;
}

.page-content table { border-collapse: collapse; margin: 1rem 0; }
.page-content th,
.page-content td { border: 1px solid #dfe1e6; padding: .35rem .6rem; }

/* Quill's alignment and indent classes, which the sanitiser allows through. */
.page-content .ql-align-center  { text-align: center; }
.page-content .ql-align-right   { text-align: right; }
.page-content .ql-align-justify { text-align: justify; }
.page-content .ql-indent-1 { padding-left: 3em; }
.page-content .ql-indent-2 { padding-left: 6em; }
.page-content .ql-indent-3 { padding-left: 9em; }
.page-content .ql-indent-4 { padding-left: 12em; }

/* The editor itself: give it a workable height rather than collapsing to one line. */
#editor { min-height: 24rem; background: #fff; }
#editor .ql-editor { min-height: 24rem; font-size: 1rem; }

/* ---------------------------------------------------------------------------------------
   Version comparison
   --------------------------------------------------------------------------------------- */
.diff-table td {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .85rem;
  white-space: pre-wrap;
  word-break: break-word;
  vertical-align: top;
}

.diff-inserted { background: #e3fcef; }
.diff-deleted  { background: #ffebe6; }
.diff-modified { background: #fffae6; }
.diff-blank    { background: #f4f5f7; }

/* ---------------------------------------------------------------------------------------
   Comments
   --------------------------------------------------------------------------------------- */
.comment {
  padding: .5rem .75rem;
  border-left: 2px solid #dfe1e6;
  margin-bottom: .5rem;
}

.comment-body {
  font-size: .95rem;
}

.comment-body p:last-child { margin-bottom: 0; }

.comment-thread { max-width: 760px; }

/* ---------------------------------------------------------------------------------------
   Who did it
   ---------------------------------------------------------------------------------------
   The actor column in the Activity log, the author of a note on a user's Comments tab, and
   the address shown under a user's name. All three answer "who", and all three read better
   for being visually unlike the prose around them.

   Courier, in the ordinary text colour. It used to be red as well, and the red was the
   thing people noticed: down a long table it read as a warning on every row, and beside a
   user's name it made a plain email address look like an error. The typeface alone is
   enough to set a name apart from the prose around it. Inheriting the colour also means it
   follows dark mode for free.

   Declared here rather than left to Bootstrap's <code> styling because these are names and
   addresses, not code, and a <span> carrying its own class says so.
   --------------------------------------------------------------------------------------- */
.audit-actor {
  font-family: "Courier New", Courier, ui-monospace, monospace;
  color: inherit;
  font-size: .875rem;
  word-break: break-word;
}

/* The one place a name is coloured: the activity log on a membership enquiry, where the
   name sits beside a grey timestamp at the end of a long line and wants picking out. Blue
   rather than red, because red on every entry reads as a warning on every entry. The same
   blue as an email address, lifted for dark mode the same way. */
.activity-who { color: #1f5fa8; }
:root[data-mode="dark"] .activity-who { color: #7fb3f0; }

/* ---------------------------------------------------------------------------------------
   Graphs on the Activity dashboard
   ---------------------------------------------------------------------------------------
   Eight categorical colours in a fixed order, plus a grey for "Other". The order is not
   cosmetic: it was checked so that every adjacent pair stays distinct under the common
   forms of colour-blindness, in both modes. The dark set is the same eight hues re-stepped
   for the dark card surface, not a lightened copy of the light set.

   Three of the light colours (aqua, yellow, pink) sit under 3:1 against white, which is why
   every graph on that page has a legend with the numbers on it and a table underneath.

   --viz-surface is the card the graph sits on: the gaps between slices and the ring round
   each marker are drawn in it, so the separation is the surface showing through rather
   than an outline.
   --------------------------------------------------------------------------------------- */
.viz, .viz-legend {
  --viz-1: #2a78d6;
  --viz-2: #eb6834;
  --viz-3: #1baf7a;
  --viz-4: #eda100;
  --viz-5: #e87ba4;
  --viz-6: #008300;
  --viz-7: #4a3aa7;
  --viz-8: #e34948;
  --viz-other: #9a9a94;
  --viz-surface: var(--c-white, #ffffff);
}

:root[data-mode="dark"] .viz,
:root[data-mode="dark"] .viz-legend {
  --viz-1: #3987e5;
  --viz-2: #d95926;
  --viz-3: #199e70;
  --viz-4: #c98500;
  --viz-5: #d55181;
  --viz-6: #008300;
  --viz-7: #9085e9;
  --viz-8: #e66767;
  --viz-other: #6f6f6a;
}

.viz-swatch {
  display: inline-block;
  width: .75rem;
  height: .75rem;
  border-radius: 2px;
  vertical-align: -1px;
  margin-right: .35rem;
}

/* The Modules page's heading icon: a plain grey box, drawn rather than an emoji, because no
   emoji is a grey box on every platform. Sized to sit beside the h4 the way the emoji on the
   other module pages do. */
.modules-icon {
  display: inline-block;
  width: 1.1em;
  height: 1.1em;
  background: #8a929c;
  border-radius: .2em;
  vertical-align: -.15em;
  margin-right: .1em;
}

:root[data-mode="dark"] .modules-icon { background: #7c8794; }

/* The four headline numbers. Large, plain, and in the text colour: the number is the chart. */
.viz-figure {
  font-size: 2.25rem;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------------------
   Tab panes that are not direct children of .tab-content
   ---------------------------------------------------------------------------------------
   Bootstrap hides inactive panes with

       .tab-content > .tab-pane { display: none; }

   -- a DIRECT child selector. On the user record the editable panes sit inside the <form>
   that saves them, so they are grandchildren of .tab-content and that rule never matches.
   Every pane then renders at once, and the inactive ones, being .fade without .show, are
   merely transparent rather than absent: invisible, full height, and stacked. The symptom
   is a tab that opens with a screenful of blank space above and below its content.

   Wrapping each pane in its own form instead would mean either nested forms (invalid) or
   scattering the HTML5 form= attribute across every input -- which also puts those inputs
   out of reach of jquery-validation, since it finds fields by walking the form's subtree.
   So the DOM stays as it is and the selector is relaxed to a descendant one.

   Scoped to .tabs-flush rather than applied globally, because overriding a framework rule
   everywhere to fix one page is how a stylesheet becomes impossible to reason about.
   --------------------------------------------------------------------------------------- */
.tabs-flush .tab-pane { display: none; }
.tabs-flush .tab-pane.active { display: block; }

/* The first heading or paragraph in a pane must not push the content down: the tab strip
   above it is already the separation. */
.tabs-flush .tab-pane > :first-child { margin-top: 0; }

/* ---------------------------------------------------------------------------------------
   The summary band on a record
   ---------------------------------------------------------------------------------------
   The identifying facts about the thing you are looking at -- the address, when it was
   created, when it was last used -- set in a band the full width of the panel below it, so
   it reads as a header for the whole record rather than as a stray line of small print.

   Its own tokens rather than Bootstrap's bg-body-secondary, because that resolves against
   the framework's theme and this page has two of its own: the tint the reader chose, and
   dark mode. A grey that ignores both is a grey that is wrong half the time.
   --------------------------------------------------------------------------------------- */
.record-summary {
  background: #f1f2f4;
  border: 1px solid #dfe1e6;
  border-radius: .25rem;
  padding: .5rem .75rem;
  margin-bottom: 1rem;
  font-size: .875rem;
  color: #5e6c84;
}

.record-summary .audit-actor { font-size: .875rem; }

:root[data-mode="dark"] .record-summary {
  background: #1b1f27;
  border-color: #2c3340;
  color: #9aa5b1;
}

/* ---------------------------------------------------------------------------------------
   The comment log on a user record
   ---------------------------------------------------------------------------------------
   One comment per row, kept as flat as the content allows: the author and the timestamp sit
   on the same line as the start of the note, and the note itself wraps underneath only when
   it genuinely needs to.

   white-space: pre-wrap is what supports a multi-line comment -- somebody pasting three
   lines of a telephone conversation gets three lines back, rather than one run-on paragraph
   -- while a one-line comment still occupies one line.
   --------------------------------------------------------------------------------------- */
.comment-log-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .25rem .75rem;
  padding: .4rem .75rem;
}

.comment-log-meta {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  flex: 0 0 auto;
}

.comment-log-body {
  flex: 1 1 20rem;
  min-width: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ---------------------------------------------------------------------------------------
   What the enquirer said on the form
   ---------------------------------------------------------------------------------------
   A yellow note, so this card reads as a different KIND of thing from the ones around it:
   it is what somebody typed into the public form, verbatim, and it cannot be edited. The
   cards above it are the club's own record and can. Making the two look alike invites
   somebody to try to correct the one that is evidence.

   Written as .card.form-answers, not .form-answers, and that matters: theme.css loads after
   this file and sets background, border and radius on .card. A single-class selector here
   ties on specificity and loses on order, and the note quietly came out white. The extra
   class wins on specificity whatever the order.
   --------------------------------------------------------------------------------------- */
.card.form-answers {
  /* A note lying on the page rather than a panel set into it: no border, a soft drop
     shadow, and square corners. Yellow with black text, like the pad it is imitating --
     and black rather than a tinted dark, because the whole point of a note is that it is
     the most legible thing on the page. */
  background: #fdf6c9;
  color: #000;
  border: none;
  border-radius: 0;
  box-shadow: 2px 3px 6px rgba(9, 30, 66, .25);

  /* Barely off square. Enough to say "stuck on" and not enough to be annoying to read;
     straightened on hover so the text is upright while somebody is reading it. */
  transform: rotate(-.5deg);
  transition: transform .15s ease;
}

.form-answers:hover { transform: rotate(0deg); }

/* Everything inside is black. Bootstrap's muted greys are tuned for a white ground and go
   muddy on yellow, and .text-muted in particular carries !important. */
.form-answers,
.form-answers .card-title,
.form-answers dt,
.form-answers dd,
.form-answers p,
.form-answers .text-muted {
  color: #000 !important;
}

/* The questions read as labels without needing a colour of their own. */
.form-answers dt { font-weight: 400; opacity: .72; }
.form-answers dd { font-weight: 600; }

/* Dark mode keeps it yellow. The point of the colour is that this block is a different kind
   of thing, and that has to survive the theme -- a note that turns grey at night is just a
   card again. Slightly deeper so it does not glare. */
:root[data-mode="dark"] .card.form-answers {
  background: #efe3a8;
  box-shadow: 2px 3px 8px rgba(0, 0, 0, .5);
}

/* ---------------------------------------------------------------------------------------
   Status badges and the Statistics boxes
   ---------------------------------------------------------------------------------------
   Driven by MembershipStatuses.Colour, which holds a name -- green, red, grey -- rather than
   a class. The mapping from name to appearance lives here, so restyling is a stylesheet
   change and recolouring a status is a database change, and neither needs the other.

   Every pair is chosen for contrast against its own background rather than inherited from
   Bootstrap's contextual colours, several of which fail against white at small sizes.
   --------------------------------------------------------------------------------------- */
.status-badge {
  font-weight: 600;
  border: 1px solid transparent;
}

/* Each colour rule below also names the button states, because the pipeline's filter
   toggles wear these classes on a .btn and Bootstrap repaints a button on hover: without
   the extra selectors a red "new" toggle went transparent the moment the mouse reached it. */

.status-green, .btn.status-green:hover, .btn.status-green:focus, .btn.status-green:active { background: #1f7a3d; color: #fff; }
.status-red, .btn.status-red:hover, .btn.status-red:focus, .btn.status-red:active { background: #c0392b; color: #fff; }
.status-amber, .btn.status-amber:hover, .btn.status-amber:focus, .btn.status-amber:active { background: #b06f00; color: #fff; }
.status-orange, .btn.status-orange:hover, .btn.status-orange:focus, .btn.status-orange:active { background: #d9480f; color: #fff; }
.status-blue, .btn.status-blue:hover, .btn.status-blue:focus, .btn.status-blue:active { background: #1f5fa8; color: #fff; }
.status-grey, .btn.status-grey:hover, .btn.status-grey:focus, .btn.status-grey:active { background: #6b7280; color: #fff; }
.status-neutral, .btn.status-neutral:hover, .btn.status-neutral:focus, .btn.status-neutral:active { background: #e4e6ea; color: #2b2f36; border-color: #c9ced6; }

:root[data-mode="dark"] .status-green, :root[data-mode="dark"] .btn.status-green:hover, :root[data-mode="dark"] .btn.status-green:focus, :root[data-mode="dark"] .btn.status-green:active { background: #2e9c53; }
:root[data-mode="dark"] .status-red, :root[data-mode="dark"] .btn.status-red:hover, :root[data-mode="dark"] .btn.status-red:focus, :root[data-mode="dark"] .btn.status-red:active { background: #d75043; }
:root[data-mode="dark"] .status-amber, :root[data-mode="dark"] .btn.status-amber:hover, :root[data-mode="dark"] .btn.status-amber:focus, :root[data-mode="dark"] .btn.status-amber:active { background: #c98a17; }
:root[data-mode="dark"] .status-orange, :root[data-mode="dark"] .btn.status-orange:hover, :root[data-mode="dark"] .btn.status-orange:focus, :root[data-mode="dark"] .btn.status-orange:active { background: #e8590c; }
:root[data-mode="dark"] .status-blue, :root[data-mode="dark"] .btn.status-blue:hover, :root[data-mode="dark"] .btn.status-blue:focus, :root[data-mode="dark"] .btn.status-blue:active { background: #3a7fcc; }
:root[data-mode="dark"] .status-grey, :root[data-mode="dark"] .btn.status-grey:hover, :root[data-mode="dark"] .btn.status-grey:focus, :root[data-mode="dark"] .btn.status-grey:active { background: #7c8794; }
:root[data-mode="dark"] .status-neutral, :root[data-mode="dark"] .btn.status-neutral:hover, :root[data-mode="dark"] .btn.status-neutral:focus, :root[data-mode="dark"] .btn.status-neutral:active { background: #2c3340; color: #dfe3ea; border-color: #3c4553; }

/* The summary boxes on the Statistics tab, tinted to agree with the badges. A wash rather
   than the full colour: a row of saturated boxes competes with the numbers on them. */
.stat-green   { background: #e6f4ea; border-color: #a8d5b8; }
.stat-red     { background: #fdecea; border-color: #f0b8b1; }
.stat-amber   { background: #fdf3e2; border-color: #e8cf9e; }
.stat-blue    { background: #e9f1fb; border-color: #b3ceec; }
.stat-grey    { background: #f1f2f4; border-color: #d3d7dd; }

:root[data-mode="dark"] .stat-green   { background: #16301f; border-color: #2c5c3b; }
:root[data-mode="dark"] .stat-red     { background: #351a17; border-color: #6b332c; }
:root[data-mode="dark"] .stat-amber   { background: #33280f; border-color: #6b5420; }
:root[data-mode="dark"] .stat-blue    { background: #16232f; border-color: #2d4a66; }
:root[data-mode="dark"] .stat-grey    { background: #22262d; border-color: #3a4049; }

/* ---------------------------------------------------------------------------------------
   The membership activity log
   ---------------------------------------------------------------------------------------
   One entry per row wherever the content allows it: the type, the summary, who and when all
   sit on one line, with the body opening underneath only when somebody asks for it. The log
   is scanned far more often than it is read, and a scannable log is a compact one.
   --------------------------------------------------------------------------------------- */
.activity-item {
  padding: .4rem .75rem;
}

.activity-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .25rem .5rem;
}

.activity-summary {
  flex: 1 1 12rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

.activity-meta {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: .5rem;
  white-space: nowrap;
}

.activity-detail {
  white-space: pre-wrap;
  word-break: break-word;
  margin-top: .25rem;
}

/* The disclosure arrow on its own line wastes a row per entry. */
.activity-item details > summary {
  display: inline;
  cursor: pointer;
}

/* ---------------------------------------------------------------------------------------
   Pipeline status toggles
   ---------------------------------------------------------------------------------------
   The tick or cross in front of the label carries the state as well as the colour does, so
   the control still works for a reader who cannot separate the green from the grey.
   --------------------------------------------------------------------------------------- */
.status-toggle { text-transform: none; }

.status-toggle span[aria-hidden] {
  font-weight: 700;
  margin-right: .15rem;
}


/* ---------------------------------------------------------------------------------------
   The orange button
   ---------------------------------------------------------------------------------------
   The club's action colour: #FFCC66 with black text. Used for signing in and for opening a
   module -- the things a person came to the page to do.

   Black text rather than white, and not negotiable: #FFCC66 is a light colour, and white on
   it fails contrast badly enough to be unreadable in sunlight on a phone. Black on it is
   comfortably past the threshold.

   The hover and active shades are darker rather than lighter, so the button still reads as
   pressed on a screen where the ambient light has washed the difference out.
   --------------------------------------------------------------------------------------- */
.btn-orange {
  background-color: #ffcc66;
  border-color: #e6b84d;
  color: #000;
  font-weight: 600;
}

.btn-orange:hover,
.btn-orange:focus {
  background-color: #f5bd47;
  border-color: #d9a733;
  color: #000;
}

.btn-orange:active,
.btn-orange.active {
  background-color: #e6ae38;
  border-color: #c9971f;
  color: #000;
}

.btn-orange:focus-visible {
  outline: 2px solid #7a5a00;
  outline-offset: 2px;
  box-shadow: none;
}

.btn-orange:disabled,
.btn-orange.disabled {
  background-color: #ffe4ab;
  border-color: #ffe4ab;
  color: #6b6b6b;
}

/* Dark mode leaves the fill alone. It is a brand colour and a call to action -- dimming it
   would make the one thing the page wants you to press the quietest thing on it. */

/* ---------------------------------------------------------------------------------------
   Email addresses in the membership pipeline
   ---------------------------------------------------------------------------------------
   Blue, because an address is a thing you act on -- you write to it -- and it should look
   different from the telephone number sitting under it. Not a link: there is nothing to
   click, and styling non-links as links teaches people to distrust the ones that are.
   --------------------------------------------------------------------------------------- */
.email-text {
  color: #1f5fa8;
  word-break: break-word;
}

:root[data-mode="dark"] .email-text { color: #7fb3f0; }

/* ---------------------------------------------------------------------------------------
   The orange button in dark mode
   ---------------------------------------------------------------------------------------
   theme.css carries

       :root[data-mode="dark"] a { color: #8dc2ea; }

   which is more specific than .btn-orange, so every orange button rendered as an anchor --
   which is all of the module buttons -- took pale blue text on an orange ground at night.
   The Sign in button escaped it only because it happens to be a <button>.

   Restated here at a specificity that wins, for the anchors and the buttons alike. The fill
   stays orange in both themes on purpose: it is the brand's call to action, and dimming the
   one thing the page wants pressed would be the wrong kind of dark mode.
   --------------------------------------------------------------------------------------- */
:root[data-mode="dark"] .btn-orange,
:root[data-mode="dark"] a.btn-orange,
:root[data-mode="dark"] .btn-orange:hover,
:root[data-mode="dark"] .btn-orange:focus,
:root[data-mode="dark"] .btn-orange:active {
  color: #000;
}

:root[data-mode="dark"] .btn-orange:hover,
:root[data-mode="dark"] .btn-orange:focus {
  background-color: #f5bd47;
  border-color: #d9a733;
}

/* ---------------------------------------------------------------------------------------
   The Connections Guide, in the Webmail module.

   The shapes here are the ones webmailhelper.md asks for by name -- an orange warning box
   with an exclamation mark, and a small blue box for the port numbers. Kept as classes
   rather than inline styles because the file can grow more of them at any time and they
   should all look the same when it does.
   --------------------------------------------------------------------------------------- */

.guide-body p { margin-bottom: .75rem; }
.guide-body > h3:first-child { margin-top: 0 !important; }

.guide-warning {
    display: flex;
    gap: .6rem;
    align-items: flex-start;
    background: #FFF3CD;
    border: 1px solid #E5C55B;
    border-left-width: 4px;
    border-radius: .375rem;
    color: #000;
    padding: .7rem .85rem;
    margin-bottom: 1rem;
    font-size: .925rem;
}

/* The exclamation mark the file asks for. A circle rather than bare punctuation, so it reads
   as a mark against the box rather than as the first character of the sentence. */
.guide-warning-mark {
    flex: none;
    display: grid;
    place-items: center;
    width: 1.35rem;
    height: 1.35rem;
    border-radius: 50%;
    background: #B8860B;
    color: #fff;
    font-weight: 700;
    font-size: .8rem;
    line-height: 1;
}

/* Small, blue, and inline: the ports arrive as three of these in a row and should sit
   together rather than each claiming a line of its own. */
.guide-note {
    display: inline-block;
    background: #CFE2FF;
    border: 1px solid #9EC5FE;
    border-radius: .375rem;
    color: #052C65;
    padding: .3rem .6rem;
    margin: 0 .4rem .5rem 0;
    font-size: .875rem;
    font-weight: 600;
}

/* A setting and its value. The label is the quiet half: somebody scanning this is looking for
   the server name, not for the words "Incoming mail server". */
.guide-setting {
    display: flex;
    flex-wrap: wrap;
    gap: .25rem 1rem;
    align-items: baseline;
    padding: .45rem 0;
    border-bottom: 1px solid var(--bs-border-color);
}

.guide-setting-label { flex: 1 1 14rem; color: var(--bs-secondary-color); font-size: .9rem; }
.guide-setting-value { flex: 2 1 16rem; font-family: var(--bs-font-monospace); font-size: .925rem; }

/* A value the file described rather than gave -- "[your club email address, for example ...]".
   Deliberately NOT monospace: everything in that column is there to be copied, and a sentence
   dressed as a server name is a sentence somebody will paste into Outlook. */
.guide-setting-example {
    flex: 2 1 16rem;
    font-size: .9rem;
    font-style: italic;
    color: var(--bs-secondary-color);
}

.guide-steps { padding-left: 1.25rem; margin-bottom: 1rem; }
.guide-steps li { margin-bottom: .35rem; }

/* Dark mode. The three boxes keep their own colours -- a warning that changes colour with the
   theme stops being a warning -- but the text on them is pinned dark so it never inherits the
   pale body colour and disappears into its own background. */
:root[data-mode="dark"] .guide-warning,
:root[data-mode="dark"] .guide-warning span,
:root[data-mode="dark"] .guide-note {
    color: #000;
}

:root[data-mode="dark"] .guide-note { color: #052C65; }

/* ---------------------------------------------------------------------------------------
   Move a module up or down, from the corner of its tile on the home page.

   Small and quiet on purpose. These sit next to the module's icon and must not compete with
   it -- they are a thing you go looking for once, when the order is wrong, not part of what
   the tile is telling you.
   --------------------------------------------------------------------------------------- */

.module-move { display: flex; gap: 2px; margin: 0; }

.module-move-btn {
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 1px solid var(--bs-border-color);
    border-radius: .25rem;
    background: var(--bs-body-bg);
    color: var(--bs-secondary-color);
    font-size: .6rem;
    line-height: 1;
    cursor: pointer;
    transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}

.module-move-btn:hover:not(:disabled) {
    background: #FFCC66;
    border-color: #E0A93A;
    color: #000;
}

/* Disabled rather than hidden at the ends of the list. An arrow that vanishes moves the other
   one under the pointer, so the second click of two lands on the wrong button. */
.module-move-btn:disabled { opacity: .35; cursor: default; }

/* ---------------------------------------------------------------------------------------
   Members
   ---------------------------------------------------------------------------------------
   Adult and child are the dashboard palette's first two colours, so they mean the same
   thing in the list, the graph and the charts. The graph script carries the same two values
   for its canvas, which cannot read CSS variables.
   --------------------------------------------------------------------------------------- */
.member-dot {
  display: inline-block;
  width: .7rem;
  height: .7rem;
  border-radius: 50%;
  margin-right: .4rem;
  vertical-align: -.05em;
}
.member-dot.is-adult { background: #2a78d6; }
.member-dot.is-child { background: #eb6834; width: .55rem; height: .55rem; }
:root[data-mode="dark"] .member-dot.is-adult { background: #3987e5; }
:root[data-mode="dark"] .member-dot.is-child { background: #d95926; }

.member-group-swatch {
  display: inline-block;
  width: 1.1rem;
  height: .8rem;
  border: 1.5px dashed #9a9a94;
  border-radius: .4rem;
  margin-right: .4rem;
  vertical-align: -.1em;
}

.member-contact { display: inline-flex; flex-wrap: wrap; gap: .1rem .75rem; }

/* A table heading that sorts: the label, then a small up and down arrow. The arrow in force
   is full strength; the others are faint until pointed at. */
.sort-heading { display: inline-flex; align-items: center; gap: .3rem; white-space: nowrap; }
.sort-arrows { display: inline-flex; flex-direction: column; line-height: .7; font-size: .6rem; }
.sort-arrow { color: var(--c-ink-muted, #6c757d); opacity: .45; text-decoration: none; padding: 0 .1rem; }
.sort-arrow:hover, .sort-arrow:focus-visible { opacity: 1; color: var(--c-ink, #212529); }
.sort-arrow.is-active { opacity: 1; color: var(--c-ink, #212529); }
:root[data-mode="dark"] .sort-arrow:hover, :root[data-mode="dark"] .sort-arrow:focus-visible,
:root[data-mode="dark"] .sort-arrow.is-active { color: var(--c-ink, #e8e8e3); }

/* A comment reached from a note on the graph: marked for a moment so the eye finds it. */
.member-comment { scroll-margin-top: 5rem; }
.member-comment.is-pointed { animation: member-comment-pointed 2.4s ease-out; }
@keyframes member-comment-pointed {
  0%, 40% { background-color: #fff3a6; box-shadow: inset 4px 0 0 #d9bf3c; }
  100% { background-color: transparent; box-shadow: inset 4px 0 0 transparent; }
}
:root[data-mode="dark"] .member-comment.is-pointed { animation-name: member-comment-pointed-dark; }
@keyframes member-comment-pointed-dark {
  0%, 40% { background-color: #4a4318; box-shadow: inset 4px 0 0 #d9bf3c; }
  100% { background-color: transparent; box-shadow: inset 4px 0 0 transparent; }
}

/* The members list: every cell, heading included, starts at the top left of its cell, so
   a long email that wraps does not leave the rest of the row floating mid-height. */
.members-list > :is(thead, tbody) > tr > :is(th, td) { vertical-align: top; text-align: left; }
.members-list .member-contact { justify-content: flex-start; align-items: flex-start; }

/* WhatsApp's own green, as a small pill beside the number it messages. */
.member-whatsapp { background: #1f8f4e; color: #fff; font-weight: 500; }
.member-whatsapp:hover { background: #17733e; color: #fff; }

.member-graph,
.member-map {
  width: 100%;
  height: 70vh;
  min-height: 420px;
  border: 1px solid var(--bs-border-color);
  border-radius: .375rem;
}
/* The heat map's hover popup: small, and wrapping rather than one very long line. */
.member-map-tip {
  max-width: 18rem;
  white-space: normal;
  font-size: .8rem;
  line-height: 1.35;
}
.member-graph { background: var(--c-white, #fff); }
.member-graph:fullscreen { height: 100vh; border: 0; border-radius: 0; }

/* The graph on a member's Groups tab: one family, so it needs far less room. */
.member-graph.member-graph-compact { height: 360px; min-height: 0; }

/* A member's record has six tabs. On a phone they stay on one line and scroll sideways
   inside the bar, rather than wrapping into a stack of half-width tabs. */
@media (max-width: 767.98px) {
  .member-tabs { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; }
  .member-tabs .nav-link { white-space: nowrap; }
}

/* The male or female switch: the same sliding switch as adult or child. Wide enough for
   "Female" so the switches after it do not shuffle along as it is slid. */
.gender-switch { min-width: 6rem; }

/* No gender recorded: the switch sits half way in red, not Bootstrap's blue (which reads as
   "on"), and the label says Missing. Cleared by site.js once the switch is slid. */
.gender-switch.is-missing .form-check-input,
.gender-switch.is-missing .form-check-input:indeterminate {
  background-color: #f8d7da;
  border-color: #dc3545;
  background-position: center;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23dc3545'/%3e%3c/svg%3e");
}
.gender-switch.is-missing .form-check-label { color: #b02a37; font-weight: 600; }
:root[data-mode="dark"] .gender-switch.is-missing .form-check-input,
:root[data-mode="dark"] .gender-switch.is-missing .form-check-input:indeterminate { background-color: #3a1d21; border-color: #e66767; }
:root[data-mode="dark"] .gender-switch.is-missing .form-check-label { color: #f1aeb5; }
.member-switch-row { min-height: 2.375rem; }

/* The members of a group on a member's record: one card per person. */
.member-mate {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
}

/* The panels the module's pages are built from. The theme's surface token rather than
   Bootstrap's bg-body, which ignores dark mode and stays white. */
.member-panel { background: var(--c-white, #fff); color: var(--c-ink); }

/* A tinted box round a set of ticks on a member's record -- contact channels, filters --
   a step darker than the panel it sits on, so each set reads as one thing. */
.member-box {
  background: #f1f2f4;
  border: 1px solid #dfe1e6;
  border-radius: .375rem;
  padding: .6rem .9rem .75rem;
  margin: 0;
}
.member-box-title {
  float: none;
  width: auto;
  font-size: .875rem;
  font-weight: 600;
  margin-bottom: .4rem;
  padding: 0;
}
.member-filter-list { display: grid; gap: .35rem; }
:root[data-mode="dark"] .member-box {
  background: #15181e;
  border-color: #2c3340;
}

/* The copy button after an email address: just the icon, the colour of the text, swapping
   to a tick for a moment once it has copied. */
.member-copy {
  display: inline-grid;
  place-items: center;
  padding: .1rem .2rem;
  margin-left: .15rem;
  border: 0;
  border-radius: .25rem;
  background: none;
  color: var(--c-ink-muted, #6c757d);
  vertical-align: -.1em;
  cursor: pointer;
  line-height: 1;
}
.member-copy:hover { background: rgba(127, 127, 127, .15); color: var(--c-ink); }
.member-copy .member-copy-done { display: none; }
.member-copy.is-copied { color: #1f8f4e; }
.member-copy.is-copied .member-copy-idle { display: none; }
.member-copy.is-copied .member-copy-done { display: inline; }

/* The latest comment on the list: quiet, and clearly a way into the comments. */
.member-comment-snippet { color: var(--c-ink-muted, #6c757d); text-decoration: none; }
.member-comment-snippet:hover { text-decoration: underline; }
:root[data-mode="dark"] .member-comment-snippet { color: var(--c-ink-muted); }

/* The graph's legend: the same person icon and note the graph draws. */
.member-legend-icon,
.member-legend-box { display: inline-block; vertical-align: -.2em; margin-right: .3rem; }
.member-icon-adult { fill: #2a78d6; }
.member-icon-child { fill: #eb6834; }
:root[data-mode="dark"] .member-icon-adult { fill: #3987e5; }
:root[data-mode="dark"] .member-icon-child { fill: #d95926; }
.member-legend-note {
  display: inline-block;
  width: 1.1rem;
  height: .8rem;
  background: #fff3a6;
  border: 1px solid #d9bf3c;
  border-radius: 2px;
  margin-right: .4rem;
  vertical-align: -.1em;
}

/* Leaflet draws its popups white in either mode, so the dark theme's pale link colour has
   to be put back to one that reads on white. */
:root[data-mode="dark"] .leaflet-popup-content a { color: #1a65a6; }
:root[data-mode="dark"] .leaflet-popup-content .text-muted { color: #6c757d !important; }

/* A member's other details: name and value pairs. The name is a fixed width so the values
   line up down the list; the value takes the rest of the row, one line high however long. */
.member-detail-list { display: grid; gap: .4rem; }
.member-detail-row { display: flex; gap: .5rem; align-items: center; }
.member-detail-edit { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; flex: 1 1 auto; min-width: 0; }
.member-detail-edit .member-detail-name { flex: 0 1 14rem; min-width: 9rem; }
.member-detail-edit .member-detail-value { flex: 1 1 16rem; min-width: 10rem; width: auto; }
div.member-detail-name { flex: 0 0 14rem; font-weight: 600; font-size: .875rem; }
div.member-detail-value { flex: 1 1 auto; font-size: .875rem; overflow-wrap: anywhere; }
.member-detail-add { padding-top: .75rem; border-top: 1px solid var(--c-line, #dee2e6); }

/* On a phone held upright the 14rem name column leaves no room, so each detail shrinks to
   one line: the data type and the value side by side, cut short if long, and the buttons
   out of sight. Tapping a row (site.js adds is-open) opens it out to full width, with Save
   and Remove back. Only one row is open at a time. */
@media (max-width: 575.98px) and (orientation: portrait) {
  .member-detail-row { flex-wrap: wrap; cursor: pointer; }
  .member-detail-row div.member-detail-name { flex: 0 0 40%; min-width: 0; }
  .member-detail-row div.member-detail-value { flex: 1 1 0; min-width: 0; }
  .member-detail-row:not(.is-open) div.member-detail-name,
  .member-detail-row:not(.is-open) div.member-detail-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .member-detail-row.is-open div.member-detail-name,
  .member-detail-row.is-open div.member-detail-value { flex-basis: 100%; }

  .member-detail-row .member-detail-edit { flex-wrap: nowrap; }
  .member-detail-row .member-detail-edit .member-detail-name,
  .member-detail-row .member-detail-edit .member-detail-value { flex: 1 1 0; min-width: 0; }
  .member-detail-row:not(.is-open) .btn,
  .member-detail-row:not(.is-open) > form:not(.member-detail-edit) { display: none; }
  .member-detail-row.is-open { cursor: auto; }
  .member-detail-row.is-open .member-detail-edit { flex-wrap: wrap; flex-basis: 100%; }
  .member-detail-row.is-open .member-detail-edit .member-detail-name,
  .member-detail-row.is-open .member-detail-edit .member-detail-value { flex-basis: 100%; }

  /* The adult or child switch, out of its own row and up beside the box's title, on the
     right above the last name. */
  .has-child-switch { position: relative; }
  .has-child-switch .child-switch-col {
    position: absolute; top: .6rem; right: 1rem;
    width: auto; margin-top: 0; padding: 0;
  }
  .has-child-switch .child-switch-col > div { min-height: 0 !important; }

  /* The add row: the two boxes side by side, the button on the line below. */
  .member-detail-add .member-detail-name,
  .member-detail-add .member-detail-value { flex: 1 1 calc(50% - .25rem); min-width: 0; }
}

/* ---------------------------------------------------------------------------------------
   Shopping and Billing
   ---------------------------------------------------------------------------------------
   The classic invoice is a sheet of paper: white with dark ink in both modes, because that
   is what it is and what it prints as. Everything around it follows the theme.
   --------------------------------------------------------------------------------------- */
.invoice-paper {
  background: #fff;
  color: #1f1f1c;
  border: 1px solid #d5d7dc;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .06);
  padding: 2rem 2.25rem;
  max-width: 52rem;
  margin: 0 auto 1.5rem;
  font-size: .95rem;
}
.invoice-paper a { color: #1a65a6; }
.invoice-paper .text-muted { color: #6b6f76 !important; }
.invoice-paper.is-cancelled .invoice-lines tbody td,
.invoice-paper.is-cancelled .invoice-total td { text-decoration: line-through; color: #8a8d93; }
.invoice-head { display: flex; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.invoice-from { display: flex; align-items: flex-start; gap: .85rem; }
.invoice-logo { height: 3.5rem; width: auto; flex: 0 0 auto; }
.invoice-club { font-size: 1.15rem; font-weight: 700; }
.invoice-address { font-size: .85rem; color: #555; margin-top: .25rem; }
.invoice-heading { font-size: 1.9rem; font-weight: 700; letter-spacing: .06em; line-height: 1; margin-bottom: .5rem; }
.invoice-meta { display: grid; grid-template-columns: auto auto; gap: .1rem .75rem; justify-content: end; margin: 0; font-size: .875rem; }
.invoice-meta dt { font-weight: 400; color: #666; }
.invoice-meta dd { margin: 0; text-align: right; }
.invoice-label { font-size: .75rem; text-transform: uppercase; letter-spacing: .06em; color: #777; }
.invoice-to { margin-bottom: 1rem; }
.invoice-note { background: #f6f3e8; border-left: 3px solid #c9a227; padding: .5rem .75rem; font-size: .9rem; }
.invoice-lines { width: 100%; border-collapse: collapse; margin: 1rem 0; }
.invoice-lines th { font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: #555; border-bottom: 2px solid #333; padding: .4rem .5rem; }
.invoice-lines td { border-bottom: 1px solid #e3e3e3; padding: .45rem .5rem; vertical-align: top; }
.invoice-lines tfoot td { border-bottom: 0; padding-top: .35rem; padding-bottom: .1rem; }
.invoice-lines .invoice-total td { font-weight: 700; }
.invoice-lines tfoot tr:first-child td { border-top: 2px solid #333; }
.invoice-lines tr.is-rejected td { color: #999; text-decoration: line-through; }
.invoice-line-status { white-space: nowrap; }
.invoice-line-status.is-pending { color: #a46400; font-weight: 600; }
.invoice-line-status.is-approved { color: #1f7a3d; }
.invoice-line-status.is-rejected { color: #c0392b; text-decoration: none; }
.invoice-pay { border: 1px solid #1a65a6; border-left-width: 4px; border-radius: .25rem; padding: .75rem 1rem; margin: 1rem 0; background: #f2f7fc; }
.invoice-notes { font-size: .9rem; white-space: pre-line; color: #444; }
.invoice-footer { border-top: 1px solid #ddd; margin-top: 1.5rem; padding-top: .75rem; font-size: .85rem; color: #666; white-space: pre-line; }
@media (max-width: 575.98px) { .invoice-paper { padding: 1.25rem 1rem; } .invoice-heading { font-size: 1.5rem; } }

/* The manual invoice editor: the same paper, with fields in place of print. */
.invoice-paper .form-control, .invoice-paper .form-select { background: #fff; color: #1f1f1c; border-color: #c7cad1; }
.invoice-editor-line td { padding: .3rem .25rem; }

/* The shop. */
.shop-admin-bar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem;
  border: 1px dashed #c9a227; background: rgba(201, 162, 39, .08); border-radius: .375rem; padding: .4rem .75rem; margin-bottom: .6rem; }
.shop-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: .5rem;
  background: var(--c-white, #fff); border: 1px solid var(--c-line, #dee2e6); border-radius: .375rem; padding: .4rem .6rem; margin-bottom: 1rem; }
.shop-nav .nav-link { padding: .4rem .8rem; }
.shop-basket-link { display: inline-flex; align-items: center; gap: .35rem; text-decoration: none; padding: .35rem .7rem;
  border: 1px solid var(--c-line, #dee2e6); border-radius: 2rem; font-size: 1.05rem; color: inherit; }
.shop-basket-link.is-current { border-color: #1a65a6; }
.shop-basket-label { font-size: .9rem; }
.shop-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr)); gap: 1rem; }
.shop-card { display: flex; flex-direction: column; height: 100%; }
.shop-card .card-body { display: flex; flex-direction: column; }
.shop-card-img, .shop-detail-img, .shop-thumb {
  display: grid; place-items: center; background: var(--c-cream, #f4f1ea); color: var(--c-ink-muted, #777); overflow: hidden; }
.shop-card-img { height: 9rem; font-size: 2.5rem; border-bottom: 1px solid var(--c-line, #dee2e6); }
.shop-card-img img, .shop-detail-img img, .shop-thumb img { width: 100%; height: 100%; object-fit: contain; }
.shop-detail-img { height: 18rem; font-size: 5rem; border-radius: .375rem; }
.shop-thumb { width: 3rem; height: 3rem; border-radius: .25rem; font-size: 1.4rem; flex: none; }
.shop-price { font-size: 1.15rem; font-weight: 700; }
.shop-chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.shop-toast { position: fixed; right: 1rem; bottom: 1rem; z-index: 1080; max-width: 22rem; }
.shop-steps { display: flex; gap: .5rem; margin-bottom: 1.25rem; list-style: none; padding: 0; }
.shop-steps li { flex: 1; text-align: center; padding: .45rem .25rem; border-bottom: 4px solid var(--c-line, #dee2e6); color: var(--c-ink-muted, #777); font-size: .9rem; }
.shop-steps li.is-done { border-color: #7fa7cf; }
.shop-steps li.is-current { border-color: #1a65a6; color: var(--c-ink, #222); font-weight: 600; }

/* Billing. */
.billing-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 1rem; }
.billing-tile { display: block; text-decoration: none; color: inherit; height: 100%; }
.billing-tile:hover .card { border-color: #1a65a6; }
.billing-tile-icon { font-size: 1.75rem; }
.timeline-list { list-style: none; padding: 0; margin: 0; }
.timeline-list li { border-left: 3px solid var(--c-line, #dee2e6); padding: .1rem 0 .7rem .85rem; position: relative; }
.timeline-list li::before { content: ""; position: absolute; left: -.42rem; top: .35rem; width: .7rem; height: .7rem; border-radius: 50%; background: #9aa5b1; }
.timeline-list li.ev-money::before { background: #1f7a3d; }
.timeline-list li.ev-warn::before { background: #c0392b; }
.timeline-list li.ev-mail::before { background: #1a65a6; }
.timeline-list li.ev-member::before { background: #b06f00; }
.billing-timeline { overflow-x: auto; border: 1px solid var(--c-line, #dee2e6); border-radius: .375rem; background: var(--c-white, #fff); }
.billing-timeline svg { display: block; }
.billing-timeline text { fill: var(--c-ink, #222); font-size: 11px; }
.billing-timeline .axis { stroke: var(--c-line, #ccc); }
.report-table td, .report-table th { white-space: nowrap; }
.report-table td.wrap { white-space: normal; min-width: 14rem; }
.report-table tr.is-total td { font-weight: 700; border-top: 2px solid var(--c-ink-muted, #888); }

@media print {
  .app-navbar, body > header, body > footer, footer.footer, .no-print, .breadcrumb, nav[aria-label="breadcrumb"] { display: none !important; }
  body { background: #fff !important; }
  .container { max-width: none !important; }
  .invoice-paper { border: 0; box-shadow: none; padding: 0; max-width: none; }
}

/* Dark mode: Bootstrap's progress track is a light grey that glares on the dark surface. */
:root[data-mode="dark"] .progress { background-color: var(--c-cream); }

/* The paper stays paper in dark mode: its fields and buttons keep their light look. */
:root[data-mode="dark"] .invoice-paper .form-control,
:root[data-mode="dark"] .invoice-paper .form-select { background-color: #fff; color: #1f1f1c; border-color: #c7cad1; }
:root[data-mode="dark"] .invoice-paper .btn-outline-secondary { color: #333; border-color: #999; }
:root[data-mode="dark"] .invoice-paper .btn-outline-secondary:hover { background-color: #eee; color: #111; }
:root[data-mode="dark"] .invoice-paper .text-muted { color: #6b6f76 !important; }
:root[data-mode="dark"] .invoice-paper .border-top, :root[data-mode="dark"] .invoice-paper .border { border-color: #ddd !important; }

/* The shop on a phone: two cards to a row with smaller pictures, basket rows that wrap,
   and buttons big enough for a thumb. */
@media (max-width: 575.98px) {
  .shop-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; }
  .shop-card-img { height: 6rem; font-size: 1.8rem; }
  .shop-card .card-body { padding: .6rem; }
  .shop-card h2 { font-size: .95rem; }
  .shop-card .btn { width: 100%; padding: .45rem .5rem; }
  .shop-detail-img { height: 12rem; }
  .shop-steps li { font-size: .75rem; padding: .35rem .1rem; }
  .shop-basket-row { flex-wrap: wrap; }
  .shop-basket-row .shop-basket-main { flex: 1 1 calc(100% - 4rem); }
  .shop-basket-row .shop-basket-price { margin-left: 3.75rem; }
  .shop-detail-row { flex-wrap: wrap; }
  .shop-detail-row input[name=detailName] { max-width: none !important; flex: 1 1 100%; }
  .shop-actions .btn { flex: 1 1 auto; }
}

/* Membership enquiries: a duplicate email address, in solid red on the list and the record. */
.enquiry-duplicate { background: #c62828; color: #fff; border: 0; }
.enquiry-duplicate .alert-link { color: #fff; text-decoration: underline; }

/* The status box above an enquiry's activity log: dark grey, the word and the dropdown. */
.enquiry-status-box {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .6rem .75rem;
    border-radius: .375rem;
    background: #3a3a37;
    color: #fff;
}
.enquiry-status-box label { font-weight: 600; margin: 0; }

/* ---------------------------------------------------------------------------------------
   AI Reporting
   --------------------------------------------------------------------------------------- */
/* The Ask tab's box: room for a question of a few lines, and it can be pulled taller. */
.ai-question { min-height: 10rem; resize: vertical; }

/* The View tab's answer. --ai-zoom is set by the zoom buttons and scales the chart's height
   and the table's text; the chart's corner can be dragged to make it taller or shorter. */
.ai-chart-wrap {
  position: relative; height: calc(22rem * var(--ai-zoom, 1)); min-height: 10rem; margin-bottom: 1rem;
  resize: vertical; overflow: hidden;
}
@media (max-width: 575.98px) { .ai-chart-wrap { height: calc(16rem * var(--ai-zoom, 1)); } }
.ai-table-wrap { max-height: calc(32rem * var(--ai-zoom, 1)); overflow: auto; }
.ai-result .ai-table-wrap table { font-size: calc(.875rem * var(--ai-zoom, 1)); }
.ai-zoom-level { min-width: 3.75rem; }
.ai-result:fullscreen { overflow: auto; border-radius: 0; background: var(--bs-body-bg); }
.ai-result:fullscreen .ai-chart-wrap { height: calc(55vh * var(--ai-zoom, 1)); }
.ai-result:fullscreen .ai-table-wrap { max-height: none; }
.ai-sql {
  background: var(--bs-tertiary-bg, #f3f4f6);
  border: 1px solid var(--c-line, #dee2e6);
  border-radius: .375rem;
  padding: .6rem .75rem;
  font-size: .8rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.ai-mic { flex: 0 0 auto; }
/* Red and pulsing while it records, so nobody wonders whether it is listening. */
.ai-mic.is-recording { color: #fff; background: #c0392b; border-color: #c0392b; animation: ai-pulse 1.2s ease-in-out infinite; }
@keyframes ai-pulse { 50% { box-shadow: 0 0 0 .35rem rgba(192, 57, 43, .25); } }
@media (prefers-reduced-motion: reduce) { .ai-mic.is-recording { animation: none; } }

/* AI Reporting: the progress panel. Five steps joined by a line; each lights up as it runs
   and ticks when it is done. Across the card on a wide screen, down it on a phone. */
.ai-steps { list-style: none; margin: 0; padding: 0; display: flex; gap: .5rem; }
.ai-step { position: relative; flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; text-align: center; }
.ai-step::before {                              /* the line to the previous step */
  content: ""; position: absolute; top: .875rem; right: calc(50% + 1.1rem); width: calc(100% - 1.7rem);
  height: 3px; border-radius: 2px; background: var(--bs-border-color); transition: background .3s;
}
.ai-step:first-child::before { display: none; }
.ai-step-dot {
  position: relative; z-index: 1; width: 1.75rem; height: 1.75rem; border-radius: 50%;
  display: grid; place-items: center; font-size: .85rem; font-weight: 700; line-height: 1;
  border: 2px solid var(--bs-border-color); background: var(--bs-body-bg); color: var(--bs-secondary-color);
  transition: background .3s, border-color .3s, color .3s, transform .3s;
}
.ai-step-text { display: flex; flex-direction: column; margin-top: .4rem; min-width: 0; }
.ai-step-label { font-size: .85rem; font-weight: 600; color: var(--bs-secondary-color); transition: color .3s; }
.ai-step-detail { font-size: .75rem; color: var(--bs-secondary-color); overflow-wrap: anywhere; }

.ai-step[data-state="running"] .ai-step-dot {
  border-color: #1a65a6; border-top-color: transparent; animation: ai-spin .8s linear infinite;
}
.ai-step[data-state="running"] .ai-step-label { color: #1a65a6; }
.ai-step[data-state="done"] .ai-step-dot { background: #1f7a3d; border-color: #1f7a3d; color: #fff; }
.ai-step[data-state="done"] .ai-step-dot::after { content: "\2713"; }
.ai-step[data-state="done"] .ai-step-label { color: var(--bs-body-color); }
.ai-step[data-state="done"]::before,
.ai-step[data-state="running"]::before,
.ai-step[data-state="failed"]::before { background: #1f7a3d; }
.ai-step[data-state="failed"] .ai-step-dot { background: #c0392b; border-color: #c0392b; color: #fff; }
.ai-step[data-state="failed"] .ai-step-dot::after { content: "\2715"; }
.ai-step[data-state="failed"] .ai-step-label { color: #c0392b; }
.ai-step[data-state="skipped"] .ai-step-dot { border-style: dashed; }
.ai-step[data-state="skipped"] .ai-step-dot::after { content: "\2013"; }
:root[data-mode="dark"] .ai-step[data-state="running"] .ai-step-dot { border-color: #5aa0e0; border-top-color: transparent; }
:root[data-mode="dark"] .ai-step[data-state="running"] .ai-step-label { color: #5aa0e0; }
:root[data-mode="dark"] .ai-step[data-state="failed"] .ai-step-label { color: #e8796d; }

#ai-progress { transition: opacity .3s, transform .3s; }
#ai-progress.is-leaving { opacity: 0; transform: translateY(-.5rem); }
@keyframes ai-spin { to { transform: rotate(360deg); } }

@media (max-width: 575.98px) {
  .ai-steps { flex-direction: column; gap: .75rem; }
  .ai-step { flex-direction: row; align-items: flex-start; text-align: left; gap: .6rem; }
  .ai-step::before { top: auto; bottom: calc(100% - .1rem); left: .8rem; right: auto; width: 3px; height: .9rem; }
  .ai-step-text { margin-top: .15rem; }
}
@media (prefers-reduced-motion: reduce) {
  .ai-step[data-state="running"] .ai-step-dot { animation: none; border-top-color: #1a65a6; }
  #ai-progress { transition: none; }
}

/* AI Reporting: the Saved and History tabs. The name always shows in full; the question gives
   way. A clicked row stays lit, with what can be done with it in the row underneath. */
.ai-history .ai-history-question { width: 38%; max-width: 26rem; }
.ai-history .ai-history-title { min-width: 14rem; max-width: 22rem; }
.ai-history-text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.ai-history-row { cursor: pointer; }
.ai-history-row:hover > td, .ai-history-row:focus-visible > td,
.ai-history-row.is-selected > td { background: var(--bs-tertiary-bg, rgba(0, 0, 0, .04)); }
.ai-history .ai-row-buttons { text-align: right; min-width: 9.5rem; }
.ai-history-row.is-selected > td { border-bottom-color: transparent; }
.ai-history-row.is-selected > td:first-child { box-shadow: inset 3px 0 0 #1a65a6; }
.ai-history-row:focus-visible { outline: 2px solid #1a65a6; outline-offset: -2px; }

/* Dark mode: --bs-tertiary-bg is Bootstrap's light grey whatever this panel's mode, so the
   lit row, its actions row and the SQL box were pale under pale text. Restated on the dark
   tokens here. */
:root[data-mode="dark"] .ai-history-row:hover > td,
:root[data-mode="dark"] .ai-history-row:focus-visible > td,
:root[data-mode="dark"] .ai-history-row.is-selected > td { background: var(--c-cream, #262b34); color: var(--c-ink, #e4e1e8); }
:root[data-mode="dark"] .ai-history-row.is-selected > td:first-child { box-shadow: inset 3px 0 0 #5aa0e0; }
:root[data-mode="dark"] .ai-history-row:focus-visible { outline-color: #5aa0e0; }
:root[data-mode="dark"] .ai-sql { background: var(--c-cream-wash, #14171c); color: var(--c-ink, #e4e1e8); border-color: var(--c-line, #333945); }
:root[data-mode="dark"] .ai-history details summary { color: var(--c-ink-muted, #9d97a8) !important; }
:root[data-mode="dark"] .ai-history .text-danger { color: #e8796d !important; }

/* Settings > AI: a model test reply keeps its line breaks. */
.ai-test-reply { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------------------
   Facilities
   --------------------------------------------------------------------------------------- */

/* The quick add row heads the facilities table, tinted so it reads as the place to type. */
.facility-add-row > td { background: var(--bs-tertiary-bg, rgba(0, 0, 0, .03)); }

/* The facilities pages in dark mode. The theme darkens through data-mode, not Bootstrap's
   own dark theme, so Bootstrap's tertiary background and its emphasis colours stay their
   light-mode selves: a pale add row under pale text, and an amber or green that all but
   disappears on the dark panel. */
:root[data-mode="dark"] .facility-add-row > td { background: var(--c-cream, #262b34); }
:root[data-mode="dark"] .text-warning-emphasis { color: #e0b252 !important; }
:root[data-mode="dark"] .text-success { color: #5cc27d !important; }

/* The allocation wizard's progress, always on show: Type -> Facility -> Member -> Confirm.
   Transparent, so the steps never read as buttons. Black (white in dark mode) and underlined
   for the current step, grey for an answered one with what was picked underneath, a paler
   grey for one not reached yet. */
.wizard-steps { display: flex; flex-wrap: wrap; align-items: flex-start; gap: .25rem .6rem; max-width: 46rem; font-size: .875rem; }
.wizard-steps > li { display: flex; align-items: flex-start; min-width: 0; }
.wizard-steps > li + li::before { content: "\2192"; color: #adb5bd; margin-right: .6rem; padding-top: .15rem; }
.wizard-step { display: block; min-width: 0; padding: .15rem 0; border-bottom: 2px solid transparent; color: #adb5bd; text-decoration: none; }
.wizard-step-label { display: block; font-weight: 600; }
.wizard-step-picked { display: block; max-width: 10rem; font-size: .75rem; font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wizard-steps > li.is-done .wizard-step { color: #6c757d; }
a.wizard-step:hover, a.wizard-step:focus-visible { color: #000; border-bottom-color: #adb5bd; }
.wizard-steps > li.is-current .wizard-step { color: #000; border-bottom-color: #000; }
:root[data-mode="dark"] .wizard-steps > li + li::before,
:root[data-mode="dark"] .wizard-step { color: #5f6672; }
:root[data-mode="dark"] .wizard-steps > li.is-done .wizard-step { color: #a3a9b3; }
:root[data-mode="dark"] a.wizard-step:hover,
:root[data-mode="dark"] a.wizard-step:focus-visible { color: #fff; border-bottom-color: #5f6672; }
:root[data-mode="dark"] .wizard-steps > li.is-current .wizard-step { color: #fff; border-bottom-color: #fff; }

/* A type on the first step: a grey button the full width of its column, left aligned so
   the name and the count read as a label rather than a caption. */
.wizard-choice { display: block; width: 100%; text-align: left; padding: .6rem 1rem; }

/* ---------------------------------------------------------------------------------------
   Deleted members
   ---------------------------------------------------------------------------------------
   The way in to deleted members, a pink bar across the foot of the members list rather
   than a tab beside List and Map.
   --------------------------------------------------------------------------------------- */
.deleted-members-bar {
  display: block; padding: .6rem 1rem; margin-bottom: 1rem; border-radius: .375rem;
  background: #f8d7da; border: 1px solid #f1aeb5; color: #842029;
  font-weight: 600; text-align: left; text-decoration: none;
}
.deleted-members-bar:hover, .deleted-members-bar:focus-visible { background: #f1aeb5; color: #58151c; }
:root[data-mode="dark"] .deleted-members-bar { background: #3a1d21; border-color: #6b2c33; color: #f1aeb5; }
:root[data-mode="dark"] .deleted-members-bar:hover,
:root[data-mode="dark"] .deleted-members-bar:focus-visible { background: #4d242a; color: #fff; }

/* ---------------------------------------------------------------------------------------
   One button colour
   ---------------------------------------------------------------------------------------
   Every button in the page body is grey with white text, whatever Bootstrap or the theme
   would make it: primary blue, outline white, red delete, and the underlined text-only
   Cancel all look the same. The orange call-to-action (.btn-orange, the module tiles) is
   left alone, as are the controls that show a state or a choice rather than doing
   something: status toggles (.status-*), button groups such as the period and view
   pickers, and the password eye. Add .btn-keep to anything else that must keep its look.

   Selected through :root and :not() so it outranks the theme's own .btn-primary and its
   dark-mode overrides, which load after this file.
   --------------------------------------------------------------------------------------- */
:root main .btn:is(.btn-primary, .btn-secondary, .btn-success, .btn-danger, .btn-warning, .btn-info,
  .btn-light, .btn-dark, .btn-link, .btn-outline-primary, .btn-outline-secondary, .btn-outline-success,
  .btn-outline-danger, .btn-outline-warning, .btn-outline-info, .btn-outline-light, .btn-outline-dark):not(.btn-group > .btn):not([class*="status-"]):not(.password-reveal):not(.btn-keep):not(.btn-link.p-0) {
  background-color: #6c757d;
  border-color: #6c757d;
  color: #fff;
  text-decoration: none;
}
:root main .btn:is(.btn-primary, .btn-secondary, .btn-success, .btn-danger, .btn-warning, .btn-info,
  .btn-light, .btn-dark, .btn-link, .btn-outline-primary, .btn-outline-secondary, .btn-outline-success,
  .btn-outline-danger, .btn-outline-warning, .btn-outline-info, .btn-outline-light, .btn-outline-dark):not(.btn-group > .btn):not([class*="status-"]):not(.password-reveal):not(.btn-keep):not(.btn-link.p-0):is(:hover, :focus-visible, :active, .active) {
  background-color: #565e64;
  border-color: #51585e;
  color: #fff;
}
:root main .btn:is(.btn-primary, .btn-secondary, .btn-success, .btn-danger, .btn-warning, .btn-info,
  .btn-light, .btn-dark, .btn-link, .btn-outline-primary, .btn-outline-secondary, .btn-outline-success,
  .btn-outline-danger, .btn-outline-warning, .btn-outline-info, .btn-outline-light, .btn-outline-dark):not(.btn-group > .btn):not([class*="status-"]):not(.password-reveal):not(.btn-keep):not(.btn-link.p-0):is(:disabled, .disabled) {
  background-color: #6c757d;
  border-color: #6c757d;
  color: #fff;
  opacity: .55;
}

/* -----------------------------------------------------------------------------------------
   Attachments shown in the body of a page, beneath the title: images, PDFs and Word
   documents. Surface tokens with fallbacks, so dark mode and either theme carry them.
   ----------------------------------------------------------------------------------------- */
.attachment-preview {
    margin: 0 0 1.25rem;
    border: 1px solid var(--c-line, #dee2e6);
    border-radius: 4px;
    overflow: hidden;
}

/* After the page's own text, a clear gap; at the foot of the panel, none. */
.page-content + .attachment-preview { margin-top: 1.5rem; }
.attachment-preview:last-child { margin-bottom: 0; }

.attachment-preview-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    padding: .45rem .75rem;
    border-bottom: 1px solid var(--c-line, #dee2e6);
    font-size: .875rem;
}

/* Open and Download, under the file at the left. */
.attachment-preview-actions {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    padding: .45rem .75rem;
    border-top: 1px solid var(--c-line, #dee2e6);
}

.attachment-preview-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    max-width: 100%;
}

.attachment-preview-image {
    display: block;
    max-width: 100%;
    max-height: 80vh;
    margin: 0 auto;
}

.attachment-preview-pdf {
    display: block;
    width: 100%;
    height: 80vh;
    min-height: 420px;
    border: 0;
}

.attachment-preview-word {
    padding: 1rem 1.25rem;
    max-height: 80vh;
    overflow: auto;
}

.attachment-preview-word table { border-collapse: collapse; margin-bottom: 1rem; }
.attachment-preview-word td { border: 1px solid var(--c-line, #dee2e6); padding: .35rem .6rem; vertical-align: top; }
.attachment-preview > .form-text { padding: 0 1.25rem .75rem; margin: 0; }

/* The banner shown while administrator mode is switched off for the session. */
.view-mode-banner {
    background: #fff3cd;
    color: #664d03;
    border-bottom: 1px solid #ffe69c;
    font-size: .875rem;

    /* Tucked up under the brand bar, which carries its own bottom margin. */
    margin-top: -1rem;
    margin-bottom: 1rem;
}

.view-mode-banner-btn,
.view-mode-banner-btn:hover,
.view-mode-banner-btn:focus {
    color: inherit;
    border: 1px solid currentColor;
}

:root[data-mode="dark"] .view-mode-banner {
    background: #332701;
    color: #ffda6a;
    border-bottom-color: #664d03;
}

/* The country beside an IP address, in the activity lists. Not monospace, unlike the address. */
.ip-place { font-family: var(--bs-body-font-family); white-space: nowrap; }

/* "Last seen" under each country in the Members graph's places pop-up. */
.globe-place-seen { color: var(--c-ink-muted, #6c757d); font-size: .75rem; }


/* The space icon picker (Create and Settings): radio buttons dressed as the page icon picker's
   buttons, so choosing needs no script. The radio itself is hidden but still focusable. */
.icon-choice-radio { position: relative; margin: 0; }
.icon-choice-radio input { position: absolute; opacity: 0; width: 0; height: 0; }
.icon-choice-radio .icon-choice { display: inline-flex; align-items: center; justify-content: center; }
.icon-choice-radio input:checked + .icon-choice { outline: 2px solid var(--c-brand); outline-offset: 1px; border-color: var(--c-brand); }
.icon-choice-radio input:focus-visible + .icon-choice { outline: 2px solid var(--c-ink-muted); outline-offset: 1px; }




/* The members graph's people, by gender and age (members-graph.js draws the same). */
.member-icon-man { fill: #2a78d6; }
.member-icon-woman { fill: #8b4fc9; }
.member-icon-boy { fill: #6fb2f0; }
.member-icon-girl { fill: #e05aa6; }
.member-icon-adult-unknown { fill: #6b7280; }
:root[data-mode="dark"] .member-icon-man { fill: #3987e5; }
:root[data-mode="dark"] .member-icon-woman { fill: #a874e0; }
:root[data-mode="dark"] .member-icon-boy { fill: #8cc4f5; }
:root[data-mode="dark"] .member-icon-girl { fill: #ee7fbd; }
:root[data-mode="dark"] .member-icon-adult-unknown { fill: #9aa1ad; }

/* The graph's switches: each label carries the same little picture as the legend. */
.member-graph-toggles .form-check-label { display: inline-flex; align-items: center; gap: .3rem; }
.member-graph-toggles .member-legend-note,
.member-graph-toggles .member-legend-box { margin-right: 0; }


/* The Billing dashboard's headline figures. Six to a row, so they are smaller than the rest of
   the module's figures: a value such as £149,122.37 has to fit its card on one line. */
.viz-figure-compact {
  font-size: clamp(1rem, 1.25vw, 1.4rem);
  white-space: nowrap;
}


/* The Members map, full screen: the map fills it, and Esc (or its own button) comes back. */
.member-map:fullscreen { height: 100vh; border: 0; border-radius: 0; }
.member-map-full {
  width: 34px;
  height: 34px;
  font-size: 1.2rem;
  line-height: 1;
  background: #fff;
  color: #333;
  cursor: pointer;
}
.member-map-full:hover { background: #f4f4f4; }


/* The invoice status filters: each status's own badge is the button. On is the full colour;
   off is faded and struck through. The checkbox behind it is Bootstrap's hidden btn-check. */
.status-toggle {
  cursor: pointer;
  user-select: none;
  font-size: .8rem;
  padding: .4em .7em;
  transition: opacity .1s;
}
.btn-check + .status-toggle { opacity: .4; text-decoration: line-through; }
.btn-check:checked + .status-toggle { opacity: 1; text-decoration: none; }
.btn-check + .status-toggle:hover { opacity: .75; }
.btn-check:checked + .status-toggle:hover { opacity: .9; }
.btn-check:focus-visible + .status-toggle { outline: 2px solid var(--bs-primary); outline-offset: 2px; }

/* Documents search bar: Legacy, Vector and Search are one strip of primary buttons after the
   text box. Legacy and Vector hold a real checkbox, drawn white so it shows on the blue; a thin
   light line separates the three. */
.doc-search-kind {
    display: inline-flex; align-items: center; gap: .45rem; white-space: nowrap;
}
.doc-search-kind + .doc-search-kind,
.doc-search-kind + .doc-search-go { border-left-color: rgba(255, 255, 255, .45); }
.doc-search-kind .form-check-input {
    background-color: #fff; border-color: #fff; cursor: pointer;
}
.doc-search-kind .form-check-input:checked {
    background-color: #fff; border-color: #fff;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%230b3d91' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
.doc-search-kind .form-check-input:focus { box-shadow: 0 0 0 .2rem rgba(255, 255, 255, .5); }
.doc-search-kind.disabled { opacity: .55; }
