:root {

  --palette-black: rgb(1,22,30);
  --palette-teal: rgb(18,69,89);
  --palette-blue: rgb(164, 0, 0);
  --palette-green: rgb(174,195,176);
  --palette-beige: rgb(239,246,224);
  --palette-white: rgb(246,246,246);
  --palette-breaking: linear-gradient(0deg, rgb(255, 171, 0) 0%, rgb(249, 255, 0) 85%);

  --color-bg: var(--palette-white);
  --color-text: var(--palette-black);
  --color-muted: #6b6b6b;
  --color-border: #e2e2e2;
  --color-accent: rgb(50,147,111);
  /* RedTop's own masthead/navbar bands - deliberately separate from
     --palette-teal/--palette-blue above (unchanged, still used elsewhere -
     tag pills, flag badges, the pager, the trending-topics gradient - a
     RedTop-specific header colour has no business retinting any of that
     too). header.site-header and .site-nav-bar are these two variables'
     only consumers. */
  --color-masthead: rgb(198,0,0);
  --color-navbar: rgb(164, 0, 0);
  --font-headline: 'Freeman', Georgia, 'Times New Roman', serif;
  --font-body: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --max-width: 850px;
  /* Header/footer inner band - wider than the article/showcase reading
     column (--max-width), roughly matching a large news masthead. */
  --max-width-wide: 1000px;
  /* The 24px left/right inset main > * (below) uses everywhere - named so
     the L/XL templates' full-bleed breakouts can cancel out exactly this
     much padding without a hardcoded number silently drifting out of
     sync with it if this ever changes. */
  --gutter: 24px;
}

/* Self-hosted from this template's own fon/ folder (harvested from Google
   Fonts) rather than linked to fonts.googleapis.com, so rendering this
   page never depends on a third-party request. Inter ships as a single
   variable file covering its whole weight range - one @font-face with a
   weight range, not one file per weight the way a static font would
   need (same "latin" subset file Google's CSS2 API hands back
   identically for every discrete weight in the 300-800 range requested -
   confirmed by byte-identical URLs across those responses, not assumed).
   Freeman has no variable axis at all on Google Fonts - just the one
   static regular weight, so its own rule below is a plain single weight,
   not a range. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('/asset/templates/redtop/fon/inter-variable.woff2') format('woff2');
}

@font-face {
  font-family: 'Freeman';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/asset/templates/redtop/fon/freeman-regular.woff2') format('woff2');
}

* { box-sizing: border-box; }

body {
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-bg);
  margin: 0;
  line-height: 1.5;
  /* General safety net against any accidental horizontal overflow -
     nothing currently relies on it deliberately, cheap to keep. */
  overflow-x: hidden;
  /* Sticky footer: main is the only flex-growing child, so the footer
     always sits at the bottom of the viewport when content is short,
     and below the fold otherwise. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  font-size: 1.15em;
}

a { color: inherit; }

/* ── App store banner (Admin > Titles > App tab, migration 089) ──────── */
/* Site-wide, rendered above the header itself (renderAppBanner(),
   render_helpers.php - main.php calls it as the very first thing inside
   <body>) whenever a visitor on iOS/Android hits a title with that
   platform's own store link set. Same --color-masthead as the header
   just below it, not a fresh colour of its own - reads as part of this
   site's own chrome rather than a third-party ad bolted on top of it. A
   plain flex child of body (body's own display:flex;flex-direction:column,
   above), not position:fixed/sticky - scrolls away with the rest of the
   page, same as everything else in this masthead already does. */
.app-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 10px var(--gutter);
  background: var(--color-masthead);
  color: var(--palette-white);
  text-align: center;
}
.app-banner-link { display: inline-flex; align-items: center; gap: 8px; color: inherit; text-decoration: none; font-weight: 700; font-size: 0.95em; }
.app-banner-link:hover .app-banner-text { text-decoration: underline; }
.app-banner-icon { flex-shrink: 0; width: 20px; height: 20px; }
/* &times; itself, not an SVG/icon font - matches .site-nav-toggle-btn's
   own plain-markup spirit (no icon font loaded site-wide for one glyph). */
.app-banner-dismiss { flex-shrink: 0; color: inherit; text-decoration: none; font-size: 1.3em; line-height: 1; opacity: 0.75; }
.app-banner-dismiss:hover { opacity: 1; }

/* ── Masthead ─────────────────────────────────────────────────── */
header.site-header {
  flex-shrink: 0;
  background: var(--color-masthead);
  color: rgba(255,255,255,1);
}

.site-nav-toggle-checkbox { display: none; }

/* Logo row only - the nav lives in its own .site-nav-bar strip below,
   not in here (see main.php). */
.site-header-inner {
  max-width: var(--max-width-wide);
  margin: 0 auto;
  padding: 16px 24px;
  display: flex;
  align-items: center;
}

/* inline-flex so the anchor's box hugs the image exactly - a plain
   inline <a> around an <img> leaves a few px of baseline gap underneath. */
.site-logo-link { display: inline-flex; }

.site-logo-link img.site-logo { height: 50px; width: auto; margin: 0.5em 0; }

/* Hidden on desktop (shown again in the small-screens block below) - the
   checkbox it's paired with lives at the body level, not nested in here,
   purely so the CSS below can reach .site-nav-bar as a sibling; this
   label only needs the matching `for` id to still work. */
.site-nav-toggle-btn {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 26px;
  height: 20px;
  margin-left: auto;
  cursor: pointer;
}

.site-nav-toggle-btn span {
  display: block;
  height: 2px;
  width: 100%;
  background: rgba(255, 255, 255, 1);
}

/* Its own strip, separate from the header band above it - a flat solid
   rather than the header's gradient, and much slimmer padding, so it
   reads as a distinct nav bar sitting under the masthead rather than
   more of the masthead itself. */
.site-nav-bar {
  flex-shrink: 0;
  background: var(--color-navbar);
  color: rgba(255, 255, 255, 1);
}

.site-nav-inner {
  max-width: var(--max-width-wide);
  margin: 0 auto;
  padding: 0 24px;
  /* flex + margin-left:auto on .site-search-toggle-btn below is what
     pushes the search icon to the right edge, same technique
     .site-nav-toggle-btn already uses within .site-header-inner. */
  display: flex;
  align-items: center;
}

.site-nav-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  padding: 0;
  margin: 0 0 0 -0.6em;
}

/* Anchor point for a dropdown (.site-nav-sublist, absolutely positioned
   against this) - every item gets it, not just ones with children, so
   nesting/un-nesting an item in the admin never needs a matching CSS change. */
.site-nav-item { position: relative; }

.site-nav-link {
  display: inline-block;
  text-decoration: none;
  font-size: 1.15em;
  font-weight: 600;
  letter-spacing: 0;
  padding: 0.1em 0.4em 0.2em 0.4em;
  border-top: 4px solid transparent;
}

/* Plain white, not the red accent - that's reserved for in-page content
   (showcase card hover, pagination links), not this chrome. */
.site-nav-link:hover,
.site-nav-item.is-active > .site-nav-link {
  border-top-color: rgba(255, 255, 255, 0.95);
}

/* Dropdown - hidden until its parent .has-children item is hovered/
   focused, so keyboard nav (Tab into a link inside it) reveals it too,
   not just a mouse hover. */
.site-nav-sublist {
  display: none;
  flex-direction: column;
  list-style: none;
  margin: 0;
  padding: 8px 0;
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 180px;
  background: rgb(24, 24, 24);
  border: 1px solid rgba(255, 255, 255, 0.1);
  z-index: 10;
}

.site-nav-item.has-children:hover > .site-nav-sublist,
.site-nav-item.has-children:focus-within > .site-nav-sublist {
  display: flex;
}

.site-nav-sublist .site-nav-link {
  display: block;
  padding: 8px 16px;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 500;
  border-bottom: none;
}

.site-nav-sublist .site-nav-link:hover,
.site-nav-sublist .site-nav-item.is-active > .site-nav-link { background: rgba(255, 255, 255, 0.08); }

/* A third-level dropdown opens to the side of its parent rather than
   further down, so it doesn't stack underneath and cover its own trigger. */
.site-nav-sublist .site-nav-sublist { top: 0; left: 100%; }

/* ── Search ────────────────────────────────────────────────────── */
.site-search-toggle-checkbox { display: none; }

.site-search-toggle-btn {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  color: rgba(255, 255, 255, 1);
  cursor: pointer;
}

/* display can't be transitioned, hence max-height + overflow:hidden as
   the open/close mechanism - this codebase's first CSS transition (the
   mobile nav toggle this pattern is otherwise copied from is a hard,
   instant display:none/block swap, no animation). Driven off the same
   :checked ~ sibling-selector idiom as .site-nav-toggle-checkbox:checked
   ~ .site-nav-bar further down - see main.php's own comment on why
   site-search-toggle has to be a body-level sibling of this for that to
   reach it. 80px is enough for one input row plus the bar's own padding;
   generous rather than exact since overflow:hidden clips anything past it
   either way. */
.site-search-bar {
  max-height: 0;
  overflow: hidden;
  background: var(--color-masthead);
  transition: max-height 0.25s ease;
}
.site-search-toggle-checkbox:checked ~ .site-search-bar { max-height: 80px; }

.site-search-bar-inner {
  max-width: var(--max-width-wide);
  margin: 0 auto;
  padding: 14px 24px;
}
.site-search-bar-inner form { display: flex; gap: 8px; }

/* Shared with search.php's own results-page form (component-defaults) -
   one input/button style for both places search appears, rather than two
   near-duplicate rule sets. */
.site-search-input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.95);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 1em;
}
.site-search-submit {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  background: var(--color-accent);
  color: #fff;
  font-family: var(--font-body);
  font-weight: 600;
  cursor: pointer;
}

/* ── Footer ────────────────────────────────────────────────────── */
footer.site-footer {
  flex-shrink: 0;
  font-size: 0.8em;
  background: rgb(48,48,54);
  color: rgba(255,255,255,1);
}

.site-footer-inner {
  max-width: var(--max-width-wide);
  margin: 0 auto;
  padding: 20px 24px;
}

/* Four column slots (col 2 is intentionally left empty - no menu is wired
   up behind it). Empty grid cells just collapse to whitespace, same as an
   empty renderMenu() call anywhere else. Columns 2-4 are a fixed 22% each;
   the first column takes whatever that frees up (grid resolves the
   percentage tracks first, gaps are already excluded from that math, then
   hands the 1fr track everything left over) rather than a plain equal
   25% split. Column gap is capped at 1em (resolves against this element's
   own 0.8em, inherited from footer.site-footer) - row-gap only ever
   matters once the 800px breakpoint below stacks this into two rows, and
   stays a plain px value since nothing asked it to track the type scale.
*/
.site-footer-columns {
  display: grid;
  grid-template-columns: 1fr 22% 22% 22%;
  gap: 24px 1em;
  padding-bottom: 20px;
}

/* renderMenu()'s default markup (nav.site-nav > ul.site-nav-list > li >
   a) is the same one the horizontal header nav uses - flipped to a
   vertical list here purely with CSS, same idiom as the mobile nav
   override further down. Hover feedback switches from the header's
   top-border tick (reads oddly to the left of a stacked list) to a plain
   underline. */
.site-footer-col .site-nav-list {
  flex-direction: column;
  gap: 6px;
  margin: 0;
}

.site-footer-col .site-nav-link {
  padding: 0;
  border-top: none;
}

.site-footer-col .site-nav-link:hover { text-decoration: underline; }

/* Horizontal strip, immediately above the copyright line - keeps the
   header nav's row layout and hover tick as-is, just smaller and
   separated from the columns above by a hairline. */
.site-footer-base {
  border-top: 1px solid rgba(255, 255, 255, 0.15);
}

.site-footer-base .site-nav-link { border-top: none; }

@media (max-width: 800px) {
  .site-footer-columns { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 560px) {
  .site-footer-columns { grid-template-columns: 1fr; gap: 16px; }
}

/* ── Main content column ─────────────────────────────────────────*/
/* 1em top padding - breathing room between the nav bar and every page's
   own content, on the wrapper every page kind already shares, rather than
   each new component (category, article, ...) needing its own one-off
   top margin to avoid sitting flush against the nav bar. */
main {
  flex: 1 0 auto;
  padding: 1em 0 60px;
}

/* Every direct child of <main> gets the same width/centering/edge-padding
   recipe .site-header-inner/.site-nav-inner/.site-footer-inner already
   use (var(--max-width-wide) + auto margins + var(--gutter) padding), so
   an article's or category listing's content lines up with the
   masthead's own left/right edges - EXCEPT .showcase, reset back to
   genuinely full-width immediately below (matching main exactly, the
   same "just don't constrain it" approach that already makes header/nav-
   bar/footer reliably full-width - see .showcase-section further down
   for why the constraint moves to a level below .showcase itself instead
   of being dropped here).

   Plain `main > *`, not `main > *:not(.showcase)` - :not()'s argument
   counts toward specificity as if it were written plain (a class
   selector, here), which made the exclusion-selector version of this
   rule MORE specific than a bare element selector like `article {}` or a
   single class like `.category-header {}` - so article's own narrower
   max-width (below) and, before this fix, a category page's title block
   were both silently losing their own overrides back to this rule's
   wider max-width, specificity beating source order regardless of which
   one actually came later in the file. A plain `main > *` has the same
   specificity as those single-selector rules, so normal source-order
   cascade (last one wins) does what was actually intended all along. */
main > * {
  max-width: var(--max-width-wide);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

main > .showcase {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: 0;
  padding-right: 0;
}

/* ── Showcase sections ────────────────────────────────────────────*/
/* Each section carries its own width/centering now, not the .showcase
   wrapper (main > .showcase above deliberately resets it back out) -
   because the L/XL templates (below) need to be full-width, and they can
   only actually reach that by having a genuinely unconstrained parent to
   sit inside; nesting them inside a constrained .showcase and then trying
   to calculate a breakout back out past that constraint is exactly what
   caused the last several rounds of trouble (vw vs. the scrollbar,
   container-query units vs. main's own flex sizing, etc.) - all fragile
   workarounds for a self-imposed problem. A normal section keeps the
   masthead-matching width via this rule; L/XL cancel it straight out
   below, the same "just don't constrain it" fix as main itself gets from
   its own parent. */
.showcase-section {
  max-width: var(--max-width-wide);
  margin: 0 auto 20px;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* Design-authored, admin-supplied markup (see showcase.php) - only
   minimal spacing here, no opinionated typography, since a prefix/suffix
   is expected to bring its own. */
.showcase-section-prefix { margin: 0 0 14px; }
.showcase-section-suffix { margin: 14px 0 0; }

.showcase-breaking {background: var(--palette-breaking);}

/* .showcase-home-trending - one-off full-bleed var(--palette-blue) band
   for the home page's "Trending topics" tags section (set via the
   Builder tab's own CSS class field, not auto-added by a Template the way
   showcase-l/showcase-xl below are). Same break-out as those: cancel
   .showcase-section's own max-width/margin-left/margin-right straight
   back to genuinely full-width, then re-apply that same width+centering
   one level down to the section's actual content (prefix/.showcase-tags/
   suffix - see _renderShowcaseTagsSection(), showcase.php) so it lines up
   with the rest of the page inside the full-bleed band. A bespoke class
   rather than a new Template option since this is one specific section's
   look, not a reusable layout. */
.showcase-home-trending {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding: 32px 0;
  text-align: center;
  background: linear-gradient(132deg, rgba(42, 123, 155, 1) 0%, rgba(87, 199, 133, 1) 50%, rgba(83, 173, 237, 1) 100%);
}

.showcase-home-trending .showcase-section-prefix,
.showcase-home-trending .showcase-tags,
.showcase-home-trending .showcase-section-suffix {
  max-width: var(--max-width-wide);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* .showcase-tags is itself a flex row (see its own rule further down) -
   text-align: center above only centers inline/block content, so the
   pill row needs its own justify-content to actually center as a group
   rather than just hugging the left edge of its (still full-width, now
   centered-text) container. */
.showcase-home-trending .showcase-tags {
  justify-content: center;
}

/* Overrides the home page's shared title styling (#showcase-home-page
   .showcase-section-prefix, further down) for this one band only - an id
   selector beats any number of classes on specificity alone, so this has
   to repeat that same id rather than just adding a .showcase-home-trending
   rule, or it would silently lose to the shared rule regardless of source
   order. Shared font-size/line-height/font-weight/::after still apply;
   only what actually needs to differ against a coloured band - no
   uppercase, white with a subtle shadow for legibility over the gradient's
   lighter end - is repeated here. */
#showcase-home-page .showcase-home-trending .showcase-section-prefix {
  text-transform: none;
  color: var(--palette-white);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

/* "L" template (Template dropdown, Builder tab) - its markup lives in
   components/section-templates/l.php, but its styling lives here with
   everything else; showcase.php auto-adds a "showcase-l" class to any
   section using that template, giving these rules a stable selector
   regardless of what's (or isn't) in the section's own css_class field.
   Full-bleed width, edge to edge (no background of its own - combine
   with a utility class like showcase-breaking, below, in the section's
   own CSS class field for one), while its content stays lined up with
   everything else. This section lives inside .showcase, and every
   .showcase-section (above) already gets its own width/centering
   directly (var(--max-width-wide), centered, var(--gutter) padding)
   specifically so .showcase itself is never constrained in the first
   place. That means this template doesn't have to calculate a breakout
   past anything (earlier attempts at exactly that - width:100vw or
   100cqw plus a negative margin - kept hitting new edge cases: vw
   doesn't subtract the scrollbar's width on most desktop browsers; cqw
   needs a query container, and main establishing one fought its own flex
   sizing). It just cancels the same three properties it inherits from
   .showcase-section (max-width/margin-left/margin-right) back to nothing
   - the same "just don't constrain it" fix main itself gets from body. */
.showcase-l {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding: 16px 0;
  color: var(--palette-black);
}

.showcase-l .showcase-section-prefix,
.showcase-l .showcase-grid,
.showcase-l .showcase-section-suffix {
  max-width: var(--max-width-wide);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* L defaults to the page's dark body colour (var(--palette-black), set
   above) - only illegible when combined with an accent-coloured band
   like .showcase-breaking (via the section's own css_class field), so
   the white override - plus a lighter placeholder tint for images
   without one, matching the masthead's own image-less-card treatment
   logic - only applies then, not unconditionally. */
.showcase-l.showcase-breaking,
.showcase-l.showcase-breaking .showcase-card-link {
  color: rgba(255, 255, 255, 1);
}

.showcase-l.showcase-breaking .showcase-card img { background: rgba(255, 255, 255, 0.12); }

/* var(--color-muted) (the meta line's default colour) is a dark grey -
   low-contrast on the same accent band the two rules above exist to fix,
   so it gets its own translucent-white override alongside them. */
.showcase-l.showcase-breaking .showcase-card-meta { color: rgba(255, 255, 255, 0.75); }

/* Hero layout, not the standard repeating card grid - one column (so a
   second curated item stacks below rather than squeezing in beside the
   first), each card laid out image-beside-headline instead of
   image-above-headline. .showcase-card-link only exists when the item
   actually has a link (see item-card.php) - .showcase-card itself is
   targeted too so an unlinked item (no resolvable URL yet) still gets the
   same side-by-side treatment instead of silently falling back to the
   default stacked layout. */
/* minmax(0, 1fr), not bare 1fr - a bare 1fr track is implicitly
   minmax(auto, 1fr), and "auto" resolves to the item's max-content size.
   The card's image has flex-shrink: 0 (never shrinks below its 50%
   basis), so paired with a long headline its automatic minimum width
   can exceed the track's real width and blow the card out past the
   grid's edge instead of shrinking to fit. */
.showcase-l .showcase-grid { grid-template-columns: minmax(0, 1fr); gap: 28px; }

.showcase-l .showcase-card,
.showcase-l .showcase-card-link {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 28px;
}

/* overflow: hidden is what actually makes border-radius round the image's
   own corners too, not just the text panel's - clipping both children to
   one unified rounded rectangle rather than rounding each separately.
   background: #fff (not just .showcase-card-body's own white, above) -
   the image's aspect-ratio fixes its height independent of the row's
   actual stretched height, so a row taller than the image (a two-line
   headline) leaves the image short of the row's full height; this is
   what shows through that leftover space, and through the flex gap
   beside it, instead of the grey page background. */
.showcase-l .showcase-card {
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
}

/* When the item has a link, .showcase-card-link (not .showcase-card) is
   the one actually holding the image+headline row - but as a flex ITEM
   of .showcase-card's own row, it defaults to hugging its content's
   width rather than filling the card, which left the nested img's
   flex-basis: 50% (below) resolving against that too-small width instead
   of the card's real width - min-width: 0 overrides the default
   flex-item min-width: auto, which otherwise refuses to shrink narrower
   than the image's intrinsic size and would reintroduce the same
   too-wide-image symptom this whole rule exists to fix. */
.showcase-l .showcase-card-link {
  flex: 1;
  min-width: 0;
}

.showcase-l .showcase-card img {
  /* 33% (a third of the row) rather than XL's 50% - L reads as a denser
     feed entry, not a second hero. */
  flex: 0 0 33%;
  min-width: 0;
  width: auto;
  /* Overrides the "first section's lead card" hero rule further down
     (21/9, meant for a full-width image above a headline, and explicitly
     excludes .showcase-l/.showcase-xl/.showcase-m/.showcase-s via :not()
     so it can't silently outrank this on specificity) - at a third of the
     row's width beside the headline rather than above it, that ratio
     reads as an odd thin strip; object-fit: cover (base .showcase-card
     img rule) does the actual re-cropping to this ratio. Same 16:9 as M
     now, for one consistent crop across every feed row size. */
  aspect-ratio: 16 / 9;
}

/* .showcase-card-body (title + meta) takes over h3's old flex-item role
   here - it, not h3 alone, is what sits beside the image now that a
   .showcase-card-meta line lives underneath the headline (see item-card.php). */
.showcase-l .showcase-card-body {
  flex: 1;
  min-width: 0;
  background: #fff;
  /* Explicit, not inherited - .showcase-l.showcase-breaking (further up)
     turns the whole row's text white for legibility on a coloured band,
     which would otherwise cascade in here and vanish against this panel's
     own opaque white. */
  color: var(--color-text);
  justify-content: center;
  padding: 24px;
}

.showcase-l .showcase-card h3 {
  margin: 0;
  font-size: 1.9em;
  line-height: 1.25;
}

/* "XL" template - components/section-templates/xl.php, auto-tagged
   "showcase-xl" the same way L is tagged "showcase-l" (see above). The
   straight Major -> XL rename: same full-bleed width breakout and same
   image-beside-headline row layout as L, just bigger (larger headline
   type, wider image column) to match Major's original proportions. */
.showcase-xl {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding: 16px 0;
  color: var(--palette-black);
}

.showcase-xl .showcase-section-prefix,
.showcase-xl .showcase-grid,
.showcase-xl .showcase-section-suffix {
  max-width: var(--max-width-wide);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* Same conditional white-on-accent-band override as L, above - XL
   defaults to var(--palette-black) (set above) too. */
.showcase-xl.showcase-breaking,
.showcase-xl.showcase-breaking .showcase-card-link {
  color: rgba(255, 255, 255, 1);
}

.showcase-xl.showcase-breaking .showcase-card img { background: rgba(255, 255, 255, 0.12); }
.showcase-xl.showcase-breaking .showcase-card-meta { color: rgba(255, 255, 255, 0.75); }

/* Hero layout, same shape as L (image-beside-headline, single column so
   a second curated item stacks below) - see .showcase-l above for why
   .showcase-card-link is targeted alongside .showcase-card. XL's own
   proportions run bigger throughout: wider gap, a larger image column,
   and (below) larger headline type. */
/* minmax(0, 1fr), not bare 1fr - same grid-blowout fix as L, above. */
.showcase-xl .showcase-grid { grid-template-columns: minmax(0, 1fr); gap: 32px; }

.showcase-xl .showcase-card,
.showcase-xl .showcase-card-link {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 32px;
}

.showcase-xl .showcase-card {
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
}

.showcase-xl .showcase-card-link {
  flex: 1;
  min-width: 0;
}

.showcase-xl .showcase-card img {
  /* Roughly half the row, the biggest image column of the four feed
     sizes (L 33%, M/S narrower still - see their own rules below) -
     landscape 3/2, wide enough at this width to read as a real photo
     rather than a crop. */
  flex: 0 0 50%;
  min-width: 0;
  width: auto;
  aspect-ratio: 3 / 2;
}

/* .showcase-card-body (title + meta) takes over h3's old flex-item role
   here - same reasoning as .showcase-l .showcase-card-body above. */
.showcase-xl .showcase-card-body {
  flex: 1;
  min-width: 0;
  background: #fff;
  color: var(--color-text);
  justify-content: center;
  padding: 28px;
}

.showcase-xl .showcase-card h3 {
  margin: 0;
  font-size: 2.4em;
  line-height: 1.2;
}

.showcase-xl .showcase-card-link:hover h3 { text-decoration: underline; }

/* "M" template - components/section-templates/m.php (identical markup to
   every other section template - see that file's own comment). Unlike L/
   XL, M is NOT full-bleed. It also isn't just left at .showcase-section's
   own default width either - L's own row content is ALSO capped at
   var(--max-width-wide) once full-bleed's background is accounted for
   (.showcase-l .showcase-grid, above), so the two would otherwise render
   at the exact same content width and only differ by L's background
   being able to bleed past it. This explicit, narrower max-width is what
   actually reads as "M, inset with real margin either side" rather than
   "L with a shorter background". Same image-beside-headline row shape as
   L, just smaller throughout (width, image column, gap, type) - "L,
   scaled down", not a different layout. */
.showcase-m {
  max-width: 880px;
}

.showcase-m .showcase-grid { grid-template-columns: minmax(0, 1fr); gap: 20px; }

.showcase-m .showcase-card,
.showcase-m .showcase-card-link {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 20px;
}

.showcase-m .showcase-card {
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
}

.showcase-m .showcase-card-link {
  flex: 1;
  min-width: 0;
}

.showcase-m .showcase-card img {
  /* Same 33% share of the row as L (this is "L, scaled down", not a
     different ratio) - it reads smaller in practice purely because M's
     own row is narrower (880px vs. L's full-bleed/~1000px content). */
  flex: 0 0 33%;
  min-width: 0;
  width: auto;
  aspect-ratio: 16 / 9;
}

.showcase-m .showcase-card-body {
  flex: 1;
  min-width: 0;
  background: #fff;
  color: var(--color-text);
  justify-content: center;
  padding: 18px;
}

.showcase-m .showcase-card h3 {
  margin: 0;
  font-size: 1.35em;
  line-height: 1.3;
}

/* "S" template - components/section-templates/s.php. The smallest feed
   row: narrower still than M's own column (var(--max-width), the same
   narrower reading-column width article/page content uses, rather than
   M's var(--max-width-wide)), a smaller image share, and a near-square
   crop rather than L/M's 4/3 - at this width a landscape crop leaves too
   thin a strip to read as a real photo. */
.showcase-s {
  max-width: var(--max-width);
}

.showcase-s .showcase-grid { grid-template-columns: minmax(0, 1fr); gap: 14px; }

.showcase-s .showcase-card,
.showcase-s .showcase-card-link {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 14px;
}

.showcase-s .showcase-card {
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
}

.showcase-s .showcase-card-link {
  flex: 1;
  min-width: 0;
}

.showcase-s .showcase-card img {
  flex: 0 0 28%;
  min-width: 0;
  width: auto;
  aspect-ratio: 1 / 1;
}

.showcase-s .showcase-card-body {
  flex: 1;
  min-width: 0;
  background: #fff;
  color: var(--color-text);
  justify-content: center;
  padding: 12px;
}

.showcase-s .showcase-card h3 {
  margin: 0;
  font-size: 1.05em;
  line-height: 1.3;
}

.showcase-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 24px 20px;
}

/* Columns support (showcase.php's own row_group grouping) - two or more
   sections sharing a Builder-tab "Row group" value render as N even
   columns instead of stacked full-width. --showcase-row-columns is set
   inline per-row (however many sections share that row_group), not a
   fixed set of N-up classes, since a row's own column count is whatever
   the admin grouped together, not a small fixed set of layouts.
   max-width/margin/padding here are the SAME width-capping/centering/
   gutter treatment .showcase-section normally gives itself (below) -
   moved up to the row as a whole rather than left on each section, since
   a lone section's own max-width does nothing useful once its "section"
   is really just one narrow grid column (the max-width is already wider
   than the column, so it never binds) - without this the row stretched
   edge-to-edge past the page's own content column instead of lining up
   with every section above/below it. */
.showcase-row {
  display: grid;
  grid-template-columns: repeat(var(--showcase-row-columns, 1), 1fr);
  gap: 32px;
  max-width: var(--max-width-wide);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}
/* Each column now sits inside the row's own width cap/gutter (above), so
   its own copy of the same centering/gutter would just add a second,
   redundant inset - reset here to fill the grid cell edge-to-edge
   instead. margin-top/-bottom deliberately NOT reset (only left/right,
   and via longhands, not the margin shorthand) - the row still wants
   each column's normal trailing space below it.

   padding-top:0 is a second, separate fix: a section's own Template
   (S/M/L/XL) can add its own top/bottom padding independently of any of
   this (.showcase-l/.showcase-xl's own padding:16px 0, above - no
   equivalent rule for S/M at all), so two row-mates on different
   templates would otherwise start at different heights - an L column's
   card sitting 16px lower than an M column's beside it, reading as
   broken alignment even though each section is doing exactly what its
   own template intends outside a row. This specificity (two classes)
   already beats .showcase-l/-xl's own (one class) for padding-left/
   -right above; the same rule now wins padding-top too. padding-bottom
   deliberately left alone - unlike the top edge, different-height
   columns ending at different points lower down is normal column
   behaviour, not a mismatch to flatten. */
.showcase-row .showcase-section {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-top: 0;
  padding-left: 0;
  padding-right: 0;
}

/* L/XL's OWN gutter/centering doesn't live on .showcase-section at all
   (that one's already max-width:none/margin:0 for them, so their section
   can go full-bleed edge to edge) - it's on .showcase-grid/-prefix/-suffix
   INSTEAD (.showcase-l/.showcase-xl .showcase-grid, near the top of this
   file: its own max-width:var(--max-width-wide), margin-left/right:auto,
   padding-left/right:var(--gutter)). The reset above only ever touched
   the section itself, so an L/XL column in a row was getting the gutter
   applied TWICE - once from .showcase-row's own padding-left/right, and
   AGAIN from this inner re-centering, which has no idea it's now sitting
   inside an already-narrow row column instead of a full viewport section
   - that's the extra left/right inset that made an L/XL column read as
   indented compared to an "M" column beside it (M has no such
   inner-grid gutter to begin with, so it was only ever getting the row's
   own single gutter). Three classes here beats that rule's own two, so
   this wins on specificity, not just source order. */
.showcase-row .showcase-l .showcase-grid,
.showcase-row .showcase-l .showcase-section-prefix,
.showcase-row .showcase-l .showcase-section-suffix,
.showcase-row .showcase-xl .showcase-grid,
.showcase-row .showcase-xl .showcase-section-prefix,
.showcase-row .showcase-xl .showcase-section-suffix {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: 0;
  padding-right: 0;
}

@media (max-width: 720px) {
  /* Stacked, not squeezed - a 3-up row of card grids has no readable
     width left per column once the viewport itself is phone-sized.
     Reverts to one column; each section's own unconditional margin-bottom
     (unaffected by any of this) is what actually spaces them apart once
     stacked, same as it always has. */
  .showcase-row { grid-template-columns: 1fr; gap: 0; }
}

.showcase-card { display: flex; flex-direction: column; }

.showcase-card img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  display: block;
  background: #f0f0f0;
}

.showcase-card h3 {
  font-family: var(--font-headline);
  font-size: 1.15em;
  line-height: 1.3;
  font-weight: 800;
  margin: 10px 0 0;
}

/* Same structure-only/no-colour base as .article-flags/.article-flag (see
   that comment) - sits above the headline in the card's own text block,
   ahead of h3's own margin-top so a flagged card doesn't get extra top
   spacing on top of this row's own. Square corners throughout (no
   border-radius anywhere in this file's flag rules) - this title's own
   look, not a default worth assuming for every template. */
.showcase-card-flags { display: flex; flex-wrap: wrap; gap: 4px; }
.showcase-card-flag {
  display: inline-block;
  padding: 2px 7px;
  font-size: 0.7em;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  background: var(--palette-blue);
  color: #fff;
}
.showcase-card-flags + h3 { margin-top: 6px; }

/* "Breaking News" example (flagkey breaking-news) - black badge / yellow
   text, same var(--palette-breaking) gradient .showcase-breaking already
   uses site-wide. The parent modifier targets .showcase-card-body
   specifically, not the whole .showcase-card - that's what keeps the
   image itself from getting tinted too in the standard grid (M/S, image
   above text, no flex gap between the two to worry about). Add a
   --{flagkey} pair like this for every flag that needs its own look;
   anything without one keeps the neutral default above. */
.showcase-card-flag--breaking-news { background: var(--palette-black); color: rgb(249, 255, 0); }
.showcase-card--flag-breaking-news .showcase-card-body {
  background: var(--palette-breaking);
  padding: 14px;
}

/* Feed rows (L/M/S/XL) only, below - the standard grid above needs none
   of this. */

/* The badge sits directly against the headline otherwise (h3's own
   margin-top is 0 on every feed row template - see each one's own
   .showcase-card h3 rule, which wins on specificity over
   .showcase-card-flags + h3 above regardless of source order) - this
   gives it real breathing room instead. */
.showcase-l .showcase-card-flags,
.showcase-m .showcase-card-flags,
.showcase-s .showcase-card-flags,
.showcase-xl .showcase-card-flags {
  margin-bottom: 1em;
}

/* Every feed row template's own gap (L 28px, XL 32px, M 20px, S 14px -
   see each one's .showcase-card rule above) sits between the image and
   .showcase-card-body - color on .showcase-card-body alone (above)
   doesn't reach into that gap, so it showed as an undesired band of plain
   page background between the image and the coloured text panel.
   Colouring .showcase-card itself instead (its own flex container, gap
   included) fills that band too - the image sits opaquely on top of its
   own share of the row so it's never actually visible through it, only
   the gap and the text panel are. */
.showcase-l .showcase-card--flag-breaking-news,
.showcase-m .showcase-card--flag-breaking-news,
.showcase-s .showcase-card--flag-breaking-news,
.showcase-xl .showcase-card--flag-breaking-news {
  background: var(--palette-breaking);
}
/* Every feed row template's .showcase-card-body now has its own opaque
   white background by default (each template's own rule, above) so the
   gradient coloured onto .showcase-card itself (just above) would
   otherwise be hidden behind it in the text panel - only the image-to-
   text gap would actually show the gradient. Cancelling back to
   transparent here lets it show through the whole row, same as before
   .showcase-card-body had a background of its own to get in the way.
   align-items/justify-content/padding need no flagged-state override any
   more - stretch + centered + padded text is every feed row's own
   default now, not something only the flagged state opts into. */
.showcase-l .showcase-card--flag-breaking-news .showcase-card-body,
.showcase-m .showcase-card--flag-breaking-news .showcase-card-body,
.showcase-s .showcase-card--flag-breaking-news .showcase-card-body,
.showcase-xl .showcase-card--flag-breaking-news .showcase-card-body {
  background: transparent;
}

/* Wraps title + meta together (see item-card.php) - lets L/XL's flex row
   put the whole text block beside the image as one flex item instead of
   the image row splitting three ways once a meta line exists alongside
   the headline (see .showcase-l/.showcase-xl .showcase-card-body above).
   In the default column layout (M/S) it's also what makes the meta line
   cling to the bottom of the card: a flex column of its own, so
   .showcase-card-meta's margin-top: auto (below) has somewhere to push
   against - title stays put, meta eats whatever space a shorter title
   (one line vs. two) leaves below it, so every card in a row lines its
   meta up at the same height regardless of title length. flex: 1 is what
   gives it that spare space to push into in the first place - see
   .showcase-card-link below for why it's there to hand down. */
.showcase-card-body { display: flex; flex-direction: column; flex: 1; min-width: 0; }

/* Category · time, same secondary-text treatment as the article byline
   (.byline, below) - one line, small, muted. The category/time separator
   is a ::after on category rather than a static string between two spans,
   so it disappears on its own when time is absent (:not(:last-child)) -
   an item can have a category with no resolvable time, or vice versa.
   margin-top: auto (not a fixed value) is what clings this to the bottom
   of .showcase-card-body's flex column - padding-top is a floor under
   that, so title and meta never actually touch even when there's no
   spare height to push into (a two-line title filling the card). */
.showcase-card-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding-top: 6px;
  color: var(--color-muted);
  font-size: 0.8em;
}
.showcase-card-category {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.showcase-card-category:not(:last-child)::after { content: '·'; margin-left: 6px; font-weight: 400; }

/* Category page tiles (.category-grid, category.php's own fixed 4-across
   grid) want the date on its own line below the category, instead of the
   default row's "category · time" - still an image-above-text tile grid,
   unlike every showcase section template now (L/M/S/XL are all feed rows
   sharing the base row-meta layout, so none of them need this override
   any more - see their own .showcase-card-meta rules elsewhere in this
   file for the ones that still diverge). The category/time separator is
   only meaningful in a row, so it's dropped here too. */
.category-grid .showcase-card-meta {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.category-grid .showcase-card-category:not(:last-child)::after {
  content: none;
}

/* flex: 1 (not the block it used to be) is what gives .showcase-card-body
   the row's full spare height to work with in the default column layout -
   .showcase-card itself already stretches to the tallest card in its grid
   row (CSS Grid's own default align-items: stretch), but that extra height
   was previously just dead space after a shorter card's content; now it's
   available for .showcase-card-meta to push into. L/XL override display/
   flex-direction back to their own row layout (see .showcase-l/.showcase-xl
   .showcase-card-link above) - flex: 1 there is the same value they already
   set themselves, so this changes nothing for them. */
.showcase-card-link { display: flex; flex-direction: column; flex: 1; color: inherit; text-decoration: none; }

.showcase-card-link:hover h3 { text-decoration: underline; text-decoration-color: var(--color-accent); }

/* The first section's lead story reads bigger, like a front-page splash -
   plain grid-column span, not a row-span/track-size trick, so it stays
   predictable regardless of how many other items are in that section.
   :not(.showcase-l):not(.showcase-m):not(.showcase-s):not(.showcase-xl)
   because those are their own, more specific feed-row templates with
   their own sizing - without the exclusion these three rules outrank
   their own image/h3 rules on specificity alone (5 class-level selectors
   here vs. 2 there) and silently win regardless of source order,
   overriding their aspect-ratio and font-size with values meant for a
   completely different (grid, image-above-text) layout. */
.showcase-section:first-of-type:not(.showcase-l):not(.showcase-m):not(.showcase-s):not(.showcase-xl) .showcase-grid .showcase-card:first-child { grid-column: span 2; }
.showcase-section:first-of-type:not(.showcase-l):not(.showcase-m):not(.showcase-s):not(.showcase-xl) .showcase-grid .showcase-card:first-child img { aspect-ratio: 21 / 9; }
.showcase-section:first-of-type:not(.showcase-l):not(.showcase-m):not(.showcase-s):not(.showcase-xl) .showcase-grid .showcase-card:first-child h3 { font-size: 1.5em; }

/* ── Article / static page ────────────────────────────────────────*/
article { max-width: 720px; margin: 0 auto; }

/* .page-view (component-defaults/page.php) - a Page's own dedicated width
   hook, matching var(--max-width-wide) - the same masthead-matching band
   main > * already gives every other page kind (showcase sections,
   category listings, etc.) - NOT the narrower 720px article rule above,
   which is specific to the reading-column article layout and was never
   meant to apply here. Overriding max-width back to the (equal, via
   main > *) wide value looks redundant but isn't - it's what stops a
   future edit to the more specific `article` rule above from silently
   narrowing pages too, since without this .page-view has no width
   opinion of its own at all beyond whatever main > * already provides.

   .breakout is the escape hatch, for a specific element WITHIN the page
   body (applied by hand via the page editor's Source Code view - there's
   no picker for it yet) that needs to be wider even than that - a
   full-width image, a wide table, an embed. Same "break all the way out
   to the real viewport edges" meaning as .showcase-l/.showcase-xl/
   .showcase-home-trending elsewhere in this file, not a second, weaker
   kind of "wide" - one breakout vocabulary sitewide. The classic
   centered-column breakout trick (viewport-relative negative margins) -
   works regardless of how deeply .page-view itself is nested, as long as
   it's horizontally centered with no other offset, which it is. */
.page-view {
  max-width: var(--max-width-wide);
  margin-left: auto;
  margin-right: auto;
}
.page-view .breakout {
  max-width: none;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}

article h1 {
  font-family: var(--font-headline);
  font-size: 2.1em;
  line-height: 1.2;
  font-weight: 800;
  margin: 0 0 10px;
}

.byline {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 0 0 20px;
}

.byline-author {
  display: flex;
  align-items: center;
  gap: 8px;
}

.byline-photo {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}

.byline-meta {
  display: flex;
  flex-direction: column;
  font-size: 0.88em;
  line-height: 1.3;
}

.byline-name { color: var(--color-text); font-weight: 600; }
.byline-position { color: var(--color-muted); }

article > img { width: 100%; height: auto; margin-bottom: 20px; }

.page-body { font-size: 1.08em; line-height: 1.7; color: var(--palette-black); }
/* Direct-child div, not just p - the item body editor's output is a mix of
   <p> and plain top-level <div> depending on how a piece was originally
   authored/pasted (same quirk TheSelebian app's own styles.css documents
   for its equivalent rule), so a p-only selector left some paragraphs
   sitting flush against the one before with no gap at all. */
.page-body p,
.page-body > div { margin: 0 0 1em; }
.page-body img { max-width: 100%; height: auto; }

/* ── Live blog (migration 043/044) - replaces .page-body when is_liveblog
   is on (see article.php). Newest (and pinned) posts lead - same ordering
   liveblog_posts.php's own query already returns, maintained incrementally
   client-side as new ones arrive (item-card.php's renderLiveblogStreamScript).
   Two-column layout - a fixed time column on the left (relative label over
   the literal clock time, both computed once at render/insert - see
   _liveblogRelativeTime()), a bordered box on the right holding the
   post itself. Square corners throughout, no border-radius - this title's
   own look (same call already made for flag badges/cards elsewhere in
   this file), not a default worth assuming for every template. ─*/
.liveblog-stream { display: flex; flex-direction: column; gap: 20px; }

/* Summary pane above the stream (migration 046) - tinted the same way a
   pinned post already is (rgba(89,131,146,0.06)) so it reads as "the
   standing context for everything below", not just another entry. */
.liveblog-summary-box {
  background: rgba(89, 131, 146, 0.06);
  border: 1px solid var(--palette-blue);
  padding: 20px 24px;
  margin-bottom: 20px;
  font-size: 1.05em;
  line-height: 1.6;
  color: var(--palette-black);
}
.liveblog-summary-box p { margin: 0 0 1em; }
.liveblog-summary-box p:last-child { margin-bottom: 0; }
.liveblog-summary-box img { max-width: 100%; height: auto; display: block; margin: 10px 0; }

.liveblog-post {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}

/* padding-top roughly levels the clock time with the box's own first line
   of text (its padding-top, below) rather than the box's outer edge.
   Clock first (bold, the actual time), relative underneath (muted) -
   markup order matches (item-card.php's own renderLiveblogPost()/its own
   SSE-update JS twin), block-level elements stack in that order without
   needing a layout mode of their own here. */
.liveblog-post-time {
  flex: 0 0 64px;
  text-align: right;
  padding-top: 20px;
}
.liveblog-post-clock {
  display: block;
  font-weight: 800;
  font-size: 0.95em;
  color: var(--palette-black);
  margin-bottom: 2px;
}
.liveblog-post-relative {
  font-size: 0.8em;
  color: var(--color-muted);
}

.liveblog-post-box {
  flex: 1;
  min-width: 0;
  background: #fff;
  border: 1px solid rgba(1, 22, 30, 0.14);
  padding: 20px 24px;
}
.liveblog-post--pinned .liveblog-post-box {
  border-color: var(--palette-blue);
  background: rgba(89, 131, 146, 0.06);
}

.liveblog-post-pin-badge {
  display: block;
  margin-bottom: 8px;
  text-transform: uppercase;
  font-weight: 700;
  font-size: 0.75em;
  letter-spacing: 0.02em;
  color: var(--palette-blue);
}

.liveblog-post-title {
  font-family: var(--font-headline);
  font-size: 1.3em;
  line-height: 1.3;
  font-weight: 800;
  margin: 0 0 10px;
  color: var(--palette-black);
}

.liveblog-post-body { font-size: 1.05em; line-height: 1.6; color: var(--palette-black); }
.liveblog-post-body p { margin: 0 0 1em; }
.liveblog-post-body p:last-child { margin-bottom: 0; }
.liveblog-post-body img { max-width: 100%; height: auto; display: block; margin: 10px 0; }

.liveblog-post-author {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid rgba(1, 22, 30, 0.1);
  font-size: 0.8em;
  font-weight: 700;
  color: var(--color-muted);
}

/* ── Article view (redtop's own components/article.php override) ────*/
/* .article-content-area itself needs no rule of its own - it's a direct
   child of <main> (not .showcase), so width/centering/gutter-padding
   already comes from main > * above, same masthead-matching content
   width every other page type gets. Everything in the header block below
   (title, cut, time, image) is a plain block/replaced element with no
   width rule of its own, so it just fills this box's already-constrained
   width - 100% of "the content area" is the default, not something to
   declare. */
/* Flag badges (migration 040/042) - structure only here (size, weight,
   spacing); colour is deliberately NOT set on the base class. Each flag
   gets its own modifier class keyed off flagkey
   (article-flag--{flagkey}/showcase-card-flag--{flagkey}, stamped on by
   article.php/item-card.php) - that's what a real flag's actual look
   comes from, same idea as showcase-l/showcase-xl/showcase-breaking
   already being plain classes this stylesheet - not the admin portal -
   decides the look of. A flag with no matching modifier rule yet just
   falls back to this neutral default rather than rendering unstyled. */
.article-flags { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
.article-flag {
  display: inline-block;
  padding: 4px 10px;
  font-size: 0.8em;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: var(--palette-blue);
  color: #fff;
}

/* "Breaking News" example (flagkey breaking-news, from the Flags admin's
   own slug-from-label default) - black badge / yellow text, same
   var(--palette-breaking) gradient .showcase-breaking already uses
   site-wide for an urgent-news band, applied to just .article-flag-header
   (the badge + headline, article.php) - not the whole
   .article-content-area (cut/category/time/hero image too), which read as
   a mistake rather than emphasis. Square corners (no border-radius) - this
   title's own look. Add a --{flagkey} rule like this pair for every flag
   that needs its own look; anything without one keeps the neutral default
   above. */
.article-flag--breaking-news { background: var(--palette-black); color: rgb(249, 255, 0); }
.article-flag-header--flag-breaking-news {
  background: var(--palette-breaking);
  padding: 24px;
  margin: 0 0 20px;
}

/* Same treatment as .showcase-card-category (a showcase/category-tile's
   own category label) - uppercase, bold, letter-spaced - so a category
   name reads the same way wherever it shows up on the site. */
.article-category {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-accent);
  margin: 0 0 8px;
}

.article-title {
  font-family: var(--font-headline);
  font-size: 3.75em; /* 2.1em + 15% */
  line-height: 1.2;
  font-weight: 800;
  margin: 0 0 12px;
}

/* cut is TinyMCE-authored (see item-edit.js), same trust/escaping model
   as .page-body - raw HTML, its own <p> margins reset since this block
   already carries its own. */
.article-cut {
  font-size: 1.2em;
  line-height: 1.5;
  color: var(--color-muted);
  margin: 0 0 14px;
}
.article-cut p { margin: 0; }

.article-time {
  display: block;
  font-size: 0.85em;
  color: var(--color-muted);
  margin: 0 0 20px;
}

.article-image-wrap { position: relative; margin: 0 0 24px; }
.article-image { width: 100%; height: auto; display: block; }

/* Hero video (migration 082) - same wrap/credit/caption shell as the
   image case above (.article-image-credit/-caption are reused as-is, see
   article.php), just two different fills: a plain <video> (an uploaded
   file, sized the same way .article-image is) or an aspect-ratio box
   around an <iframe> (a YouTube embed, which - unlike a <video> or <img> -
   has no intrinsic size of its own to size against). */
.article-video-wrap { position: relative; margin: 0 0 24px; }
.article-video { width: 100%; height: auto; display: block; background: black; }
.article-video-embed { position: relative; width: 100%; aspect-ratio: 16 / 9; background: black; }
.article-video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.article-image-credit {
  position: absolute;
  top: 12px;
  right: 12px;
  color: #fff;
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  /* Straight drop shadow (no blur radius) rather than the soft/diffused
     look a blur would give - just an offset copy behind the text so it
     stays legible over a bright patch of the hero image. */
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
}

.article-image-caption {
  background: var(--color-border);
  color: var(--palette-black);
  font-size: 0.85em;
  padding: 8px 12px;
}

/* A plain sibling of .article-area (not nested inside it - see
   article.php), so it isn't subject to .article-area's own width
   constraint below and instead just fills .article-content-area at 100%,
   the same as the header block above it. Used in place of the byline's
   old border-bottom so the rule reads as spanning the whole content area,
   not just the narrower reading column. */
.article-rule {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: 0 0 20px;
}

/* Overrides the shared `article { max-width: 720px; margin: 0 auto; }`
   rule above - same element, but nested here (not a direct child of
   <main> the way component-defaults/article.php's own bare <article>
   is), so this class-level rule's higher specificity wins for every
   property it actually redeclares. width: 70% of .article-content-area's
   own already-constrained/padded box (not the viewport) - explicit
   margin-left/right: 0 (not the inherited margin: 0 auto) is what makes
   it sit at the box's LEFT edge instead of centering, leaving the
   remaining 30% clear on the right. Goes to 100% under the small-screens
   breakpoint below - there's no room to spare a 30% gutter once the page
   itself is phone-width. */
.article-area {
  width: 70%;
  max-width: none;
  margin: 0;
}

/* ── Category listing ─────────────────────────────────────────────*/
/* Direct child of <main> (not .showcase) - width/centering/gutter-padding
   comes from main > * above; top spacing from the nav bar comes from
   main's own padding-top now (not a category-specific margin-top - see
   main above), so this is just the gap before the content below. */
.category-header {
  margin-bottom: 20px;
}

/* Normal body font, not the headline serif article/showcase titles use -
   a category listing's own title reads more like a section label than a
   headline. The rule sits directly under the title as its own border,
   not on .category-header as a whole, so the description (a plain sibling
   below - see .category-description) reads BELOW the rule, not inside
   the same bordered block as the title. */
.category-title {
  font-family: var(--font-body);
  font-size: 1.8em;
  font-weight: 800;
  margin: 0;
  padding-bottom: 12px;
  border-bottom: 3px solid var(--palette-blue);
}

.category-description {
  margin: 10px 0 0;
  color: var(--color-muted);
}

/* Abbey's own category.php override (component-defaults/category.php's
   shared fallback has no equivalent - it uses plain .showcase-grid at
   .showcase-grid's own auto-fill sizing, see below) - a fixed 4-across
   grid ("3 rows of 4"), not auto-fill's however-many-fit-at-220px. Narrows
   to 2 then 1 alongside the existing small-screens breakpoint further
   down, since a hard 4-up would otherwise squeeze each tile far narrower
   than any other card on the site gets. */
.category-grid { grid-template-columns: repeat(4, 1fr); }

@media (max-width: 800px) {
  .category-grid { grid-template-columns: repeat(2, 1fr); }
}

/* The no-JS fallback is a real link (see components/category.php's own
   comment, and category-infinite-scroll.js) - styled like one, centered
   under the grid it's extending, not like a button pretending to be
   something more interactive than a plain page reload actually is. */
.category-load-more {
  text-align: center;
  margin-top: 32px;
}
.category-load-more a {
  display: inline-block;
  padding: 10px 28px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  text-decoration: none;
  color: var(--color-accent);
  font-weight: 600;
  font-size: 0.9em;
}
.category-load-more a:hover { border-color: var(--color-accent); }

/* ── Pagination ────────────────────────────────────────────────────*/
.pager {
  display: flex;
  justify-content: space-between;
  margin-top: 36px;
  padding-top: 16px;
  border-top: 1px solid var(--color-border);
  font-size: 0.9em;
  font-weight: 600;
}

.pager a { text-decoration: none; color: var(--color-accent); }
.pager a:hover { text-decoration: underline; }

/* ── Small screens ─────────────────────────────────────────────────*/
@media (max-width: 560px) {
  .site-nav-toggle-btn { display: flex; }

  /* Collapsed by default, revealed by the checkbox hack (see main.php) -
     :checked lives on an input that's a sibling of .site-nav-bar (both
     direct children of <body>), not inside it, so this selector can
     reach across to it. */
  .site-nav-bar { display: none; }
  .site-nav-toggle-checkbox:checked ~ .site-nav-bar { display: block; }

  .site-nav-inner { padding: 10px 24px; }
  .site-nav-list { flex-direction: column; gap: 4px; }

  /* Stacked input/button, taller allowance for the extra row - same
     "collapsed by default, :checked reveals it" mechanism as .site-nav-bar
     above, just via max-height instead of display (see the transition on
     .site-search-bar itself). */
  .site-search-bar-inner { padding: 10px 24px; }
  .site-search-bar-inner form { flex-direction: column; gap: 8px; }
  .site-search-toggle-checkbox:checked ~ .site-search-bar { max-height: 130px; }

  /* No hover on touch - a submenu just renders open, stacked and
     indented under its parent, instead of relying on :hover/:focus-within. */
  .site-nav-sublist {
    display: flex;
    position: static;
    background: none;
    border: none;
    padding: 4px 0 4px 16px;
  }

  .showcase-section:first-of-type .showcase-grid .showcase-card:first-child { grid-column: span 1; }

  /* The fixed-width time column has no room to sit beside the box once
     the reading column itself is this narrow - stacked instead, directly
     above its own post. Clock/relative stay stacked on top of each other
     too (not side by side) - same order/emphasis as the desktop layout,
     just left-aligned instead of right. */
  .liveblog-post { flex-direction: column; gap: 4px; }
  .liveblog-post-time {
    flex: 0 0 auto;
    text-align: left;
    padding-top: 0;
  }
  .liveblog-post-clock { margin-bottom: 0; }
  .liveblog-post-box { padding: 16px; width: 100%; }

  /* Side-by-side is too cramped at phone widths - stack image above
     headline instead, same shape as an ordinary card, just bigger. Every
     feed row template (L/M/S/XL) gets the same treatment. */
  .showcase-l .showcase-card,
  .showcase-l .showcase-card-link,
  .showcase-m .showcase-card,
  .showcase-m .showcase-card-link,
  .showcase-s .showcase-card,
  .showcase-s .showcase-card-link,
  .showcase-xl .showcase-card,
  .showcase-xl .showcase-card-link {
    flex-direction: column;
    align-items: stretch;
  }
  .showcase-l .showcase-card img,
  .showcase-m .showcase-card img,
  .showcase-s .showcase-card img,
  .showcase-xl .showcase-card img {
    flex-basis: auto;
    width: 100%;
  }

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

  .showcase-xl .showcase-card h3 { font-size: 1.8em; }

  /* No 30% gutter to spare at phone widths - the reading column fills
     the same full content area the header block above it already does. */
  .article-area { width: 100%; }
}

/* ── Weather page (migration 052/053, component=7) ──────────────────*/
/* .weather-page is a plain <div>, not <article> - see weatherPage.php's
   own comment for why (the `article { max-width: 720px }` rule above
   would otherwise silently narrow it to the reading-column width). Being
   a bare child of <main>, it already gets the same width/centering
   main > * gives every other page kind - nothing here needs to set that
   itself. */
.weather-page h1 {
  font-family: var(--font-headline);
  font-size: 2.1em;
  line-height: 1.2;
  font-weight: 800;
  margin: 0 0 16px;
}

.weather-page-current {
  display: flex;
  gap: 16px;
}

.weather-page-current-box {
  width: 120px;
  height: 120px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The temp-bg-* classes themselves - _weatherTempBandClass()'s own 15
   bands (weather.php), same rgb() values as the reference CasparCG
   weather-bar script's getTempColour() that function ports (see its own
   comment). Referenced by this page's temp box below AND the forecast
   hi/lo badges further down, AND now the fuel_prices/weather showcase
   widget (item-card.php) - genuinely just never had rules defined here
   despite being applied (a pre-existing gap this template shipped with,
   not something specific to any one caller), so the real Weather page's
   own temp box has been rendering colourless this whole time until now. */
.temp-bg-m19      { background: rgb(29,70,154); }
.temp-bg-m18-m12  { background: rgb(22,116,182); }
.temp-bg-m11-m7   { background: rgb(54,138,199); }
.temp-bg-m6-m4    { background: rgb(63,163,218); }
.temp-bg-m3-0     { background: rgb(78,192,238); }
.temp-bg-1-2      { background: rgb(174,220,216); }
.temp-bg-3-6      { background: rgb(158,208,125); }
.temp-bg-7-10     { background: rgb(174,211,82); }
.temp-bg-11-14    { background: rgb(252,222,4); }
.temp-bg-15-17    { background: rgb(252,183,22); }
.temp-bg-18-21    { background: rgb(246,138,31); }
.temp-bg-22-25    { background: rgb(242,106,47); }
.temp-bg-26-29    { background: rgb(235,81,58); }
.temp-bg-30-34    { background: rgb(237,42,42); }
.temp-bg-35       { background: rgb(155,27,29); }

/* Black text, not white - same reasoning as the reference CasparCG
   script's own temp badge (weather.php's own getTempColour() port,
   see weather.php): the temp-bg-* backgrounds span dark blues through
   bright yellows to dark reds, and black-with-a-light-stroke is what the
   original relied on for legibility across all of them rather than
   choosing a text colour per band. */
.weather-page-current-temp-box {
  font-family: var(--font-headline);
  font-size: 2.4em;
  font-weight: 800;
  color: black;
  -webkit-text-stroke: 0.5px rgba(255, 255, 255, 0.5);
}

.weather-page-current-icon-box {
  background: var(--palette-beige);
}

.weather-page-current-icon-box img {
  width: 76px;
  height: 76px;
}

.weather-page-current--unavailable {
  color: var(--color-muted);
  margin-bottom: 16px;
}

.weather-page-current-observed {
  color: var(--color-muted);
  font-size: 0.85em;
  margin: 8px 0 0;
}

/* "Divs styled like a table" - display:table/table-row/table-cell on the
   same <dl>/<div>/<dt>/<dd> markup (a <dl> is the semantically correct
   element for a set of label/value pairs), not a real <table> element. */
.weather-page-details {
  display: table;
  width: 100%;
  max-width: 420px;
  margin: 24px 0 32px;
  border-collapse: collapse;
}

.weather-page-detail {
  display: table-row;
}

.weather-page-detail dt,
.weather-page-detail dd {
  display: table-cell;
  padding: 8px 12px;
  border-bottom: 1px solid var(--color-border);
  font-size: 0.92em;
}

.weather-page-detail dt {
  color: var(--color-muted);
  font-weight: 600;
  width: 40%;
}

.weather-page-detail dd {
  margin: 0;
  color: var(--color-text);
}

/* Two rows per day: the full weekday name, then Hi/Lo/Precip%/icon/UV in
   a row underneath - not the table-cell treatment .weather-page-details
   above uses, since each day's second row has a fixed, known set of
   items rather than an arbitrary label/value list, so a plain flex row
   is simpler and doesn't need column-alignment across rows the way the
   details table does. */
.weather-page-forecast-day {
  padding: 12px 0;
  border-bottom: 1px solid var(--color-border);
}

.weather-page-forecast-day:last-child {
  border-bottom: none;
}

.weather-page-forecast-day-name {
  font-weight: 700;
  margin-bottom: 8px;
}

.weather-page-forecast-day-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

/* Same "black text, band-coloured background" treatment as the current-
   conditions temp box (see above) - just a smaller pill instead of a
   120px square, since two of these now sit inline in a row of five. */
.weather-page-forecast-hi,
.weather-page-forecast-lo {
  padding: 4px 10px;
  font-weight: 700;
  font-size: 0.9em;
  color: black;
  -webkit-text-stroke: 0.5px rgba(255, 255, 255, 0.5);
}

.weather-page-forecast-precip,
.weather-page-forecast-uv {
  color: var(--color-muted);
  font-size: 0.9em;
}

.weather-page-forecast-icon img {
  width: 32px;
  height: 32px;
  display: block;
}

/* ── Fuel prices (migration 078/079/080/084/085) ──────────────────────*/
/* Same "plain <div>, not <article>" reasoning as .weather-page above -
   see fuelPricesPage.php's own comment. Three sections (title+address,
   2-col price/map, 2-col hours/facilities), a "back to overview" link at
   top and bottom - no more per-station switcher nav (removed, see that
   file's own header comment). */
.fuel-page-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-decoration: none;
  color: var(--color-accent);
  font-weight: 600;
  font-size: 0.9em;
}
.fuel-page-back:hover { text-decoration: underline; }
.fuel-page-back--bottom { margin-top: 32px; }
/* iOS-style chevron, not a full arrow glyph - a plain box rotated 45deg
   with only its bottom-left two sides bordered, currentColor so it always
   matches the link's own colour rather than needing its own. */
.fuel-page-back-chevron {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}

.fuel-page-section { margin-top: 28px; }
.fuel-page-title-section { margin-top: 16px; }

.fuel-page-title-section h1 {
  font-family: var(--font-headline);
  font-size: 2.1em;
  line-height: 1.2;
  font-weight: 800;
  margin: 0 0 4px;
}

/* Inline after the trading name, not its own paragraph - see
   fuelPricesPage.php's own header comment. */
.fuel-page-brand {
  font-family: var(--font-body);
  font-size: 0.6em;
  font-weight: 600;
  color: var(--color-muted);
}

.fuel-page-address { color: var(--color-muted); margin: 0 0 4px; }

.fuel-page-notice {
  color: var(--color-muted);
  font-style: italic;
  margin: 8px 0 0;
}

/* Price (left) / map (right) - degrades to pricing alone, full width,
   when a station has no coordinates (fuelPricesPage.php's own $hasMap
   check - the modifier class here just drops the grid down to one
   column, .fuel-page-pricing itself needs no change either way). */
.fuel-page-price-location { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; align-items: start; }
.fuel-page-price-location--no-map { grid-template-columns: 1fr; }

/* Stacked rows, one per fuel type - a hybrid of the OLD component-
   defaults version (a row per type) and the landing page's own coloured-
   box look, see fuelPricesPage.php's own comment. */
.fuel-page-pricing { display: flex; flex-direction: column; gap: 10px; }
.fuel-page-price-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.fuel-page-price-label { font-weight: 600; }

/* .fuel-price-pair-box/-petrol/-diesel/-value are the SAME classes
   fuelPricesLanding.php's own station rows use (see this file's own
   comment on that section) - reused verbatim here, just a touch larger
   (this page has a whole column to itself, not a cramped map overlay).
   No .fuel-price-pair-label inside the box on THIS page - the fuel
   type's own label already sits to its left (.fuel-page-price-label
   above), so the box only ever holds the value + an optional "as of
   HH:MM" line the landing page's own boxes never needed room for. */
.fuel-price-pair-box { padding: 10px 16px; }
.fuel-price-pair-value { font-size: 1.3em; }
.fuel-price-pair-updated { font-size: 0.65em; font-weight: 400; opacity: 0.85; }

.fuel-page-prices--unavailable { color: var(--color-muted); }

.fuel-page-map { width: 100%; height: 320px; border-radius: 6px; overflow: hidden; }

/* Hours (left) / facilities (right) - same 2-col shape as the price/map
   row above, always both columns (an empty side shows
   .fuel-page-info-empty rather than collapsing to one column, so the row
   stays visually balanced regardless of which side has data). */
.fuel-page-info { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; align-items: start; }
/* .fuel-page-section h2, not scoped to .fuel-page-info specifically - the
   Price History section below reuses the exact same heading style. */
.fuel-page-section h2 {
  font-family: var(--font-headline);
  font-size: 1.1em;
  font-weight: 700;
  margin: 0 0 10px;
}
.fuel-page-info-empty { color: var(--color-muted); margin: 0; }

.fuel-page-amenities {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
  padding: 0;
  margin: 0;
}

.fuel-page-amenities li {
  background: var(--palette-beige);
  border-radius: 20px;
  padding: 4px 12px;
  font-size: 0.82em;
}

.fuel-page-hours {
  border-collapse: collapse;
  width: 100%;
}

.fuel-page-hours th,
.fuel-page-hours td {
  text-align: left;
  padding: 4px 16px 4px 0;
  font-size: 0.9em;
  border-bottom: 1px solid var(--color-border);
}

.fuel-page-hours th {
  color: var(--color-muted);
  font-weight: 600;
}

/* Price history chart - Chart.js (CDN), see fuelPricesPage.php's own
   _fuelPriceHistoryChart() comment. Its own legend/axes/tooltips are
   drawn on the canvas itself, so there's nothing else here to style. */
.fuel-page-history-window { font-size: 0.7em; font-weight: 400; color: var(--color-muted); }
.fuel-page-history-chart-wrap { position: relative; width: 100%; height: 260px; }

@media (max-width: 640px) {
  .fuel-page-price-location,
  .fuel-page-info { grid-template-columns: 1fr; }
}

/* ── Fuel prices landing page (migration 078/079/080) ────────────────*/
/* Genuinely full-VIEWPORT-width, not just full-masthead-width - same
   breakout main > .showcase already uses just above (max-width:none +
   margin/padding:0, since main itself has no max-width of its own, only
   `main > *` does by default) - reused here rather than reinventing a
   100vw/negative-margin trick for the same result. margin-top cancels
   main's own `padding: 1em 0 60px` (see main{} above) so the map starts
   with zero gap directly under the navbar, not just flush against main's
   own inset top padding. */
main > .fuel-landing {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  margin-top: -1em;
  padding-left: 0;
  padding-right: 0;
}

/* .fuel-map is full-bleed by inheriting .fuel-landing's own unconstrained
   width above - no border-radius here either, a genuinely edge-to-edge
   map has no corner to round. A fixed height since a map has no natural
   content-driven one the way everything else on this page does.
   position:relative so .fuel-map-overlay (price boxes/pins, below) can
   sit exactly over MapLibre's own canvas via position:absolute. */
.fuel-map {
  position: relative;
  width: 100%;
  height: 890px;
  margin: 0 0 24px;
  overflow: hidden; /* clips MapLibre's own canvas - no radius to match now, just keeps its own edges tidy */
}

/* A normal-flow block ABOVE the map (fuelPricesLanding.php's own markup
   puts it before #fuel-prices-map, not nested inside it) - pushes the map
   down rather than overlaying its top edge, so it no longer needs
   position:absolute/pointer-events:none the way the price-box overlay
   still does. Solid, full-width colour band in the masthead's own
   tertiary colour (var(--color-accent) - green here, see :root above),
   INNER content lined up with the masthead's own left/right edges, same
   shape header.site-header/.site-header-inner use for their own logo/nav
   row. */
.fuel-map-title-band {
  background: var(--color-accent);
}
.fuel-map-title-band-inner {
  max-width: var(--max-width-wide);
  margin: 0 auto;
  padding: 10px var(--gutter);
}
.fuel-map-title {
  margin: 0;
  font-family: var(--font-headline);
  font-size: 2.1em;
  font-weight: 800;
  color: var(--palette-white);
}

/* The station list below the map is NOT full-bleed - back to the normal
   inset width, same recipe as .fuel-map-title-band-inner/.site-header-
   inner above (and .showcase-section's own re-inset within main >
   .showcase, the same overall pattern this whole page borrows). */
.fuel-landing-content {
  max-width: var(--max-width-wide);
  margin: 24px auto 0;
  padding: 0 var(--gutter);
}

/* .article-content-area normally gets its width for free from `main > *`
   (main.css's own default) - it no longer qualifies for that rule here
   (fuelPricesLanding.php's own no-coordinates fallback), since main >
   .fuel-landing above resets THAT direct child back to unconstrained and
   .article-content-area is now nested a level deeper, not main's own
   direct child. Reapplied explicitly so the fallback (no stations have
   coordinates to map at all) still reads as a normal inset page, not a
   stray full-bleed title with nothing to justify it. */
.fuel-landing > .article-content-area {
  max-width: var(--max-width-wide);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* Everything below is built by fuelPricesLanding.php's own inline script
   (_fuelMapScript()), not MapLibre itself - a plain dot at each station's
   real coordinate (.fuel-map-pin, a MapLibre Marker), and a price box
   positioned NEAR it by hand (.fuel-price-box, NOT a Marker - see that
   script's own comment on why: its position has to react to where every
   OTHER station's box landed, which only a real per-frame layout pass can
   do, not MapLibre's fixed-offset Marker anchoring). .fuel-map-overlay
   sits over MapLibre's own canvas but doesn't intercept its drag/zoom
   gestures (pointer-events:none) - only the boxes/pin themselves opt back
   into being clickable. */
.fuel-map-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}
.fuel-map-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.fuel-map-line {
  stroke: rgba(0,0,0,0.5);
  stroke-width: 1px;
}
.fuel-map-pin {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--palette-white);
  border: 2px solid var(--color-masthead); /* same colour as this template's own masthead */
  box-shadow: 0 0 2px rgba(0,0,0,0.6);
}
/* Exact spec: 0.6px solid rgba(0,0,0,0.5) border, rgba(255,255,255,0.2)
   background, 5px radius, 2px padding around the two price segments -
   which now need their own border-radius/gap (below) since padding means
   they no longer sit flush against this box's own edges/each other.
   position:absolute + the JS-set transform (layoutBoxes(),
   _fuelMapScript()) is what actually places it - top/left:0 here is just
   the transform's own origin. */
.fuel-price-box {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  gap: 3px;
  padding: 2px;
  pointer-events: auto;
  border: 0.6px solid rgba(0,0,0,0.5);
  background: rgba(255,255,255,0.2);
  border-radius: 5px;
  text-decoration: none;
  font-weight: 700;
  /* MapLibre GL's own stylesheet sets `font: 12px/20px "Helvetica
     Neue",...` directly on .maplibregl-map (the #fuel-prices-map
     container itself) - an explicit, not merely inherited, declaration,
     which would otherwise cascade straight down through
     .fuel-map-overlay into this box and override the site's own font
     without this. Setting it here, explicitly, on the nearest ancestor
     that actually needs real text, wins over that inherited value
     regardless of MapLibre's own specificity. */
  font-family: var(--font-body);
}
.fuel-price-box-petrol,
.fuel-price-box-diesel {
  padding: 3px 6px;
  border-radius: 3px;
  /* rem, not em - em would be relative to .fuel-price-box's OWN computed
     size, which is only safely 16px-ish because of the font-family fix
     above; rem is relative to the page root regardless, so this stays
     correct even if MapLibre's CSS ever changes what it sets on its own
     container. */
  font-size: 0.85rem;
  white-space: nowrap;
}
.fuel-price-box-petrol { background: rgba(46,125,50,0.85); color: #fff; } /* petrol (E10) - green, per spec */
.fuel-price-box-diesel { background: rgba(20,20,20,0.85); color: #fff; } /* diesel (B7) - black, per spec */

/* ── Fuel Prices insights (replaces the old flat "every station" grid) ──
   fuelPricesLanding.php's own _fuelInsightsSwitch()/_fuelInsightsPanel()
   comments explain the markup shape this targets - summary here is just
   the CSS mechanism: .fuel-insights is a flex row, wrapped, so the two
   (visually hidden but focusable) radios + their labels lay out as a
   centred switch on their own line, then each .fuel-insights-panel takes
   flex-basis:100% to force itself onto its own full-width line below -
   no extra wrapper div was needed for "centred switch, full-width columns
   underneath", which matters because the radio/label/panel elements all
   need to stay FLAT SIBLINGS of one shared parent for the plain-CSS
   :checked ~ sibling trick below to reach across from radio to panel. */
.fuel-insights { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; }

.fuel-insights-radio { position: absolute; opacity: 0; width: 1px; height: 1px; overflow: hidden; }
.fuel-insights-switch-label {
  cursor: pointer;
  padding: 8px 22px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.85em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border: 1px solid var(--color-text);
  color: var(--color-text);
  background: transparent;
}
.fuel-insights-switch-label--petrol { border-radius: 20px 0 0 20px; }
.fuel-insights-switch-label--diesel { border-radius: 0 20px 20px 0; margin-left: -1px; }
.fuel-insights-radio:checked + .fuel-insights-switch-label {
  background: var(--color-text);
  color: var(--palette-white);
}

.fuel-insights-panel {
  flex: 1 1 100%; /* forces its own full-width row below the switch, see this block's own header comment */
  display: none;
  grid-template-columns: 1fr 1fr;
  gap: 32px;
  margin-top: 28px;
}
.fuel-insights-panel--petrol { display: grid; } /* petrol radio starts :checked (see fuelPricesLanding.php) */
#fuel-insights-diesel:checked ~ .fuel-insights-panel--petrol { display: none; }
#fuel-insights-diesel:checked ~ .fuel-insights-panel--diesel { display: grid; }

.fuel-insights-col { display: flex; flex-direction: column; gap: 16px; }

.fuel-insight-card {
  background: var(--palette-beige);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 14px 16px;
}
/* Font-family deliberately left unset on every insight element below -
   the page's own default (var(--font-body) on body) is what applies, per
   "standard font everywhere except the page title itself" - .fuel-map-
   title/.article-title (this page's own title elements) are the only
   things on this page still set to var(--font-headline). */
.fuel-insight-card-label {
  font-weight: 700;
  font-size: 0.95em;
  margin-bottom: 10px;
}
.fuel-insight-empty { color: var(--color-muted); font-size: 0.9em; }

/* Groups get their own card too - same background/border as
   .fuel-insight-card above, just wrapping one or two station rows plus a
   heading/tag pair rather than one metric. */
.fuel-insight-group {
  width: 100%;
  background: var(--palette-beige);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 14px 16px;
}
.fuel-insight-group-name {
  font-weight: 700;
  font-size: 0.95em;
  margin-bottom: 10px;
}
/* Wide colour band, not just a small muted label - NOT a full edge-to-
   edge bleed though (a -16px margin, cancelling .fuel-insight-group's own
   padding exactly, read as too flush against the card's own border) - a
   -10px margin leaves a deliberate 6px of card background breathing room
   either side instead. Green for cheapest (same green as the price
   boxes' own petrol colour, so "cheapest" and "petrol" don't accidentally
   read as two different colour languages on one page), red for most
   expensive - both white text for contrast. */
.fuel-insight-group-tag {
  display: block;
  margin: 10px -10px 8px;
  padding: 6px 10px;
  font-size: 0.75em;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #fff;
}
.fuel-insight-group-tag--cheapest { background: #2e7d32; }
.fuel-insight-group-tag--priciest { background: #c62828; }

/* One station's own row - name/address on the left, both fuel prices on
   the right (see fuelPricesLanding.php's own comment on why both, not
   just whichever fuel type this insight is ranked by). */
.fuel-insight-station { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.fuel-insight-station-info { text-decoration: none; color: var(--color-text); min-width: 0; }
.fuel-insight-station-name { font-weight: 700; }
.fuel-insight-station-info:hover .fuel-insight-station-name { text-decoration: underline; text-decoration-color: var(--color-accent); }
.fuel-insight-station-address { color: var(--color-muted); font-size: 0.85em; margin-top: 2px; }
.fuel-insight-station-prices { display: flex; gap: 8px; flex-shrink: 0; }

/* Bigger sibling of the map's own .fuel-price-box - same green/black
   convention (style.css's own .fuel-price-box-petrol/-diesel, this page's
   only other price box), but with room for a label under the value this
   time, since these sit in a real content column rather than squeezed
   next to a map pin. */
.fuel-price-pair-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px 10px;
  border-radius: 5px;
  text-decoration: none;
  color: #fff;
  font-family: var(--font-body);
}
.fuel-price-pair-box-petrol { background: rgba(46,125,50,0.85); } /* petrol (E10) - green, same as the map's own box */
.fuel-price-pair-box-diesel { background: rgba(20,20,20,0.85); } /* diesel (B7) - black, same as the map's own box */
.fuel-price-pair-value { font-weight: 700; font-size: 0.95em; white-space: nowrap; }
.fuel-price-pair-label { font-size: 0.68em; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.85; }

/* A dropped-in "fuel_prices" library block on a showcase page (item-
   card.php's own _itemFuelPricesBlockHtml()) - a miniature version of the
   real Fuel Prices landing page's own per-group card, reusing
   .fuel-insight-group/-name/-tag/-station and .fuel-price-pair-box
   wholesale (above/elsewhere in this file) rather than a second visual
   language for the same "cheapest/most expensive" concept - no CSS of
   its own needed at all, unlike the weather block below (it lists both
   fuels' cheapest/priciest rows directly, no Petrol/Diesel switch, so
   there's no new markup shape here beyond what those existing classes
   already style). */

/* A dropped-in "weather" library block (item-card.php's own
   _itemWeatherBlockHtml()) - a fixed pale-blue OUTER card, independent of
   the real Weather page's own dynamic per-reading temp-band colours
   (weather.php's own _weatherTempBandClass()) applied to the location
   name/card background - a card reads better with one calm, recognisable
   "this is weather" identity regardless of the actual reading, same
   reasoning .fuel-insight-group's own fixed beige gives the fuel_prices
   block next to it. A literal colour, not var(--palette-blue) - despite
   the name, THIS template's own --palette-blue (:root, top of file) is
   actually rgb(164,0,0), a red (RedTop's masthead colour - the variable
   name is just shared with abbey's template, where it really is blue),
   so reusing it here would paint this card red instead.

   The temp BOX inside it is exactly the opposite on purpose: that one
   deliberately IS the dynamic per-reading band colour (temp-bg-* above,
   and .weather-page-current-temp-box's own comment on why black text
   with a white stroke, not a fixed colour per band, keeps it legible
   across all of them) - same layout and reasoning as that page's own
   .weather-page-current (a temp box and an icon box side by side), just
   smaller. The icon box keeps the SAME beige background that page's own
   .weather-page-current-icon-box uses rather than sitting on this card's
   pale blue - several of the icon SVGs have white/near-white cloud
   shapes only ever designed against a light-neutral background; pale
   blue swallows them almost entirely, reading as a broken icon rather
   than a cloud. */
.block-weather-card { text-decoration: none; color: inherit; display: block; }
.block-weather-body {
  width: 100%;
  background: rgb(224, 238, 245);
  border-radius: 6px;
  padding: 14px 16px;
}
.block-weather-location { font-weight: 700; font-size: 0.95em; margin-bottom: 10px; }
.block-weather-boxes { display: flex; gap: 10px; }
.block-weather-box {
  width: 64px;
  height: 64px;
  flex: none;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Same "black text, 0.5px white stroke" legibility choice as
   .weather-page-current-temp-box (above) - the temp-bg-* backgrounds
   (also above) span dark blues through bright yellows to dark reds, and
   this is what stays readable across all of them without a colour
   picked per band. */
.block-weather-temp-box {
  font-family: var(--font-body);
  font-size: 1.6em;
  font-weight: 800;
  color: black;
  -webkit-text-stroke: 0.5px rgba(255, 255, 255, 0.5);
}
.block-weather-icon-box { background: var(--palette-beige); }
/* Every property here has to be an explicit override, not just width/
   height - this <img> is also matched by the generic .showcase-card img
   rule (this file, above), which exists for regular article thumbnails
   (a 16:10 crop plus a light grey #f0f0f0 loading-placeholder background)
   and has the SAME selector specificity as this rule. Width/height alone
   winning (this rule comes later in the file) still left aspect-ratio/
   object-fit/background bleeding through from that rule - the grey
   placeholder showing through the SVG's own genuinely-transparent
   background is exactly the "white/grey box behind the icon" look that
   caused, and object-fit:cover cropping a square icon to a 16:10 box
   would have distorted it too. */
.block-weather-icon-box img {
  width: 44px;
  height: 44px;
  aspect-ratio: auto;
  object-fit: contain;
  background: transparent;
}

/* A block-kind item (weather/fuel_prices/html - _itemHtml(),
   renderItemCard()'s own comment) spans every column of whatever grid
   it's placed in, regardless of which section Template (S/M/L/XL) that
   section happens to use - "M"'s own default .showcase-grid
   (auto-fill(minmax(220px,1fr))) otherwise leaves a single block stuck
   inside just ONE auto-generated track while the others it reserved room
   for sit empty (auto-fill keeps that reserved space rather than
   collapsing it away the way auto-fit would), so the widget reads as
   narrow/off-centre instead of filling its row. 1/-1 spans first line to
   last regardless of how many tracks exist, so this is a no-op (and
   harmless) on templates like "L" that already use a single full-width
   column. */
.showcase-card--block { grid-column: 1 / -1; }

/* "L"/"XL" go further than just the grid track fixed above - they also
   turn .showcase-card itself into a flex ROW (.showcase-l/.showcase-xl
   .showcase-card, near the top of this file) for their own "image beside
   headline" hero layout. That treatment expects a .showcase-card-link
   flex item inside to claim flex:1 and stretch (that page's own comment
   explains why) - a block has no such link wrapper at all (item-card.php
   never wraps _itemHtml()'s output in one), so its raw markup becomes
   the ONE flex item instead, and with no flex:1/width of its own it just
   shrinks to its own content width, leaving empty space beside it that
   reads as "not full width" the same way the ungrouped grid-column fix
   above addresses for "M". This cancels the row-flex behaviour outright
   (a widget was never meant to sit in a layout built for an image slot
   it doesn't have) - belt-and-braces alongside width:100% on the block's
   own content itself (.fuel-insight-group, .block-weather-body - each
   one's own rule), which is what actually guarantees full width
   regardless of whatever the ancestor chain's display ends up computing
   to. */
.showcase-l .showcase-card--block,
.showcase-xl .showcase-card--block {
  display: block;
}

@media (max-width: 640px) {
  .fuel-insights-panel { grid-template-columns: 1fr; }
}

#showcase-home-page .showcase-section-prefix { font-size: 1.5em; line-height: 80%; text-transform: uppercase; font-weight: 600; }
#showcase-home-page .showcase-section-prefix::after {height: 0.3em; display: block;content:'';}

/* ── Streams (migration 071) ────────────────────────────────────────*/
/* Same "plain <div>, not <article>" reasoning as .weather-page above -
   see streamsPage.php's own comment. */
.streams-page h1 {
  font-family: var(--font-headline);
  font-size: 2.1em;
  line-height: 1.2;
  font-weight: 800;
  margin: 0 0 8px;
}

.streams-page-description {
  color: var(--color-muted);
  margin: 0 0 16px;
}

/* Sized here, not on .streams-page-player itself - the actual <video> is
   created client-side and fills this box at 100%/100% (see
   mountFreshVideo() in streamsPage.php's own script), but the LOADING
   overlay below needs something already the right size to position
   absolutely against before the video element even exists. */
.streams-page-player-wrap {
  position: relative;
  width: 100%;
  max-width: 100%;
  aspect-ratio: 16 / 9;
  background: black;
  margin-bottom: 24px;
}

.streams-page-player {
  display: block;
  width: 100%;
  height: 100%;
}

/* Shown until the video element's own canplay event fires - see
   streamsPage.php's own comment on why native controls stay off (and this
   stays visible) until then. */
.streams-page-player-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--palette-white);
  font-size: 0.95em;
}

/* A stream with no feed configured yet (migration 074) - same box
   dimensions as .streams-page-player-wrap above (self-contained rather
   than sharing .streams-page-player's own rules, since there's no video/
   loading state to size around here), just a message instead. */
.streams-page-player--unavailable {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 100%;
  aspect-ratio: 16 / 9;
  margin-bottom: 24px;
  background: var(--palette-beige);
  color: var(--color-muted);
  text-align: center;
  padding: 24px;
  box-sizing: border-box;
}

.streams-page-feeds {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: -12px 0 24px;
}

.streams-page-feed-btn {
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 0.9em;
  font-weight: 700;
  padding: 6px 14px;
  border-radius: 999px;
  cursor: pointer;
}

.streams-page-feed-btn--active {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--palette-white);
}

.streams-page-thumbnails {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.streams-page-thumbnail {
  display: block;
  width: 200px;
  color: inherit;
  text-decoration: none;
}

.streams-page-thumbnail-image-wrap {
  position: relative;
  display: block;
}

.streams-page-thumbnail img,
.streams-page-thumbnail-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--palette-beige);
  color: var(--palette-teal);
  font-size: 2em;
}

/* The one matching whichever stream is currently loaded in the player
   above - a highlighted outline plus the badge below, rather than
   omitted from the row entirely (streamsPage.php's own comment on why:
   the row should always show the full picture, even when there's only
   one stream to show at all). */
.streams-page-thumbnail--current .streams-page-thumbnail-image-wrap {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
}

.streams-page-thumbnail-badge {
  position: absolute;
  left: 8px;
  bottom: 8px;
  background: var(--color-accent);
  color: var(--palette-white);
  font-size: 0.75em;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  padding: 2px 8px;
  border-radius: 999px;
}

.streams-page-thumbnail-name {
  display: block;
  margin-top: 6px;
  font-weight: 700;
}

.streams-page-thumbnail:hover .streams-page-thumbnail-name {
  color: var(--color-accent);
}

/* ── Tags (migration 049/056) ─────────────────────────────────────*/
/* White background, --color-accent text+border - the same blue an
   article's own .article-category already uses, just as a genuine pill
   (background/border/radius) rather than plain text, since no pill style
   existed anywhere to copy before this. Shared by the article-bottom tag
   list, the showcase "Tags" section (.showcase-tags), and anywhere else
   a tag renders, so a tag always looks the same wherever it shows up. */
.tag-pill {
  display: inline-block;
  padding: 4px 12px;
  border: 1px solid var(--color-accent);
  border-radius: 999px;
  background: var(--palette-white);
  color: var(--color-accent);
  font-size: 0.85em;
  font-weight: 600;
  text-decoration: none;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

a.tag-pill:hover {
  background: var(--color-accent);
  color: var(--palette-white);
}

.article-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--color-border);
}

/* Migration 076's "Report an issue" link - deliberately understated
   (muted, no border-top of its own) rather than styled like another tag
   pill: it's a low-traffic utility link, not something that should
   compete with the tags row above it for attention. */
.article-clarification-link {
  margin-top: 12px;
  font-size: 0.9em;
}
.article-clarification-link a {
  color: var(--color-muted);
  text-decoration: underline;
}

.showcase-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* ── Contact form (migration 062/076) - unstyled until now, hence the
   whole block landing at once rather than growing alongside the feature
   itself the way most of this file did. Narrower than main > *'s own
   masthead width (a single-column form of short text inputs reads oddly
   stretched across ~800px+) but not as narrow as .article-area's 70% -
   a fixed, comfortable line length instead. ───────────────────────── */
.contact-page {
  max-width: 560px;
}

.contact-page h1 {
  font-family: var(--font-headline);
  font-size: 2.1em;
  line-height: 1.2;
  font-weight: 800;
  margin: 0 0 24px;
}

.contact-page-lede {
  color: var(--color-muted);
  line-height: 1.6;
  margin: 0;
}

.contact-form-error {
  background: rgba(200, 40, 40, 0.08);
  border: 1px solid rgba(200, 40, 40, 0.35);
  border-radius: 4px;
  padding: 10px 14px;
  color: rgb(160, 30, 30);
  margin: 0 0 20px;
}

/* "Regarding: <article>" - migration 076, only present when this page
   was reached via an article's own "Report an issue" link, see
   contact.php's own comment. */
.contact-form-reference {
  background: var(--palette-beige);
  border-radius: 4px;
  padding: 10px 14px;
  margin: 0 0 20px;
  color: var(--palette-black);
  font-size: 0.95em;
}
.contact-form-reference a {
  color: var(--color-accent);
}

/* Admin > Titles > Contact tab (migration 088) - same beige callout
   treatment as .contact-form-reference above, reused rather than a
   second visual language for "a highlighted info box on this page".
   Shown below both page states (the form and the post-submit "thanks"
   block, see contact.php's own _contactPageInfoHtml()) - top margin,
   not bottom, since it's now the LAST thing in .contact-page either way,
   separating it from whichever content sits above it instead. */
.contact-page-info {
  background: var(--palette-beige);
  border-radius: 4px;
  padding: 14px 16px;
  margin: 24px 0 0;
  color: var(--palette-black);
  font-size: 0.95em;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Label ("Address"/"Phone"/"Email") on its own bold line, the actual
   value on the next - a column-direction flex item, not just a loose
   wrapping div, per row. */
.contact-page-info-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.contact-page-info-label {
  font-weight: 700;
}
.contact-page-info-row address {
  font-style: normal;
}
/* Underlined only on hover, not permanently - same convention as
   .pager a/.fuel-page-back elsewhere in this file, rather than the bare
   browser-default underline a plain `a { color: inherit; }` (top of
   this file) would otherwise leave in place. */
.contact-page-info a {
  color: var(--color-accent);
  text-decoration: none;
}
.contact-page-info a:hover {
  text-decoration: underline;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.contact-form-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.contact-form-field label {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.9em;
}
.contact-form-optional {
  font-weight: 400;
  color: var(--color-muted);
}

/* Same padding/border/radius as .site-search-input's own rule above -
   one input style across the whole site rather than a second, near-
   identical one just for this form. */
.contact-form-field input[type="text"],
.contact-form-field input[type="email"],
.contact-form-field select,
.contact-form-field textarea {
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--palette-white);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 1em;
}
.contact-form-field textarea {
  resize: vertical;
  min-height: 140px;
}
.contact-form-field input:focus,
.contact-form-field select:focus,
.contact-form-field textarea:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
  border-color: var(--color-accent);
}

/* Replaces the <select> above when category is locked (migration 076) -
   same box as a real input so the field doesn't visibly jump size
   depending on which state rendered, just not interactive. */
.contact-form-static {
  padding: 10px 12px;
  border-radius: 4px;
  background: var(--palette-beige);
  color: var(--palette-black);
  font-weight: 600;
}

.contact-form-submit {
  align-self: flex-start;
  padding: 10px 28px;
  border: none;
  border-radius: 4px;
  background: var(--color-accent);
  color: #fff;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1em;
  cursor: pointer;
}
.contact-form-submit:hover {
  opacity: 0.9;
}