/* 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;
}

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

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

.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;
}

/* 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;
}

/* 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;
}

/* 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;
  }
}

.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;
}

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

/* No colour coding, no icon set. The words already say what the sky is doing,
   and 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. */

.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);
}

.weather-outlook > summary {
  cursor: pointer;
  padding: var(--space-2) 0;
  /* Past the 44px touch target, since this is the only thing on the home page
     that is tapped rather than followed. */
  min-height: 2.75rem;
  display: flex;
  align-items: center;
  color: var(--accent);
}

.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);
}
