/* static/css/site.css

   The one stylesheet for the whole site, public pages and portal alike.

   It styles semantic HTML directly rather than through classes, so a template needs no
   markup of its own to look right. Classes appear only where the public section pages
   need a layout plain HTML cannot express.

   Brand: purple #7F44BA, blue #8AB8E6, green #84E291. The fonts are a system stack;
   swap the --font tokens if brand fonts ever arrive. */

:root {
  --brand-purple: #7F44BA;
  --brand-purple-strong: #63329a;   /* darker, for hovers/contrast */
  --brand-blue: #8AB8E6;
  --brand-blue-soft: #e7f0fb;        /* tint for surfaces */
  --brand-green: #84E291;
  --brand-green-strong: #4bbf62;

  --ink: #1b1b22;
  --muted: #5a5a68;
  --bg: #ffffff;
  --surface: #f7f5fc;                /* faint purple tint */
  --surface-2: #efeaf8;
  --border: #ddd7ec;
  --link: #6f38ab;                   /* purple, AA on white */
  --link-hover: var(--brand-purple-strong);
  --danger: #b3261e;

  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-head: var(--font-body);
  --radius: 8px;
  --shadow: 0 1px 3px rgba(31, 20, 45, .12), 0 4px 12px rgba(31, 20, 45, .06);
  --maxw: 64rem;      /* app chrome: header, footer, and the portal's wide tables */
  --measure: 46rem;   /* prose: the public content pages only — see body.public main */

  /* LIGHT ONLY, on every page. There is one palette and no dark variant.

     This declaration is load-bearing, not decoration. Without it a visitor whose
     system is set to dark still gets dark NATIVE controls — text inputs, selects,
     scrollbars — painted by the browser against our light surfaces. */
  color-scheme: light;
}

* { box-sizing: border-box; }

body {
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--bg);
  margin: 0;
  line-height: 1.6;
  font-size: 1rem;
}

/* ---- layout ---- */
main { padding: 1.75rem 1.25rem; max-width: var(--maxw); margin: 0 auto; }

/* The reading measure. 64rem suits the portal's wide data tables and is far too wide
   for prose, so the narrow measure is scoped to body.public and the portal keeps its
   full width. This is the ONE rule deliberately not applied to both; everything below
   changes the portal too. */
body.public main { max-width: var(--measure); }

h1, h2, h3, h4 { font-family: var(--font-head); line-height: 1.2; color: var(--ink); }
h1 { font-size: 2.4rem; margin: 0 0 1rem; letter-spacing: -.02em; font-weight: 800; }
h2 { font-size: 1.45rem; margin: 2.5rem 0 .75rem; letter-spacing: -.01em; }
h3 { font-size: 1.15rem; margin: 1.5rem 0 .5rem; }
p { margin: .5rem 0 1rem; }

/* The opening paragraph of a content page: larger and lighter than body text, so the
   page has an entry point rather than starting flat. */
.lede { font-size: 1.2rem; line-height: 1.55; color: var(--muted); margin-bottom: 1.5rem; }

/* A hairline above each sub-headed block, so a content page reads as distinct parts
   rather than one undifferentiated column. */
body.public main section + section { border-top: 1px solid var(--border); padding-top: .5rem; }
body.public main section > h2:first-child { margin-top: 2rem; }

a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--link-hover); }

/* ---- header and navigation, built on <details> dropdowns ---- */
header {
  padding: .85rem 1.25rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  display: flex; gap: 1.5rem; align-items: center; flex-wrap: wrap;
}
header .brand {
  font-weight: 800; font-size: 1.2rem; text-decoration: none;
  color: var(--brand-purple); letter-spacing: -.01em;
  /* The logo mark and the wordmark share one baseline inside a single home link. */
  display: inline-flex; align-items: center; gap: .6rem;
}
header .brand-logo { height: 38px; width: auto; display: block; }
header .spacer { flex: 1; }
header nav { display: flex; gap: 1.1rem; align-items: center; flex-wrap: wrap; }
header nav > a, header summary { color: var(--ink); text-decoration: none; font-weight: 600; }
header nav > a:hover, header summary:hover { color: var(--brand-purple); }

header details { position: relative; }
header details > summary {
  cursor: pointer; list-style: none;
  display: inline-flex; align-items: center; gap: .35rem;
}
header details > summary::-webkit-details-marker { display: none; }
/* The title links to the page and the caret opens the list. Both live inside the
   summary, so each needs its own hit area rather than inheriting one. */
header .menu-title { color: inherit; text-decoration: none; font-weight: 600; }
header .menu-title:hover { color: var(--brand-purple); }
header .menu-caret { font-size: .75em; opacity: .7; padding: .25rem; }
header details[open] .menu-caret { transform: rotate(180deg); }
header details ul {
  position: absolute; margin: .4rem 0 0; padding: .4rem 0; list-style: none;
  border: 1px solid var(--border); background: var(--bg); min-width: 15rem;
  border-radius: var(--radius); box-shadow: var(--shadow); z-index: 20;
}
header details ul li a { display: block; padding: .45rem 1rem; text-decoration: none; color: var(--ink); }
header details ul li a:hover { background: var(--surface-2); color: var(--brand-purple); }

/* ---- buttons ---- */
button, .donate-btn, input[type="submit"], .btn {
  font: inherit; cursor: pointer; border: 1px solid transparent;
  border-radius: var(--radius); padding: .5rem .9rem; font-weight: 600;
  background: var(--brand-purple); color: #fff; text-decoration: none;
  display: inline-block; line-height: 1.2;
}
button:hover, input[type="submit"]:hover, .btn:hover { background: var(--brand-purple-strong); color: #fff; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, summary:focus-visible {
  outline: 3px solid var(--brand-blue); outline-offset: 2px;
}
/* The public Donate call to action stands out in the accent green. */
.donate-btn { background: var(--brand-green); color: #123; border-color: var(--brand-green-strong); }
.donate-btn:hover { background: var(--brand-green-strong); color: #06210d; }

/* ---- forms ---- */
label { display: inline-block; font-weight: 600; margin: .35rem 0 .2rem; }
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]),
select, textarea {
  font: inherit; color: var(--ink); background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: .5rem .6rem; max-width: 100%;
}
main form { margin-bottom: 1.5rem; }
form p { margin: .4rem 0; }
ul.errorlist, .errorlist { color: var(--danger); font-weight: 600; }

/* ---- tables. Many templates carry <table border="1">; neutralise it and restyle ---- */
table { border-collapse: collapse; width: 100%; margin: .5rem 0 1.25rem; border: 0; }
table[border] { border: 0; }
th, td { border: 1px solid var(--border); padding: .5rem .65rem; text-align: left; vertical-align: top; }
thead th { background: var(--surface-2); }
tbody tr:nth-child(even) { background: var(--surface); }
/* A wide table scrolls rather than breaking the layout on a small screen. */
main > table { display: block; overflow-x: auto; }

/* ---- flash messages ---- */
ul.messages { list-style: none; padding: 0; margin: 0 0 1.25rem; }
ul.messages li {
  padding: .6rem .85rem; border-radius: var(--radius); margin-bottom: .5rem;
  background: var(--brand-blue-soft); border: 1px solid var(--brand-blue);
}

/* ---- footer ---- */
footer {
  padding: 1.5rem 1.25rem; border-top: 1px solid var(--border);
  background: var(--surface); color: var(--muted); font-size: .9rem; margin-top: 2.5rem;
}
footer .block { margin-bottom: .6rem; max-width: var(--maxw); margin-inline: auto; }

/* ---- the honeypot: an off-screen field a bot tends to fill in ---- */
.hp { position: absolute !important; left: -9999px !important; }

/* =========================================================================
   THE SECTION PAGES — alternating bands that reveal on scroll.
   Home and the flow pages are unaffected: they are prose and keep the narrow
   reading measure.
   ========================================================================= */

/* FULL BLEED. A section page is NOT a centred column: the bands run the full width of
   the window and the picture touches the edge of the screen.

   The proportions below are the design: the picture takes about 35% flush to the edge,
   the text the remaining 65% with generous padding, the picture has no radius, no
   shadow and no gap beside the text, and alternation is a flex-direction flip rather
   than a reordering of the markup. */
body.public.sections main { max-width: none; padding: 0; }

/* The page title still behaves like prose — only the bands go full width. */
.section-intro {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 2.5rem 1.5rem 0;
}

.band { padding: 0; scroll-margin-top: 0; }
/* A band is focusable only so that following an anchor moves keyboard focus into it,
   so it must not draw a focus ring it never asked for. */
.band:focus { outline: none; }
.band + .band { border-top: 1px solid var(--border); }

.band-inner {
  display: flex;
  align-items: stretch;   /* the picture fills the band's full height */
}

/* Alternation. The text is FIRST in the markup on every band, and the flip is a change
   of flex DIRECTION only, so a screen reader and the keyboard always meet the heading
   and then its text, whichever side the picture is on. Do not reorder the markup. */
.band-left  .band-inner { flex-direction: row; }        /* picture on the right */
.band-right .band-inner { flex-direction: row-reverse; } /* picture on the left  */

.band-text {
  flex: 1 1 auto;
  min-width: 0;           /* without this one long unbroken word forces an overflow */
  padding: 4rem;
}
.band-text > h2:first-child { margin-top: 0; }

.band-media {
  flex: 0 0 35%;
  display: flex;          /* lets the image stretch to the band's height */
  min-width: 0;
}
.band-media img, .band-media video {
  width: 100%; height: 100%; display: block;
  object-fit: cover;      /* fill the panel, cropping rather than distorting */
  background: var(--surface-2);   /* seen while a lazy image is still loading */
}

/* A band with no picture: without this the text would run the entire window width,
   which is unreadable, so give it the prose measure. */
.band-inner-solo .band-text { max-width: calc(var(--measure) + 8rem); margin-inline: auto; }

/* The logo standing in for a photograph that does not exist yet. It fills exactly the
   panel a real photograph will, so the layout on screen is already the real layout,
   but it is CONTAINED rather than cropped and sits on a tinted ground, so it reads as
   a reserved slot rather than a choice. A real photograph simply does not carry this
   class, so nothing here needs removing later. */
.band-media .band-image-placeholder {
  object-fit: contain;
  padding: 3rem;
  background: var(--surface);
  opacity: .45;
}

/* ---- the column layout ----
   One column down the middle, no picture beside the text and no alternation. A page
   using it does NOT get the `sections` body class, so the ordinary reading measure
   already centres it and all this adds is the spacing between bands. */
.band-column { padding: 2.5rem 0; }
.band-column .band-column-inner > h2:first-child { margin-top: 0; }

/* =========================================================================
   PUBLIC TYPOGRAPHY

   Scoped to body.public deliberately: this is an editorial voice for a marketing
   site — large, LIGHT headings with tight negative tracking — and it would be
   wrong on the portal's dense data tables, which keep their bold compact headings.

   NO WEBFONT. The system stack stays; only weight, size and tracking change. A
   system without a 300 weight falls back to its regular one, which is a graceful
   loss rather than a broken page.
   ========================================================================= */
body.public h1,
body.public h2,
body.public .band-headline {
  font-weight: 300;
  letter-spacing: -.035em;
}
body.public h1 { font-size: 3.1rem; line-height: 1.08; }
body.public .band h2 { font-size: 2.1rem; line-height: 1.15; margin: 0 0 1rem; }
/* h3 keeps its weight: it labels forms and sub-blocks, where clarity beats elegance. */
body.public h3 { font-size: 1.15rem; font-weight: 600; letter-spacing: 0; }

/* Body copy at the same light weight as the headings, with the size nudged up to buy
   back some of the legibility that light text costs. If it ever reads too faint on a
   real screen, the fix is this one line back to weight 400. */
/* One type treatment for body copy, wherever it sits. A learn page is not a band, so
   without naming it here its paragraphs would fall back to the browser default and a
   paragraph would change weight and size simply by moving between the two. */
body.public .band p,
body.public .band li,
body.public .learn-body p,
body.public .learn-body li { font-weight: 300; font-size: 1.05rem; line-height: 1.7; }
body.public .lede { font-size: 1.3rem; line-height: 1.5; font-weight: 300; color: var(--muted); }

/* Two steps down rather than one cliff. A single drop at the layout breakpoint gave
   phone-sized headings to an ordinary windowed desktop browser, which read as the type
   never getting bigger. The layout still stacks at 52rem; only the type steps earlier
   and more gently. */
@media (max-width: 64rem) {
  body.public h1 { font-size: 2.7rem; }
  body.public .band h2 { font-size: 1.95rem; }
}
@media (max-width: 40rem) {
  body.public h1 { font-size: 2.1rem; }
  body.public .band h2 { font-size: 1.6rem; }
  body.public .lede { font-size: 1.1rem; }
}

/* ---- the banner: one band per page, set as the showpiece ----
   A near-full-screen opening panel with an even split, applied to whichever band asks
   for it in public/content.py. */
.band-banner .band-inner { min-height: 78vh; }
.band-banner .band-media { flex: 0 0 50%; }
.band-banner .band-text { padding: 5rem 4rem; }
.band-banner h2 { font-size: 3.1rem !important; line-height: 1.05; }

@media (max-width: 64rem) {
  .band-banner h2 { font-size: 2.7rem !important; }
}
@media (max-width: 52rem) {
  .band-banner .band-inner { min-height: 0; }
  .band-banner .band-text { padding: 2rem 1.5rem; }
  .band-banner h2 { font-size: 2rem !important; }
}

/* ---- the band slideshow ----
   The photographs stack in one box and cross-fade. They are absolutely positioned so
   the box does not grow to the sum of their heights; the panel's size still comes from
   `.band-media`, exactly as it did for a single image, so the crop numbers in
   IMAGE-SPEC.md are unaffected.

   `.is-current` is written by the SERVER on the first slide, not only by the script.
   With JavaScript off, that one photograph stays visible and the rest stay hidden, so
   the band degrades to a still picture rather than an empty panel. */
.band-slideshow { position: relative; width: 100%; height: 100%; overflow: hidden; }
.band-slide {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;               /* crop, never distort, as for a single image */
  opacity: 0;
  transition: opacity 1.1s ease-in-out;
  background: var(--surface-2);
}
.band-slide.is-current { opacity: 1; }

/* The cross-fade runs for EVERY visitor, including anyone who has asked for reduced
   motion. There is deliberately no guard here and none in slideshow.js: with the
   script running regardless, a CSS guard would only turn the fade into an instant CUT,
   which is more jarring than the fade it would be protecting against.

   The cost is real — people set that preference because motion can make them ill. If
   this is ever revisited, slowing the fade or lengthening the hold would soften it. */

/* ---- "Learn more" ----
   The link a band shows to its own learn page. Deliberately a text link rather than a
   second pill: the pill is the call to action, and two pills in one band compete. */
.band-learn { margin: 1.25rem 0 0; }
.band-learn a {
  font-weight: 600;
  text-decoration: none;
  border-bottom: 2px solid currentColor;
  padding-bottom: 2px;
}
.band-learn a:hover,
.band-learn a:focus { border-bottom-color: transparent; }

.learn-back { margin-top: 2.5rem; }

/* Text for screen readers only. Every "Learn more" link would otherwise read
   identically out of context, and a screen reader can list every link on a page, so
   four entries saying "Learn more" name nothing. This span carries what each one is
   about. Clip-based rather than display:none, which would remove it from the
   accessibility tree along with the screen. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ---- the pill button, for a button block inside a band ----
   Fully rounded, a transparent ground with a hairline inset, small uppercase text with
   wide tracking. It reads as quieter than a filled button, which is what lets it sit
   inside body copy without shouting. */
.band-button {
  display: inline-block;
  margin: 1.25rem 0 .5rem;
  padding: 0 2.4rem;
  height: 3.2rem; line-height: 3.2rem;
  border-radius: 3.2rem;
  background: transparent;
  color: var(--brand-purple);
  box-shadow: inset 0 0 0 1px var(--brand-purple);
  font-size: .8rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none;
}
.band-button:hover {
  background: var(--brand-purple); color: #fff;
  box-shadow: inset 0 0 0 1px var(--brand-purple);
}
/* Filled variant, for the one action on a page that should carry the most weight. */
.band-button.is-primary { background: var(--brand-purple); color: #fff; }
.band-button.is-primary:hover { background: var(--brand-purple-strong); box-shadow: inset 0 0 0 1px var(--brand-purple-strong); }

/* ---- lists inside a band ---- */
.band-bullets { margin: .75rem 0 1.25rem; padding-inline-start: 1.4rem; }
.band-bullets li { margin-bottom: .4rem; }

/* An items list: each entry opens with a bold lead-in and then its sentence. No
   bullet marker, because the lead-in is what starts the line. */
.band-items { margin: .75rem 0 1.25rem; padding: 0; list-style: none; }
.band-items li {
  margin-bottom: .9rem; padding-inline-start: 1rem;
  border-inline-start: 2px solid var(--border);
}
.band-items li strong { color: var(--ink); }

.band-form {
  margin-top: 1.75rem; padding: 1.25rem;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius);
}
.band-form > h3:first-child { margin-top: 0; }
.band-form + .band-form { margin-top: 1rem; }

/* ---- the reveal ----
   static/js/reveal.js adds html.js-reveal. With JavaScript off, or on a browser
   without IntersectionObserver, the class is never added and NOTHING below applies, so
   every band renders plainly visible.

   That is the entire no-JavaScript safety net. DO NOT move these rules out from under
   the .js-reveal prefix — a band hidden by CSS that no script will ever reveal is a
   blank page. */
.js-reveal .band {
  opacity: 0;
  transform: translateY(2rem);
  transition: opacity .7s ease, transform .7s ease;
}
.js-reveal .band.is-revealed { opacity: 1; transform: none; }

/* The picture slides in from its own side, so the two halves do not move as one slab. */
.js-reveal .band-media { transition: transform .8s ease .1s; }
.js-reveal .band-left .band-media { transform: translateX(1.5rem); }
.js-reveal .band-right .band-media { transform: translateX(-1.5rem); }
.js-reveal .band.is-revealed .band-media { transform: none; }

/* ---- reduced motion ----
   Anyone whose system asks for less motion gets the content with no movement at all.
   reveal.js says the same thing by never adding .js-reveal, so a band is visible even
   if this stylesheet loads late. The duplication is deliberate. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .band,
  .js-reveal .band-media {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  html { scroll-behavior: auto; }
}

/* Smooth scrolling for the in-page menu jumps, switched off above for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

@media (max-width: 52rem) {
  /* One column, with the picture ABOVE its text. column-reverse rather than column,
     because the picture comes second in the markup and on a phone it should introduce
     the band rather than trail it. Both alternation rules must be overridden, or a
     band keeps trying to lay itself out as a row. */
  .band-left .band-inner,
  .band-right .band-inner { flex-direction: column-reverse; }
  .band-text { padding: 2rem 1.5rem; }
  /* A PROPORTIONAL panel rather than a fixed strip. `object-fit: cover` crops rather
     than letterboxes, so one photograph has to survive every shape this panel takes;
     a fixed height spread those shapes so far apart that only a small share of the
     frame was guaranteed visible at all of them. Capping this end at 16/9 roughly
     doubles it. The crop numbers in IMAGE-SPEC.md follow from this ratio — re-measure
     them if it ever changes.

     `min-height: 0` is LOAD-BEARING. A flex item defaults to `min-height: auto`, which
     floors it at the image's own intrinsic height and silently overrules the
     aspect-ratio. `height: auto` likewise clears any inherited fixed height, so the
     ratio and not a leftover drives the box. */
  .band-media { flex: 0 0 auto; height: auto; min-height: 0; aspect-ratio: 16 / 9; }
  .band-inner-solo .band-text { max-width: none; }
  /* Stacked, the picture has no side to slide in from. */
  .js-reveal .band-left .band-media,
  .js-reveal .band-right .band-media { transform: none; }
}

/* ---- small screens ---- */
@media (max-width: 40rem) {
  header { gap: .9rem; }
  h1 { font-size: 1.55rem; }
  main { padding: 1.25rem 1rem; }
}


/* ================================================================================
   BODY BLOCKS — the pieces public/blocks.py can put inside a band or a learn page.
   The shape of each one follows the HTML5 UP "Story" template, redrawn in this
   site's own palette and type rather than copied: no vendor stylesheet is loaded.
   ================================================================================ */

/* ---- headings inside copy ----
   heading1 is an <h2>, the band's own level, so it inherits the band heading's size
   without restating it. On a learn page there is no .band wrapper, so the size is set
   here. heading2 is the level below and always smaller than whatever heading1 is. */
.block-heading1 { margin: 2.25rem 0 1rem; }
.block-heading1:first-child { margin-top: 0; }
body.public .block-heading2 { font-size: 1.35rem; margin: 1.75rem 0 .6rem; font-weight: 600; }
body.public .band .block-heading2 { font-size: 1.5rem; }
/* A learn page is not a band, so the band's heading sizes do not reach it. Repeated
   here for the same reason as the paragraph rule above: a heading should not change
   size when copy moves between a band and its learn page. */
body.public .learn-body .block-heading1 { font-size: 2.1rem; line-height: 1.15; }
body.public .learn-body .block-heading2 { font-size: 1.5rem; }

/* ---- gridItems ----
   Story's "Items (style1)": cells of equal width separated by hairline rules, with no
   rule above the first row or left of the first column. The rules come from each
   cell's own top and left border, and the :nth-child rules below strip the ones that
   would otherwise draw a box around the outside.

   Three across, dropping to two and then one. The counts are repeated per breakpoint
   because the "which cells sit on the top row" arithmetic changes with them. */
.block-grid-items {
  display: flex;
  flex-wrap: wrap;
  margin: 2rem 0;
}
.block-grid-items > section {
  flex: 0 0 auto;
  width: 33.3333%;
  padding: 1.75rem 1.75rem .25rem;
  border-style: solid;
  border-color: var(--border);
  border-width: 1px 0 0 1px;
}
.block-grid-items > section:nth-child(-n + 3) { border-top-width: 0; }
.block-grid-items > section:nth-child(3n + 1) { border-left-width: 0; }
.block-grid-items > section h3 { margin: 0 0 .35rem; font-size: 1.05rem; }
.block-grid-items > section p { margin: 0 0 1.25rem; }

@media (max-width: 64rem) {
  .block-grid-items > section { width: 50%; }
  .block-grid-items > section:nth-child(-n + 3) { border-top-width: 1px; }
  .block-grid-items > section:nth-child(3n + 1) { border-left-width: 1px; }
  .block-grid-items > section:nth-child(-n + 2) { border-top-width: 0; }
  .block-grid-items > section:nth-child(2n + 1) { border-left-width: 0; }
}
@media (max-width: 40rem) {
  .block-grid-items > section { width: 100%; padding: 1.25rem 0 .25rem; }
  .block-grid-items > section:nth-child(-n + 2) { border-top-width: 1px; }
  .block-grid-items > section:nth-child(2n + 1) { border-left-width: 1px; }
  .block-grid-items > section { border-left-width: 0; }
  .block-grid-items > section:first-child { border-top-width: 0; }
}

/* ---- blockquote ----
   Story's: italic, indented, a rule down the left edge. The attribution, when there is
   one, sits under the quotation in smaller muted type. */
.block-quote { margin: 0 0 2rem; }
.block-quote blockquote,
blockquote.block-quote {
  margin: 0;
  padding: .5rem 0 .5rem 2rem;
  border-left: 4px solid var(--border);
  font-style: italic;
}
.block-quote figcaption {
  margin: .5rem 0 0 2rem;
  font-size: .9rem;
  color: var(--muted);
}
.block-quote figcaption::before { content: "— "; }

/* ---- gallery ----
   Story's "Gallery (style2)": a strip of fixed-width pictures that scrolls sideways
   rather than reflowing, so the row keeps its rhythm at any width. Touch scrolling is
   native; the scroll snap makes a swipe land on a picture rather than between two.

   Each picture is a link to the full-size file. The hover state is a slight lift and
   dim, which is the affordance saying it can be opened — there are no captions to fade
   in, because the folder, not the copy, decides what these photographs are. */
.block-gallery {
  display: flex;
  gap: .5rem;
  margin: 2rem 0;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding-bottom: .5rem;
}
.block-gallery article {
  flex: 0 0 auto;
  width: 22.5rem;
  max-width: 75vw;
  scroll-snap-align: start;
}
.block-gallery .image {
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  border-radius: var(--radius);
}
.block-gallery .image img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;                 /* crop, never distort */
  transition: transform .25s ease-in-out, opacity .25s ease-in-out;
}
.block-gallery .image:hover img,
.block-gallery .image:focus-visible img { transform: scale(1.04); opacity: .85; }
.block-gallery .image:focus-visible { outline: 3px solid var(--brand-blue); outline-offset: 2px; }

/* The enlarged view built by static/js/lightbox.js. It exists only while a picture is
   open; with JavaScript off the link opens the image on its own, which is why nothing
   here is needed for the gallery to work. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  background: rgba(10, 6, 18, .92);
  opacity: 0;
  transition: opacity .2s ease-in-out;
}
.lightbox.is-open { opacity: 1; }
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--radius);
}
.lightbox-close {
  position: absolute;
  top: 1rem; right: 1rem;
  width: 3rem; height: 3rem;
  border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .12);
  color: #fff;
  font-size: 1.5rem; line-height: 1;
  cursor: pointer;
}
.lightbox-close:hover { background: rgba(255, 255, 255, .24); color: #fff; }

/* Someone who has asked for reduced motion gets the enlarged picture without the fade
   and without the hover zoom. Unlike the band slideshow — which runs regardless,
   because stopping it would turn a fade into a cut — nothing here is lost by removing
   the animation: the picture simply appears. */
@media (prefers-reduced-motion: reduce) {
  .lightbox { transition: none; }
  .block-gallery .image img { transition: none; }
  .block-gallery .image:hover img,
  .block-gallery .image:focus-visible img { transform: none; }
}
