/* ==========================================================================
   How the corridor filled in — the scrollytelling read.

   Only what story.html needs on top of components.css. The type scale, the
   rules, the banners and the buttons are all the shared ones.

   Two things drive the whole file:

   1. ONE LAYOUT, TWO SHAPES. There is no second DOM for the phone. The
      graphic rail is the first child of the story so that on a narrow screen
      it sits above the text and sticks to the top of the viewport while the
      steps scroll under it. At 900px the same two children become a grid and
      the rail moves to the right-hand column, full height. Nothing is
      duplicated, nothing is moved by script.

   2. THE PALETTE IS THE LEGEND. Teal is operational, amber under
      construction, violet proposed — the same meanings the map carries, so a
      reader who has used the map already knows how to read these charts. The
      money panel deliberately uses none of them: a tax figure is not a
      facility state, and colouring it from the status ramp would put a
      meaning in the palette the map then has to contradict. Its bars are ink
      on a sunken track, like money.html's.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The run
   -------------------------------------------------------------------------- */

.story {
  border-top: var(--rule-width) solid var(--rule);
  border-bottom: var(--rule-width) solid var(--rule);
  background: var(--bg);
}

.story__inner {
  display: block;
}

/* --------------------------------------------------------------------------
   The graphic rail

   Sticky rather than fixed, so it releases at the end of the run instead of
   floating over the closing sections. It carries its own background because
   the steps scroll underneath it on a phone.
   -------------------------------------------------------------------------- */

.story__graphic {
  position: sticky;
  top: var(--header-height);
  z-index: 3;
  background: var(--bg);
  /* A little air under the caption: the steps scroll beneath this rail on a
     phone, and the cut wants to land in whitespace rather than through the
     first line of the paragraph below. */
  padding-block: var(--space-3);
  margin-inline: calc(var(--space-4) * -1);
  padding-inline: var(--space-4);
  border-bottom: var(--rule-width) solid var(--rule);
}

/* .container's own gutter widens at 768px; the rail's bleed has to widen with
   it or a strip of scrolling text shows through beside the pinned graphic. */
@media (min-width: 768px) and (max-width: 899px) {
  .story__graphic {
    margin-inline: calc(var(--space-6) * -1);
    padding-inline: var(--space-6);
  }
}

.story-panels {
  position: relative;
  /* A definite height is what lets the panels stack on top of one another and
     cross-fade. Tuned so a 375 x 667 phone keeps roughly half the viewport
     for the words, which is the half carrying the numbers. */
  height: 40vh;
  min-height: 208px;
}

@media (min-width: 560px) {
  .story-panels {
    height: 44vh;
  }
}

.story-panel {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-base) var(--ease-out),
    visibility 0s linear var(--duration-base);
}

.story-panel.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--duration-base) var(--ease-out), visibility 0s;
}

.story-panel__frame {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.story-svg {
  width: 100%;
  height: 100%;
  max-height: 100%;
  /* preserveAspectRatio="meet" keeps every drawn thing inside the viewBox, so
     clipping here can only ever catch a rounding error — and it guarantees no
     panel can push the page sideways on a 375px phone. */
  overflow: hidden;
}

.story-panel__caption {
  flex: 0 0 auto;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--text-muted);
  min-height: 2.2em;
}

/* --------------------------------------------------------------------------
   Legend

   Above the panels rather than inside one, because four of the five panels
   use the same three colours and repeating the key in each of them would be
   four chances for one copy to drift.
   -------------------------------------------------------------------------- */

.story-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  list-style: none;
  margin: 0 0 var(--space-2);
  padding: 0;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--text-muted);
}

@media (min-width: 700px) {
  .story-legend {
    gap: var(--space-2) var(--space-4);
    margin-bottom: var(--space-3);
  }
}

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

.story-legend__swatch {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-pill);
  flex: 0 0 auto;
}

.story-legend__swatch--operational {
  background: var(--status-operational);
}
.story-legend__swatch--construction {
  background: var(--status-construction);
}
.story-legend__swatch--proposed {
  background: var(--status-proposed);
}

.story-legend__count {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* --------------------------------------------------------------------------
   The map

   Districts are a flat tint so the dots carry all the colour. The emphasis
   attribute is set on the figure by story.js and drives four group opacities
   — not 244 circle opacities, which is what keeps the swap cheap.
   -------------------------------------------------------------------------- */

.story-map__district {
  fill: var(--bg-sunken);
  stroke: var(--bg);
  stroke-width: 2;
}

.story-map__outline {
  fill: none;
  stroke: var(--text-subtle);
  stroke-width: 2.5;
  stroke-linejoin: round;
}

.story-map__layer {
  transition: opacity var(--duration-base) var(--ease-out);
}

.story-map__dot--operational {
  fill: var(--status-operational);
}
.story-map__dot--construction {
  fill: var(--status-construction);
}
.story-map__dot--proposed {
  fill: var(--status-proposed);
}

.story-map__dot {
  fill-opacity: 0.85;
}

.story-map__ring {
  fill: none;
  stroke: var(--text);
  stroke-width: 3;
}

/* Emphasis modes. The unemphasised statuses stay visible rather than
   disappearing — the point of every map step is where the highlighted set
   sits relative to everything else. */
.story-panel[data-emphasis="early"] .story-map__layer--operational,
.story-panel[data-emphasis="early"] .story-map__layer--construction,
.story-panel[data-emphasis="early"] .story-map__layer--proposed {
  opacity: 0.3;
}
.story-panel[data-emphasis="early"] .story-map__layer--early {
  opacity: 1;
}

.story-panel[data-emphasis="all"] .story-map__layer--early {
  opacity: 0;
}

.story-panel[data-emphasis="construction"] .story-map__layer--early,
.story-panel[data-emphasis="proposed"] .story-map__layer--early {
  opacity: 0;
}

.story-panel[data-emphasis="construction"] .story-map__layer--operational,
.story-panel[data-emphasis="construction"] .story-map__layer--proposed {
  opacity: 0.3;
}

.story-panel[data-emphasis="proposed"] .story-map__layer--operational,
.story-panel[data-emphasis="proposed"] .story-map__layer--construction {
  opacity: 0.3;
}

/* The money panel is the one graphic that uses none of the status ramp, so
   the shared key would be actively misleading over it. Hidden rather than
   removed, so the panels below it do not jump when the step changes. */
.story__graphic[data-active-panel="money"] .story-legend {
  visibility: hidden;
}

/* --------------------------------------------------------------------------
   Charts

   Every bar starts at zero and every chart says what it is measuring in its
   own heading, because these panels swap under the reader and a chart that
   needs the paragraph next to it to be legible is not doing its job.
   -------------------------------------------------------------------------- */

.story-bar--operational {
  fill: var(--status-operational);
}
.story-bar--construction {
  fill: var(--status-construction);
}
.story-bar--proposed {
  fill: var(--status-proposed);
}
.story-bar--population {
  fill: var(--text-subtle);
}
.story-bar--dated {
  fill: var(--text-muted);
}
.story-bar--undated {
  fill: var(--bg-sunken);
  stroke: var(--text-subtle);
  stroke-width: 1;
}

/* No status colour on the money panel — see the note at the head of the
   file. Ink for what is collected, a hatch-free outline for what is not. */
.story-bar--money {
  fill: var(--bg-ink);
}
.story-bar--forgoes {
  fill: var(--text-subtle);
}

.story-chart__grid {
  stroke: var(--rule);
  stroke-width: 1;
}

.story-chart__rule {
  stroke: var(--border-strong);
  stroke-width: 1;
}

.story-chart__zero {
  stroke: var(--text-subtle);
  stroke-width: 2;
}

.story-chart__head {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  fill: var(--text);
}

.story-chart__tick {
  font-family: var(--font-mono);
  font-size: 10px;
  fill: var(--text-muted);
}

.story-chart__row-label {
  font-family: var(--font-sans);
  font-size: 11.5px;
  fill: var(--text);
}

.story-chart__value {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--weight-semibold);
  fill: var(--text);
}

.story-chart__big {
  font-family: var(--font-mono);
  font-size: 16px;
  font-weight: var(--weight-bold);
  fill: var(--text);
}

/* --------------------------------------------------------------------------
   The steps
   -------------------------------------------------------------------------- */

.story__steps {
  padding-block: var(--space-6) var(--space-8);
}

.story-step {
  padding-block: var(--space-6);
  border-top: var(--rule-width) solid var(--rule);
}

.story-step:first-child {
  border-top: 0;
  padding-top: var(--space-5);
}

.story-step__index {
  margin: 0 0 var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  color: var(--text-subtle);
}

.story-step__title {
  font-size: var(--text-2xl);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  margin: 0 0 var(--space-4);
}

.story-step .prose p {
  margin: 0 0 var(--space-4);
}

/* A hairline on the left of the active step. Enough to say "this one" without
   moving anything, which on a page where the reader is scrolling is the whole
   requirement. */
@media (min-width: 900px) {
  .story-step {
    border-top: 0;
    border-left: var(--rule-width) solid var(--rule);
    padding-left: var(--space-5);
    min-height: 78vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* The active step is marked by its rule going to full ink, not by dimming
       the others. Fading inactive text would drop it under 4.5:1, and a step
       a reader has just passed is still text they may want to re-read. */
    transition: border-color var(--duration-base) var(--ease-out),
      border-left-width var(--duration-base) var(--ease-out);
  }

  .story-step:first-child {
    padding-top: var(--space-6);
  }

  .story-step.is-active {
    border-left-color: var(--text);
    border-left-width: var(--rule-heavy);
    padding-left: calc(var(--space-5) - var(--rule-heavy) + var(--rule-width));
  }
}

/* --------------------------------------------------------------------------
   Facts

   A figure is never printed without the sentence that says what it counts,
   and a step is never printed without the file it came from. Both live in
   the same block so a later redesign cannot separate them.
   -------------------------------------------------------------------------- */

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

@media (min-width: 640px) and (max-width: 899px) {
  .story-facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.story-fact {
  border-top: var(--rule-heavy) solid var(--text);
  padding-top: var(--space-2);
}

.story-fact__value {
  margin: 0 0 var(--space-1);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text);
  overflow-wrap: anywhere;
}

.story-fact--hero .story-fact__value {
  font-size: var(--text-2xl);
}

.story-fact--gap {
  border-top-color: var(--border-strong);
}

.story-fact--gap .story-fact__value {
  color: var(--text-muted);
}

.story-fact__text {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

.story-fact__text code,
.story-note code {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

.story-note {
  margin: 0 0 var(--space-3);
  font-family: var(--font-serif);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--text);
}

/* What the record does not hold, marked so it cannot be skimmed past as
   though it were another finding. */
.story-note--unknown {
  border-left: var(--rule-heavy) solid var(--border-strong);
  padding-left: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.story-note__tag {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-kicker);
  text-transform: uppercase;
  color: var(--text);
  margin-right: var(--space-1);
}

.story-source {
  margin: var(--space-4) 0 0;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
  color: var(--text-subtle);
  overflow-wrap: anywhere;
}

.story-source__tag {
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-kicker);
  text-transform: uppercase;
  color: var(--text);
}

.story-source__sep {
  color: var(--border-strong);
}

/* --------------------------------------------------------------------------
   Desktop: two columns

   The rail goes right and the words go left, which puts the numbers on the
   side a left-to-right reader starts from. The graphic is placed by grid
   rather than moved by script, so the phone order — graphic first, above its
   text — is also the DOM order.
   -------------------------------------------------------------------------- */

@media (min-width: 900px) {
  .story__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
    gap: var(--space-7);
    align-items: start;
  }

  .story__graphic {
    grid-column: 2;
    grid-row: 1;
    top: calc(var(--header-height) + var(--space-4));
    height: calc(100vh - var(--header-height) - var(--space-6));
    display: flex;
    flex-direction: column;
    margin-inline: 0;
    padding-inline: 0;
    border-bottom: 0;
    padding-block: var(--space-4);
  }

  .story-panels {
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
  }

  .story__steps {
    grid-column: 1;
    grid-row: 1;
    padding-block: 0 var(--space-9);
  }

  .story-panel__caption {
    font-size: var(--text-sm);
  }
}

/* Where IntersectionObserver is unavailable the rail is unpinned and every
   panel is shown in sequence — an illustrated article rather than a broken
   scrollytelling one. */
.story--static .story__graphic {
  position: static;
  height: auto;
}

.story--static .story-panels {
  position: static;
  height: auto;
  min-height: 0;
  display: grid;
  gap: var(--space-6);
}

.story--static .story-panel {
  position: static;
  opacity: 1;
  visibility: visible;
}

.story--static .story-panel__frame {
  height: 44vh;
}

/* --------------------------------------------------------------------------
   The closing sections
   -------------------------------------------------------------------------- */

.story-gaps {
  margin: var(--space-5) 0 0;
  display: grid;
  gap: var(--space-4);
}

@media (min-width: 800px) {
  .story-gaps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.story-gaps__item {
  border-top: var(--rule-heavy) solid var(--border-strong);
  padding-top: var(--space-3);
}

.story-gaps__claim {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  color: var(--text);
}

.story-gaps__why {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

.story-do {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-5);
  grid-template-columns: 1fr;
}

@media (min-width: 800px) {
  .story-do {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.story-do__card h3 {
  margin-top: 0;
  font-size: var(--text-xl);
}

.story-do__card p:last-child {
  margin-bottom: 0;
}

.story-sources {
  list-style: none;
  margin: var(--space-5) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
  grid-template-columns: 1fr;
}

@media (min-width: 700px) {
  .story-sources {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.story-sources__item {
  border-top: var(--rule-width) solid var(--rule);
  padding-top: var(--space-3);
}

.story-sources__file {
  margin: 0 0 var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

.story-sources__what {
  margin: 0 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

.story-sources__meta {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--text-subtle);
  overflow-wrap: anywhere;
}

.story-sources__foot {
  margin-top: var(--space-5);
}

/* --------------------------------------------------------------------------
   Reduced motion

   The steps still land — the panels still swap on the same signal — they just
   swap instantly instead of crossing over. Nothing on this page depends on an
   animation to become readable.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .story-panel,
  .story-panel.is-active,
  .story-map__layer,
  .story-step {
    transition: none !important;
  }

}
