
/* Cardiff shared masthead + anchor offsets */
:root{
  --hdr-bg:#3d2810;
  --hdr-rule:rgba(255,215,140,0.09);
  --hdr-text:#f5ead8;
  --hdr-dim:rgba(245,234,216,0.82);
  --hdr-dimmer:rgba(245,234,216,0.65);
  --hdr-nav:rgba(245,234,216,0.82);
}
html{scroll-behavior:smooth;}
a:hover{text-decoration:underline;}
[hidden]{display:none !important;}
[id]{scroll-margin-top:var(--masthead-offset,150px);}
.cardiff-masthead{background:var(--hdr-bg)!important;border-bottom:3px solid var(--red)!important;position:sticky!important;top:0!important;z-index:100!important;}
/* No rule under the identity bar. The old hairline was cream at 9%, which over
   the masthead brown came out light enough to read as a pale strip above the
   nav rather than a separator. The masthead is one uniform brown from the top
   of the wordmark down to the red strip. */
.mh-identity{display:grid!important;grid-template-columns:1fr auto 1fr!important;align-items:center!important;padding:0.75rem 1.5rem!important;gap:1rem!important;border-bottom:0!important;min-height:72px!important;}
.mh-left-info{font-family:var(--mono)!important;font-size:0.75rem!important;letter-spacing:0.07em!important;color:var(--hdr-dim)!important;line-height:1.75!important;white-space:nowrap!important;}
.mh-brand{text-align:center!important;}
.mh-brand-link{text-decoration:none!important;display:inline-block!important;}
.mh-brand-name{font-family:var(--sans)!important;font-size:clamp(2rem,5vw,4rem)!important;font-weight:800!important;letter-spacing:-0.02em!important;line-height:1!important;color:var(--hdr-text)!important;text-shadow:0 1px 4px rgba(0,0,0,0.35)!important;white-space:nowrap!important;}
.mh-brand-name .comma{color:var(--hdr-dimmer)!important;font-weight:300!important;}
.mh-brand-sub{font-family:var(--mono)!important;font-size:0.62rem!important;letter-spacing:0.12em!important;text-transform:uppercase!important;color:var(--hdr-dimmer)!important;margin-top:0.22rem!important;display:block!important;}
.mh-right-info{font-family:var(--mono)!important;font-size:0.75rem!important;letter-spacing:0.06em!important;color:var(--hdr-dim)!important;text-align:right!important;line-height:1.75!important;white-space:nowrap!important;}
.mh-right-info .wx-hi{color:rgba(245,234,216,0.88)!important;font-weight:500!important;font-size:0.88rem!important;}
.mh-wx-link{text-decoration:none!important;color:inherit!important;}
/* Invisible button: looks exactly like the masthead text, whole label clickable. */
.mh-left-btn{appearance:none!important;-webkit-appearance:none!important;background:none!important;border:0!important;margin:0!important;padding:0!important;font:inherit!important;color:inherit!important;letter-spacing:inherit!important;line-height:inherit!important;text-align:left!important;cursor:pointer!important;}
.mh-mobile-pills{display:none!important;}
/* Navigation strip. It sits inside the sticky masthead, above the
   announcement strip, so the nav travels down the page with everything else.

   Masthead brown, the same value as the identity bar above it, so the two
   read as one block. Nothing divides them: no border on the nav, and none on
   .mh-identity either. No bottom border either, because the red strip is
   already sitting there doing that job.

   Hover draws the red underline out from the centre. The page you are on
   already has it drawn. Transform only, so nothing reflows, and it holds
   still for a reader who has asked for reduced motion.

   The underline sits 8px up from the bottom, not on it. Flush against the
   bottom edge it butted straight onto the red announcement strip and the two
   reds ran together into one band. Lifted, it reads as an underline beneath
   the word, with brown either side of it. That is what sets the bar at 46px
   rather than the 44px tap target floor: the two extra pixels buy the gap. */
.mh-tabs{display:flex!important;overflow-x:auto!important;scrollbar-width:none!important;background:var(--hdr-bg)!important;border-top:0!important;border-bottom:0!important;}
.mh-tabs::-webkit-scrollbar{display:none!important;}
.mh-tabs a{font-family:var(--sans)!important;font-size:14px!important;font-weight:600!important;letter-spacing:0.01em!important;text-transform:none!important;color:var(--hdr-nav)!important;text-decoration:none!important;padding:11px 13px!important;border-right:0!important;border-bottom:0!important;white-space:nowrap!important;flex-shrink:0!important;transition:color 160ms ease!important;position:relative!important;scroll-snap-align:center!important;}

/* The underline sweeps. It grows in from the left edge of the word on the way
   in, and on the way out it does not reverse: the origin flips to the right
   and it runs off that end instead, so the bar reads as something passing
   under the word rather than a bar being switched on and off. Left to right
   is west to east, the same direction as everything else here.

   The inset matches the horizontal padding above, so the bar is exactly as
   wide as the label. Change one and change the other. */
.mh-tabs a::after{content:""!important;position:absolute!important;left:13px!important;right:13px!important;bottom:8px!important;height:3px!important;border-radius:2px!important;background:var(--red)!important;transform:scaleX(0)!important;transform-origin:right center!important;transition:transform 200ms cubic-bezier(.4,0,.6,1)!important;}
.mh-tabs a:hover,.mh-tabs a:focus-visible{color:var(--hdr-text)!important;background:transparent!important;}
.mh-tabs a:hover::after,.mh-tabs a:focus-visible::after{transform:scaleX(1)!important;transform-origin:left center!important;transition-duration:240ms!important;}
.mh-tabs a.active{color:var(--hdr-text)!important;}
/* The current page keeps its bar up. Origin left so a hover on top of it does
   not make it jump ends. */
.mh-tabs a.active::after{transform:scaleX(1)!important;transform-origin:left center!important;}
@media(prefers-reduced-motion:reduce){
  .mh-tabs a,.mh-tabs a::after{transition:none!important;}
}
.announce-strip{background:var(--red)!important;color:#fff!important;height:26px!important;min-height:26px!important;display:flex!important;align-items:center!important;overflow:hidden!important;flex-shrink:0!important;}
.announce-strip-scroll{flex:1!important;overflow:hidden!important;position:relative!important;padding:0 1rem!important;}
.announce-strip-text{display:inline-block!important;white-space:nowrap!important;font-family:var(--mono)!important;font-size:0.63rem!important;letter-spacing:0.05em!important;animation:none!important;padding-left:0!important;}
@keyframes marquee{0%{transform:translateX(0);}100%{transform:translateX(-100%);}}
/* Below ~800px the three-column masthead gets too tight for the watershed/gauge
   lines (which never wrap), so hide them and keep the brand pinned to the center
   track. (Below 600px the single-column mobile layout below takes over.) */
@media(min-width:601px) and (max-width:800px){
  .mh-left-info,.mh-right-info{display:none!important;}
  .mh-brand{grid-column:2!important;}
}
@media(max-width:600px){
  .mh-identity{grid-template-columns:1fr!important;justify-items:center!important;gap:0.25rem!important;padding:0.6rem 1rem 0!important;min-height:unset!important;}
  .mh-left-info,.mh-right-info{display:none!important;}
  .mh-brand-name{font-size:clamp(1.8rem,8vw,2.8rem)!important;}
  .mh-mobile-pills{display:flex!important;gap:8px!important;width:100%!important;padding:8px 0 10px!important;}
  .mh-pill{flex:1!important;min-height:44px!important;display:flex!important;align-items:center!important;justify-content:center!important;background:rgba(255,255,255,0.12)!important;color:#fff!important;padding:8px 10px!important;border-radius:8px!important;font-size:12px!important;font-weight:600!important;text-decoration:none!important;text-align:center!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}
  .mh-pill:hover{background:rgba(255,255,255,0.2)!important;}
  /* Seven items do not fit 390px. Five did, which is why this rule used to
     divide the width evenly between them with flex:1 1 0 and no scrolling.
     Home, Calendar and About went in and Field Guide got its full name, and
     the row measures 509px at 14px, so it scrolls now. Measured, not guessed,
     and the same 509px on all twenty seven pages.

     Shrinking to fit was the alternative and it is the wrong trade. It would
     have meant 12px labels or clipped ones, and readers here skew older.
     A strip that scrolls is a strip that scrolls; a nav nobody can read is
     broken.

     Two things make the scroll legible rather than a trap. Whichever tab the
     right edge lands on is cut through rather than ending flush, which is the
     thing that tells a thumb there is more. And centerActiveTab() in
     fivemile-common.js scrolls the current page's tab into the middle on load
     and on resize, so a reader who lands on Heritage sees Almanac and About
     either side of it rather than a strip that looks like it stops at Field
     Guide.

     Known and accepted: on the homepage, where Home is the active tab and the
     strip sits at scrollLeft 0, About is off the right edge until the reader
     scrolls. Seven items do not fit 375px at a size these readers can read,
     and that is the cost of the seventh. If it turns out to matter, the fix
     is to let the strip wrap to two rows on a phone, not to shrink the type.
     See DECISIONS.md 38. */
  .mh-tabs a{padding:11px 9px 14px!important;font-size:14px!important;flex:0 0 auto!important;}
  .mh-tabs a::after{left:9px!important;right:9px!important;}
  .mh-tabs{padding-inline:0!important;}
}

/* ---------------------------------------------------------------------------
   Wordmark

   The masthead wordmark is the one place on the site that uses a serif. This
   is a deliberate exception to the no serif rule in CLAUDE.md, not an
   oversight. See DECISIONS.md section 10.

   The reason is the letter I. In a sans at this size, FIVEMILE reads as two
   bare vertical strokes sitting inside the word, and the eye has to work out
   whether they are I or L or part of the M. A serifed I has crossbars and
   settles it.

   Changing the face is one edit here. Body and UI type do not change.
   --------------------------------------------------------------------------- */
:root{--wordmark:"Playfair Display",Georgia,"Times New Roman",serif;}

.mh-brand-name{
  font-family:var(--wordmark)!important;
  font-weight:800!important;
  /* Up from clamp(2rem,5vw,4rem). Two reasons. The six middle letters are set
     at 0.9em by the arc below, so the word reads smaller than the old flat
     setting at the same base, and Playfair runs small for its size next to
     the sans it replaced. Set here rather than in the twelve page style
     blocks, so the nameplate is one size everywhere. */
  font-size:clamp(2.2rem,5.6vw,4.5rem)!important;
  /* Opened up. The old value was -0.02em, which packed the letters together
     and made the two I strokes worse. 0.075em fixed that, 0.095em gives the
     word the air the taller end letters below need. */
  letter-spacing:0.095em!important;
  /* letter-spacing adds a trailing gap after the final E, which pulls a
     centred word visually left. This puts it back on centre. */
  text-indent:0.095em!important;
  /* The two big letters hang below the line the other six sit on, and the
     intro clips this box to run the sheen across it. Without the bottom
     padding it would cut their feet off for the first second of a new
     reader's first visit.

     The negative margin hands most of that space back. Not all of it: the
     0.11em it keeps is the clearance the hanging F and E need above the town
     line, which without this they sit directly on top of. Both values are in
     em, so they follow the wordmark up and down with its size. */
  padding:0 0 0.16em!important;
  margin:0 0 -0.05em!important;

  /* The three numbers the arc is built from. Override these, not the rules
     below. --fm-cap is the cap height of Playfair Display as a fraction of
     its em, measured: 0.71 for every letter in FIVEMILE, and not one of them
     descends. That is what makes the offset below exact rather than eyeballed
     and is worth rechecking if the wordmark face ever changes. */
  --fm-end:1.1!important;
  --fm-mid:0.9!important;
  --fm-cap:0.71!important;
}

/* The arc. F and E, the two ends of the word, are set larger than the six
   letters between them, and all eight are hung from a common cap line rather
   than stood on a common baseline. The top edge of FIVEMILE runs dead flat
   and the two big letters drop below the others. This is the oldest trick on
   a newspaper nameplate and it is the whole reason the wordmark is a serif.

   Hanging, not rising, is the point. Standing them on a shared baseline was
   the first attempt and it pushed the ends up into the masthead padding,
   which read as a wobble rather than a nameplate.

   It hangs on the per letter spans that fivemile-intro.js writes. That split
   runs on every load, not only when the intro plays, so this holds for a
   returning reader too. With JavaScript off there are no spans, the word
   paints flat at one size, and nothing is broken. */
.mh-brand-name .fm-let{
  font-size:calc(var(--fm-mid) * 1em)!important;
  line-height:1!important;
}
/* Dropped by the exact difference between the two cap heights, so the tops
   land flush. The divide by --fm-end is the unit conversion: em inside this
   rule is the big letter's own size, not the wordmark's. */
.mh-brand-name .fm-let:first-child,
.mh-brand-name .fm-let:last-child{
  font-size:calc(var(--fm-end) * 1em)!important;
  position:relative!important;
  top:calc(var(--fm-cap) * (var(--fm-end) - var(--fm-mid)) / var(--fm-end) * 1em)!important;
}

@media(max-width:600px){
  /* Slightly tighter on a phone so the wordmark keeps its margins at 390px,
     and the arc comes down with it. Eight letters at 8vw is already close to
     the edge and the ends cannot afford the full drop. The offset follows
     from these two on its own. */
  .mh-brand-name{
    font-size:clamp(2rem,10vw,3.2rem)!important;
    letter-spacing:0.07em!important;text-indent:0.07em!important;
    --fm-end:1.06!important;
    --fm-mid:0.92!important;
  }
}

/* Nav is seven items. They center on desktop, and every one of them clears
   the 44px tap target minimum on every width.

   Centred with auto margins on the two end tabs rather than with
   justify-content:center, and that is not a style preference. This strip
   scrolls now that it holds seven items, and a centred flex row that
   overflows its scroll container overflows it at both ends. scrollLeft cannot
   go below zero, so the leading overflow is unreachable: on a phone, Home
   would sit off the left edge with no way to scroll back to it. Auto margins
   do the same centring when there is free space and resolve to zero when
   there is not, so the row falls back to starting at Home on its own.

   Physical left and right rather than the logical properties. The site is
   LTR only and these are the oldest browsers on the site's oldest phones. */
.mh-tabs{justify-content:flex-start!important;}
.mh-tabs a:first-child{margin-left:auto!important;}
.mh-tabs a:last-child{margin-right:auto!important;}
/* 46px, not 44. The two pixels over the tap target floor are the clearance
   the underline needs to stay off the announcement strip. The label sits a
   little high in the box for the same reason. */
.mh-tabs a{min-height:46px!important;display:flex!important;align-items:center!important;justify-content:center!important;padding-bottom:14px!important;}

/* ---------------------------------------------------------------------------
   Masthead intro

   Plays once a day. The head gate on each page sets data-intro, and it only
   ever sets "pending" for someone who has not seen it today and has not asked
   for reduced motion at the OS level.

   Every number below is set from STRIKE_MS in fivemile-intro.js and the two
   files have to agree. The order to walk them in is written at the top of
   that file. See DECISIONS.md 16.

   Opacity and transform only. Nothing here changes the size or position of any
   box, so the page below the masthead never moves and no text reflows.

   Everything hidden below is hidden only under [data-intro="pending"], which
   nothing but JavaScript can set. With JavaScript off, the attribute is absent
   and the masthead paints normally.
   --------------------------------------------------------------------------- */

:root[data-intro="pending"] .mh-brand-name,
:root[data-intro="pending"] .mh-brand-sub{opacity:0!important;}

:root[data-intro="play"] .mh-brand-name,
:root[data-intro="play"] .mh-brand-sub{opacity:1!important;}

.fm-let,.fm-town,.fm-sep{display:inline-block!important;}
.fm-space{display:inline-block!important;}

/* The strike. A letter arrives a shade oversized and a hair high, then slams
   down onto the line and stops dead. Two things do the work: the ink is at
   full weight less than halfway through, so it reads as struck rather than
   faded up, and there is no spring on the landing. A typebar hitting paper
   does not bounce back.

   This replaced a drop-in with an overshoot curve and a light sweeping across
   the wordmark, which was a film title sequence. This is a newspaper. */
@keyframes fm-strike{
  from{opacity:0;transform:translateY(-0.05em) scale(1.3);}
  55%{opacity:1;}
  to{opacity:1;transform:translateY(0) scale(1);}
}
@keyframes fm-town-in{
  from{opacity:0;transform:translateY(4px);}
  to{opacity:1;transform:translateY(0);}
}

/* Struck left to right, west to east. The delay in milliseconds comes from a
   --d custom property that fivemile-intro.js writes on each span, so any
   length of wordmark works without one rule each.

   --d is not a flat multiple of the index. It carries a small fixed wobble,
   because a machine typing at a perfectly even rate is a metronome and a
   person at a keyboard is not. The wobble is deterministic, so the intro is
   the same every time rather than different on every load. */
/* The fill mode is "backwards" and there is no will-change, and both of those
   are the fix for a flick at the end. Read this before changing either.

   A letter that is mid-transform sits on its own compositing layer and its
   text is drawn with grayscale antialiasing. A letter with no transform is
   drawn into the page with subpixel antialiasing. The two are not the same
   pixels, so whenever an element crosses between them the text resharpens.
   Nothing moves, and it still reads as a shake.

   With "both" the letters held their final frame, transform and layer and all,
   until finish() flipped data-intro to "done" at RUN_MS. That is 320ms after
   the last town has stopped, so all eleven spans dropped their layer in the
   same frame, in silence, after the nameplate had visibly come to rest. One
   synchronised resharpen across the whole wordmark is exactly the thing you
   notice.

   "backwards" still holds the "from" state through the delay, which is what
   keeps a letter hidden until it is struck. What it does not do is hold the
   "to" state afterwards, so each letter drops its layer at the end of its own
   190ms, staggered, in the same frame it stops moving. A resharpen underneath
   a letter that is still landing is not visible. By the time the towns are
   done every span is already in its resting state and finish() has nothing
   left to change.

   will-change had to go with it. will-change:transform promotes a layer
   whether or not a transform is currently applied, so leaving it here would
   have kept all eleven layers alive until RUN_MS and put the synchronised
   flick straight back. It was buying a first-frame hint on eight short
   animations over eight small spans, which is not worth a visible artefact. */
:root[data-intro="play"] .fm-let{
  animation:fm-strike 190ms cubic-bezier(.15,.85,.25,1) backwards!important;
  animation-delay:calc(var(--d,0) * 1ms)!important;
}

/* Then the towns come up underneath, left to right. That is west to east,
   which is the locked order. See DECISIONS.md section 1. Same hard landing as
   the letters, so the line below does not float in under a struck wordmark.

   1240ms is where the wordmark is finished: the eighth letter is struck at
   7 x 135ms plus at most 20ms of wobble and takes 190ms to land, which is
   1155ms. The gap after it is the beat a nameplate needs before the towns
   arrive, and it is the reason the whole thing reads as two movements rather
   than one long one.

   The separator leads its own town by 30ms so the dot is already sitting
   there when the name lands on it, rather than the two appearing together and
   reading as one wider word. */
:root[data-intro="play"] .fm-town{
  animation:fm-town-in 300ms cubic-bezier(.15,.85,.25,1) backwards!important;
  animation-delay:calc(1240ms + var(--i,0) * 170ms)!important;
}
:root[data-intro="play"] .fm-sep{
  animation:fm-town-in 300ms cubic-bezier(.15,.85,.25,1) backwards!important;
  animation-delay:calc(1210ms + var(--i,0) * 170ms)!important;
}

/* The head gate already prevents the intro from being set to pending when
   reduced motion is requested. This is the second line of defence, for the
   case where the setting is turned on while a page is open. */
@media(prefers-reduced-motion:reduce){
  :root[data-intro] .mh-brand-name,
  :root[data-intro] .mh-brand-sub{opacity:1!important;}
  :root[data-intro="play"] .fm-let,
  :root[data-intro="play"] .fm-town,
  :root[data-intro="play"] .fm-sep{
    animation:none!important;opacity:1!important;transform:none!important;
  }
}

/* Shared infobox treatment */
.info-box[data-kicker]{
  --ibox-pad-x:1.25rem;
  padding-top:2.2rem !important;
}
.info-box[data-kicker]::before{
  content:attr(data-kicker);
  position:absolute;
  top:0.58rem;
  left:var(--ibox-pad-x);
  right:var(--ibox-pad-x);
  font-family:var(--mono);
  font-size:0.58rem;
  letter-spacing:0.14em;
  text-transform:uppercase;
  color:var(--red);
  line-height:1.3;
  padding-left:0.95rem;
  background:linear-gradient(var(--red),var(--red)) left center/8px 2px no-repeat;
}

/* Uniform Cardiff-red top bar on every infobox/card, site-wide.
   Uses ::after so the dark accent bands keep their ::before texture.
   overflow:hidden clips the bar flat to the rounded corners, so every
   card matches regardless of which page defined it. */
.card,.band,.photo-shell,.news-shell,.quote-card,
.hero-copy,
.notice-card,
/* .alm-card and .hero-box came off this list when the almanac moved onto the
   card system. Those two classes no longer exist anywhere in the repo, and the
   five almanac-family pages take their card treatment from
   fivemile-cards.css, which draws no red top bar. */
.feature-card,.lane-card,.footer-band,.quick-card,
.vision-card,
.order-tile,
.aside-card,.texture-card,.cat-card,.board-shell,.post-shell,
.recipe-list-shell,.recipe-detail,.sub-card,.tip-band,
.role-card,.letter-card,
.side,.scanner,.priority,.announce-card,.cn-card,
.mobile-status-card,.alert-band,
.cta-band,.notify-card,.donate-band,
.cardiff-app-desk{
  position:relative;
  overflow:hidden;
}
.card::after,.band::after,.photo-shell::after,.news-shell::after,.quote-card::after,
.hero-copy::after,
.notice-card::after,
.alm-card::after,.hero-box::after,
.feature-card::after,.lane-card::after,.footer-band::after,.quick-card::after,
.vision-card::after,
.order-tile::after,
.aside-card::after,.texture-card::after,.cat-card::after,.board-shell::after,.post-shell::after,
.recipe-list-shell::after,.recipe-detail::after,.sub-card::after,.tip-band::after,
.role-card::after,.letter-card::after,
.side::after,.scanner::after,.priority::after,.announce-card::after,.cn-card::after,
.mobile-status-card::after,.alert-band::after,
.cta-band::after,.notify-card::after,.donate-band::after,
.cardiff-app-desk::after{
  content:"";
  position:absolute;
  top:0;
  left:0;
  right:0;
  height:3px;
  background:var(--red);
  pointer-events:none;
  z-index:1;
}
.site-kicker,
.site-card .site-kicker,
.section-kicker,
.hero-kicker,
.mini-kicker,
.list-kicker,
.detail-kicker,
.contact-note{
  font-family:var(--mono) !important;
  font-size:0.58rem !important;
  letter-spacing:0.14em !important;
  text-transform:uppercase !important;
  color:var(--red) !important;
  display:flex !important;
  align-items:center !important;
  gap:0.35rem !important;
  padding-bottom:0 !important;
  margin-bottom:0.35rem !important;
  border-bottom:none !important;
}
.list-kicker,
.detail-kicker{
  padding-bottom:0 !important;
  margin-bottom:0 !important;
  border-bottom:none !important;
}
.site-kicker::before,
.site-card .site-kicker::before,
.section-kicker::before,
.hero-kicker::before,
.mini-kicker::before,
.list-kicker::before,
.detail-kicker::before,
.contact-note::before{
  content:"" !important;
  width:8px !important;
  height:2px !important;
  border-radius:999px !important;
  background:var(--red) !important;
  display:inline-block !important;
  flex-shrink:0;
}
.site-card .card-title,
.photo-shell .photo-title,
.hero-panel .card-title,
.quick-card .quick-title,
.section-card .card-title,
.plain-card .card-title,
.project-card .card-title,
.fact-card .card-title,
.step-card .card-title,
.watch-card .card-title,
.card .card-title,
.footer-band .footer-title,
.info-box[data-kicker] > :is(.role-name,.vision-title,.cat-name,.sub-title,.contact-body,.tip-title){
  display:block;
  grid-column:1 / -1;
  width:100%;
  margin-top:0 !important;
  padding-bottom:0.5rem;
  margin-bottom:0.45rem !important;
  border-bottom:1px solid var(--rule);
}
.hero-card .hero-title,
.lead-card .lead-title{
  display:block;
  margin-top:0 !important;
  padding-bottom:0.5rem;
  margin-bottom:0.45rem !important;
  border-bottom:1px solid var(--rule);
}
.list-head,
.detail-head{
  padding-bottom:0;
  margin-bottom:0.5rem !important;
  border-bottom:none;
}
.card-title{
  font-family:var(--sans) !important;
  font-size:1rem !important;
  font-weight:800 !important;
  color:var(--ink,#1c1208) !important;
  letter-spacing:-0.01em !important;
}
.section-rule,.rule{display:flex;align-items:center;gap:.75rem;margin:40px 0;box-shadow:none;}
.section-rule h2,.rule h2{font-family:var(--mono);font-size:.62rem;font-weight:500;letter-spacing:.18em;text-transform:uppercase;color:var(--ink3);white-space:nowrap;}
.section-rule hr,.rule hr{flex:1;border:none;border-top:1px solid #DDCFB8;box-shadow:none;}

/* Shared inline link treatment for body copy */
:where(
  .sub,
  .hero-sub,
  .card-body,
  .tiny,
  .list-copy,
  .quote-text,
  .featured-note,
  .photo-copy,
  .mini-copy,
  .mini-value,
  .meta-copy,
  .body-copy,
  .story-body,
  .story-source,
  .resources-copy,
  .holiday-copy,
  .note-box,
  .letter-body p,
  .letter-body li,
  .today-list .side-val,
  .today-list .side-sub
) a:not(.btn):not(.site-link):not(.cta-btn):not(.cs-btn):not(.sub-btn):not(.donate-btn):not(.donate-btn-primary):not(.donate-btn-ghost):not(.icon-btn){
  color:var(--red) !important;
  font-weight:700 !important;
  text-decoration:none !important;
  text-underline-offset:0.12em;
}

:where(
  .sub,
  .hero-sub,
  .card-body,
  .tiny,
  .list-copy,
  .quote-text,
  .featured-note,
  .photo-copy,
  .mini-copy,
  .mini-value,
  .meta-copy,
  .body-copy,
  .story-body,
  .story-source,
  .resources-copy,
  .holiday-copy,
  .note-box,
  .letter-body p,
  .letter-body li,
  .today-list .side-val,
  .today-list .side-sub
) a:not(.btn):not(.site-link):not(.cta-btn):not(.cs-btn):not(.sub-btn):not(.donate-btn):not(.donate-btn-primary):not(.donate-btn-ghost):not(.icon-btn):hover{
  color:var(--red-mid) !important;
  text-decoration:underline !important;
}

:where(
  .sub,
  .hero-sub,
  .card-body,
  .tiny,
  .list-copy,
  .quote-text,
  .featured-note,
  .photo-copy,
  .mini-copy,
  .mini-value,
  .meta-copy,
  .body-copy,
  .story-body,
  .story-source,
  .resources-copy,
  .holiday-copy,
  .note-box,
  .letter-body p,
  .letter-body li,
  .today-list .side-val,
  .today-list .side-sub
) a:not(.btn):not(.site-link):not(.cta-btn):not(.cs-btn):not(.sub-btn):not(.donate-btn):not(.donate-btn-primary):not(.donate-btn-ghost):not(.icon-btn):focus-visible{
  outline:2px solid rgba(200,16,46,0.24) !important;
  outline-offset:2px !important;
}

/* Shared button treatment */
.btn,
.btn.primary,
.btn.ghost,
.site-link,
.cta-btn,
.cs-btn,
.sub-btn,
.donate-btn,
.donate-btn-primary,
.donate-btn-ghost,
.icon-btn,
.notify-form button,
.sub-form button,
.submit-form button{
  background:var(--red) !important;
  color:#fff !important;
  border:1px solid var(--red) !important;
  border-radius:var(--r) !important;
  box-shadow:none !important;
  display:inline-flex !important;
  align-items:center !important;
  justify-content:center !important;
  font-family:var(--sans,inherit) !important;
  font-weight:700 !important;
  line-height:1.2 !important;
  padding:.72rem 1.4rem !important;
  gap:.45rem !important;
  white-space:nowrap !important;
  width:auto !important;
  max-width:100% !important;
  flex:0 0 auto !important;
}

.btn:hover,
.btn.primary:hover,
.btn.ghost:hover,
.site-link:hover,
.cta-btn:hover,
.cs-btn:hover,
.sub-btn:hover,
.donate-btn:hover,
.donate-btn-primary:hover,
.donate-btn-ghost:hover,
.icon-btn:hover,
.notify-form button:hover,
.sub-form button:hover,
.submit-form button:hover{
  background:var(--red-mid) !important;
  color:#fff !important;
  border-color:var(--red-mid) !important;
}

.btn:focus-visible,
.site-link:focus-visible,
.cta-btn:focus-visible,
.cs-btn:focus-visible,
.sub-btn:focus-visible,
.donate-btn:focus-visible,
.icon-btn:focus-visible,
.notify-form button:focus-visible,
.sub-form button:focus-visible,
.submit-form button:focus-visible{
  outline:2px solid rgba(200,16,46,0.24) !important;
  outline-offset:2px !important;
}

.sub-btn,
.sub-form button,
.submit-form button{
  align-self:flex-start !important;
  justify-self:flex-start !important;
  width:fit-content !important;
  max-width:max-content !important;
}

[hidden],
.btn[hidden],
button[hidden],
a[hidden]{
  display:none !important;
}

.cardiff-site-footer{
  max-width:980px;
  margin:3rem auto 0;
  padding:0 2rem 2.5rem;
}

.cardiff-site-footer-inner{
  border-top:1px solid var(--rule);
  padding-top:1.4rem;
  text-align:left;
}

/* Footer nav is the site map. Four named columns, grouped the way somebody
   would go looking for something. Tight on a mouse, 44px rows on a finger:
   the tap target floor in CLAUDE.md is about thumbs, so it applies on touch
   and a pointer device gets the compact version. See DECISIONS.md 33. */
.cardiff-site-footer-nav{
  margin-bottom:1.4rem;
  text-align:left;
}

.fnav-cols{
  list-style:none;
  margin:0;
  padding:0;
  display:grid;
  gap:1.5rem 1.25rem;
  grid-template-columns:repeat(2,minmax(0,1fr));
}

@media(min-width:700px){
  .fnav-cols{grid-template-columns:repeat(4,minmax(0,1fr));}
}

.fnav-col{min-width:0;}

.fnav-head{
  display:block;
  font-family:var(--mono);
  font-size:0.64rem;
  letter-spacing:0.14em;
  text-transform:uppercase;
  font-weight:500;
  color:var(--ink);
  margin-bottom:0.35rem;
}

.fnav-list{
  list-style:none;
  margin:0;
  padding:0;
}

.fnav-list a{
  display:flex;
  align-items:center;
  min-height:44px;
  font-family:var(--sans);
  font-size:0.88rem;
  line-height:1.3;
  color:var(--ink3);
  text-decoration:none;
  transition:color var(--ease);
}

/* A phone gets the 44px rows the tap target floor asks for. From tablet up,
   where this is read with a cursor, the rows tighten. Width rather than a
   pointer query, because pointer:fine is reported inconsistently and a footer
   that silently keeps the tall rows is the bug this replaced. */
@media(min-width:700px){
  .fnav-list a{
    min-height:0;
    padding:0.22rem 0;
    font-size:0.85rem;
  }
}

.fnav-list a:hover,
.fnav-list a:focus-visible{
  color:var(--red);
  text-decoration:underline;
}

.fnav-list a[aria-current="page"]{
  color:var(--red);
  font-weight:700;
}

.cardiff-site-footer-copy{
  font-family:var(--mono);
  font-size:0.66rem;
  letter-spacing:0.14em;
  text-transform:uppercase;
  color:var(--ink3);
}

.cardiff-app-desk{
  max-width:980px;
  margin:2rem auto 0;
  padding:1.15rem 1.2rem;
  background:var(--card,#faf6ee);
  border:1px solid var(--border,rgba(80,44,8,0.13));
  border-radius:14px;
  box-shadow:var(--shadow,0 2px 10px rgba(80,44,8,0.09)),inset 0 0 0 1px rgba(200,16,46,0.09);
  position:relative;
}

.cardiff-app-row{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  gap:1rem;
  align-items:start;
}

.cardiff-app-title{
  font-size:1rem;
  font-weight:800;
  color:var(--ink,#1c1208);
  margin-bottom:0.3rem;
}

.cardiff-app-copy,
.cardiff-app-panel-copy,
.cardiff-app-status{
  font-size:0.82rem;
  line-height:1.62;
  color:var(--ink2,#4a3418);
}

.cardiff-app-actions{
  display:flex;
  gap:0.6rem;
  flex-wrap:wrap;
  justify-content:flex-end;
}

.cardiff-app-panel{
  margin-top:0.95rem;
  padding-top:0.95rem;
  border-top:1px solid var(--rule,rgba(80,44,8,0.15));
}

.cardiff-app-panel-title{
  font-size:0.94rem;
  font-weight:800;
  color:var(--ink,#1c1208);
  margin-bottom:0.35rem;
}

.cardiff-app-options{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:0.55rem 0.9rem;
  margin-top:0.8rem;
}

.cardiff-app-option{
  display:flex;
  align-items:flex-start;
  gap:0.5rem;
  font-size:0.8rem;
  line-height:1.5;
  color:var(--ink2,#4a3418);
}

.cardiff-app-option input{
  margin-top:0.12rem;
}

.cardiff-app-status{
  margin-top:0.75rem;
  color:var(--ink3,#7a5838);
}

@media(max-width:600px){
  .cardiff-site-footer{
    padding:0 1rem 2rem;
  }
  .cardiff-app-desk{
    margin:1.4rem 1rem 0;
    padding:1rem;
  }
  .cardiff-app-row{
    grid-template-columns:1fr;
  }
  .cardiff-app-actions{
    justify-content:flex-start;
  }
  .cardiff-app-options{
    grid-template-columns:1fr;
  }
}

::placeholder{font-style:italic;opacity:.75;}

/* Skip link. The first tab stop on every page, so somebody using a keyboard or
   a switch is not made to walk the watershed button, the nameplate, five tabs
   and the creek link before reaching the page, on every page, every time.
   Off screen until focused, then it lands over the masthead, which is z-index
   100. The target carries tabindex="-1" so focus really moves. See
   DECISIONS.md 34. */
.skip-link{
  position:fixed;
  top:0;
  left:-9999px;
  z-index:200;
  display:inline-flex;
  align-items:center;
  min-height:44px;
  padding:12px 20px;
  background:var(--red);
  color:#fff;
  font-family:var(--sans);
  font-size:1rem;
  font-weight:700;
  text-decoration:none;
  border-bottom-right-radius:8px;
}

.skip-link:focus{left:0;}

/* The landmark is a focus target, not a focus ring. */
main:focus{outline:none;}

/* For headings a page needs semantically but does not show. Not display:none,
   which would hide it from screen readers too. */
.visually-hidden{
  position:absolute;
  width:1px;
  height:1px;
  margin:-1px;
  padding:0;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}
