/* Mobile-first foundation. Phase 3 builds the directory on top of these
   primitives; the custom properties are the only thing later phases should
   need to reach for when adding components. */

:root {
  --bg: #ffffff;
  --surface: #f5f5f4;
  --text: #1c1917;
  --text-muted: #57534e;
  --border: #e7e5e4;
  --accent: #0f766e;
  --accent-contrast: #ffffff;
  --warn: #9a3412;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2.5rem;

  --radius: 0.5rem;
  --measure: 44rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1c1917;
    --surface: #292524;
    --text: #f5f5f4;
    --text-muted: #a8a29e;
    --border: #44403c;
    --accent: #5eead4;
    --accent-contrast: #1c1917;
    --warn: #fdba74;
  }
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 1rem;
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
}

a {
  color: var(--accent);
}

h1 {
  font-size: 1.75rem;
  line-height: 1.2;
  margin: 0;
}

h2 {
  font-size: 1.25rem;
  line-height: 1.25;
  margin: 0;
}

h3 {
  font-size: 1.0625rem;
  line-height: 1.3;
  margin: 0;
}

/* Accessibility helpers ---------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
}

.skip-link:focus {
  left: var(--space-3);
  top: var(--space-3);
  z-index: 10;
  padding: var(--space-2) var(--space-3);
  background: var(--accent);
  color: var(--accent-contrast);
  border-radius: var(--radius);
}

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

/* Layout ------------------------------------------------------------------ */

.site-header {
  padding: var(--space-3);
  border-bottom: 1px solid var(--border);
}

.site-header__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  max-width: var(--measure);
  margin: 0 auto;
}

.site-title {
  font-weight: 650;
  font-size: 1.125rem;
  text-decoration: none;
  color: var(--text);
}

.site-nav a {
  font-size: 0.9375rem;
}

/* The village switcher. Sized down and set in the header's own measure so it
   reads as a setting rather than as a fifth navigation item — it answers "where
   am I reading from", which is a different question from "where do I go next".
   It scrolls sideways rather than wrapping, for the same reason the intent bar
   does: a header that grows a second line on a phone pushes the content down
   the page on every single view. */
.community-switcher {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  max-width: var(--measure);
  margin: var(--space-2) auto 0;
  overflow-x: auto;
  scrollbar-width: none;
}

.community-switcher::-webkit-scrollbar {
  display: none;
}

.community-switcher__label {
  font-size: 0.8125rem;
  color: var(--text-muted);
  flex: none;
}

.community-switcher ul {
  display: flex;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}

.community-switcher li {
  flex: none;
}

.community-switcher a {
  display: block;
  padding: 0.125rem var(--space-2);
  border-radius: var(--radius);
  font-size: 0.8125rem;
  text-decoration: none;
  color: var(--text-muted);
  white-space: nowrap;
}

.community-switcher a:hover {
  background: var(--surface);
  color: var(--text);
}

/* The one you are reading, marked for everybody rather than for a screen reader
   alone — the switcher is only useful if it says where you already are. */
.community-switcher a[aria-current="true"] {
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}

main {
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-4) var(--space-3) var(--space-5);
}

main > * + * {
  margin-top: var(--space-4);
}

/* The shell: the doors and the content in one box.
   `.shell` alone is deliberately not a layout — no display, no width, nothing.
   Every page that has ever rendered still renders the same way, with the doors
   in a scrolling row under the header and the content beneath. The two-column
   version is `.shell--feed` below, and only a page with a feed asks for it. */

.shell {
  display: block;
}

.site-footer {
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-4) var(--space-3);
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 0.875rem;
}

.attribution {
  font-size: 0.8125rem;
}

/* Primitives -------------------------------------------------------------- */

.stack > * + * {
  margin-top: var(--space-3);
}

.lede {
  font-size: 1.125rem;
  color: var(--text-muted);
  margin: 0;
}

.muted {
  color: var(--text-muted);
  font-size: 0.9375rem;
  margin: 0;
}

.note {
  padding: var(--space-3);
  background: var(--surface);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 0.9375rem;
}

.breadcrumb {
  font-size: 0.875rem;
}

button,
.button,
input[type="search"],
input[type="text"] {
  min-height: 2.75rem;
  font: inherit;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  padding: 0 var(--space-3);
}

.button {
  display: inline-flex;
  align-items: center;
  background: var(--accent);
  color: var(--accent-contrast);
  text-decoration: none;
  border-color: transparent;
}

button {
  background: var(--accent);
  color: var(--accent-contrast);
  border-color: transparent;
  cursor: pointer;
}

/* Search ------------------------------------------------------------------ */

.search {
  display: flex;
  gap: var(--space-2);
  max-width: var(--measure);
  margin: var(--space-3) auto 0;
}

.search input[type="search"] {
  flex: 1;
  min-width: 0;
  background: var(--bg);
  color: var(--text);
}

.htmx-indicator {
  display: block;
  max-width: var(--measure);
  margin: var(--space-2) auto 0;
  font-size: 0.8125rem;
  color: var(--text-muted);
  opacity: 0;
  transition: opacity 120ms ease-in;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  opacity: 1;
}

/* Intent bar ---------------------------------------------------------------
   The doors, under the header on every page. A single row that scrolls
   sideways rather than wrapping: `docs/feed-intents.md` specifies a scrolling
   chip row on a phone, and wrapping would make the header a different height on
   different pages of the same site as the labels change length.

   The list is the scroll container rather than a wrapper around it, so the
   scrollable box is the same 44rem box the rest of the page is centred in.
   Overflow only happens when the doors outgrow it, which at three is a phone
   and at nine is everywhere. */

.intent-bar {
  border-bottom: 1px solid var(--border);
}

.intent-bar__list {
  list-style: none;
  margin: 0 auto;
  padding: var(--space-2) var(--space-3);
  max-width: var(--measure);
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-2);
  overflow-x: auto;
  /* Nothing is clipped at the ends of the scroll, which is what makes a
     partly-scrolled row look like a row rather than a cut-off one. */
  scroll-padding: var(--space-3);
  scrollbar-width: thin;
}

.intent-bar__list > li {
  flex: 0 0 auto;
}

.intent-bar__list a {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* The same touch target the category grid uses; this is tapped with a thumb
     while walking, which is most of what the intents are for. */
  min-height: 2.75rem;
  padding: 0 var(--space-3);
  background: var(--surface);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
  white-space: nowrap;
  font-size: 0.9375rem;
}

.intent-bar__list a:hover {
  background: var(--border);
}

/* The door you are standing in. A border rather than a fill, so the current
   page is marked without turning one chip into the loudest thing above the
   content it belongs to. */
.intent-bar__list a[aria-current="page"] {
  background: var(--bg);
  box-shadow: inset 0 0 0 2px var(--accent);
  font-weight: 600;
}

.intent-bar__count {
  color: var(--text-muted);
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
}

/* The rail, at last ------------------------------------------------------
   `docs/feed-intents.md` puts the doors in a sticky left rail beside a single
   column of cards. It shipped as a scrolling row at every width because the
   rail is half of a two-column shell whose other half is the feed, and the feed
   did not exist. It does now, so on a viewport with room for both, the row
   becomes the rail — same markup, same context processor, same counts.

   72rem, and the arithmetic is why: the content column stays exactly where it
   is on every other page (44rem, centred), and the rail sits in the left margin
   beside it. That needs a margin at least as wide as the rail, so 44 + 2 × 14 =
   72. A narrower viewport is not a compromised rail; it is the row, which is
   the layout the doc specifies for phones and which works everywhere.

   The column not moving is the point. A reader who follows a door from the home
   page to an intent page should find the content in the same place, and a
   layout that centred the pair would have shifted every page under them. */

@media (min-width: 72rem) {
  .shell--feed {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--measure)) minmax(0, 1fr);
    align-items: start;
  }

  .shell--feed .intent-bar {
    grid-column: 1;
    justify-self: end;
    width: 13rem;
    margin-right: var(--space-3);
    /* Sticky to the viewport, so the doors stay reachable however far down the
       column somebody has scrolled — which is the one thing a rail does that a
       row above the content cannot. */
    position: sticky;
    top: var(--space-3);
    /* The row's rule under the header separated it from the content below it.
       In the margin there is nothing below it to separate from. */
    border-bottom: 0;
  }

  .shell--feed .intent-bar__list {
    flex-direction: column;
    align-items: stretch;
    max-width: none;
    padding: var(--space-4) 0 0;
    overflow-x: visible;
  }

  .shell--feed .intent-bar__list a {
    justify-content: space-between;
    /* The row could not wrap without changing the header's height from page to
       page. A rail has a fixed width and a variable one, so "Take care of my
       car" is allowed the second line it wants. */
    white-space: normal;
    padding: var(--space-2) var(--space-3);
  }

  .shell--feed main {
    grid-column: 2;
  }
}

/* Category grid ----------------------------------------------------------- */

.category-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  grid-template-columns: 1fr;
}

@media (min-width: 30rem) {
  .category-grid {
    grid-template-columns: 1fr 1fr;
  }
}

.category-grid a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  /* Comfortably past the 44px touch target this will mostly be tapped with. */
  min-height: 3rem;
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
}

.category-grid a:hover {
  background: var(--border);
}

.category-grid__count {
  color: var(--text-muted);
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
}

/* Village picker -----------------------------------------------------------
   The root page. Same card-in-a-grid idea as the category grid above, but
   stacked rather than justified: a village carries a name, a line of its own
   words and a count, and three items pushed to opposite ends of one row would
   read as a table nobody asked for.

   Two columns from the same breakpoint the category grid uses, so the two pages
   change shape together rather than at different widths. */

.village-picker {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  grid-template-columns: 1fr;
}

@media (min-width: 30rem) {
  .village-picker {
    grid-template-columns: 1fr 1fr;
  }
}

.village-picker a {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  height: 100%;
  padding: var(--space-3);
  background: var(--surface);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
}

.village-picker a:hover {
  background: var(--border);
}

.village-picker__name {
  font-size: 1.125rem;
  font-weight: 600;
}

.village-picker__tagline,
.village-picker__count {
  color: var(--text-muted);
  font-size: 0.875rem;
}

.village-picker__count {
  font-variant-numeric: tabular-nums;
}

/* Browse groups ------------------------------------------------------------
   The category page's headings. `main > * + *` already spaces the sections
   apart, so all this needs is the gap between a heading, its one-line note and
   the cards under it.

   The note is not decoration. "Check before you go" is the group for listings
   whose hours we cannot stand behind, and a heading alone would leave a reader
   to guess whether that meant closed. */

.group__note {
  margin: var(--space-1) 0 var(--space-3);
  color: var(--text-muted);
  font-size: 0.875rem;
}

/* Cards ------------------------------------------------------------------- */

.cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.card__link {
  display: block;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
}

.card__link:hover {
  background: var(--surface);
}

.card__address,
.card__meta {
  margin: var(--space-1) 0 0;
  font-size: 0.875rem;
  color: var(--text-muted);
}

.card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.card__description {
  margin: var(--space-2) 0 0;
  font-size: 0.9375rem;
}

.tag {
  display: inline-block;
  padding: 0.125rem var(--space-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.75rem;
  color: var(--text-muted);
  text-decoration: none;
}

.tag--warn {
  color: var(--warn);
  border-color: currentColor;
}

/* An attribute, not a category. The two sit on the same row and are different
   kinds of claim — "Restaurants" is where a listing is filed, "Ice cream" is
   something it has — so the fill is dropped to tell them apart without adding
   a colour, which is already spoken for by the opening-hours badges. */
.tag--attribute {
  background: none;
  border-style: dashed;
}

/* Live opening status.
   The words carry the meaning — "Open", "Closing soon", "Closed", "Opening
   soon" are spelled out in every badge — and colour only repeats them, so the
   set reads correctly in monochrome and to anyone who does not see teal and
   amber as different. That is why "Closing soon" and "Opening soon" can share
   an accent: nothing rests on telling them apart by eye.

   The amber is the same --warn the not-trading tag uses, and the two never
   appear on one card: a listing that is not trading is given no status badge
   at all. */
.tag--open {
  color: var(--accent);
  border-color: currentColor;
}

.tag--closing_soon,
.tag--opening_soon {
  color: var(--warn);
  border-color: currentColor;
}

.tag--closed {
  color: var(--text-muted);
}

/* Detail page ------------------------------------------------------------- */

.details {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-3);
  margin: 0;
}

.details dt {
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  padding-top: 0.15rem;
}

.details dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.hours {
  font-variant-numeric: tabular-nums;
}

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

/* Two columns on a phone, because the days column is short and fixed and the
   times column is what varies. flex-wrap rather than a grid so a rule with two
   spans in it ("7am–noon, 3pm–8pm") can fall onto its own line on a narrow
   screen instead of squeezing the day name. */
.hours__week li {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-2);
}

.hours__days {
  flex: none;
  min-width: 5.5rem;
  color: var(--text-muted);
}

/* The raw OSM string, kept reachable underneath the prose. Quiet, because a
   resident does not need it — but present, because an OSM contributor does,
   and because it is the only place a `;` rule overriding a whole day shows. */
.hours__raw {
  margin: var(--space-2) 0 0;
  font-size: 0.8125rem;
  color: var(--text-muted);
}

.hours__raw code {
  font-size: inherit;
  overflow-wrap: anywhere;
}

/* Map --------------------------------------------------------------------- */

#map {
  /* Tall enough to be useful on a phone without pushing the page's other
     content entirely below the fold. */
  height: min(70vh, 32rem);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  font-size: 0.875rem;
  color: var(--text-muted);
}

.legend li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.legend__dot {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  display: inline-block;
}

.legend__dot--business {
  background: #0f766e;
}

.legend__dot--civic {
  background: #1d4ed8;
}

.legend__dot--nonprofit {
  background: #7c3aed;
}

.popup__name {
  font-weight: 600;
}

.popup__meta {
  margin: var(--space-1) 0 0;
  color: #57534e;
  font-size: 0.8125rem;
}

/* Leaflet renders its own popups on a white surface regardless of theme, so
   pin the text colour rather than inheriting a light-on-light value. */
.leaflet-popup-content {
  color: #1c1917;
}

/* Pagination -------------------------------------------------------------- */

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: 0.9375rem;
}

.pagination__disabled {
  color: var(--text-muted);
}

.pagination__status {
  color: var(--text-muted);
  font-size: 0.875rem;
}

/* Images ------------------------------------------------------------------ */

.card__name {
  display: flex;
  align-items: center;
  /* The minimum clearance between a name long enough to reach the logo and the
     logo itself, not the usual spacing — the two normally sit at opposite ends
     of the row. */
  gap: var(--space-3);
}

/* The name takes whatever is left and wraps; the logo keeps its own size.
   min-width lets the name shrink past its longest word rather than shoving the
   logo off the right-hand edge, and overflow-wrap then breaks that word instead
   of letting it run under the logo. */
.card__name > span,
.place__heading > h1 {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Height is fixed and width follows the image, rather than a square box.
   Brand marks are mostly wordmarks — several times wider than they are tall —
   and a square would render the Subway logo at a fraction of its legible size
   while a round one filled the same space entirely. The max-width stops an
   unusually long wordmark from crowding out the name beside it.

   margin-left: auto puts the logo hard against the right edge, so a column of
   results has its names on one left edge and its logos on one right edge.
   Logos differ enormously in width, and with the logo leading, every name in
   the list began at a different place. Nothing is reserved when there is no
   logo: the rule lives on the image, which simply is not rendered. */
.card__logo,
.place__logo {
  flex: none;
  width: auto;
  margin-left: auto;
  object-fit: contain;
  border-radius: calc(var(--radius) / 2);
}

.card__logo {
  height: 2rem;
  max-width: 6rem;
}

/* No flex-wrap: with the logo at the right-hand end, wrapping dropped it onto a
   line of its own below a long name, orphaned against the right margin. The
   heading stays one row and the name wraps within it instead. */
.place__heading {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.place__logo {
  height: 2.5rem;
  max-width: 9rem;
}

/* Local news demonstration ------------------------------------------------- */

/* Loud enough that nobody arrives at a list of somebody else's headlines and
   takes it for a shipped feature, quiet enough to read as a note rather than
   an error. */
.demo-notice {
  padding: var(--space-3);
  border: 1px solid var(--accent);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 0.9375rem;
}

/* Both rules on the children rather than on `p`, and in this order: a rule
   naming the element would out-specify the sibling combinator and flatten the
   spacing back to nothing. */
.demo-notice > * {
  margin: 0;
}

.demo-notice > * + * {
  margin-top: var(--space-2);
}

.demo-notice__flag {
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.8125rem;
  color: var(--accent);
}

.news-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}

.news-item {
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border);
}

.news-item:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.news-item__headline {
  font-size: 1.0625rem;
  line-height: 1.3;
  margin: 0;
}

.news-item__meta {
  margin: var(--space-1) 0 0;
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* Their sentence, marked as a quotation rather than set as body text — on a
   page about not republishing someone, whose words these are should be visible
   without reading the caption. */
.article__excerpt {
  margin: 0;
  padding-left: var(--space-3);
  border-left: 3px solid var(--border);
  font-size: 1.125rem;
}

.article__excerpt p {
  margin: 0;
}

.placeholder {
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 0.9375rem;
}

.placeholder > * {
  margin: 0;
}

.placeholder > * + * {
  margin-top: var(--space-3);
}

.placeholder__title {
  font-size: 1.0625rem;
}

/* The whole argument of this page is that reading the original is the obvious
   next thing to do, so the link out is a full-width target on a phone. */
.placeholder__action .button {
  width: 100%;
  justify-content: center;
  min-height: 2.75rem;
  padding: var(--space-2) var(--space-3);
  text-align: center;
}

@media (min-width: 30rem) {
  .placeholder__action .button {
    width: auto;
  }
}

/* Cinema showtimes --------------------------------------------------------- */

/* There is no rule here for the note's image, and that is deliberate: the
   picture the week is read off is a curation tool and lives in the admin. No
   resident surface renders it, so there is nothing here to style. */

/* "CLOSED MONDAY" — a statement about the week rather than about any showing on
   it, so it sits above the films rather than on a card. */
.showtimes__closure {
  margin: 0;
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* The day a row of times is on. Film-first grouping puts every day a film plays
   inside that film's own card, so the day is a label on its row rather than a
   heading over the page — small and quiet, because the film titles are the
   thing being scanned and a week that shouted its own days would compete.

   The fixed minimum is what makes the times line up down the card. A film with
   a six-day run is six rows, and ragged left edges on the times would undo most
   of what listing it once bought. */
.showings__day {
  flex: 0 0 auto;
  min-width: 5.5rem;
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.showing__times {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1);
  margin: var(--space-2) 0 0;
}

/* Where the week came from, on every surface that lists it. Quiet, but never
   absent: it is the only thing on the page that says the listing is what the
   cinema posted rather than everything it is showing. */
.showtimes__provenance {
  margin: var(--space-3) 0 0;
  font-size: 0.8125rem;
  color: var(--text-muted);
}

/* Showtimes read as data, not as decoration, so they are heavier than a
   category tag and sit on the text colour rather than the muted one.
   tabular-nums so a column of times does not shimmer. */
.tag--time {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.gallery {
  display: grid;
  gap: var(--space-3);
  /* One column on a phone. The lead photo keeps the full width at every size;
     the rest pair up once there is room for two without either going postage
     stamp sized. */
  grid-template-columns: 1fr;
}

@media (min-width: 34rem) {
  .gallery {
    grid-template-columns: 1fr 1fr;
  }

  .gallery__item--lead {
    grid-column: 1 / -1;
  }
}

.gallery__item {
  margin: 0;
}

.gallery__item img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  background: var(--surface);
}

.gallery__item figcaption {
  margin-top: var(--space-1);
  font-size: 0.875rem;
  color: var(--text-muted);
}

.gallery__credit {
  display: block;
  font-size: 0.8125rem;
}

.attribution {
  font-size: 0.8125rem;
}

/* The feed ------------------------------------------------------------------
   One column, never a grid, and the reason is in docs/feed-intents.md: a grid
   flattens the difference between a boil-water advisory and an ice cream card,
   and the varying card height is what makes the feed feel alive. So there is no
   `grid-template-columns` here at any width, and cards are whatever height
   their contents are.

   The gap is larger than the one between browse cards. A list of listings is
   one thing scanned twenty times; a feed is twenty things read once each, and
   they need room to read as separate. */

.feed {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}

/* Full-bleed on a phone, as the doc specifies. The negative margin is exactly
   `main`'s horizontal padding, so the cards reach the screen edge and the text
   inside them keeps its own inset — and the radius goes, because a rounded
   corner against the edge of the screen looks like a mistake rather than a
   card. Above this width the column is narrower than the viewport and there is
   nothing to bleed into. */
@media (max-width: 34rem) {
  .feed {
    margin-inline: calc(var(--space-3) * -1);
  }

  .feed__card > * {
    border-radius: 0;
    border-left-width: 0;
    border-right-width: 0;
  }
}

.feed-event,
.feed-notice,
.feed-films {
  display: block;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}

/* A notice is text-first and urgent-shaped: no photograph, no metadata above
   the words, and a heavier surface than an event so it does not read as one
   more thing happening on Saturday. */
.feed-notice {
  background: var(--surface);
}

.feed-event__when {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-1);
  font-size: 0.875rem;
  color: var(--text-muted);
}

.feed-event__title,
.feed-notice__title,
.feed-films__title {
  font-size: 1.125rem;
  margin: 0;
}

.feed-event__venue {
  margin: var(--space-1) 0 0;
  font-size: 0.875rem;
  color: var(--text-muted);
}

.feed-event__body,
.feed-notice__body {
  margin: var(--space-2) 0 0;
  font-size: 0.9375rem;
}

.feed-films__provenance {
  margin: var(--space-2) 0 0;
  font-size: 0.8125rem;
  color: var(--text-muted);
}

.feed-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: var(--space-2) 0 0;
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* The one tag on a feed card that is a live claim rather than a label, so it is
   the one that takes the accent. */
.tag--now {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.feed__empty {
  margin: 0;
}

/* The floor: everything else in the village.
   Set apart from the feed by a rule rather than by space alone, because the
   column above it has no fixed end and a reader needs to be told the
   suggestions have stopped and the index has started. */

.feed-floor {
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

.feed-floor__heading {
  color: var(--text-muted);
  font-size: 0.9375rem;
  font-weight: 650;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* Weather ----------------------------------------------------------------- */

/* No icon set. A weather icon font is a dependency and a set of judgements
   ("is 60% cloud 'partly sunny'?") this site has no reason to take on when the
   National Weather Service already writes the answer in English.

   Colour is a different question and the card does use it, because the card is
   read before a single digit of it is: a wet November afternoon must not look
   like a clear August one. Six discrete grounds, chosen in weather/card.py from
   the station's own words, so this file can never be handed a value nobody
   anticipated. Each sets its own ink, since the ground does not follow the
   reader's light or dark theme and the theme's text colours would land on the
   wrong side of half of them. */

.weather-card {
  --card-ground: var(--surface);
  --card-ink: var(--text);
  --card-ink-muted: var(--text-muted);

  display: block;
  padding: var(--space-3);
  border-radius: var(--radius);
  background: var(--card-ground);
  color: var(--card-ink);
}

/* The unavailable card keeps the default above. A card with no reading gets no
   sky, because the sky is read off the reading. */

.weather-card--clear-day {
  --card-ground: linear-gradient(160deg, #0ea5e9, #7dd3fc);
  --card-ink: #082f49;
  --card-ink-muted: #0c4a6e;
}

.weather-card--overcast {
  --card-ground: linear-gradient(160deg, #94a3b8, #cbd5e1);
  --card-ink: #1e293b;
  --card-ink-muted: #334155;
}

.weather-card--precipitation {
  --card-ground: linear-gradient(160deg, #334155, #475569);
  --card-ink: #f8fafc;
  --card-ink-muted: #dbe2ea;
}

.weather-card--clear-night {
  --card-ground: linear-gradient(160deg, #172554, #312e81);
  --card-ink: #eef2ff;
  --card-ink-muted: #cbd5f5;
}

.weather-card--cloudy-night {
  --card-ground: linear-gradient(160deg, #27272a, #44403c);
  --card-ink: #fafaf9;
  --card-ink-muted: #e0dedb;
}

.weather-card--severe {
  --card-ground: linear-gradient(160deg, #7f1d1d, #9a3412);
  --card-ink: #fff7ed;
  --card-ink-muted: #fde4cd;
}

.weather-card__now {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 var(--space-3);
  margin: 0;
}

.weather-card__temperature {
  font-size: 2rem;
  font-weight: 650;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

.weather-card__condition {
  font-size: 1.0625rem;
}

.weather-card__feels {
  font-size: 0.875rem;
  color: var(--card-ink-muted);
}

.weather-card__refused {
  margin: 0;
  font-size: 1.0625rem;
}

.weather-card__ahead {
  margin: var(--space-1) 0 0;
  font-size: 0.9375rem;
}

.weather-card__source {
  margin: var(--space-2) 0 0;
  font-size: 0.8125rem;
  color: var(--card-ink-muted);
}

/* The accent teal is unreadable on four of the six grounds, so the link takes
   the card's own ink and earns its affordance from the underline instead. */
.weather-card__source a {
  color: inherit;
}

.weather__now {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 var(--space-3);
  margin: 0;
}

.weather__temperature {
  font-size: 2.25rem;
  font-weight: 650;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.weather__description {
  font-size: 1.125rem;
}

.weather__detail,
.weather__source {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.875rem;
}

.weather__source {
  font-size: 0.8125rem;
}

/* Three across on a phone, six once there is room — the whole window sits on
   screen either way, so there is no horizontal scroll to discover and nothing
   is hidden from a reader who does not think to swipe. */
.weather-hours {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}

@media (min-width: 30rem) {
  .weather-hours {
    grid-template-columns: repeat(6, 1fr);
  }
}

.weather-hours li {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  background: var(--surface);
  border-radius: var(--radius);
  font-size: 0.8125rem;
}

.weather-hours__time {
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.weather-hours__temperature {
  font-size: 1.25rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.weather-hours__forecast {
  color: var(--text-muted);
}

.weather-hours__rain {
  color: var(--warn);
}

/* The outlook lost its <details> when it moved to /weather/: on a page whose
   job is the weather it is what the reader came for, not something to disclose.
   The list styling below is the same. */

.weather-outlook__list {
  margin: 0;
  padding: 0;
}

.weather-outlook__list dt {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-2);
  margin-top: var(--space-3);
  font-weight: 650;
}

.weather-outlook__list dt:first-child {
  margin-top: 0;
}

.weather-outlook__temperature {
  font-weight: 400;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.weather-outlook__list dd {
  margin: var(--space-1) 0 0;
  font-size: 0.9375rem;
  color: var(--text-muted);
}
