/* The site notice bar.
 *
 * A thin strip above the header on every page, carrying whatever message
 * notice.js holds: the rebrand first, an event or a new feature after that.
 * Nothing here names any one message. The strip itself is written into the page
 * at deploy time by _deploy/inject-site-config.mjs. Its message element is filled
 * as the page is parsed, from the data embedded at deploy. Editing notice.js and
 * deploying changes the words. A deploy that issued no message references this
 * file from no page at all.
 *
 * A LIGHT BAND, AND LIGHT IS THE ABSENCE OF A THEME. This element carried
 * data-theme="dark" until now. It carries no theme attribute at all, and that is the
 * whole of the change: every page's html element already declares data-theme="light",
 * so css/tokens.css resolves this subtree against the same light layer it resolves
 * the page around it against.
 *
 * WHICH IS WORTH STATING, because the absence only works while that declaration is
 * there. The token file's dark block is an unqualified [data-theme="dark"] selector,
 * and its prefers-color-scheme block is :root:not([data-theme="light"]). The pages'
 * own attribute is what keeps the second from ever matching. Take it off the html
 * elements and this bar goes dark again under a visitor whose system is dark, with
 * nothing else on the page to say why. The notice spec in _qa asserts it for that
 * reason.
 *
 * WHY THE SUNKEN SURFACE AND NOT THE SURFACE TOKEN. --color-bg-surface is #FFFFFF
 * in the light layer, and 10 of the 27 pages paint white under the header, so a
 * white band has nothing to separate itself from on better than a third of the
 * site: the message floats above the logo with no bar around it. --color-bg-sunken
 * is #E7E6EC, a published Campus light neutral, and it names what this strip is,
 * a plane the page sits above and scrolls over.
 *
 * WHY NOT THE ACTION COLOUR. --color-action-primary-bg is what "click this" means
 * everywhere else on the site, and a passive band in it weakens that signal on
 * every page. The Campus secondary is worse mechanically: tokens.css carries
 * #8A2F7B as data.secondary and leaves the whole action.secondary group
 * UNRESOLVED, so reaching for it means hardcoding a hex the export declines to
 * publish for this use.
 *
 * WHY NOT A STATUS COLOUR. tokens.css states that blue means a mandatory
 * instruction here and that neutral information uses neutral tokens. A rebrand
 * or an event notice is neutral information, so the amber, red, green and blue
 * status triples are all wrong for it however well they would show up.
 *
 * THE INK IS THE ONE THING THE SYSTEM DOES NOT PUBLISH, and --sw-notice-ink below
 * says what was done about it.
 *
 * WHY IT SCROLLS AWAY. The header is position: sticky with top: 0, and 17 of
 * the 27 pages pull their first block up behind it with margin-top: -80px, the
 * header's own height, so that it floats over a hero. A bar that is static in
 * the flow shifts both by exactly its own height and leaves that relationship
 * untouched; the header still pins to the viewport top once the notice has
 * scrolled past. Making the notice sticky instead would mean changing the
 * header's own top offset on all 27 pages to clear it.
 */

.sw-notice {
  /* The header's own gutter and measure. Both are literals in .padding-global-2
   * and .container-large in css/qa-campus.webflow.css and neither has a token, so
   * they are held here once rather than repeated down the file.
   *
   * THEY BOUND THE GROUP, THEY NO LONGER PLACE IT. Until the message and the
   * dismiss control were centred together, the gutter was what put the message on
   * the same vertical as the Campus logo below. The group is centred on the bar
   * now, so at 1440 the 55-character event notice starts on 509 rather than on the
   * 80px gutter, and lines up with nothing in the header. What these two still
   * decide is how wide the group may grow before the message has to shrink into its
   * ellipsis. The gutter does still place the message wherever the two together run
   * out of room and the message takes everything left: that is the event notice at
   * 390 and at 320, where it begins on the logo's vertical exactly as it used to at
   * every width. */
  --sw-notice-gutter: 2.5rem;
  --sw-notice-measure: 80rem;

  /* THE LIGHT THEME PUBLISHES NO TEXT COLOUR, which is the one value this bar needed and
   * could not take from the semantic layer. css/tokens.css carries --color-text-default,
   * --color-text-muted and --color-text-subtle commented UNRESOLVED in both themes and says
   * why: a plausible value with a citation beside it is a guess that has learned to pass
   * review. The only resolved text token is --color-text-on-inverse, and it is white, which
   * is the foreground this bar has just stopped needing.
   *
   * SO THE BAND'S OLD COLOUR BECOMES ITS TYPE. --primitive-campus-surface-dark is the
   * #171820 this bar was painted in until this commit, so nothing new enters the site: the
   * two colours have swapped roles. Reading a primitive from a component is against the
   * token file's own rule, and it is done here with the rule in view rather than around it.
   * The two alternatives are worse: a bare hex, in a file that declares none, or
   * --color-logo-field, which holds the right value under a name that means the field a logo
   * sits on and would read as that in every later search for it. The
   * reference is to the primitive rather than to the hex so that a change to the Campus dark
   * value moves this with it.
   *
   * IT IS MEASURED: 14.25:1 on #E7E6EC, against the 4.5:1 WCAG 2.2 SC 1.4.3 asks of 14px
   * text, and past 7:1 as well. tokens.css says a new foreground and background pairing is a
   * change to the token system rather than a styling decision, and is measured and registered
   * before it ships. This is the measurement; the registration is --color-text-default in the
   * light theme, and it belongs to whoever next opens the design system. */
  --sw-notice-ink: var(--primitive-campus-surface-dark);

  background-color: var(--color-bg-sunken);

  /* LIFTED OUT OF THE BLOCK LAYER. A static block paints below every positioned
   * element on the page, and the home page hangs a position: fixed video
   * backdrop off the body whose own z-index is auto. That backdrop painted its
   * #f0f0f0 reveal panel straight over this bar: correct geometry, invisible
   * band. z-index 1 is the smallest value that clears it, and it stays well
   * under the header's own chrome layers, which run from 900 to 9999, so the
   * navigation dropdowns and the mobile menu still open over everything. */
  position: relative;
  z-index: 1;

  /* The gutter sits outside the measure, not inside it, because that is the order
   * the header uses: .padding-global-2 pads the full width and .container-large
   * centres within what is left. Both orders centre on the same vertical, so this
   * is now a question of room rather than of position: padding inside the measure
   * would leave the group 1200px to grow into at 1440 where this leaves it 1280. */
  padding: 0 var(--sw-notice-gutter);
}

/* THE TYPE IS SET ON EVERY PART THAT CARRIES TEXT, not once on the bar and
 * inherited. css/webflow.css styles bare elements: p is 16px/24px in 70% black,
 * and a rule that matches an element beats a value inherited into it however
 * specific the ancestor's rule is. Setting the bar alone gave a 40px strip with
 * a black message on a near-black band.
 *
 * THE DISMISS CONTROL IS IN THIS LIST FOR THE COLOUR ALONE. It draws a mark rather
 * than setting a character, so the type properties do nothing on it. The colour is
 * still needed, and for the same reason the rest of the list is here: a button takes
 * its foreground from the UA stylesheet rather than from its parent, and the mark is
 * stroked in currentColor. */
.sw-notice,
.sw-notice__message,
.sw-notice__dismiss {
  color: var(--sw-notice-ink);
  font-family: var(--font-sans);
  font-size: var(--type-ui-size);
  line-height: var(--type-ui-line);
  /* Brand book v4 section 2.2: on dark surfaces and in compact interface, 500 replaces 400.
   * The bar was both and is now the second alone, which is enough on its own. */
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--type-ui-tracking);
}

/* THE MESSAGE AND THE CONTROL ARE ONE CENTRED GROUP. They are flex siblings, so
 * centring is justify-content on this line and nothing else: the browser lays the
 * two out at their content widths, adds the gap, and puts the leftover space
 * either side in equal halves. The pair is what gets centred, which is the point.
 * The control follows the end of the message instead of standing at the far edge,
 * where at 1440 it sat roughly 894px clear of the last word and read as stranded.
 *
 * CENTRED ON THE BAR, NOT ON SOMETHING BESIDE IT. This line centres within this
 * box, and this box is symmetric about the bar: the gutter above pads both edges
 * equally and margin: 0 auto centres the measure in what is left. Nothing here is
 * measured from the message, so the group holds its centre when the message is
 * re-issued at a different length or the control changes size. That symmetry is
 * also why the control no longer carries a negative inline-end margin. See the
 * note on .sw-notice__dismiss. */
.sw-notice__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
  max-width: var(--sw-notice-measure);
  margin: 0 auto;
  padding: var(--space-2) 0;
}

/* ONE LINE WIDE, TWO AT MOST NARROW, CLIPPED PAST THAT. A wide viewport fits any
 * message the injector will accept, so the message never wraps there: a two-line
 * strip at 1440 reads as a fault rather than as a notice. A 390px viewport fits
 * about 40 characters, which cuts an ordinary event notice mid-sentence, so the
 * narrow block at the foot of this file lets it take a second line. Two is the
 * ceiling at every width, because a strip that can grow to four lines is a
 * banner and would push the header and the hero down with it. Text past the
 * ceiling is clipped with an ellipsis. The string itself stays whole in the
 * document, so a screen reader still announces all of it, and a message long
 * enough to clip on a phone should be read as a summary whose detail is behind
 * the link. */
.sw-notice__message {
  /* SHRINK, NEVER GROW, and this is the half of the centring that is easy to miss.
   * justify-content has no leftover space to share out while an item is willing to
   * take it, so flex-grow: 1 here would stretch the message across the bar and
   * leave the control pinned to the right exactly as before, with the centring
   * rule above reading as though it were in force. The shrink factor stays at 1,
   * with min-width: 0 to let it act, because a message near the 120-character
   * ceiling still has to give way and take the ellipsis. */
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* VIOLET, BY THE 2026-09-08 DECISION THAT LINKS ARE VIOLET, which called the ink this inherited until now
 * an error. --color-link is #5B3FD1 and its 6.83:1 is measured on white, which is not this
 * band: on --color-bg-sunken #E7E6EC it measures 5.50:1, against the 4.5:1 WCAG 2.2 SC 1.4.3
 * asks of 14px text. The underline stays, so the link is not told from the message by hue
 * alone. */
.sw-notice__link {
  color: var(--color-link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  transition: text-decoration-thickness var(--motion-micro) var(--easing-standard);
}

.sw-notice__link:hover,
.sw-notice__link:focus-visible {
  text-decoration-thickness: 2px;
}

/* WHY THE MARK IS DRAWN AND NOT SET. The control shows a mark rather than the word
 * Dismiss because horizontal space is this bar's binding constraint, and the word spent
 * about 50px of it on the one axis the message has none to spare. Every character that
 * could stand in for it is wrong in its own way. U+2715 MULTIPLICATION X falls outside
 * the Google subsets css/fonts.css serves, so a browser would draw it from whatever
 * system face it fell back to and it would not match the message beside it. U+00D7 is in
 * the latin subset, but it is the multiplication sign: short arms sitting on the maths
 * axis, which reads as an operator rather than as a control. U+0058, the capital letter
 * this carried until now, is the bar's own face at the bar's own weight and reads as
 * exactly what it is, a letter. Two strokes drawn in the document depend on no font at
 * all, which is what the constraint against the multiplication sign was protecting, and
 * an inline svg is not an icon font either. Nothing a screen reader announces rests on
 * any of this: the button carries aria-label="Dismiss", so its accessible name is the
 * word, and the svg is aria-hidden.
 *
 * THE MARK IS SIZED AGAINST THE TYPE, and the figures are this bar's own, weighed off an 8x
 * capture rather than read out of the font. IBM Plex Sans at 500 and 14px gives a cap height
 * of 9.81px here, an x-height of 7.31, and a stem the browser paints 1.63px wide. The mark
 * spans 7.5px corner to corner and 9.13 including its stroke.
 *
 * THE STROKE IS THAT STEM WIDTH, 1.63px, and matching it is the whole of the rule. The mark
 * carried 1.3 until now, chosen by eye at 8x, and it read faint beside a sentence set at
 * weight 500: 1.3 is 80% of the stem, and a fifth less ink over the same length is a
 * difference the eye reports as something being out of place rather than as a lighter mark.
 * A diagonal and a vertical of the same perpendicular width lay down the same ink per unit
 * of line, so the stem is the target and not a floor to work up from. Rendered back at 8x
 * against those stems, 1.50 reads light and 1.75 reads heavy, which is what an eighth either
 * side of the stem looks like at this size.
 *
 * THIS IS NOT THE 9px SPAN THAT WAS REJECTED EARLIER. That one paired a wider span with a
 * 1.5px stroke for a drawn extent of 10.5px, half a pixel over the cap height, and it read
 * as a larger object than the sentence. The span has not moved: 9.13 is still inside the
 * cap height, and only the weight has changed.
 *
 * IT IS CENTRED OPTICALLY AND NOT ON THE LINE BOX, and 0.72px is the whole of the
 * difference. block-size holds the box at the bar's own height, so left alone the mark
 * centres in the same 22px line box the message sets its type in, and at 1440 that put the
 * message, the button and the mark all on y=19.0 exactly. That geometry was right and it
 * still read wrong. A line box reserves room for descenders these messages mostly do not
 * use, so the type's mass sits below the box's centre: with the baseline 24px below the
 * bar's top, the x-height centre is 20.34 and the cap-height centre 19.09, and the mark sat
 * above both.
 *
 * WHICH OF THOSE TWO IT SHOULD ANSWER TO DEPENDS ON THE MESSAGE, so it answers to neither.
 * The message this bar is sized for ends on a lowercase word, and its painted ink beside the
 * mark centres on 20.45, against which 19.0 reads high by a pixel and a half. A message
 * ending on a capital or on a date carries its ink in the cap band instead, whose centre is
 * 19.09, and the same correction would read low there. The mark takes the midpoint of the
 * two axes, 19.72, which is the one least wrong for either, and that is 0.72px below where
 * the box alone would put it. The figure holds at 390 as well, where the message runs to two
 * lines: the block centres between the two baselines and each line's optical centre is
 * displaced by that same 0.72.
 *
 * WHY IT IS AN OFFSET AND NOT A TRANSFORM. The two render the same here, and captured at 8x
 * the painted edges are equally soft, so this is not a sharpness argument. relative and
 * inset-block-start move the box, which is the currency block-size and the block margins
 * above are already in, so a reader changing one of those can see what it does to this. An
 * svg set inline with vertical-align: middle would land on 20.34 by accident of the inline
 * box model, which follows the x-height alone and overshoots the axis chosen here; stating
 * the nudge is what makes it a number somebody can measure and disagree with.
 *
 * NO UNDERLINE, AND THE FOCUS RING DOES NOT DEPEND ON ONE. The control used to inherit
 * the link's underline treatment, which is half of why it read as text. What the
 * underline carried was the pointer cue, 1px thickening to 2px, and the mark keeps that
 * idea in its own terms: the stroke thickens instead. The focus ring is the rule below,
 * an outline that was never part of the underline and survives its removal.
 *
 * THE TARGET IS PADDED OUT AROUND THE MARK, NOT SIZED BY IT, and that is the whole hazard
 * in trading a word for a drawing. The mark is a 10px box, so a control sized by its own
 * content would be a 10px target where the word gave roughly 50. The horizontal padding
 * buys the width back to 34, and block-size holds the height at 38 where a box that took
 * the mark's own height would come out at 26. Both clear the 24px WCAG 2.2 SC 2.5.8 asks
 * for. The vertical half is why block-size is stated rather than left to the content, and
 * the negative block margin takes the box back out over the bar's own padding so the bar
 * still measures 38.
 *
 * NO NEGATIVE INLINE MARGIN, AND THAT IS A CHANGE. This control used to pull its
 * right edge 12px past the measure so the glyph landed on the same vertical as the
 * Login button's box in the header. That alignment was worth having while the control
 * stood at the bar's right edge, and it is meaningless now that it travels with the
 * message. Keeping it would also be wrong rather than merely pointless: a negative
 * end margin shortens the group's outer width without shortening what is drawn, so
 * justify-content would centre the outer width and push everything visible 6px right
 * of the bar's centre. Dropping it bought back an alignment of its own at the widths
 * where the message fills the bar: at 390 the control's right edge now lands on 370,
 * which is where the header's menu button ends, in place of overhanging it by 12. */
.sw-notice__dismiss {
  flex: none;
  display: inline-flex;
  align-items: center;
  block-size: calc(var(--type-ui-line) + var(--space-2) * 2);
  margin-block: calc(var(--space-2) * -1);
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: none;
  cursor: pointer;
}

/* currentColor is the point of it. The bar's foreground is one value in one place, and the
 * mark followed it through the change from white on a dark band to #171820 on a light one
 * with nothing here touched. fill: none is not decorative either: a path with no fill of its own is
 * filled black by default, which between two crossed open subpaths is four wedges, near
 * enough invisible on this band to be found late. */
.sw-notice__mark {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.63;
  /* The optical nudge, derived in the note above. It changes nothing about the layout: the
     box is still 10px and the control is still 34 by 38. */
  position: relative;
  inset-block-start: 0.72px;
  transition: stroke-width var(--motion-micro) var(--easing-standard);
}

/* A fifth heavier, which is the proportion the 1.3 to 1.6 pair carried. The pointer cue is
   the change and not the size it reaches, so the proportion is what moves with the weight. */
.sw-notice__dismiss:hover .sw-notice__mark,
.sw-notice__dismiss:focus-visible .sw-notice__mark {
  stroke-width: 1.95;
}

/* Inset, because an offset ring on a control stretched to the bar's own height would be
 * clipped by the bar's edges.
 *
 * THE RING IS THE THING THAT BREAKS SILENTLY WHEN A SURFACE FLIPS, and this one would have.
 * It was --color-focus-ring-on-dark, which css/tokens.css publishes only inside its dark
 * block; with the bar in the light layer that name resolves to nothing, an outline shorthand
 * holding an unresolved var is invalid at computed-value time, and outline-style falls back
 * to none. A keyboard visitor would have lost the control with no other symptom. The light
 * layer's own --color-focus-ring is #7C5CFA, which measures 3.55:1 on this band against the
 * 3:1 WCAG 2.2 SC 1.4.11 asks of a non-text indicator. The 4.40:1 in the comment beside its
 * declaration is measured on white and does not apply here. */
.sw-notice__link:focus-visible,
.sw-notice__dismiss:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}

/* Not shown to this visitor. Two things set it and the name covers both: a
 * dismissal stored on an earlier page, and an end date that has gone by. Set on
 * <html> rather than on the bar, because the block that decides runs in the head,
 * before this element has been parsed, which is what keeps a notice this visitor
 * is not being shown from painting and then vanishing. The click path ends here
 * too, so there is one hidden state rather than three. */
[data-sw-notice-off] .sw-notice {
  display: none;
}

/* The click path only: collapses the strip so the page rises into the space
 * instead of jumping by the bar's height. js/notice-bar.js pins that height,
 * flips it to zero
 * and sets the dismissed attribute when the transition ends. */
.sw-notice[data-sw-notice-collapsing] {
  overflow: hidden;
  transition: height var(--motion-short) var(--easing-exit);
}

/* The collapse rule above is .sw-notice[data-sw-notice-collapsing], so a plain
 * .sw-notice here would not outrank it and the strip would still animate. The
 * dismissal itself still works: js/notice-bar.js waits for the transition that
 * never comes and falls through to its timeout. */
@media (prefers-reduced-motion: reduce) {
  .sw-notice[data-sw-notice-collapsing],
  .sw-notice__link,
  .sw-notice__mark {
    transition: none;
  }
}

@media screen and (max-width: 767px) {
  /* The breakpoint and the value are the header's own, from the
   * max-width: 767px block in css/qa-campus.webflow.css. */
  .sw-notice {
    --sw-notice-gutter: 1.25rem;
  }

  .sw-notice__inner {
    gap: var(--space-4);
  }

  /* THE SECOND LINE, AND THE CEILING ON IT. A 53-character event notice loses its
   * last three words to the ellipsis at 390, so below the header's own breakpoint
   * the message wraps instead. -webkit-line-clamp is what stops it at two and puts
   * the ellipsis on the end of the second line; text-overflow cannot, because it
   * only ever acts on a single line box. The clamp needs display: -webkit-box with
   * a vertical box-orient, which is the form every current browser implements, and
   * the unprefixed line-clamp is set beside it for the ones that have moved on.
   * The overflow: hidden both forms clip against comes from the rule above.
   *
   * WHY NOT max-height. A height ceiling cuts the second line through its glyphs
   * and produces no ellipsis, so a clipped message reads as a rendering fault
   * rather than as a summary with its detail behind the link. */
  .sw-notice__message {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    white-space: normal;
  }
}
