/* ════════════════════════════════════════════════════════════════════════════
   website_campaigns.css — the three surfaces a dated announcement can occupy.

   `utils/website_campaigns.py` decides WHAT is showing and WHERE (migration
   0015's `surface` column). This file draws it, and nothing here re-derives a
   date: every word, and the countdown, arrives already resolved from Jinja.

   ── Three surfaces, one campaign ──────────────────────────────────────────
     .wc-strip   a one-line ribbon under the fixed nav. Calm, always there
                 while the campaign runs. The default and the common case.
     .wc-band    an in-page card under the hero. Room for a sentence and a
                 button — the annual function, an exam notice.
     .wc-popup   a modal, shown once per visitor. The only one that interrupts.

   Exactly one is ever in the DOM: the server renders the block the winning
   campaign names, so there is no CSS here hiding a surface that lost.

   ── Three tones, expressed as tokens and not as three sets of rules ───────
   Every surface reads --wc-bg / --wc-ink / --wc-dim / --wc-accent, and the tone
   classes are the only place those are set. Adding a tone is one block; it
   cannot half-apply to two surfaces and miss the third.

   ── The focus ring is per-tone, and that is not a detail ──────────────────
   base/a11y.css draws the ring in --focus-ring, navy by default. Navy is right
   on paper and invisible on the navy surfaces, so `info` and `celebrate`
   re-declare it gold exactly as the template does for its own dark panels.
   `urgent` must NOT: its field IS gold, and a gold ring on gold is no ring at
   all. It keeps the navy default, which is 6.7:1 there.

   ── Phone first ───────────────────────────────────────────────────────────
   Everything below is written for a 360px screen and widened with min-width
   queries. The public site is the one page a parent reaches on their own phone
   before they have any login (audit 7.1), and an announcement that only works
   on a desktop announces nothing.
════════════════════════════════════════════════════════════════════════════ */

/* ── Tones ─────────────────────────────────────────────────────────────────
   --wc-ink is for the words that must be read; --wc-dim for the supporting
   line. Both are solid colours, never white-at-an-opacity: opacity on text
   silently un-does an AA token (audit 7.3, A11) and there is no reason to
   spend the contrast here. Measured on their own fields: ink >= 12:1,
   dim >= 6.7:1. */
.wc-info {
  --wc-bg: var(--navy);
  --wc-ink: #ffffff;
  --wc-dim: #cbd7e8;
  --wc-accent: var(--gold);
  --wc-cta-bg: var(--gold);
  --wc-cta-ink: var(--navy-deep);
  --wc-hair: rgba(255, 255, 255, .14);
  --focus-ring: var(--gold);
}

.wc-urgent {
  --wc-bg: var(--gold);
  --wc-ink: var(--navy-deep);
  --wc-dim: var(--navy);
  --wc-accent: var(--navy-deep);
  --wc-cta-bg: var(--navy-deep);
  --wc-cta-ink: #ffffff;
  --wc-hair: rgba(7, 32, 63, .18);
  /* No --focus-ring override on purpose — see the header. */
}

.wc-celebrate {
  --wc-bg: var(--navy-deep);
  --wc-ink: #ffffff;
  --wc-dim: #e3d3ab;
  --wc-accent: var(--gold);
  --wc-cta-bg: var(--gold);
  --wc-cta-ink: var(--navy-deep);
  --wc-hair: rgba(244, 163, 0, .22);
  --focus-ring: var(--gold);
}

/* The one flourish that separates a greeting from a notice: a slow sunrise
   behind the words. Only `celebrate` gets it, and it is a background — under
   prefers-reduced-motion it simply sits still, because nothing here moves. */
.wc-celebrate {
  background-image:
    radial-gradient(720px 240px at 18% -40%, rgba(244, 163, 0, .30), transparent 70%),
    radial-gradient(520px 200px at 88% 130%, rgba(245, 130, 31, .22), transparent 70%);
}

/* ── Shared bits ───────────────────────────────────────────────────────────
   Deliberately no "Announcement" eyebrow above the title, tempting as the
   site's Cinzel kicker is here. Every word on this website is the office's to
   edit (utils/website_content.py) and a label baked into the template would be
   the one string on the page they could not change. The campaign's own title
   is the kicker. */

/* The countdown. Rendered only when the end is close enough to matter — the
   template decides that, because a `same_dates` campaign can be 300 days from
   its end and "300 days left" is not news. */
.wc-days {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .3px;
  padding: 4px 11px;
  border-radius: var(--r-pill);
  color: var(--wc-ink);
  border: 1px solid var(--wc-hair);
  background: var(--wc-hair);
}

.wc-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex-shrink: 0;
  font-family: var(--sans);
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: .2px;
  /* 40px tall at this padding — above the 32px tap floor the public site
     committed to at 360px (audit 7.1), with room to spare because this is the
     one control the whole band exists to offer. */
  padding: 11px 20px;
  border-radius: var(--r-pill);
  background: var(--wc-cta-bg);
  color: var(--wc-cta-ink);
  transition: transform .22s var(--ease), box-shadow .22s var(--ease);
}

.wc-cta:hover {
  transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(0, 0, 0, .22);
}

/* ══════════════════════════════════════════════════════════════════════════
   1. STRIP — the ribbon under the nav
   ──────────────────────────────────────────────────────────────────────────
   In normal flow, NOT fixed. The nav above it is `position: fixed`, so the
   strip clears it with a margin exactly the nav's height (3px accent + 72px
   bar) and the hero — which carries that same 75px as padding for the same
   reason — drops its copy when a strip is present. That is the whole reason
   for the `wc-has-strip` class on <body>: two elements cannot both pay for
   the same 75px, and the alternative (measuring the header in JS and feeding
   it back as a custom property) is a resize listener and a layout shift to
   solve arithmetic that is already constant.

   Scrolls away with the page rather than sticking. A campaign that ends on its
   own does not also need to follow you down the page.
   ══════════════════════════════════════════════════════════════════════════ */
.wc-strip {
  /* The nav's 75px, plus the legal-entity ribbon above it when the office has
     that switched on (school_website.html). --legal-bar-h is 0px by default, so
     with the ribbon off this is still exactly the 75px described above. */
  margin-top: calc(75px + var(--legal-bar-h, 0px));
  background: var(--wc-bg);
  color: var(--wc-ink);
  border-bottom: 1px solid var(--wc-hair);
}

body.wc-has-strip .hero {
  padding-top: 0;
}

.wc-strip-in {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  max-width: 1180px;
  margin: 0 auto;
  padding: 10px 20px;
}

/* Hidden on a phone, where the message runs to three lines and a dot centred
   against all three reads as a bullet for a list that is not there. It earns
   its place only on the single centred line a desktop gets. */
.wc-strip-dot {
  display: none;
  width: 7px;
  height: 7px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--wc-accent);
}

/* Two lines, hard, and then the whole action row beneath — a ribbon that grows
   with its copy is not a ribbon. Measured at 360px: the seeded admissions
   campaign ran to three lines and stacked the countdown above the link, 125px
   of a 780px screen before a parent saw the school's name.

   `flex-basis: 100%` is what puts the countdown and the link together on the
   second row instead of letting the text shove them onto rows of their own.

   The clamp is a real editorial constraint, not just a crop: on a narrow phone
   a long title can fill both lines on its own and the message is then simply
   not read. That is the correct trade for THIS surface — a strip carries a
   headline and an action, and an office with a paragraph to deliver should pick
   the `card` surface, which is sized for one. The ellipsis says so honestly,
   and the call to action is the way to the rest. */
.wc-strip-text {
  flex: 1 1 100%;
  min-width: 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--wc-dim);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.wc-strip-text b {
  font-weight: 700;
  color: var(--wc-ink);
}

/* The strip's call to action is a link, not a button: it is one line of text
   and a chevron, so a filled pill would out-shout the ribbon it sits in. */
.wc-strip-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 32px — the public site's tap floor, reached with padding rather than a
     height so the text still centres when it wraps. */
  min-height: 32px;
  flex-shrink: 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--wc-ink);
  border-bottom: 1.5px solid var(--wc-accent);
}

.wc-strip-cta:hover {
  color: var(--wc-accent);
}

@media (min-width: 768px) {
  .wc-strip-dot {
    display: block;
  }

  .wc-strip-in {
    flex-wrap: nowrap;
    justify-content: center;
    padding: 10px 40px;
  }

  .wc-strip-text {
    flex: 0 1 auto;
    font-size: 14px;
    /* One line on a desktop, with an ellipsis rather than a second row: the
       strip's height is load-bearing (the hero's clearance assumes it).
       `display: block` is what switches the phone's two-line clamp off — a
       -webkit-box is the only display the clamp applies to, so restoring the
       normal box is both the off switch and the single-line layout. */
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   2. BAND — the in-page card, under the hero
   ══════════════════════════════════════════════════════════════════════════ */
.wc-band {
  background: var(--wc-bg);
  color: var(--wc-ink);
  padding: 26px 0;
  position: relative;
}

/* The same gold hairline the stats bar and the principal panel use to say
   "this is a deliberate dark band, not the page ending". */
.wc-band::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}

.wc-band-in {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 20px;
}

.wc-band-glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  flex-shrink: 0;
  border-radius: var(--r-md);
  font-size: 21px;
  color: var(--wc-cta-ink);
  background: var(--wc-accent);
}

/* The poster. On a phone it goes full width above the words, which is the only
   honest place for it — a 100px thumbnail beside a heading is neither a picture
   nor a layout. `aspect-ratio` + `object-fit` mean the office can upload the
   portrait poster they were given by a printer and the band still holds its
   shape; the crop is centred, so a poster with its title in the middle survives
   and one with it along the bottom edge does not. The settings screen shows the
   same crop, so what is approved there is what ships. */
.wc-band-poster {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--r-md);
  border: 1px solid var(--wc-hair);
  background: var(--wc-hair);
}

.wc-band-body {
  flex: 1;
  min-width: 0;
}

.wc-band-title {
  font-family: var(--serif);
  font-size: 21px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--wc-ink);
  margin: 7px 0 0;
}

.wc-band-msg {
  font-size: 14px;
  line-height: 1.65;
  color: var(--wc-dim);
  margin-top: 7px;
}

.wc-band-side {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

@media (min-width: 768px) {
  .wc-band {
    padding: 30px 0;
  }

  .wc-band-in {
    flex-direction: row;
    align-items: center;
    gap: 22px;
    padding: 0 40px;
  }

  /* Beside the words from 768px, where there is room for both. 260px is the
     narrowest a 16:9 poster stays readable at. (The decorative glyph is dropped
     by the template when a poster is present — a picture and an icon standing in
     for a picture are the same job done twice. Done there rather than with
     `:has()` here, so the fallback on an older phone browser is not "both".) */
  .wc-band-poster {
    flex: 0 0 260px;
    width: 260px;
  }

  .wc-band-title {
    font-size: 24px;
  }

  .wc-band-side {
    flex-direction: column;
    align-items: flex-end;
    flex-shrink: 0;
    gap: 9px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   3. POPUP — the modal
   ──────────────────────────────────────────────────────────────────────────
   Declares itself with role="dialog" + aria-modal="true", which is the ONE
   opt-in erp_focus.js watches for (audit 7.2). That is where focus-in, the
   trap, Escape and focus-restore come from — none of it is re-implemented in
   school_website.js, and a dialog that hand-rolled them would be the fifteenth
   slightly-different copy 7.2 existed to delete.

   Closed with `visibility`, not opacity alone. Two reasons, both measured:
   an element at opacity 0 is still in the tab order (the gallery lightbox held
   three tabbable buttons behind its own scrim until 7.5), and erp_focus.js
   judges visibility WITHOUT reading opacity, so an opacity-only closed state
   would look permanently open to it.
   ══════════════════════════════════════════════════════════════════════════ */
.wc-popup {
  position: fixed;
  inset: 0;
  /* Above the cookie bar (9500), below the gallery lightbox (9999). The bar
     and this never show together — school_website.js sequences them — but the
     lightbox is opened deliberately by the visitor and outranks a greeting. */
  z-index: 9600;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* No bottom padding on a phone: the sheet's bottom corners are square, so it
     has to meet the bottom of the screen or it reads as a card that failed to
     land. The 640px rule below puts the inset back when it becomes a card. */
  padding: 16px 12px 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* `visibility 0s linear .32s` and NOT `visibility .32s`, and the difference
     is the whole dialog working or not.
     Closing, both do the same useful thing: the element stays visible for the
     length of the fade and only then leaves the tab order. Opening is where
     they part. A transitioned `visibility` computes as `hidden` on the frame
     the class lands, and erp_focus.js rescans exactly once per mutation, on the
     next animation frame — so it measured a hidden dialog, declined to adopt
     it, and never looked again because nothing else changed. No focus, no trap,
     no Escape, silently. A zero-length transition with a delay flips it the
     instant `.open` arrives, so the layer sees what the visitor sees. */
  transition: opacity .32s var(--ease), visibility 0s linear .32s;
}

.wc-popup.open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity .32s var(--ease), visibility 0s linear 0s;
}

.wc-popup-scrim {
  position: absolute;
  inset: 0;
  background: rgba(7, 32, 63, .62);
  backdrop-filter: blur(3px);
}

/* Bottom sheet on a phone, centred card from 640px up. A sheet rising from the
   bottom is reachable by the thumb that is already there; a centred modal on a
   360px screen puts its close button at the top of a stretch. */
.wc-popup-box {
  position: relative;
  width: 100%;
  max-width: 480px;
  max-height: calc(100vh - 16px);
  overflow-y: auto;
  padding: 30px 24px 26px;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: var(--wc-bg);
  color: var(--wc-ink);
  border: 1px solid var(--wc-hair);
  box-shadow: 0 -18px 60px rgba(7, 32, 63, .40);
  text-align: center;
  transform: translateY(18px);
  transition: transform .38s var(--ease);
}

.wc-popup.open .wc-popup-box {
  transform: translateY(0);
}

/* Inset far enough to clear the corner radius. At 10px the button sat where the
   28px curve has already turned away and it read as floating off the sheet. */
.wc-popup-close {
  position: absolute;
  top: 14px;
  right: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 36px — the size the lightbox close button had to be raised to at 360px. */
  width: 36px;
  height: 36px;
  border-radius: 50%;
  font-size: 15px;
  color: var(--wc-ink);
  background: var(--wc-hair);
}

.wc-popup-close:hover {
  color: var(--wc-cta-ink);
  background: var(--wc-accent);
}

.wc-popup-crest {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 58px;
  height: 58px;
  margin: 0 auto 16px;
  border-radius: 50%;
  font-size: 26px;
  color: var(--wc-cta-ink);
  background: var(--wc-accent);
}

/* The poster replaces the crest (see the template), so it takes the crest's
   place in the flow: full bleed to the sheet's edges at the top, because a
   picture inset by the box's own padding reads as an attachment rather than as
   the announcement. The radius matches the sheet's top corners exactly —
   var(--r-xl) on a phone, where the popup is a bottom sheet, and var(--r-2xl)
   from 640px where it becomes a centred card. */
.wc-popup-poster {
  display: block;
  width: calc(100% + 48px);
  margin: -30px -24px 18px;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: var(--wc-hair);
}

.wc-popup-title {
  font-family: var(--serif);
  font-size: 25px;
  font-weight: 700;
  line-height: 1.24;
  color: var(--wc-ink);
  margin: 8px 0 0;
}

.wc-popup-msg {
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--wc-dim);
  margin-top: 11px;
}

.wc-popup-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  margin-top: 22px;
}

.wc-popup-actions .wc-cta {
  width: 100%;
}

/* "Not now" is a real control and needs a real target, but it must not read as
   a second offer — so it is a plain word at the tap floor, not a second pill. */
.wc-popup-later {
  min-height: 32px;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--wc-dim);
}

.wc-popup-later:hover {
  color: var(--wc-ink);
}

.wc-popup-days {
  margin-top: 14px;
}

@media (min-width: 640px) {
  .wc-popup {
    align-items: center;
    padding: 24px;
  }

  .wc-popup-close {
    top: 18px;
    right: 18px;
  }

  .wc-popup-box {
    padding: 38px 34px 30px;
    border-radius: var(--r-2xl);
    box-shadow: 0 30px 80px rgba(7, 32, 63, .46);
    transform: translateY(0) scale(.96);
  }

  /* Follows the box's padding and radius up together — the numbers are the
     box's own, negated. */
  .wc-popup-poster {
    width: calc(100% + 68px);
    margin: -38px -34px 20px;
    border-radius: var(--r-2xl) var(--r-2xl) 0 0;
  }

  .wc-popup.open .wc-popup-box {
    transform: translateY(0) scale(1);
  }

  .wc-popup-title {
    font-size: 28px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   4. PREVIEW FLAG — only an admin ever sees this
   ──────────────────────────────────────────────────────────────────────────
   Rendered only for ?wc_preview=<id> behind an admin session. Its entire job is
   to make a preview impossible to mistake for the live site, which is a
   different job from looking good: it is deliberately the loudest thing on the
   page, it does not scroll away, and it carries a way back to the real site.

   Not tone-aware, and that is the point — it must not look like part of the
   announcement it is framing. Fixed magenta on every campaign, in a palette
   that is otherwise navy and gold, so it reads as machinery rather than design.
   ══════════════════════════════════════════════════════════════════════════ */
.wc-preview-flag {
  position: fixed;
  z-index: 9999;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 12px;
  padding: 10px 16px;
  font-family: var(--sans);
  font-size: 12.5px;
  line-height: 1.5;
  text-align: center;
  color: #ffffff;
  /* 8.6:1 for the body copy, 5.4:1 for the link — both above AA on this field. */
  background: #6b1b52;
  border-top: 2px solid #ffd6f1;
  box-shadow: 0 -8px 30px rgba(0, 0, 0, .3);
}

.wc-preview-flag b {
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.wc-preview-flag a {
  flex-shrink: 0;
  padding: 5px 13px;
  border-radius: var(--r-pill);
  font-weight: 700;
  color: #4a1038;
  background: #ffd6f1;
  /* The 32px tap floor the public site holds to at 360px (audit 7.1). */
  min-height: 32px;
  display: inline-flex;
  align-items: center;
}

/* Bottom, not top: the strip surface already owns the top of the page, and a
   flag there would push the very thing being previewed out of the first
   screenful. The popup is the one surface that could cover this, so the 9999
   above is chosen against its 9600 — a preview of a popup must still be
   labelled as a preview. */
