/* ==========================================================================
   HWB Impact coming-soon — base styles
   Tokens, page chrome (header / footer / copy), background layers, intro.
   ========================================================================== */

/* Fonts live in the theme at assets/fonts/ (this file is at assets/css/,
   so ../fonts/ resolves correctly). Until the woff2 files are added the
   fallback stacks below are used — see assets/fonts/README.md. */

@font-face { font-family: "PP Neue Montreal"; src: url("../fonts/PPNeueMontreal-Regular.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "PP Neue Montreal"; src: url("../fonts/PPNeueMontreal-Medium.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "PP Neue Montreal Mono"; src: url("../fonts/PPNeueMontrealMono-Light.woff2") format("woff2"); font-weight: 300; font-style: normal; font-display: swap; }
@font-face { font-family: "PP Neue Montreal Mono"; src: url("../fonts/PPNeueMontrealMono-Book.woff2") format("woff2"); font-weight: 450; font-style: normal; font-display: swap; }
@font-face { font-family: "PP Neue Montreal Mono"; src: url("../fonts/PPNeueMontrealMono-Medium.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }

:root {
  --bg: #0b0b0b;
  --fg: #ffffff;
  --muted: #8a8a8a;
  --line: rgba(255, 255, 255, 0.12);
  --navy: #003D7C;              /* NUS navy */
  --orange: #EF7C00;            /* NUS orange */
  --accent: var(--orange);
  --sans: "PP Neue Montreal", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: "PP Neue Montreal Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --gutter: clamp(20px, 4vw, 44px);
  --grid-cols: 12;                           /* columns in the structural grid */
  --grid-line: rgba(255, 255, 255, 0.06);     /* fainter than --line */
}

* { box-sizing: border-box; }
html, body { min-height: 100%; margin: 0; }
body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;            /* vertical scroll stays available on short viewports */
  user-select: none;             /* keeps drag-interaction with the field from selecting text */
  -webkit-user-select: none;
}
.kicker, footer { user-select: text; -webkit-user-select: text; }


/* ==========================================================================
   Page chrome — header, main grid, copy column, footer
   ========================================================================== */

/* Update: touch drives the pointer effects */
.impact { position: relative; z-index: 2; min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; padding: 0 var(--gutter); color: var(--fg); touch-action: pan-y pinch-zoom; }

/* Update: drag-to-orbit affordance, desktop only. The field is the
   background, so the whole page reads as grabbable; links and controls
   take their own cursor back. */
.impact { cursor: grab; }
.impact a, .impact button { cursor: auto; }
.impact.is-orbiting,
.impact.is-orbiting a,
.impact.is-orbiting button { cursor: grabbing; }

/* The theme stylesheet (Astra) sets colours directly on headings, paragraphs and
   links, which beats inheritance - on the live site that rendered the H1 grey.
   Re-assert the token colour on headings; the muted/ accent small text keeps its
   own colours below. */
.impact h1, .impact h2, .impact h3, .impact h4 { color: var(--fg); }

/* Update: countdown moved into the header */
.impact-head {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  flex-wrap: wrap;
  padding: 24px 0; border-bottom: 1px solid var(--line);
}
.impact-head-status { display: flex; align-items: center; gap: 20px; margin-left: auto; }
.impact-mark { font-size: 0.9rem; font-weight: 500; color: var(--fg); text-decoration: none; transition: color 0.15s ease; }
.impact-mark:hover { color: var(--accent); }

.kicker {
  font-family: var(--mono); font-weight: 450; font-size: 0.68rem;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted);
}
.kicker--accent { color: var(--accent); }

/* Update: content column sits closer under the header */
.impact-main {
  flex: 1; display: flex; align-items: center;
  padding: clamp(30px, 5vh, 62px) 0 clamp(44px, 8vh, 104px);
}
.impact-copy { max-width: 600px; }

/* Co-brand lockup (NUS crest + Health & Wellbeing). Sized to stay legible:
   the mark carries small type, so it never goes below ~150px wide. */
.impact-logo { display: block; width: clamp(180px, 25vw, 268px); margin: 0 0 12px; }
.impact-logo img { display: block; width: 100%; height: auto; }
.impact-title {
  font-size: clamp(2.4rem, 5.4vw, 4.1rem); font-weight: 500; color: var(--fg);
  letter-spacing: -0.032em; line-height: 1; margin: 16px 0 22px;
}
/* Update: lead's second sentence set on its own line */
.impact-lead {
  font-size: clamp(0.98rem, 1.1vw, 1.06rem); line-height: 1.7;
  color: var(--muted); margin: 0 0 34px; max-width: 56ch;
}
.impact-lead, .impact-lead * { color: var(--muted); }

.impact-list { margin: 0 0 34px; border-top: 1px solid var(--line); }
.impact-list > div {
  display: grid; grid-template-columns: 78px 1fr; gap: 16px;
  padding: 15px 0; border-bottom: 1px solid var(--line);
}
.impact-list dt {
  font-family: var(--mono); font-weight: 450; font-size: 0.66rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); padding-top: 4px;
}
.impact-list dd { margin: 0; font-size: 0.92rem; line-height: 1.6; color: var(--fg); }
.impact-list dd span { display: block; margin-top: 3px; font-size: 0.86rem; color: var(--muted); }

.impact-actions { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.impact-btn {
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--accent); color: #0b0b0b; border-radius: 2px;
  padding: 13px 22px; font-size: 0.88rem; font-weight: 500; text-decoration: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.impact-btn:hover { opacity: 0.88; transform: translateY(-1px); }

/* Update: added the disabled report call to action */
.impact .impact-btn--soon {
  appearance: none; font-family: inherit;
  background: rgba(255, 255, 255, 0.02);
  color: var(--muted);
  border: 1px dashed var(--line);
  padding: 12px 21px;
  cursor: not-allowed;
}
.impact .impact-btn--soon:hover,
.impact .impact-btn--soon:focus { opacity: 1; transform: none; }
.impact-btn-tag {
  font-family: var(--mono); font-weight: 450; font-size: 0.58rem;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: rgba(255, 255, 255, 0.34);
  border-left: 1px solid var(--line); padding-left: 10px;
}
.impact-link {
  font-size: 0.86rem; color: var(--fg); text-decoration: none;
  border-bottom: 1px solid var(--line); padding-bottom: 3px;
  transition: border-color 0.15s ease;
}
.impact-link:hover { border-color: var(--accent); }


/* ==========================================================================
   Report / history switch + flip

   Both faces share one grid cell, so flipping cannot change the page height.
   ========================================================================== */

.impact-toggle-row { display: flex; align-items: center; gap: 14px; margin: 0 0 26px; }

/* Update: switch state shown by text colour, solid fill removed */
.impact-switch {
  position: relative; display: inline-flex; padding: 3px;
  border: 1px solid var(--line); border-radius: 999px;
}
.impact-switch-btn {
  appearance: none; flex: 1 1 0; min-width: 118px; text-align: center; white-space: nowrap;
  background: none; border: 0; border-radius: 999px; padding: 7px 14px;
  font-family: var(--mono); font-weight: 450; font-size: 0.66rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
  cursor: pointer; position: relative; z-index: 1;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.24s ease;
}
.impact-switch-btn[aria-pressed="true"] { color: var(--fg); }
/* Hover only where a pointer can actually hover, so a tap does not leave a
   phantom hover state behind on touch. */
@media (hover: hover) {
  .impact-switch-btn:hover { color: rgba(255, 255, 255, 0.62); }
  .impact-switch-btn[aria-pressed="true"]:hover { color: var(--fg); }
}

/* Update: added barcode strip */
.impact-barcode {
  display: flex; align-items: flex-end; gap: 3px;
  height: 40px; margin: 0 0 32px;
}
.impact-barcode span {
  flex: 1 1 0; height: 100%;
  transform-origin: center bottom;
  transform: scaleY(0);
  background-color: rgba(255, 255, 255, 0.18);
  background-color: color-mix(in srgb, var(--accent) calc(var(--bc-lit, 0) * 100%), rgba(255, 255, 255, 0.18));
  transition: transform 0.5s cubic-bezier(0.22, 0.8, 0.3, 1), background-color 0.18s linear;
  transition-delay: var(--bc-delay, 0ms);
}
.impact-barcode.is-in span { transform: scaleY(var(--bc-h, 1)); }
/* Reveal stagger dropped once it has played, so hover answers immediately. */
.impact-barcode.is-live span { transition-duration: 0.12s; transition-delay: 0s; }

.impact-flip { display: grid; perspective: 1400px; }
.impact-face {
  grid-area: 1 / 1; backface-visibility: hidden;
  /* Update: faces top-aligned so content groups under the switch */
  display: flex; flex-direction: column;
  transition: transform 0.62s cubic-bezier(0.22, 0.8, 0.3, 1), opacity 0.4s ease;
}
/* A transform on both faces avoids an iOS Safari flicker mid-swap. */
.impact-face--report { transform: rotateY(0deg); }
.impact-face--history { transform: rotateY(180deg); }
.impact-flip.is-history .impact-face--report { transform: rotateY(-180deg); }
.impact-flip.is-history .impact-face--history { transform: rotateY(0deg); }

.impact-face-title {
  font-family: var(--mono); font-weight: 450; font-size: 0.68rem;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted);
  margin: 0 0 16px;
}

/* Timeline: a spine with a node per milestone. Each row draws its own segment,
   so the line still lands on the nodes when a row wraps to two lines. */

.impact-timeline {
  --tl-year: 64px;      /* year column */
  --tl-track: 24px;     /* node column, the spine runs down its centre */
  --tl-lead: 21px;      /* a row's top to its node centre */
  --tl-delay: 0.18s;    /* after the flip starts */
  list-style: none; margin: 0; padding: 0;
}

.impact-timeline li {
  position: relative;
  display: grid; grid-template-columns: var(--tl-year) var(--tl-track) minmax(0, 1fr);
  padding: 13px 0;
}

/* Track; the first and last rows are trimmed to start and end on a node. */
.impact-timeline li::before {
  content: ""; position: absolute; top: 0; bottom: 0; width: 1px;
  left: calc(var(--tl-year) + (var(--tl-track) / 2));
  background: var(--line);
}
.impact-timeline li:first-child::before { top: var(--tl-lead); }
.impact-timeline li:last-child::before { bottom: calc(100% - var(--tl-lead)); }

/* Drawn segment. */
.impact-timeline li::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 1px;
  left: calc(var(--tl-year) + (var(--tl-track) / 2));
  background: rgba(239, 124, 0, var(--tl-warm, 0.6));
  transform: scaleY(0); transform-origin: top;
  transition: transform 0.48s cubic-bezier(0.22, 0.8, 0.3, 1);
  transition-delay: calc(var(--tl-delay) + var(--d, 0ms));
}
.impact-timeline li:first-child::after { top: var(--tl-lead); }
.impact-timeline li:last-child::after { bottom: calc(100% - var(--tl-lead)); }

.impact-timeline-year {
  font-family: var(--mono); font-weight: 450; font-size: 0.66rem;
  letter-spacing: 0.12em; color: var(--muted); padding: 3px 10px 0 0;
}

.impact-timeline-node {
  width: 9px; height: 9px; border-radius: 50%; box-sizing: border-box;
  background: var(--bg); border: 1px solid var(--line);
  justify-self: center; margin-top: 3.5px;
  opacity: 0; transform: scale(0.35);
  transition: opacity 0.4s ease, transform 0.5s cubic-bezier(0.22, 0.8, 0.3, 1),
              border-color 0.4s ease, background-color 0.4s ease, box-shadow 0.4s ease;
  /* Lands as its own segment reaches it. */
  transition-delay: calc(var(--tl-delay) + var(--d, 0ms) + 0.2s);
}

.impact-timeline li p {
  margin: 0; font-size: 0.92rem; line-height: 1.6; color: var(--fg);
  opacity: 0; transform: translateY(4px);
  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.22, 0.8, 0.3, 1);
  transition-delay: calc(var(--tl-delay) + var(--d, 0ms) + 0.26s);
}

/* Per-row stagger and warmth ramp; add an entry when the history grows. */
.impact-timeline li:nth-child(1) { --d: 0ms; --tl-warm: 0.30; }
.impact-timeline li:nth-child(2) { --d: 55ms; --tl-warm: 0.36; }
.impact-timeline li:nth-child(3) { --d: 110ms; --tl-warm: 0.42; }
.impact-timeline li:nth-child(4) { --d: 165ms; --tl-warm: 0.48; }
.impact-timeline li:nth-child(5) { --d: 220ms; --tl-warm: 0.54; }
.impact-timeline li:nth-child(6) { --d: 275ms; --tl-warm: 0.60; }
.impact-timeline li:nth-child(7) { --d: 330ms; --tl-warm: 0.66; }
.impact-timeline li:nth-child(8) { --d: 385ms; --tl-warm: 0.72; }
.impact-timeline li:nth-child(9) { --d: 440ms; --tl-warm: 0.78; }
.impact-timeline li:nth-child(10) { --d: 495ms; --tl-warm: 0.84; }
.impact-timeline li:nth-child(11) { --d: 550ms; --tl-warm: 0.90; }
.impact-timeline li:nth-child(12) { --d: 605ms; --tl-warm: 0.96; }
.impact-timeline li:nth-child(13) { --d: 660ms; --tl-warm: 0.97; }

.impact-flip.is-revealed .impact-timeline li::after { transform: scaleY(1); }
.impact-flip.is-revealed .impact-timeline-node {
  opacity: 1; transform: scale(1);
  background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(239, 124, 0, 0.14);
}
.impact-flip.is-revealed .impact-timeline li p { opacity: 1; transform: none; }

/* Node hover. */
.impact-timeline li:hover .impact-timeline-node { box-shadow: 0 0 0 5px rgba(239, 124, 0, 0.2); }

/* Keyboard focus. */
.impact-switch-btn:focus-visible,
.impact-btn:focus-visible,
.impact-link:focus-visible,
.impact-mark:focus-visible,
.impact-logo:focus-visible,
.impact-foot a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ==========================================================================
   Countdown clock
   ========================================================================== */

.impact-countdown { margin: 0; }

.cd-clock { display: flex; align-items: flex-start; gap: 12px; }
.cd-unit { display: flex; flex-direction: column; gap: 4px; }
/* Divider between units. */
.cd-unit + .cd-unit { padding-left: 12px; border-left: 1px solid var(--line); }

.cd-digits { display: flex; font-family: var(--mono); font-weight: 450; }
.cd-digit {
  display: block; overflow: hidden;
  font-size: 1rem; height: 1.06em; line-height: 1.06;
}
.cd-digit-inner { display: block; font-variant-numeric: tabular-nums; }

.cd-label {
  font-family: var(--mono); font-weight: 450; font-size: 0.5rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
}

/* Seconds in the accent colour. */
.cd-unit--seconds .cd-digit-inner { color: var(--accent); }

/* Staggered on the intro only, so later ticks are not delayed by position. */
.cd-digit-inner.is-intro { animation: cdUnfold 0.5s cubic-bezier(0.22, 0.8, 0.3, 1) both; animation-delay: var(--cd-delay, 0ms); }
.cd-digit-inner.is-roll { animation: cdUnfold 0.42s cubic-bezier(0.22, 0.8, 0.3, 1) both; }

@keyframes cdUnfold {
  from { transform: translateY(0.9em); opacity: 0; }
  60% { opacity: 1; }
  to { transform: none; opacity: 1; }
}

/* Visually hidden but still read out. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}


/* ==========================================================================
   The field — full-bleed background layer

   The canvas is fixed behind everything; .impact-scrim darkens the copy side
   so the type stays legible without boxing the wordmark in.
   ========================================================================== */

#field { position: fixed; inset: 0; z-index: 0; width: 100%; height: 100%; display: block; }

/* Update: the sphere stands in for the wordmark on narrow viewports.
   It owns its box, which is the whole point - the gesture needs an
   element to belong to, and a full-bleed field never had one. */
.impact-sphere { display: none; }
.impact-sphere canvas { display: block; width: 100%; height: 100%; }

/* Update: added the coordinate tag */
.impact-sphere-tag {
  position: absolute; left: 0; top: 0;
  font-family: var(--mono); font-weight: 450; font-size: 0.58rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent);
  white-space: nowrap; pointer-events: none;
  opacity: 0; will-change: transform, opacity;
}

.impact-scrim {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(
      100deg,
      rgba(11, 11, 11, 0.92) 0%,
      rgba(11, 11, 11, 0.74) 26%,
      rgba(11, 11, 11, 0.3) 46%,
      rgba(11, 11, 11, 0.06) 64%,
      rgba(11, 11, 11, 0) 78%
    );
}

/* Update: grid runs across the full width, right edge bracketed */
.impact-grid {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  padding: 0 var(--gutter);
  background-image: linear-gradient(to right, var(--grid-line) 0 1px, transparent 1px);
  background-size: calc(100% / var(--grid-cols)) 100%;
  background-origin: content-box;
  background-clip: content-box;
}
.impact-grid::after {
  content: ""; position: absolute; top: 0; bottom: 0; right: var(--gutter); width: 1px;
  background: var(--grid-line);
}



/* ==========================================================================
   Footer
   ========================================================================== */

.impact-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap;
  border-top: 1px solid var(--line); padding: 20px 0 26px;
  font-family: var(--mono); font-weight: 450; font-size: 0.66rem;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
}
.impact-foot nav { display: flex; gap: 20px; flex-wrap: wrap; }
.impact-foot a { color: var(--muted); text-decoration: none; transition: color 0.15s ease; }
.impact-foot a:hover { color: var(--accent); }


/* ==========================================================================
   Fallbacks + intro
   ========================================================================== */

html.no-webgl #field { display: none; }
html.no-webgl body,
body:has(html.no-webgl) { background: var(--bg); }
html.no-webgl .impact-scrim {
  background: radial-gradient(120% 100% at 78% 30%, rgba(0, 61, 124, 0.55) 0%, rgba(11, 11, 11, 0) 62%);
}

.impact-copy > * { animation: impactIn 0.7s cubic-bezier(0.22, 0.8, 0.3, 1) both; }
@keyframes impactIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .impact-copy > * { animation: none; }

  /* Faces swap flat, by visibility. */
  .impact-face { transition: none; }

  .impact-face,
  .impact-flip.is-history .impact-face { transform: none; opacity: 0; visibility: hidden; }
  .impact-flip .impact-face--report { opacity: 1; visibility: visible; }
  .impact-flip.is-history .impact-face--report { opacity: 0; visibility: hidden; }
  .impact-flip.is-history .impact-face--history { opacity: 1; visibility: visible; }

  /* Timeline shown already drawn. */
  .impact-timeline li::after { transform: scaleY(1); transition: none; }
  .impact-timeline-node { opacity: 1; transform: none; transition: none; }
  .impact-timeline li p { opacity: 1; transform: none; transition: none; }

  /* Clock digits appear in place. */
  .cd-digit-inner.is-intro, .cd-digit-inner.is-roll { animation: none; }

  /* Barcode drawn in place. */
  .impact-barcode span { transition: none; transform: scaleY(var(--bc-h, 1)); }
}

@media (max-width: 960px) {
  .impact-main { padding: 32px 0; align-items: flex-start; }
  .impact-copy { max-width: none; }
  .impact-grid { --grid-cols: 6; }

  /* Update: no field on narrow viewports - the poster gradient stands in.
     Portrait cannot show the wordmark: it sits behind full-width copy, under
     a scrim that must stay dark exactly where that copy is. Measured at about
     a third of the light desktop gives it, against dots already only ~11
     levels above the background. */
  #field { display: none; }
  .impact-scrim {
    background: radial-gradient(
      130% 90% at 68% 20%,
      rgba(0, 61, 124, 0.5) 0%,
      rgba(11, 11, 11, 0) 64%
    );
  }

  /* The sphere takes the dead space under the CTA, with air on all
     sides. touch-action: none keeps the swipe here instead of handing
     it to the page scroll. */
  .impact-sphere {
    display: block;
    position: relative;
    flex: none;
    height: clamp(240px, 68vw, 320px);
    margin: 36px 0 0;
    touch-action: none;
    cursor: grab;
  }
  .impact-sphere:active { cursor: grabbing; }
}
@media (max-width: 520px) {
  .impact-list > div { grid-template-columns: 1fr; gap: 6px; }
  .impact-switch-btn { padding: 7px 12px; font-size: 0.62rem; letter-spacing: 0.1em; }
  /* Timeline keeps its three columns (spine and nodes included), just tighter. */
  .impact-timeline { --tl-year: 48px; --tl-track: 20px; }
}

@media (max-width: 700px) {
  /* Clock takes its own header line, and days + hours are enough at this size. */
  .impact-head-status { margin-left: 0; width: 100%; justify-content: space-between; gap: 12px; }
  .impact-countdown .cd-unit:nth-child(3),
  .impact-countdown .cd-unit:nth-child(4) { display: none; }
}
