/* ==========================================================================
   Canada Event Spot - design system
   --------------------------------------------------------------------------
   Direction: municipal wayfinding. The country is read through its provinces,
   so province colour is a structural device, not decoration: it appears as the
   route bar on every card, the dot on every city chip, and the pin on the map.
   Everything else stays quiet - ink on paper, one accent, tabular numerals.
   ========================================================================== */

@font-face {
  font-family: 'Archivo';
  src: url('/fonts/archivo-latin.woff2') format('woff2');
  font-weight: 400 800;
  font-stretch: 100% 125%;
  font-style: normal;
  font-display: swap;
}

:root {
  /* --- ink & paper ------------------------------------------------------ */
  --ink:        #12212b;
  --ink-2:      #40545f;
  --ink-3:      #6b7f89;
  --paper:      #e9ece7;
  --surface:    #ffffff;
  --surface-2:  #f4f6f2;
  --line:       #d2d8d1;
  --line-2:     #e2e7e0;

  /* --- accents ---------------------------------------------------------- */
  --signal:     #0e6e52;   /* actions, links */
  --signal-ink: #0a5540;
  --signal-bg:  #e2f0ea;
  --amber:      #f4b942;   /* free / on now */
  --amber-ink:  #6b4a00;
  --amber-bg:   #fdf3dc;

  /* --- province lines ---------------------------------------------------
     Per-province colour is set inline from config('canada.region_colours'),
     because thirteen of them belong in one editable list rather than in two.
     What stays here is the fallback and the two the admin chrome names
     directly for alert and warning states. */
  --line-default: #6b7f89;
  --line-alert:   #c8102e;
  --line-warn:    #f4881f;

  /* --- type ------------------------------------------------------------- */
  --display: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --body: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, 'SF Mono', 'Cascadia Mono', Consolas, monospace;

  /* --- scale ------------------------------------------------------------ */
  --step--1: clamp(0.80rem, 0.78rem + 0.10vw, 0.86rem);
  --step-0:  clamp(1.00rem, 0.97rem + 0.15vw, 1.06rem);
  --step-1:  clamp(1.20rem, 1.13rem + 0.35vw, 1.40rem);
  --step-2:  clamp(1.50rem, 1.35rem + 0.75vw, 2.00rem);
  --step-3:  clamp(1.95rem, 1.65rem + 1.50vw, 2.90rem);
  --step-4:  clamp(2.40rem, 1.90rem + 2.50vw, 4.00rem);

  --gutter: clamp(1rem, 0.6rem + 2vw, 2rem);
  --radius: 4px;
  --radius-lg: 8px;

  --shadow-1: 0 1px 2px rgba(18, 33, 43, 0.06), 0 1px 1px rgba(18, 33, 43, 0.04);
  --shadow-2: 0 6px 20px -8px rgba(18, 33, 43, 0.22), 0 2px 6px rgba(18, 33, 43, 0.06);

  --z-nav: 30;
  --z-sheet: 50;

  color-scheme: light;
}

/* --------------------------------------------------------------------------
   Reset
   -------------------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  /* Backstop: a single wide child (a long venue name, a stray table) must
     never turn the whole document into a horizontal scroller. clip is used
     rather than hidden so position: sticky keeps working. */
  overflow-x: clip;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { max-width: 100%; height: auto; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }
h1, h2, h3, h4 { margin: 0; font-family: var(--display); line-height: 1.08; font-weight: 700; }
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }
a { color: var(--signal-ink); text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
a:hover { color: var(--ink); }

:focus-visible {
  outline: 3px solid var(--signal);
  outline-offset: 2px;
  border-radius: 2px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: #fff;
  padding: 0.75rem 1.25rem;
  z-index: 100;
}
.skip-link:focus { left: 0; }

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

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

.wrap {
  width: min(100% - (var(--gutter) * 2), 1180px);
  margin-inline: auto;
}
.wrap--narrow { width: min(100% - (var(--gutter) * 2), 760px); }

.stack > * + * { margin-top: var(--space, 1rem); }
.section { padding-block: clamp(2rem, 1.2rem + 3vw, 3.5rem); }
.section--tight { padding-block: clamp(1.25rem, 1rem + 1.5vw, 2rem); }

/* --------------------------------------------------------------------------
   Type helpers
   -------------------------------------------------------------------------- */

.eyebrow {
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.eyebrow::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--line);
}

.h1, .h2, .h3 { overflow-wrap: break-word; }
.h1 { font-size: var(--step-3); font-weight: 800; font-stretch: 112%; letter-spacing: -0.022em; }
.h2 { font-size: var(--step-2); font-weight: 800; font-stretch: 108%; letter-spacing: -0.018em; }
.h3 { font-size: var(--step-1); font-weight: 700; letter-spacing: -0.01em; }
.lede { font-size: var(--step-1); line-height: 1.45; color: var(--ink-2); max-width: 62ch; }
.muted { color: var(--ink-2); }
.small { font-size: var(--step--1); }
.nums { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */

.masthead {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.masthead__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 62px;
  padding-block: 0.5rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 118%;
  font-size: 1.0625rem;
  letter-spacing: -0.02em;
  text-decoration: none;
  color: var(--ink);
  white-space: nowrap;
}

/* The roundel echoes transit wayfinding: a bar through a ring. */
/* The pin mark, from public/img/logo-mark.svg. Fixed box so a slow icon
   cannot shift the masthead once it lands. */
.brand__mark {
  width: 26px;
  height: 26px;
  flex: none;
  display: block;
}

.masthead__nav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.masthead__nav::-webkit-scrollbar { display: none; }
.masthead__nav a { flex: none; }
.masthead__nav a {
  font-size: var(--step--1);
  font-weight: 600;
  text-decoration: none;
  color: var(--ink-2);
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius);
}
.masthead__nav a:hover { color: var(--ink); background: var(--surface-2); }
.masthead__nav a[aria-current='page'] { color: var(--ink); box-shadow: inset 0 -2px 0 var(--signal); }

/* Icon-only, so it needs its own square rather than the text links'
   asymmetric padding, and a hover that says it is a link to Telegram
   rather than one more section of the site. */
.masthead__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  color: var(--ink-3);
  margin-left: 0.15rem;
}
.masthead__icon:hover { color: #229ED9; background: var(--surface-2); }

.footer__brandlink { display: inline-flex; align-items: center; gap: 0.45rem; }

/* The masthead is the widest fixed row on the page, so links drop out before
   anything is allowed to push the document past the viewport. */
@media (max-width: 860px) {
  .masthead__nav a.is-optional { display: none; }
}

@media (max-width: 560px) {
  .masthead__nav a.is-optional-sm { display: none; }
  .masthead__nav a { padding: 0.5rem 0.4rem; font-size: 0.8125rem; }
  .brand { font-size: 0.9375rem; gap: 0.45rem; }
  .brand__mark { width: 22px; height: 22px; }
}

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

.searchbar {
  display: flex;
  align-items: stretch;
  background: var(--surface);
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  overflow: hidden;
}
.searchbar input {
  flex: 1;
  min-width: 0;
  border: 0;
  padding: 0.7rem 0.85rem;
  background: transparent;
  /* Stated, not inherited. The bar keeps its light background inside the dark
     signup panel, so an inherited white would type white on white. */
  color: var(--ink);
}
.searchbar input::placeholder { color: var(--ink-3); }
.searchbar input:focus { outline: none; }
.searchbar:focus-within { box-shadow: 0 0 0 3px var(--signal-bg); }
.searchbar button {
  border: 0;
  background: var(--ink);
  color: #fff;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding-inline: 1.1rem;
  cursor: pointer;
  min-height: 44px;
}
.searchbar button:hover { background: var(--signal-ink); }

/* --------------------------------------------------------------------------
   Buttons & chips
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  min-height: 44px;
  padding: 0.55rem 1.05rem;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--step--1);
  letter-spacing: 0.02em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 180ms ease, color 180ms ease, border-color 180ms ease;
}
.btn:hover { background: var(--ink); color: #fff; }
.btn--primary { background: var(--signal); border-color: var(--signal); color: #fff; }
.btn--primary:hover { background: var(--signal-ink); border-color: var(--signal-ink); color: #fff; }
.btn--ghost { border-color: var(--line); color: var(--ink-2); }
.btn--ghost:hover { border-color: var(--ink); background: var(--surface-2); color: var(--ink); }
.btn--sm { min-height: 38px; padding: 0.35rem 0.75rem; font-size: 0.8125rem; }
.btn--block { width: 100%; }

/* A disabled button that looks live is worse than no button: it swallows the
   click and the operator concludes the feature is broken. Say so visibly. */
.btn:disabled,
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.btn:disabled:hover,
.btn[disabled]:hover { background: var(--surface); border-color: var(--line); color: var(--ink-2); }
.btn--primary:disabled:hover,
.btn--primary[disabled]:hover { background: var(--signal); border-color: var(--signal); color: #fff; }

.chiprow {
  display: flex;
  gap: 0.45rem;
  overflow-x: auto;
  padding-bottom: 0.35rem;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

/* Scrolling is right on a phone and wrong on a desktop: a horizontal bar
   through the middle of a filter panel hides options behind a gesture nobody
   makes with a mouse. Above the fold width, the rows wrap instead. */
@media (min-width: 700px) {
  .chiprow--wrap {
    flex-wrap: wrap;
    overflow-x: visible;
    padding-bottom: 0;
    row-gap: 0.5rem;
  }
}
.chiprow::-webkit-scrollbar { height: 6px; }
.chiprow::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex: none;
  min-height: 38px;
  padding: 0.3rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--step--1);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 160ms ease, background-color 160ms ease, color 160ms ease;
}
.chip:hover { border-color: var(--ink-3); color: var(--ink); }
.chip[aria-pressed='true'],
.chip[aria-current],
.chip.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.chip__dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--region, var(--line-default));
  flex: none;
}

/* Province colour is the site's one structural signal, and a 9px dot on a dark
   fill does not carry it. An active place or category keeps the ink fill for
   contrast and states its colour as a ring outside the pill, where it has the
   white panel behind it to read against. */
.chip--region[aria-current] {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 5px var(--region, var(--ink));
}
/* On the dark fill the dot keeps its colour and gains a halo, so the province
   reads twice: once on the ring, once here. */
.chip--region[aria-current] .chip__dot { box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.9); }
.chip__count {
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--ink-3);
}

.chip--sm { min-height: 34px; padding: 0.2rem 0.7rem; font-size: 0.8125rem; font-weight: 500; }

/* An applied filter is a statement with a way out, not a choice to make: it
   reads filled and carries the cross that removes it. */
.chip--applied {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
  padding-right: 0.55rem;
}
.chip--applied:hover { background: var(--signal-ink); border-color: var(--signal-ink); color: #fff; }
.chip--applied .chip__x { opacity: 0.6; margin-left: 0.1rem; }
.chip--applied:hover .chip__x { opacity: 1; }
.chip--clear { border-style: dashed; color: var(--ink-3); }
.chip[aria-pressed='true'] .chip__count,
.chip[aria-current] .chip__count { color: rgba(255, 255, 255, 0.7); }

/* --------------------------------------------------------------------------
   Filter bar
   -------------------------------------------------------------------------- */

.filters {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 0.9rem;
}

/* The search and the number of results are one thought - type, press, see how
   many - so they sit on one line and the count is the largest thing in the
   panel after the field itself. */
.filters__top { display: grid; gap: 0.6rem; }
@media (min-width: 700px) {
  .filters__top { grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 1rem; }
}
.filters__count {
  margin: 0;
  font-size: var(--step--1);
  color: var(--ink-2);
  white-space: nowrap;
}
.filters__count strong { font-size: var(--step-1); color: var(--ink); margin-right: 0.15rem; }
.filters__when { display: block; color: var(--ink-3); font-size: 0.75rem; }

/* What is switched on, before the controls that switch things on. Tinted band
   so it reads as state rather than as one more group of choices. */
.filters__active {
  margin-top: 0.85rem;
  padding: 0.7rem 0.8rem;
  background: var(--surface-2);
  border-radius: var(--radius);
}

.filters__group { margin-top: 0.85rem; padding-top: 0.85rem; border-top: 1px dashed var(--line-2); }

/* The community list exists because a province was chosen. Stepping it in and
   hanging it off a rule says that, where five identical rows said nothing. */
.filters__group--nested {
  margin-left: 0.85rem;
  padding-left: 0.85rem;
  border-top: 0;
  border-left: 2px solid var(--line-2);
  padding-top: 0.6rem;
  margin-top: 0.5rem;
}

/* These narrow a set rather than choosing one, so they sit quieter. */
.filters__group--quiet .filters__label { color: var(--line-default); }
.filters__label {
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.5rem;
}
.filters__foot {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-top: 0.9rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line-2);
}

/* --------------------------------------------------------------------------
   Event card - the route bar carries the region
   -------------------------------------------------------------------------- */

.cards { display: grid; gap: 0.75rem; }

.card {
  position: relative;
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr);
  gap: 0 1rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 5px solid var(--region, var(--line-default));
  border-radius: var(--radius);
  padding: 0.9rem 1rem 0.9rem 0.85rem;
  transition: box-shadow 180ms ease, border-color 180ms ease, transform 180ms ease;
}
.card:hover { box-shadow: var(--shadow-2); border-color: var(--ink-3); border-left-color: var(--region, var(--line-default)); }
.card:has(.card__title a:focus-visible) { box-shadow: var(--shadow-2); }

/* Ticket-stub date block: tabular numerals, hairline rule, no colour. */
.card__date {
  font-family: var(--display);
  text-align: center;
  border-right: 1px solid var(--line-2);
  padding-right: 0.85rem;
  align-self: start;
}
.card__date .mon {
  display: block;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.card__date .day {
  display: block;
  font-size: 1.75rem;
  font-weight: 800;
  font-stretch: 115%;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.card__date .dow {
  display: block;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.card__date .range {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--signal-ink);
}

.card__body { min-width: 0; }

.card__title {
  font-family: var(--display);
  font-size: var(--step-1);
  font-weight: 700;
  letter-spacing: -0.012em;
  line-height: 1.2;
  margin: 0 0 0.3rem;
  overflow-wrap: anywhere;
}
.card__title a { color: inherit; text-decoration: none; }
.card__title a::after { content: ''; position: absolute; inset: 0; }
.card__title a:hover { color: var(--signal-ink); }

.card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.75rem;
  font-size: var(--step--1);
  color: var(--ink-2);
  margin-bottom: 0.4rem;
}
.card__meta > span { display: inline-flex; align-items: center; gap: 0.3rem; }

/*
 * The zone label. Quiet on purpose: on a listing of Ontario events every line
 * would read "EDT", and a label repeated forty times is noise until the one
 * line that says "MDT" needs to stand out - which it does by being different,
 * not by being loud.
 */
.tz {
  margin-left: 0.3em;
  font-size: 0.85em;
  font-variant-numeric: normal;
  letter-spacing: 0.02em;
  color: var(--ink-3);
}

/* Added by app.js, only for a reader whose clock disagrees with the venue's. */
.tz-yours {
  display: block;
  margin-top: 0.15rem;
  font-size: var(--step--1);
  color: var(--ink-3);
}
.card__meta svg { width: 14px; height: 14px; flex: none; color: var(--ink-3); }

.card__excerpt {
  font-size: var(--step--1);
  color: var(--ink-2);
  margin: 0 0 0.55rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-clamp: 2;
  overflow: hidden;
}

.card__tags { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.card__thumb {
  grid-column: 1 / -1;
  order: -1;
  margin: -0.9rem -1rem 0.9rem -0.85rem;
  aspect-ratio: 16 / 7;
  overflow: hidden;
  background: var(--surface-2);
}
.card__thumb img { width: 100%; height: 100%; object-fit: cover; }

@media (min-width: 700px) {
  .card { grid-template-columns: 74px 132px minmax(0, 1fr); }
  .card--nothumb { grid-template-columns: 74px minmax(0, 1fr); }
  .card__thumb {
    grid-column: auto;
    order: 0;
    margin: 0;
    aspect-ratio: 4 / 3;
    border-radius: 3px;
    align-self: start;
  }
}

/* --------------------------------------------------------------------------
   Tags
   -------------------------------------------------------------------------- */

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.22rem 0.5rem;
  border-radius: 2px;
  background: var(--surface-2);
  color: var(--ink-2);
  border: 1px solid var(--line-2);
  text-decoration: none;
}
.tag--free { background: var(--amber-bg); border-color: #eccd88; color: var(--amber-ink); }
.tag--now { background: var(--ink); border-color: var(--ink); color: #fff; }
.tag--cancelled { background: #fdecea; border-color: #f0b9b3; color: #8c1d13; }
.tag--cat { background: var(--surface); border-color: var(--line); color: var(--ink-2); }
.tag--cat::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--tag-color, var(--line-default));
}
a.tag:hover { border-color: var(--ink-3); color: var(--ink); }

/* --------------------------------------------------------------------------
   Departure board - the weekend signature
   -------------------------------------------------------------------------- */

.board {
  background: var(--ink);
  border-radius: var(--radius-lg);
  padding: clamp(1rem, 0.6rem + 1.6vw, 1.75rem);
  color: #fff;
}
.board__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.1rem;
}
.board__head h2 { color: #fff; font-size: var(--step-2); }
.board__head .eyebrow { color: rgba(255, 255, 255, 0.55); margin: 0 0 0.35rem; }
.board__head .eyebrow::after { background: rgba(255, 255, 255, 0.18); }
.board__head a { color: var(--amber); font-weight: 600; font-size: var(--step--1); }

.board__grid {
  display: grid;
  gap: 1px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius);
  overflow: hidden;
}
@media (min-width: 820px) {
  .board__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.board__day { background: var(--ink); padding: 0.9rem; }
.board__day h3 {
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: 0.75rem;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.board__day h3 span { color: rgba(255, 255, 255, 0.45); font-variant-numeric: tabular-nums; letter-spacing: 0.06em; }

.board__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.1rem; }

.board__row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: 0.7rem;
  padding: 0.5rem 0;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
  align-items: baseline;
}
.board__row:first-child { border-top: 0; }
.board__time {
  font-family: var(--mono);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--amber);
  letter-spacing: 0.02em;
}
.board__link { color: #fff; text-decoration: none; font-size: 0.9375rem; line-height: 1.3; font-weight: 500; }
.board__link:hover { color: var(--amber); text-decoration: underline; }
.board__where { display: block; font-size: 0.75rem; color: rgba(255, 255, 255, 0.5); margin-top: 0.1rem; }
.board__empty { color: rgba(255, 255, 255, 0.45); font-size: 0.875rem; padding: 0.5rem 0; }

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */

.hero { padding-block: clamp(1.75rem, 1rem + 3vw, 3.25rem) clamp(1.25rem, 1rem + 2vw, 2rem); }
.hero h1 { font-size: var(--step-4); font-weight: 800; font-stretch: 115%; letter-spacing: -0.03em; max-width: 16ch; }
.hero .lede { margin-top: 0.85rem; }
.hero__search { margin-top: 1.5rem; max-width: 560px; }
.hero__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  margin-top: 1rem;
  font-size: var(--step--1);
  color: var(--ink-2);
}
.hero__stats b { font-variant-numeric: tabular-nums; color: var(--ink); }

/* --------------------------------------------------------------------------
   Region legend
   -------------------------------------------------------------------------- */

.legend { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; font-size: var(--step--1); color: var(--ink-2); }
.legend span { display: inline-flex; align-items: center; gap: 0.4rem; }
.legend i { width: 14px; height: 4px; border-radius: 2px; background: var(--region); display: block; }

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

.detail { display: grid; gap: clamp(1.25rem, 1rem + 2vw, 2.5rem); align-items: start; }
@media (min-width: 900px) { .detail { grid-template-columns: minmax(0, 1fr) 330px; } }

/* Was the event hero: a 21:9 frame with object-fit:cover, declaring
   width="1200" height="514" for a set of files measured at everything from
   420 x 280 to 9,083 x 4,500. Event pages use .photoshot now, the same banner
   the place pages use, with the whole photograph one click away. */

/* The numbers a reader wants before the prose.
   A venue page answers "how do I get there" and "can I park", and the counts
   are the fastest form of that answer - so they sit above the lists rather
   than being something you arrive at by reading. */
.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.75rem;
  margin: 0;
  padding: 0.9rem 1rem;
  border: 1px solid var(--line);
  border-left: 4px solid var(--region, var(--line-default));
  border-radius: var(--radius);
  background: var(--surface);
}
.figures dt {
  font-size: var(--step--1);
  color: var(--ink-3);
  line-height: 1.3;
}
.figures dd {
  margin: 0.15rem 0 0;
  font-family: var(--display);
  font-size: var(--step-2);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
}

/* Map pins, doubling as the legend under it - one rule, so the dot beside
   "Bus stop" cannot drift from the dot on the map. */
/* Big enough to find on a busy basemap: an OSM tile at this zoom is already
   full of small coloured icons, and a 14px dot among them is camouflage. */
.venuepin {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(18, 33, 43, 0.55);
  vertical-align: -2px;
}
.venuepin--venue   { width: 22px; height: 22px; background: var(--signal); border-width: 3px; }
.venuepin--bus     { background: #1b6ca8; }
.venuepin--rail    { background: #6e44ff; }
.venuepin--parking { background: var(--amber); }

.maplegend { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 1rem; }

/* Provenance, in one block rather than as footnotes.
   The transit data is ODbL and attribution is a condition of using it, not a
   courtesy - and "where did you get this" is a fair question from a reader as
   well as from a licence. */
.sourcebox {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  padding: 1.1rem 1.25rem;
}
.sourcebox__list { margin: 0.5rem 0 0; padding: 0 0 0 1.1rem; color: var(--ink-2); font-size: var(--step--1); }
.sourcebox__list li + li { margin-top: 0.5rem; }
.sourcebox__list strong { color: var(--ink); }

/* The venue index. A list, not a card grid: these rows are a name, a place
   and three numbers, and a card would be mostly padding. */
.venuelist { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
.venuelist__item {
  padding: 0.9rem 1rem;
  border: 1px solid var(--line);
  border-left: 4px solid var(--region, var(--line-default));
  border-radius: var(--radius);
  background: var(--surface);
}
.venuelist__item a { text-decoration: none; }
.venuelist__item a:hover { text-decoration: underline; }
.venuelist__where { margin: 0.2rem 0 0; }
.venuelist__facts { margin: 0.5rem 0 0; display: flex; flex-wrap: wrap; gap: 0.35rem; }
/* Getting here.
   The one part of a venue page a reader uses standing on a pavement with a
   phone, so it is a list of plain rows rather than a card grid: stop name,
   how far, and the routes as the little numbered plates they are on the sign
   itself. The numbers are the scannable part, so they carry the weight. */
.stops { display: grid; gap: 1.1rem; }
.stops__mode {
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 0.35rem;
}
.stops__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.1rem; }
.stops__list > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.6rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--line-2);
}
.stops__list > li:first-child { border-top: 0; }
.stops__name { font-weight: 600; }
.stops__walk { color: var(--ink-3); font-size: var(--step--1); }
.stops__routes { display: inline-flex; flex-wrap: wrap; gap: 0.3rem; margin-left: auto; }

/* A route number, set the way it appears on the stop pole. */
.route {
  display: inline-block;
  min-width: 2rem;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--surface);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: var(--step--1);
  text-align: center;
  color: var(--ink);
}
/* Imported article text.
   These paragraphs are the only long-form reading on the site, so they get
   measure, leading and a lead paragraph of their own rather than inheriting
   the listing's tighter defaults. */
.prose { max-width: 68ch; color: var(--ink-2); line-height: 1.75; }
.prose p { margin: 0 0 1.05em; }
.prose p:last-child { margin-bottom: 0; }
/* The opening sentence is the one a reader decides on, and for an imported
   description it is also the definition - so it is set slightly larger and in
   the darker ink. */
.prose > p:first-of-type { font-size: 1.0625em; color: var(--ink); }
.prose h2, .prose h3 { color: var(--ink); margin: 1.6em 0 0.5em; }
.prose h2 { font-size: var(--step-1); }
.prose h3 { font-size: var(--step-0); }
.prose ul, .prose ol { padding-left: 1.2em; margin: 0 0 1em; }
.prose li + li { margin-top: 0.3em; }
.prose a { color: var(--signal-ink); }

/* The ticket panel repeats the route bar so the region stays legible. */
.ticket {
  background: var(--surface);
  border: 1px solid var(--line);
  border-top: 5px solid var(--region, var(--line-default));
  border-radius: var(--radius);
  padding: 1.1rem;
  position: sticky;
  top: 78px;
}
.ticket dl { margin: 0; display: grid; gap: 0.85rem; }
.ticket dt {
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.15rem;
}
.ticket dd { margin: 0; font-size: 0.9375rem; }
/* The same card away from the two-column event page, where sticking to the
   viewport has nothing to stick against. */
.ticket--plain { position: static; top: auto; }
.ticket__actions { display: grid; gap: 0.5rem; margin-top: 1.1rem; }
.ticket__source {
  margin-top: 1rem;
  padding-top: 0.85rem;
  border-top: 1px dashed var(--line-2);
  font-size: 0.75rem;
  color: var(--ink-3);
}

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

.mapshell { display: grid; gap: 0; border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; background: var(--surface); }
@media (min-width: 900px) { .mapshell { grid-template-columns: 360px minmax(0, 1fr); } }

.mapshell__list { max-height: 72vh; overflow-y: auto; padding: 0.75rem; border-bottom: 1px solid var(--line); }
@media (min-width: 900px) { .mapshell__list { border-bottom: 0; border-right: 1px solid var(--line); } }

.mapshell__canvas { height: 52vh; min-height: 340px; }
@media (min-width: 900px) { .mapshell__canvas { height: 72vh; } }

/* The venue map. Shorter than the events map: this one is an illustration
   beside a list, not the page itself. */
.mapshell--venue { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) { .mapshell--venue { grid-template-columns: minmax(0, 1fr); } }
.mapshell--venue .mapshell__canvas { height: 42vh; min-height: 300px; }
@media (min-width: 900px) { .mapshell--venue .mapshell__canvas { height: 46vh; } }
.mapshell--venue #venue-map { width: 100%; height: 100%; background: var(--surface-2); }

#map { width: 100%; height: 100%; background: var(--surface-2); }
.leaflet-container { font: inherit; }

.pin {
  width: 16px; height: 16px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  background: var(--region, var(--signal));
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}
.pin--free { background: var(--amber); }

/* The map's side rail is a dense index, not a card list: one line per event. */
.maplist { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }

.maprow {
  border-left: 4px solid var(--region, var(--line-default));
  padding: 0.55rem 0.7rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line-2);
}
.maprow:hover { background: var(--surface-2); }
.maprow__link {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 0.9375rem;
  line-height: 1.25;
  color: var(--ink);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.maprow__link:hover { color: var(--signal-ink); text-decoration: underline; }
.maprow__meta { display: block; font-size: 0.75rem; color: var(--ink-2); margin-top: 0.15rem; }

.pop { font-family: var(--body); min-width: 190px; }
.pop b { font-family: var(--display); font-size: 0.9375rem; display: block; margin-bottom: 0.2rem; }
.pop .pop__meta { font-size: 0.75rem; color: var(--ink-2); }
.pop a { display: inline-block; margin-top: 0.5rem; font-weight: 600; font-size: 0.8125rem; }

/* --------------------------------------------------------------------------
   Pagination, notices, forms
   -------------------------------------------------------------------------- */

.pager { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; justify-content: center; margin-top: 1.5rem; }
.pager a, .pager span {
  min-width: 42px; min-height: 42px;
  display: inline-flex; align-items: center; justify-content: center;
  padding-inline: 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  color: var(--ink-2);
}
.pager a:hover { border-color: var(--ink); color: var(--ink); }
.pager [aria-current='page'] { background: var(--ink); border-color: var(--ink); color: #fff; font-weight: 700; }
.pager .is-disabled { opacity: 0.4; }

.notice {
  border: 1px solid var(--line);
  border-left: 5px solid var(--signal);
  background: var(--surface);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
}
.notice--warn { border-left-color: var(--amber); }
.notice--error { border-left-color: #c0392b; }

.empty {
  text-align: center;
  padding: clamp(2rem, 1.5rem + 3vw, 4rem) 1rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.empty h2 { font-size: var(--step-1); margin-bottom: 0.5rem; }

.field { display: grid; gap: 0.35rem; margin-bottom: 1rem; }
.field label { font-weight: 600; font-size: var(--step--1); }
.field .hint { font-size: 0.75rem; color: var(--ink-3); }
.field input[type='text'],
.field input[type='email'],
.field input[type='url'],
.field input[type='datetime-local'],
.field select,
.field textarea {
  width: 100%;
  min-height: 44px;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.field textarea { min-height: 140px; resize: vertical; }
.field :is(input, select, textarea):focus-visible { border-color: var(--signal); outline-offset: 0; }
.field--error :is(input, select, textarea) { border-color: #c0392b; }
.field__error { color: #a8271b; font-size: var(--step--1); }

.checkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 0.4rem; }
.checkgrid label { display: flex; gap: 0.5rem; align-items: center; font-size: var(--step--1); padding: 0.4rem 0.5rem; border: 1px solid var(--line-2); border-radius: var(--radius); background: var(--surface); cursor: pointer; }
.checkgrid label:hover { border-color: var(--ink-3); }
.checkgrid input { width: 18px; height: 18px; accent-color: var(--signal); }

.honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* --------------------------------------------------------------------------
   Link grids (internal linking blocks)
   -------------------------------------------------------------------------- */

.linkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 0.4rem; list-style: none; margin: 0; padding: 0; }
.linkgrid a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--line-2);
  border-left: 3px solid var(--region, var(--line-default));
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
  color: var(--ink);
  font-size: var(--step--1);
  font-weight: 600;
}
.linkgrid a:hover { border-color: var(--ink-3); border-left-color: var(--region, var(--line-default)); }
.linkgrid .n { color: var(--ink-3); font-variant-numeric: tabular-nums; font-weight: 500; }

/* --------------------------------------------------------------------------
   Newsletter signup
   -------------------------------------------------------------------------- */

.signup {
  display: grid;
  gap: 1rem;
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 5px solid var(--amber);
  border-radius: var(--radius-lg);
  padding: clamp(1rem, 0.8rem + 1vw, 1.5rem);
}
@media (min-width: 780px) {
  .signup { grid-template-columns: 1fr minmax(0, 22rem); gap: 2rem; }
}
.signup__copy h2 { margin-bottom: 0.35rem; }
.signup__copy .small { max-width: 46ch; }
.signup__form .searchbar { border-color: var(--ink); }

.signup--inverse {
  background: var(--ink);
  border-color: var(--ink);
  border-left-color: var(--amber);
  color: rgba(255, 255, 255, 0.78);
}
.signup--inverse h2 { color: #fff; }
.signup--inverse .eyebrow { color: rgba(255, 255, 255, 0.5); }
.signup--inverse .eyebrow::after { background: rgba(255, 255, 255, 0.18); }
.signup--inverse .muted { color: rgba(255, 255, 255, 0.62); }
.signup--inverse a { color: var(--amber); }

/* --------------------------------------------------------------------------
   Confirmation pages
   --------------------------------------------------------------------------
   Reached once, read once, then left. The job is to answer three questions in
   order - did it work, is there anything left for me to do, where do I go now -
   so the page is a single column with one primary action and nothing competing
   for the eye. The mark is an outlined SVG rather than a filled badge: at this
   size a solid disc of colour reads as an alert.
   -------------------------------------------------------------------------- */

.confirm { max-width: 46rem; }

.confirm__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-bottom: 1rem;
  border-radius: 50%;
  border: 2px solid currentColor;
  color: var(--signal);
  background: var(--signal-bg);
}
.confirm__mark svg { width: 24px; height: 24px; }
.confirm__mark--inbox { color: var(--amber-ink); background: var(--amber-bg); }
.confirm__mark--stop  { color: var(--ink-2);     background: var(--surface-2); }

.confirm__body .lede { margin-bottom: 0; }

.confirm__detail {
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 5px solid var(--signal);
  border-radius: var(--radius-lg);
  padding: clamp(1rem, 0.85rem + 0.6vw, 1.35rem);
}
.confirm__detailhead {
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 0.9rem;
}

.confirm__facts { margin: 0; display: grid; gap: 0.9rem; }
@media (min-width: 600px) {
  .confirm__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem 1.75rem; }
}
.confirm__facts dt {
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.15rem;
}
.confirm__facts dd { margin: 0; font-size: var(--step--1); line-height: 1.55; }

/* Wrapping, not overflowing: two 44px targets side by side do not fit a 360px
   phone, and a button that runs off the edge is a button nobody presses. */
.confirm__actions { display: flex; flex-wrap: wrap; gap: 0.6rem; }
/* --------------------------------------------------------------------------
   Sponsored slots
   --------------------------------------------------------------------------
   Deliberately quieter than an event card and always labelled, so an ad can
   never be mistaken for a listing.
   -------------------------------------------------------------------------- */

.ad {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  padding: 0.75rem;
}
.ad__label {
  font-family: var(--display);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 0.5rem;
}
.ad__link { display: block; text-decoration: none; color: inherit; }
.ad__image { width: 100%; height: auto; border-radius: 3px; }
.ad__text { display: grid; gap: 0.3rem; }
.ad__headline {
  font-family: var(--display);
  font-size: var(--step-1);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--ink);
}
.ad__body { font-size: var(--step--1); color: var(--ink-2); }
.ad__cta {
  justify-self: start;
  margin-top: 0.35rem;
  font-family: var(--display);
  font-size: var(--step--1);
  font-weight: 700;
  color: var(--signal-ink);
  border-bottom: 2px solid var(--signal);
  padding-bottom: 1px;
}
.ad__link:hover .ad__cta { color: var(--ink); border-bottom-color: var(--ink); }
.ad__by { margin: 0.5rem 0 0; font-size: 0.6875rem; color: var(--ink-3); }
.ad__pixel { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

.ad--event_sidebar { margin-top: 1rem; }
.ad--footer { margin-block: 1.5rem; }

/* --------------------------------------------------------------------------
   Breadcrumbs & footer
   -------------------------------------------------------------------------- */

.crumbs { font-size: var(--step--1); color: var(--ink-3); padding-block: 0.9rem; }
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0; padding: 0; }
.crumbs li + li::before { content: '/'; margin-right: 0.4rem; color: var(--line); }
.crumbs a { color: var(--ink-2); text-decoration: none; }
.crumbs a:hover { color: var(--ink); text-decoration: underline; }

.footer { background: var(--ink); color: rgba(255, 255, 255, 0.72); margin-top: clamp(2.5rem, 2rem + 3vw, 4rem); padding-block: 2.5rem 2rem; }
.footer a { color: #fff; text-decoration: none; }
.footer a:hover { text-decoration: underline; }
.footer__cols { display: grid; gap: 1.75rem; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.footer h2 { font-size: 0.6875rem; letter-spacing: 0.14em; text-transform: uppercase; color: rgba(255, 255, 255, 0.5); margin-bottom: 0.6rem; }
.footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.3rem; font-size: var(--step--1); }
.footer__base { margin-top: 2rem; padding-top: 1.25rem; border-top: 1px solid rgba(255, 255, 255, 0.14); font-size: var(--step--1); display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; justify-content: space-between; }

/* --------------------------------------------------------------------------
   Utility
   -------------------------------------------------------------------------- */

.row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }
.row--between { justify-content: space-between; }
.grid-2 { display: grid; gap: 1rem; }
@media (min-width: 760px) { .grid-2 { grid-template-columns: 1fr 1fr; } }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 0.5rem; }
.mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.5rem; }
.mt-4 { margin-top: 2rem; }

/* --------------------------------------------------------------------------
   Province chooser
   --------------------------------------------------------------------------
   The home page's first question. Each tile is a destination sign: the
   province's line colour runs full-height down the leading edge, the name
   carries the weight, and the counts sit underneath in tabular numerals so
   the column of numbers reads straight down the grid.

   Thirteen is a deliberate, fixed count, so the grid is sized to land on
   clean rows - four across on a desktop, two on a phone - rather than
   leaving one orphan tile on its own row.
   -------------------------------------------------------------------------- */

.provincegrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* "Newfoundland and Labrador" wraps to two lines where every other name fits
   on one, so the tiles are stretched to the tallest in their row - a ragged
   baseline across a grid this regular reads as a bug. */
.provincegrid li {
  display: flex;
}

.provincegrid a {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 100%;
  padding: 0.8rem 0.85rem 0.75rem 1.1rem;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
  color: var(--ink);
  overflow: hidden;
}

/* The colour bar is a child rather than a border-left so it can sit flush in
   the corner radius and thicken on hover without shifting the text. */
.provincegrid__bar {
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: var(--region, var(--line-default));
  transition: width 120ms ease;
}

.provincegrid a:hover,
.provincegrid a:focus-visible {
  border-color: var(--ink-3);
}

.provincegrid a:hover .provincegrid__bar,
.provincegrid a:focus-visible .provincegrid__bar {
  width: 7px;
}

.provincegrid__name {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--step-0);
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.provincegrid__meta {
  display: block;
  margin-top: 0.2rem;
  color: var(--ink-3);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 520px) {
  .provincegrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .provincegrid a { padding: 0.65rem 0.6rem 0.6rem 0.9rem; }
}

/* --------------------------------------------------------------------------
   Attraction card

   The same card, minus the thing it does not have. An event card leads with a
   ticket-stub date because the date is what a reader is deciding on; a place
   has no date, and an empty 74px column in its place is a hole rather than a
   rhythm. The image leads instead, and where there is no image the title does.
   -------------------------------------------------------------------------- */

.card--place { grid-template-columns: minmax(0, 1fr); }

@media (min-width: 700px) {
  .card--place { grid-template-columns: 148px minmax(0, 1fr); }
  .card--place.card--nothumb { grid-template-columns: minmax(0, 1fr); }
}

/* The category, standing in for the date block as the card's fixed signal.
   Colour comes from the category rather than the province here: on a page of
   places the useful question is what kind of thing each one is.

   Half colour, half ink, and the halves are measured rather than chosen. At
   72% colour, six of the nineteen category hues fell below WCAG AA against
   their own tint - Zoos at 3.28:1, Beaches at 3.49:1 - and this text is 11px
   uppercase, which needs the full 4.5:1 rather than the large-text 3.0. At 50%
   the worst case is 5.24:1 and every category clears it with room to spare.
   Enough colour survives to tell a Museum tag from a Beach tag at a glance,
   which is all the colour was ever doing here. */
.tag--place {
  background: color-mix(in srgb, var(--tag-color, var(--ink-3)) 12%, transparent);
  color: color-mix(in srgb, var(--tag-color, var(--ink-2)) 50%, var(--ink));
}

/* Visit length and admission, the two facts that decide whether a stop fits
   into a day. Tabular so a column of them lines up. */
.card__stat { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   Attraction page
   -------------------------------------------------------------------------- */

.placefacts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
  margin: 1.25rem 0;
  padding: 0.9rem 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.placefacts dt {
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.2rem;
}

.placefacts dd {
  margin: 0;
  font-size: var(--step--1);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.placehero {
  margin: 0 0 1.25rem;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-2);
  aspect-ratio: 16 / 9;
}
.placehero img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Attribution for a reused photo or description. Small, permanent, and never
   hidden behind a toggle: the licence is a condition of using the file. */
/* Attribution.
   A licence condition rather than a footnote, so it is set as a quiet aside
   against a rule instead of grey text that reads as boilerplate to skip.
   --ink-3 was 4.18:1 on white, which fails AA for text this size; --ink-2 is
   7.9:1 and the line is small enough that the extra weight does not shout. */
.placecredit {
  font-size: var(--step--2);
  color: var(--ink-2);
  margin: 0.6rem 0 1.25rem;
  padding-left: 0.7rem;
  border-left: 2px solid var(--line);
  line-height: 1.55;
  max-width: 68ch;
}
.placecredit a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.placecredit a:hover { color: var(--ink); }

/* The photograph on a place page or an event page.
   A banner of one consistent shape, because a page header that changes height
   with the proportions of its picture reads as broken - and these proportions
   are not ours to choose: Commons files run from panoramas to a 1:2.5 portrait
   of the CN Tower, and a municipal calendar serves anything from a 420-pixel
   thumbnail to a 9,083-pixel original. So the crop is deliberate, and the whole
   picture is one click away rather than lost. 16:9 rather than the 21:9 this
   started at, which took too thin a band out of anything tall.
   It was .placeshot while places were the only pages with a photograph. */
.photoshot {
  position: relative;
  display: block;
  margin: 0 0 1.25rem;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface-2);
  aspect-ratio: 16 / 9;
  cursor: zoom-in;
}
.photoshot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 240ms ease;
}

/* A tall photograph is cropped from nearer the top. The subject of a portrait
   shot - a tower, a waterfall, a lighthouse, a church spire - is almost never
   in the bottom half, and a centred crop of the CN Tower is a picture of the
   middle of a concrete shaft. */
.photoshot--portrait img { object-position: center 28%; }

/* The affordance. Without it the crop looks like the whole photograph and
   nobody thinks to click. */
.photoshot__hint {
  position: absolute;
  right: 0.65rem;
  bottom: 0.65rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.75rem;
  border-radius: 999px;
  /* Near-black at 82% against any photograph clears AA for white text without
     a second element behind it. */
  background: rgba(9, 18, 23, 0.82);
  color: #fff;
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: 0.01em;
  opacity: 0;
  transform: translateY(0.4rem);
  transition: opacity 180ms ease, transform 180ms ease;
  pointer-events: none;
}
.photoshot:hover img,
.photoshot:focus-visible img { transform: scale(1.03); }
.photoshot:hover .photoshot__hint,
.photoshot:focus-visible .photoshot__hint { opacity: 1; transform: none; }
.photoshot:focus-visible { outline: 3px solid var(--signal); outline-offset: 3px; }

/* Touch has no hover state to reveal it with, so it is simply always there. */
@media (hover: none) {
  .photoshot__hint { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .photoshot img,
  .photoshot__hint { transition: none; }
  .photoshot:hover img,
  .photoshot:focus-visible img { transform: none; }
}

/* The enlarged view.
   A native <dialog>, which brings focus trapping, Escape and focus return
   with it - all three of which a div would have to reimplement, and usually
   only two of which it does. */
.lightbox {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  border: 0;
  padding: 0;
  background: transparent;
  /* Overriding display means the closed state has to be restored by hand. */
  display: grid;
  place-items: center;
}
.lightbox:not([open]) { display: none; }
.lightbox::backdrop { background: rgba(8, 14, 18, 0.88); }

.lightbox__frame {
  position: relative;
  max-width: min(94vw, 1400px);
  /* Sized to its contents so a click anywhere else lands on the dialog and
     closes it. */
  justify-self: center;
}
.lightbox__frame img {
  display: block;
  max-width: 100%;
  max-height: 80vh;
  width: auto;
  height: auto;
  margin: 0 auto;
  border-radius: var(--radius);
  background: var(--surface-2);
}

.lightbox__close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 999px;
  background: rgba(9, 18, 23, 0.82);
  color: #fff;
  cursor: pointer;
}
.lightbox__close:hover { background: rgba(9, 18, 23, 0.95); }
.lightbox__close:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }

.lightbox__caption {
  margin: 0.7rem auto 0;
  max-width: 68ch;
  text-align: center;
  /* 12:1 even at the backdrop's worst case, with a white page showing
     through its 12% transparency. */
  color: #e8edea;
  font-size: var(--step--1);
  line-height: 1.5;
}
.lightbox__caption a { color: inherit; text-underline-offset: 2px; }

/* --------------------------------------------------------------------------
   Trip planner

   A GET form, so the plan is a URL somebody can send to whoever is coming.
   Laid out as one row where there is width and stacked where there is not -
   the fields are a sentence, "from here to there over N days", and breaking
   that sentence across a grid loses the shape of it.
   -------------------------------------------------------------------------- */

.tripform {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;
  padding: 1rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

@media (min-width: 860px) {
  .tripform { grid-template-columns: 1fr 1fr minmax(200px, 0.8fr); align-items: start; }
}

/* The interests block spans the whole form: it is one question about the trip
   rather than a property of either end of it. */
.tripform__leg--wide { grid-column: 1 / -1; }
.tripform__leg--wide .checkgrid { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); }
/* These are choices, not field labels: the uppercase micro-label the rest of
   the form uses is right above a control and wrong on a list of options. */
.tripform__leg--wide .checkgrid label {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
  font-size: var(--step--1);
}
.tripform__leg--wide legend .hint { text-transform: none; letter-spacing: 0; font-weight: 400; }

/* Each leg is a province and a city together, because that is one decision
   made in two steps rather than two decisions. The fieldset is what says so
   to a screen reader as well as to the eye. */
.tripform__leg {
  display: grid;
  gap: 0.6rem;
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.tripform__leg legend {
  padding: 0;
  font-family: var(--display);
  font-size: var(--step--1);
  font-weight: 700;
  color: var(--ink);
}

@media (min-width: 520px) {
  .tripform__leg--short { grid-template-columns: 1fr 90px; align-items: end; }
  .tripform__leg--short legend { grid-column: 1 / -1; }
  .tripform__leg--short .field--check,
  .tripform__leg--short .field--action { grid-column: 1 / -1; }
}

/* The checkbox-with-explanation pattern. The admin has its own copy in
   admin.css; this is the public one, and they are deliberately separate -
   the two stylesheets share no cascade and a shared class that only exists
   in one of them is how a control ends up unstyled on half the site. */
.tripform .switch {
  display: flex;
  gap: 0.55rem;
  align-items: flex-start;
  padding: 0.5rem 0.6rem;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: 3px;
  cursor: pointer;
}
.tripform .switch:has(input:focus-visible) { outline: 2px solid var(--signal); outline-offset: 1px; }
.tripform .switch span { font-size: var(--step--1); font-weight: 600; text-transform: none; letter-spacing: 0; }
.tripform .switch small { display: block; font-weight: 400; color: var(--ink-2); font-size: 0.75rem; }
.tripform .switch input { width: 17px; height: 17px; accent-color: var(--signal); flex: none; margin-top: 0.15rem; }

.tripform .field { display: grid; gap: 0.3rem; min-width: 0; }

.tripform label {
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.tripform select {
  width: 100%;
  padding: 0.55rem 0.6rem;
  font: inherit;
  font-size: var(--step--1);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: 3px;
}

.tripform select:focus-visible { outline: 2px solid var(--signal); outline-offset: 1px; }
.tripform .field--action .btn { width: 100%; }

/* A select you can type into.
   Same box, same border, same height as the selects beside it - the point is
   that it does not read as a different control, only as one that answers to
   the keyboard. The <select> stays in the DOM behind it as the form control
   and is what a browser without JavaScript shows. */
.combo { position: relative; }
.combo__input {
  width: 100%;
  padding: 0.55rem 0.6rem;
  font: inherit;
  font-size: var(--step--1);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: 3px;
}
.combo__input:focus-visible { outline: 2px solid var(--signal); outline-offset: 1px; }
.combo__select { display: none; }

.combo__list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  max-height: 16rem;
  overflow-y: auto;
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
}
.combo__option {
  padding: 0.4rem 0.5rem;
  border-radius: 3px;
  font-size: var(--step--1);
  cursor: pointer;
}
.combo__option.is-active,
.combo__option:hover { background: var(--signal-bg); color: var(--signal-ink); }
.combo__empty { padding: 0.5rem; font-size: var(--step--1); color: var(--ink-3); }
/* One day of the plan. The rule above each day is the strongest divider on the
   page, because the day is the unit a reader navigates by. */
.tripday { padding-top: 1.5rem; margin-top: 1.5rem; border-top: 2px solid var(--ink); }
.tripday:first-child { margin-top: 0; }

.tripday__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin-bottom: 0.35rem;
}

.tripday__drive {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: var(--step--1);
  color: var(--ink-2);
}
.tripday__drive svg { color: var(--ink-3); }
.tripday__sleep { color: var(--ink-2); }

/* No side rail on this one, so the map takes the whole width. Without this
   it inherits the 360px list column and renders at half width with dead space
   beside it. */
.mapshell--trip { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) { .mapshell--trip { grid-template-columns: minmax(0, 1fr); } }
.mapshell--trip .mapshell__canvas { min-height: 340px; }
.mapshell--trip #trip-map { height: 100%; min-height: 340px; border-radius: var(--radius); }

/* Numbered route pins, matching the order of the cards below the map. */
.trippin span {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--signal, #0e6e52);
  color: #fff;
  font-family: var(--display);
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.35);
}

/* Postal code entry sits above the city select in each leg of the trip form.
   Uppercase because that is how a postal code is written, and monospaced
   figures so M5V and M5U do not shimmer against each other. */
.tripform input[name$="_postal"] {
  width: 100%;
  padding: 0.55rem 0.6rem;
  font: inherit;
  font-size: var(--step--1);
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: 3px;
}
.tripform input[name$="_postal"]::placeholder { text-transform: none; color: var(--ink-3); }
.tripform .hint { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--ink-3); font-size: 0.75rem; }

/* Where to sleep at the end of a day.
   Quieter than the stop cards above it: this is a shortlist to check, not a
   recommendation, and the page should not pretend it knows which is good. */
.stayrow {
  margin-top: 0.9rem;
  padding: 0.75rem 0.9rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.stayrow__label {
  margin: 0 0 0.4rem;
  font-family: var(--display);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.stayrow__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.3rem; }
.stayrow__list > li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.6rem; font-size: var(--step--1); }
.stayrow__name { font-weight: 600; }
.stayrow__far { color: var(--ink-3); }

/* The name search above a places listing. One field and a button, sitting on
   the same grid as the filter chips below it. */
.poifilter { display: grid; gap: 0.6rem; grid-template-columns: minmax(0, 1fr); align-items: end; }
@media (min-width: 600px) { .poifilter { grid-template-columns: minmax(0, 1fr) auto; } }
.poifilter .field { margin-bottom: 0; }

/* The nearby block carries a dozen rows now rather than three, so on a wide
   screen it runs in two columns instead of a single long ladder down the
   article. The rows themselves are unchanged - same component, same shape as
   the transit list above it. */
/* The dense variant: a dozen rows rather than three.

   The transit list above it is one row per line with the routes pushed to the
   right edge, which works because that column is narrow and every row is the
   same height. Neither holds here. `margin-left: auto` in a two-column layout
   throws the badge into the middle of the page, detached from the row it
   belongs to, and a long hotel name wraps while its neighbours do not - so
   adjacent columns stop lining up and the block reads as broken.

   So each row is a fixed two-line shape instead: the name, then everything
   about it on one muted line underneath. Same height whatever the name does,
   nothing floating anywhere, and the pills stay the site's own pills. */
.stops--dense .stops__list > li {
  display: block;
  padding: 0.5rem 0;
}
.stops--dense .stops__name { display: block; margin-bottom: 0.15rem; }
.stops--dense .stops__walk,
.stops--dense .stops__routes {
  display: inline;
  margin-left: 0;
}
/* The separator between the distance and the first pill, drawn rather than
   typed so it disappears when there are no pills. */
.stops--dense .stops__routes .tag { margin-left: 0.35rem; vertical-align: 1px; }

@media (min-width: 720px) {
  /* display:block, not the grid the base rule sets - a grid container ignores
     `columns` entirely, which is why the first attempt at this stayed in one
     long ladder. */
  .stops--dense .stops__list { display: block; columns: 2; column-gap: 2.25rem; }
  .stops--dense .stops__list > li { break-inside: avoid; border-top: 1px solid var(--line-2); }
  /* Every row keeps its rule, the first one included. Dropping it from the
     first row only drops it from the first column - CSS columns cannot say
     "first in this column" - so the two columns then sit a pixel apart all
     the way down, which is exactly the kind of misalignment the eye finds
     without being able to name. */
}
