/* ============================================================
   The Data Center Revolt — launch page
   American broadsheet × New Deal pamphlet. Four colours, two
   faces, hairline rules. No radii, no gradients, no shadows.
   ============================================================ */

:root {
  --ink:    #141C26;  /* near-black navy: dark bands, body text on cream */
  --paper:  #F2EDE4;  /* cream newsprint: light bands, map land fill */
  --signal: #C0161F;  /* firebrick: rules, numerals, route line, primary CTA */
  --rule:   #C9C1B4;  /* warm grey hairline: dividers, state borders */

  --serif: "Instrument Serif", Georgia, "Times New Roman", serif;
  --sans:  "Canva Sans", "Nunito Sans", "Figtree", system-ui, sans-serif;

  --measure: 62ch;
  --margin: clamp(1.25rem, 6vw, 6rem);
}

/* Display face — vendored, no external requests at runtime. */
@font-face {
  font-family: "Instrument Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/instrument-serif.woff2") format("woff2");
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/instrument-serif-italic.woff2") format("woff2");
}

/* TK: Canva Sans is proprietary to Canva and not distributable via
   Google Fonts. Once a licensed .woff2 is in hand, drop it at
   /fonts/canva-sans.woff2 and uncomment. Until then the stack
   degrades to Nunito Sans / Figtree / system-ui.

@font-face {
  font-family: "Canva Sans";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/canva-sans.woff2") format("woff2");
}
*/

* { margin: 0; padding: 0; box-sizing: border-box; }

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

body {
  font-family: var(--sans);
  font-size: clamp(1.0625rem, 1rem + 0.2vw, 1.125rem); /* 17–18px */
  line-height: 1.6;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.0;
  letter-spacing: 0;
  text-align: left;
}

img { max-width: 100%; display: block; }

::selection { background: var(--signal); color: var(--paper); }

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

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Bands --------------------------------------------------- */
/* Full-bleed alternating bands butted together, separated by a
   2px signal hairline. Rules do the work boxes do elsewhere. */

.band {
  padding: clamp(4rem, 9vw, 7.5rem) var(--margin);
  border-top: 2px solid var(--signal);
}
.band:first-of-type { border-top: none; }

.band--dark   { background: var(--ink);    color: var(--paper); }
.band--cream  { background: var(--paper);  color: var(--ink); }
.band--signal { background: var(--signal); color: var(--paper); }

.band__inner {
  max-width: 68rem;
  margin: 0 auto;
}

/* --- Eyebrows ------------------------------------------------ */

.eyebrow {
  font-size: 0.75rem;              /* 12px */
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--signal);
  margin-bottom: 2.25rem;
}
.eyebrow::after {
  content: "";
  display: block;
  width: 3.5rem;
  height: 2px;
  background: var(--signal);
  margin-top: 0.6rem;
}

/* --- Hero ---------------------------------------------------- */
/* Banner photograph over the ink underlay, desaturated and at low
   opacity so the headline reads clearly against it. */

.hero {
  position: relative;
  overflow: hidden;
  /* tighter than the default band rhythm so the banner doesn't
     eat vertical space — effectively crops the photo's bottom */
  padding-top: clamp(3rem, 6vw, 4.5rem);
  padding-bottom: clamp(2.25rem, 4.5vw, 3.5rem);
}
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  /* Dim ink gradient over the colour photograph — heaviest where
     the headline and subhead sit, lighter mid-frame so the barn,
     flag, and golden fields read through in colour. */
  background:
    linear-gradient(180deg,
      rgba(20, 28, 38, 0.78) 0%,
      rgba(20, 28, 38, 0.52) 45%,
      rgba(20, 28, 38, 0.82) 100%),
    url("assets/tx-panel.jpg") center 62% / cover no-repeat;
}
.hero .band__inner {
  position: relative;
}

.hero h1 .revolt {
  display: block;
  text-transform: uppercase;
  font-weight: 700;
  font-size: 1.28em;
  line-height: 1.05;
  color: var(--signal);
}

/* No rule above the headline: the page is embedded under a
   Squarespace header, so the hero opens straight with the H1. */

.hero h1 {
  font-size: clamp(3rem, 8.5vw, 6rem);  /* 48 → 96px */
  line-height: 0.98;
  max-width: 14ch;
}

.hero .byline {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(0.85rem, 1.7vw, 1.2rem); /* ~20% of the H1 */
  color: #ffffff;
  margin-top: 0.9rem;
}

.hero .subhead {
  font-size: clamp(1.125rem, 1rem + 0.9vw, 1.5rem);
  line-height: 1.45;
  max-width: var(--measure);
  margin-top: 2.5rem;
  opacity: 0.92;
}

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

.route-section .band__inner { max-width: 76rem; }

/* Tighter top padding than the default band rhythm — the map is
   the point of this section, so the title sits close above it. */
.route-section {
  padding-top: clamp(1.75rem, 3.5vw, 3rem);
}

/* Section title: centered, bold, set larger than the standard
   eyebrow, with the signal rule centered beneath it. */
.route-title {
  font-family: var(--sans);
  font-weight: 800;
  font-size: clamp(1.5rem, 1.1rem + 2vw, 2.4rem);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-align: center;
  color: var(--signal);
  margin-bottom: 1.5rem;
}
.route-title::after {
  content: "";
  display: block;
  width: 5rem;
  height: 2px;
  background: var(--signal);
  margin: 0.7rem auto 0;
}

.map-wrap {
  position: relative;
  /* At narrow widths the SVG bleeds past the text margins to
     stay legible — full viewport width. */
}

@media (max-width: 640px) {
  .map-wrap {
    /* full-bleed past the text margins; calc(100% + margins) rather
       than 100vw so a scrollbar never causes horizontal overflow */
    width: calc(100% + 2 * var(--margin));
    margin-left: calc(-1 * var(--margin));
  }
}

#map svg {
  display: block;
  width: 100%;
  height: auto;
}

.map-land    { fill: var(--paper); stroke: none; }
.map-borders { fill: none; stroke: var(--rule); }
.map-nation  { fill: none; stroke: var(--ink); }

.route-casing {
  fill: none;
  stroke: var(--paper);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.route-line {
  fill: none;
  stroke: var(--signal);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.route-stop { fill: var(--signal); }

/* The bus that rides the route as it draws (built in map.js). */
.route-bus .bus-body {
  fill: var(--signal);
  stroke: var(--ink);
  stroke-width: 0.6;
}
.route-bus .bus-window { fill: var(--paper); }
.route-bus .bus-wheel  { fill: var(--ink); }
.route-bus .bus-flag-field  { fill: var(--paper); }
.route-bus .bus-flag-stripe { fill: var(--signal); }
.route-bus .bus-flag-canton { fill: var(--ink); }

/* Draw-on: the one animation on the page. map.js measures the
   path, sets --route-len, and adds .map-drawn when the map
   scrolls into view. The reduced-motion block above zeroes these
   durations, so those users get the finished path instantly. */
/* The route draw itself is a Web Animations API animation driven
   from map.js (linear, 2.6s); only the dot/label fades live here. */

.map-label {
  font-family: var(--sans);
  fill: var(--ink);
  letter-spacing: 0.12em;
  /* paper halo keeps labels legible over coastline hairlines */
  stroke: var(--paper);
  stroke-width: 3px;
  stroke-linejoin: round;
  paint-order: stroke;
}
/* Tighter tracking and a lighter halo on the small stop labels
   keep them compact where the route bunches up (e.g. northern
   Virginia). */
.map-label--stop {
  letter-spacing: 0.05em;
  stroke-width: 2px;
}

/* Stop dots and labels fade in as the drawing line reaches them —
   map.js sets a per-stop transition-delay along the route. */
#map .route-stop,
#map .map-label {
  opacity: 0;
  transition: opacity 0.3s ease-out;
}
#map.map-drawn .route-stop,
#map.map-drawn .map-label { opacity: 1; }

.map-reset {
  position: absolute;
  top: 0;
  right: 0;
  font-family: var(--sans);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--ink);
  border-radius: 0;
  padding: 0.5rem 0.9rem;
  cursor: pointer;
}
.map-reset:hover {
  background: var(--ink);
  color: var(--paper);
}

/* --- Route stats ---------------------------------------------- */
/* "6000+ miles! 29+ stops! One Message!" beneath the map. The
   numbers spin up from zero when the row scrolls into view
   (reveal.js), then the message stamps in as the finale. */

.route-stats {
  margin-top: 2rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  column-gap: clamp(1.75rem, 4vw, 3.75rem);
  row-gap: 0.5rem;
  text-align: center;
}

.route-stats p {
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(1.6rem, 1.2rem + 2vw, 2.75rem);
  line-height: 1.15;
}

.route-stats .stat__num {
  color: var(--signal);
  font-size: 1.3em;
  /* keeps the row from jittering while the digits change */
  font-variant-numeric: tabular-nums;
}

/* Reveal: the stats rise in one after another while the numbers
   spin up; the message arrives last as the counts settle. */
.js .route-stats p {
  opacity: 0;
  transform: translateY(0.5em);
  transition: opacity 0.35s ease-out, transform 0.35s ease-out;
}
.js .route-stats.is-live p { opacity: 1; transform: none; }
.js .route-stats.is-live p:nth-child(2) { transition-delay: 0.25s; }
.js .route-stats.is-live p:nth-child(3) { transition-delay: 0.5s; }
.js .route-stats.is-live p:nth-child(4) { transition-delay: 1.2s; }

.map-caption {
  font-size: 0.8125rem;  /* 13px */
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink);
  opacity: 0.7;
  margin-top: 2rem;
}

/* --- The case ------------------------------------------------ */

/* Tighter than the default band rhythm — the photo carries the
   height here, so the band hugs it. */
.case {
  padding-top: clamp(3rem, 6vw, 5rem);
  padding-bottom: clamp(3rem, 6vw, 5rem);
}

.case__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 520px);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
}
@media (max-width: 900px) {
  .case__grid { grid-template-columns: 1fr; }
}

.case p {
  font-size: clamp(1.375rem, 1.1rem + 1.6vw, 2rem);
  line-height: 1.4;
  max-width: 46ch;
}
.case p + p { margin-top: 2rem; }

/* Emphasis: "unaccountable AI build out" flares signal red while
   a flat rule wipes in beneath it when the section scrolls into
   view (reveal.js adds .is-live). The gradient is a solid colour
   — it only exists to paint an animatable underline that follows
   line wraps. */
.case .emph {
  font-style: normal;
  font-weight: 700;
  color: var(--signal);
  background-image: linear-gradient(var(--signal), var(--signal));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 2px;
  transition: color 0.45s ease-out 0.3s, background-size 0.7s ease-out 0.3s;
}
.js .case:not(.is-live) .emph {
  color: inherit;
  background-size: 0% 2px;
}

.case__photo img {
  width: 100%;
  /* Square crop off the landscape original: the right side is
     trimmed (the man and the sign sit left of centre) so the
     photo stands taller without stretching the band. */
  aspect-ratio: 1 / 1;
  object-fit: cover;
  object-position: left center;
}
@media (max-width: 900px) {
  /* Stacked layout: let the photo keep its natural landscape
     shape rather than a tall square on a narrow screen. */
  .case__photo img { aspect-ratio: auto; }
}

/* --- Demands ------------------------------------------------- */

/* Demands run horizontally — three columns separated by hairline
   rules, numeral above each demand — to keep the band short. */

.demands {
  padding-bottom: clamp(2.25rem, 4.5vw, 3.25rem);
}

.demands h2 {
  font-size: clamp(2.25rem, 2rem + 2vw, 3.25rem); /* 40–52px */
  margin-bottom: 2.25rem;
  max-width: var(--measure);
}

.demands ol {
  list-style: none;
  counter-reset: demand;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.5rem, 3vw, 3rem);
}

.demands li {
  counter-increment: demand;
  border-left: 1px solid var(--rule);
  padding-left: clamp(1rem, 2vw, 1.75rem);
}
.demands li:first-child { border-left: none; padding-left: 0; }

.demands li::before {
  content: counter(demand);
  display: block;
  font-family: var(--serif);
  font-size: clamp(2.5rem, 2rem + 2vw, 3.5rem);
  line-height: 0.9;
  color: var(--signal);
  margin-bottom: 0.75rem;
}

.demands li p {
  font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.25rem);
  line-height: 1.45;
}

/* Stack back to a single column on narrow screens. */
@media (max-width: 720px) {
  .demands ol { grid-template-columns: 1fr; }
  .demands li {
    border-left: none;
    padding-left: 0;
    border-top: 1px solid var(--rule);
    padding-top: 1.5rem;
  }
  .demands li:first-child { border-top: none; padding-top: 0; }
}

/* --- Not for sale --------------------------------------------- */
/* Urgency block beneath the demands: three declarations in signal
   red that stamp in one after another when scrolled into view
   (reveal.js adds .is-live). The hidden initial state only
   applies when JavaScript is running (html.js), so the text is
   never lost without it. */

.notforsale {
  /* balanced against the demands/movement edge paddings below,
     so the stamps float in even white space */
  margin-top: clamp(5rem, 7.5vw, 6.5rem);
  text-align: center;
}

.notforsale p {
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(1.75rem, 1.3rem + 2.4vw, 3rem);
  line-height: 1.25;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  word-spacing: 0.14em;
  color: var(--signal);
}
.notforsale p + p { margin-top: 0.55em; }

.notforsale p em {
  font-style: italic;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.18em;
}

/* Stamp-in: each line drops from far overhead, over-compresses on
   impact, rebounds, and settles with an alternating tilt —
   staggered line by line like three strikes of a rubber stamp. */
.js .notforsale p { opacity: 0; }
.js .notforsale.is-live p {
  animation: stamp-in 0.6s cubic-bezier(0.3, 1.2, 0.4, 1) both;
}
.js .notforsale.is-live p:nth-child(1) { --tilt: -1.5deg; animation-delay: 0.05s; }
.js .notforsale.is-live p:nth-child(2) { --tilt: 1.1deg;  animation-delay: 0.6s; }
.js .notforsale.is-live p:nth-child(3) { --tilt: -1.1deg; animation-delay: 1.15s; }

@keyframes stamp-in {
  0%   { opacity: 0; transform: scale(2.4); }
  38%  { opacity: 1; transform: scale(0.9) rotate(var(--tilt, 0deg)); }
  62%  { transform: scale(1.07) rotate(var(--tilt, 0deg)); }
  80%  { transform: scale(0.98) rotate(var(--tilt, 0deg)); }
  100% { opacity: 1; transform: scale(1) rotate(var(--tilt, 0deg)); }
}

/* --- CRM form (Qomon embed) ---------------------------------- */
/* Sits at the end of the movement band, after the "not for sale"
   stamps. The embed renders into the inner div at its own height;
   the wrapper just gives it the band's rhythm, a form-appropriate
   measure, and full width on phones. */

.crm-form {
  margin: clamp(3rem, 6vw, 5rem) auto 0;
  max-width: 42rem;
  width: 100%;
}

/* Same treatment as the route title: bold, centered, signal red,
   with the short rule beneath. */
.crm-form__title {
  font-family: var(--sans);
  font-weight: 800;
  font-size: clamp(1.5rem, 1.1rem + 2vw, 2.4rem);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-align: center;
  color: var(--signal);
  margin-bottom: 0.5rem;
}
.crm-form__title::after {
  content: "";
  display: block;
  width: 5rem;
  height: 2px;
  background: var(--signal);
  margin: 0.7rem auto 0;
}
.crm-form .qomon-form,
.crm-form iframe {
  width: 100% !important;
  max-width: 100%;
}

/* --- Creed --------------------------------------------------- */
/* The closing creed: one still line on the signal band, framed by
   thin ink rules. Each sentence rises into place once when the
   band scrolls into view (reveal.js), then holds. */

.creed {
  padding: clamp(1.75rem, 3.5vw, 2.5rem) var(--margin);
  /* thin ink frame above and below the red band */
  border-top: 3px solid var(--ink);
  border-bottom: 3px solid var(--ink);
}

.creed__line {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  column-gap: clamp(1rem, 2.5vw, 2rem);
  row-gap: 0.4rem;
  text-align: center;
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(1.5rem, 1.2rem + 1.6vw, 2.4rem);
  line-height: 1.15;
}

.creed__line .sep {
  font-size: 0.45em;
  opacity: 0.65;
  align-self: center;
}

.js .creed__line > span {
  opacity: 0;
  transform: translateY(0.4em);
  transition: opacity 0.4s ease-out, transform 0.4s ease-out;
}
.js .creed.is-live .creed__line > span { opacity: 1; transform: none; }
.js .creed.is-live .creed__line > span:nth-child(2) { transition-delay: 0.2s; }
.js .creed.is-live .creed__line > span:nth-child(3) { transition-delay: 0.3s; }
.js .creed.is-live .creed__line > span:nth-child(4) { transition-delay: 0.5s; }
.js .creed.is-live .creed__line > span:nth-child(5) { transition-delay: 0.6s; }
.js .creed.is-live .creed__line > span:nth-child(6) { transition-delay: 0.8s; }
.js .creed.is-live .creed__line > span:nth-child(7) { transition-delay: 0.9s; }

/* --- The movement -------------------------------------------- */
/* Flows straight on from the demands band — no signal hairline,
   tightened vertical rhythm on both edges. */

.movement {
  border-top: none;
  padding-top: clamp(2.25rem, 4.5vw, 3.25rem);
  padding-bottom: clamp(2.25rem, 4.5vw, 3.25rem);
  /* clips the pre-reveal "not for sale" lines (they wait scaled
     up, which would otherwise widen the page on phones) */
  overflow: hidden;
}

.movement .lede {
  font-size: clamp(1.25rem, 1.1rem + 0.8vw, 1.5rem);
  line-height: 1.5;
  /* wide enough to set on two lines */
  max-width: 75ch;
  /* centered to align with the stamped declarations above */
  margin-inline: auto;
  text-align: center;
}

.photo-grid {
  margin-top: 3rem;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}
@media (max-width: 640px) {
  .photo-grid { grid-template-columns: 1fr; }
}

.photo-grid img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* --- Impacts / Stance / Stakes -------------------------------- */
/* Three compact position sections closing the page. Facts sit in
   a 3-up grid (2-up on tablet, 1-up on phones), each opened by a
   short signal rule; body copy is deliberately small so the bands
   stay tight. */

.band--compact {
  padding-top: clamp(2.75rem, 5.5vw, 4.5rem);
  padding-bottom: clamp(2.75rem, 5.5vw, 4.5rem);
}

/* The photo grid above flows straight into this band — no signal
   hairline, tight top edge. */
.impacts {
  border-top: none;
  padding-top: clamp(2.25rem, 4vw, 3rem);
}

.impacts h2,
.stakes h2 {
  font-size: clamp(1.85rem, 1.5rem + 1.6vw, 2.6rem);
  margin-bottom: clamp(2rem, 4vw, 3rem);
  max-width: var(--measure);
}

.fact-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.75rem, 3.5vw, 2.75rem) clamp(1.5rem, 3vw, 2.5rem);
}
@media (max-width: 900px) {
  .fact-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .fact-grid { grid-template-columns: 1fr; }
}

.fact::before {
  /* hairline column rule spanning the full item width */
  content: "";
  display: block;
  width: 100%;
  height: 1px;
  background: var(--signal);
  margin-bottom: 0.9rem;
}

.fact h3 {
  font-size: 1.55rem;
  line-height: 1.15;
  margin-bottom: 0.5rem;
}

.fact p {
  font-size: 0.9375rem; /* 15px — small on purpose */
  line-height: 1.55;
  opacity: 0.88;
}

/* American flag photograph behind the ink band at low opacity —
   the colours read through while the text stays fully legible
   (same treatment as the hero banner). Generous vertical padding
   lets the flag breathe around the copy. */
.stance {
  position: relative;
  overflow: hidden;
  padding-top: clamp(4rem, 8vw, 6.5rem);
  padding-bottom: clamp(4rem, 8vw, 6.5rem);
}
.stance::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("assets/premium_photo-1733306540683-d1c921643f35.avif")
    center / cover no-repeat;
  opacity: 0.22;
}
.stance .band__inner { position: relative; }

/* The section's only heading, so it carries more weight than a
   standard eyebrow. */
.stance .eyebrow {
  font-size: clamp(1.1rem, 1rem + 0.9vw, 1.6rem);
  font-weight: 800;
  margin-bottom: 1.75rem;
}
.stance .eyebrow::after { width: 5rem; }

.stance__cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 4.5vw, 4.5rem);
  max-width: 64rem;
}
@media (max-width: 700px) {
  .stance__cols { grid-template-columns: 1fr; gap: 1.5rem; }
}

.stance__cols p {
  font-size: 1.125rem;
  line-height: 1.65;
}
