/* =================================================================
   UNA BICENTENNIAL — 1830–2030
   Design tokens sourced from CLAUDE.md brand rules + UNA Visual
   Identity Guidelines (Pantone 268c / 1235c primary palette).

   SCOPING — every rule in this file is scoped under .una200, which sits
   on <body>. Two reasons, both about the WordPress port:

   1. Nothing leaks out. The element resets below (img, a, headings,
      box-sizing) would otherwise hit the admin bar, the active theme's
      own output and every other plugin on the page. `img{display:block}`
      and `a{color:inherit}` in particular are site-breakers unscoped.
   2. Nothing leaks in. The extra class buys one level of specificity
      over a bare theme rule, so `.una200 .module` (0,2,0) survives a
      theme's `.module` (0,1,0). Generic names like .hero/.cta/.module
      are common enough in themes that this matters.

   The tokens live on .una200 rather than :root deliberately. Custom
   properties are NOT protected by descendant scoping — a theme
   declaring `:root{--font:…}` after us would win for the whole
   document, inside .una200 included. Declaring them on .una200 means
   the nearest ancestor wins for our subtree, so the tokens are immune
   rather than merely unlikely to collide. That is also why they are
   not prefixed --una-*: they no longer need to be.

   The only intentional globals are the two `html{scroll-behavior}`
   rules — html is the root, so it cannot sit under a body-level
   wrapper. Harmless, because this stylesheet is only ever enqueued on
   this one page.

   When adding rules: scope them. When adding a module, keep its rules
   contiguous and free of cross-module selectors, so the module can
   later be lifted into a block's own style.css for per-block loading.
   ================================================================= */

.una200,
.una200 *,
.una200 *::before,
.una200 *::after{ box-sizing:border-box; }

/* `hidden` MUST ACTUALLY HIDE, and without this it does not.
   The UA stylesheet's `[hidden]{display:none}` has specificity (0,1,0), so any author rule
   that sets `display` on the same element beats it. That is not theoretical here: the news
   lead is `display:grid` inside a stacked card at (0,4,0), so `lead.hidden = true` set the
   attribute and changed nothing on screen.

   The cost of that is specific and bad. When a feed fails, settleFeed hides the sample lead
   precisely so the page does not show a fabricated headline beside a notice saying the feed
   could not be read — the comment there has described that trade since it was written. The
   CSS was quietly defeating it, so the one case the code went out of its way to prevent was
   the case that happened, on desktop only, where the stack is live. It stayed invisible
   because it needs a failing feed at desktop width and the feeds were all switched off.

   `!important` is right here rather than a specificity arms race: `hidden` is a statement
   that the element is not relevant, and nothing should be able to out-rank it. Note the
   attribute selector does NOT match `aria-hidden`, which is used all over this stylesheet
   on decorative elements that must stay visible. */
.una200 [hidden]{ display:none !important; }

.una200{
  /* --- brand: primary --- */
  --purple:        #592B8A;
  --gold:          #FFB92A;

  /* --- brand: accent --- */
  --accent-violet: #9063CD;
  --accent-grey-1: #A2AAAD;
  --accent-grey-2: #7C878E;
  --accent-cream-1:#F1E6B2;
  --accent-cream-2:#F8E08E;
  --accent-tan:    #D9C89D;

  /* --- derived shades (tints/shades of the two primaries only) --- */
  --ink:           #170B27;   /* near-black shade of purple, for cinematic backgrounds */
  --ink-soft:      #241338;
  --purple-deep:   #3E1D63;
  --gold-deep:     #8A5A00;   /* deep gold, 5.54:1 on --paper — AA for small text and links */
  --paper:         #FBF7EC;   /* light cream reading surface, tinted from accent-cream-1 */
  --paper-dim:     #F3ECDA;
  /* THE FORM SURFACE, AND IT IS NOT A CREAM. --paper is warm (#FBF7EC) and reads as cream
     against everything else on the site; this is the same job done in a faint purple tint
     off the brand hue instead. It is not white for a reason worth keeping: the Slate inputs
     are #fff, so a white panel would swallow them — measured, white gives 1.00:1 between
     input and panel, the old cream 1.07:1 and this 1.16:1. Body type on it is 16.24:1 and
     links are 5.11:1, both clear of AA. */
  --paper-cool:    #EFEDF4;
  --line-on-dark:  rgba(241,230,178,.22);
  --line-on-light: rgba(89,43,138,.14);
  --white:         #fff;      /* plain white, for type that sits on the dark panels */

  --font: 'Work Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* 17.5 + logo 48 + 17.5 + 1px border. Hard-coded because a pinned panel has to reserve
     it in padding and cannot measure the nav from CSS. Keep in step with .site-nav. */
  --nav-h: 84px;

  --wrap: 1180px;
  --measure: 66ch;
  --inner: 75vw;
  --wide: 85vw;
}

/* =================================================================
   SCROLL STACK — cards that roll over each other as the last one fades
   ================================================================= */
/* The christchurchnz.com scroll behaviour, frame by frame:
     1  the hero fills the screen
     2  the next card rises; the one above STAYS IN PLACE and washes toward the ground
     3  the card lands, pauses, and further scrolling releases it
     4  the covered card keeps washing out and shrinking until it is gone
     5  the landed card's own animations fire
     …and the last card scrolls normally into the footer.

   THE WHOLE THING TURNS ON ONE PROPERTY: every card is `position:sticky`, and they are
   SIBLINGS with no wrapper between them. A sticky box can only be shifted within its
   containing block, which for an in-flow box is its parent's content box — so with .stack as
   the parent, the allowance is everything below that card. Card N therefore stays pinned for
   the rest of the page while cards N+1, N+2 … rise over it in turn.

   *** DO NOT WRAP THE PANELS. *** A per-panel wrapper bounds the pin to that panel's own
   stretch of page, so a card releases the instant its dwell ends — which is exactly when the
   next card begins to rise. The two then travel together and the dwell reads as an empty gap
   between them. That was built, shipped and reverted; the screenshots of it were
   unmistakable. The wrapper was only ever there to bound bottom-pinning, and the negative
   `top` below does that job without bounding anything.

   THE DWELL IS `margin-bottom` on the card. It pushes the NEXT card down, so after this one
   pins there is that much scrolling with nothing covering it — frame 3's pause. It does not
   shorten the pin, because the pin's allowance is the whole rest of the stack.

   EVERY CARD MUST BE OPAQUE at rest or you see through to the one beneath. All of them are:
   .module and .hero each end their background stack with var(--ink).

   IT IS SWITCHED ON BY JAVASCRIPT, NOT BY THIS FILE — [data-stack-on="true"] is set only
   after script.js has measured the cards. See the block in script.js. */
.una200 .stack{
  position:relative;
  /* The ground the cards sit on, and what they wash into. It has to read as behind them: the
     cards carry a purple bloom at the top, so flat --ink does that without a new colour.
     .site-foot is already --ink, so the page below the stack continues it. */
  background:var(--ink);

  --stack-r:clamp(16px,2vw,28px);

  /* DWELL DISTANCES, AND THEY MUST NOT BE PURE vh. The timeline band was 275vh, which on a
     1920px-tall portrait monitor is 1056px per moment — more than a whole screen before
     anything changed. Clamping means a tall screen gets a longer dwell, not an absurd one. */
  --stack-hang:clamp(240px,45vh,520px);
  --stack-moment:clamp(260px,32vh,420px);
}

/* the off state is also the no-JS state: plain document flow, nothing pinned */
.una200 .stack > [data-stack-panel]{ margin-bottom:0; }

/* FLOW POSITIONS CAN ONLY BE READ WITH NOTHING PINNED, and this is how script.js does it.

   Once a sticky element is pinned, BOTH `offsetTop` and `getBoundingClientRect()` report its
   SHIFTED position, not its position in flow — verified in Chrome, where five different
   pinned cards all reported the same y. So any re-measure that happened while the page was
   scrolled (fonts.ready, the ResizeObserver, a window resize) wrote "wherever each card
   happens to be stuck" into the cache the fade and the arrival trigger read from. Modules
   then faded at the wrong time, or their content never revealed at all.

   script.js sets this flag, reads every position, and clears it inside a single task, so the
   browser never paints the unpinned state. More specific than the pinning rule above (four
   compound selectors against three), which is why it wins without !important. */
.una200 [data-stack-on="true"][data-stack-measure] > [data-stack-panel]{
  position:static;
}

.una200 [data-stack-on="true"] > [data-stack-panel]{
  position:sticky;

  /* THE CARDS LOOKED TRANSPARENT WITHOUT THIS, and they were never transparent at all.
     `position:sticky` with `z-index:auto` does NOT create a stacking context, so any
     descendant with a positive z-index is promoted out of its card into the page's top
     layer — where it paints above every LATER card. Four rules do this, all correct in
     isolation: .hero__inner, .memories__copy, .showcase--soft::after and .events__plate.
     The visible result was the hero's headline and the event date chips floating on top of
     the Events module, which reads exactly like a missing background.

     `isolation:isolate` makes each card a stacking context, so those are contained. Cards
     then all have z-index:auto and paint in TREE ORDER — later over earlier, which IS the
     roll-over — needing no per-card z-index arithmetic, so it keeps working when a card is
     added or removed. Do not replace it with a z-index ladder. */
  isolation:isolate;

  /* FULL-BLEED, ROUNDED ONLY AT THE LEADING EDGE. An earlier pass inset the cards on all
     four sides. Every module already clamps its own content to --wrap, so a second gutter
     outside that read as a redundant content column — two frames around the same thing.

     svh, not vh: on mobile browsers vh includes the retractable toolbars, so a 100vh card is
     taller than what can be seen and the pin hides its own bottom edge. */
  top:var(--nav-h);
  /* A DEFINITE HEIGHT, NOT A FLOOR, and both halves of the scheme depend on it.
     `min-height` let a card GROW past the frame instead of holding it, which is how modules
     ended up different heights on the same page. It also left percentage heights inside with
     nothing to resolve against, so `height:100%` on the elastic rows silently did nothing.
     With a real height the module fills exactly one card, and anything that genuinely cannot
     fit OVERFLOWS — which is what lets the page-level guard in script.js see it at all. */
  height:calc(100svh - var(--nav-h));
  border-radius:var(--stack-r) var(--stack-r) 0 0;
  /* border-radius clips a background but NOT descendant content, and the Memories module is
     deliberately full-bleed inside its section — without this its slideshow squares off the
     card's corners. */
  overflow:hidden;

  display:grid;
  /* `safe center`. These modules clear their frame by as little as 8px — measured — so any
     case where the content genuinely cannot fit the box must not centre it: plain `center`
     overflows in BOTH directions and cuts the top of the module as well as the bottom.
     `safe` falls back to `start` alignment whenever the content would overflow, so the
     heading is never the thing that vanishes.

     This is a backstop, not the fix. `min-height` is a floor rather than a cap, so a card
     that outgrows its frame normally just gets taller — and script.js watches every card for
     exactly that and switches it to scrolling, because a pinned card that is taller than the
     frame has its bottom permanently unreachable. Plain `center` first, for engines that do
     not know the keyword. */
  align-content:center;
  align-content:safe center;
  padding-top:clamp(20px,3vh,44px);
  padding-bottom:clamp(20px,3vh,44px);

  /* FRAME 3's PAUSE. Pushes the next card down, so this one holds uncovered for that long. */
  margin-bottom:var(--stack-hang);

  /* FRAME 4. --stack-out is written per card by script.js: 0 while the card is the top of
     the stack, 1 once the next card fully covers it, tracking the next card's rise up the
     viewport. Only opacity and transform are driven — both compositable without re-layout,
     which is what makes a per-frame write affordable.

     It fades ALL THE WAY to 0. An earlier pass stopped at .3, on the theory that reaching
     zero early would show bare ground between the two cards. It does not: the fade tracks
     exactly how far the incoming card has risen, so 0 arrives at the moment the card is
     completely covered. Stopping short just left a ghost that never resolved.

     The shrink is 9%. The recession does as much work as the fade — the card has to read as
     going away, not just dimming — and half of it did not register at viewport size. */
  --stack-out:0;
  opacity:calc(1 - var(--stack-out));
  transform:scale(calc(1 - var(--stack-out) * .09));
  /* above centre, so the card sinks back rather than shrinking toward its middle */
  transform-origin:50% 28%;
}

/* A CARD THAT DOES NOT FIT IS NOT PINNED AT ALL. It scrolls past like an ordinary section.

   This replaced two earlier attempts, both of which hid content:
     top-pinned      the card stops the instant its top hits the pin, so everything below
                     the fold is permanently unreachable.
     negative top    `100svh - (its height)` pins it once its BOTTOM reaches the viewport
                     bottom. Nothing is unreachable, but at rest its top is clipped under
                     the nav — measured at 110 to 157px on a 800px-tall viewport, which eats
                     the eyebrow and half the title. It reads as broken, not as designed.

   Scrolling normally hides nothing and looks like nothing is wrong. The card BEFORE it is
   still pinned and still gets rolled over, so the effect degrades one transition at a time
   rather than all at once. The dwell goes too: a dwell on an unpinned card is just a gap.

   THE EFFECT IS THEREFORE VIEWPORT-DEPENDENT, and measured rather than guessed. At 982px of
   viewport every one of the seven cards fits and the whole page behaves. At 800px five of
   them do not, and those five scroll. If more of them should pin on a laptop, the modules
   have to get shorter — the frame cannot get taller. */
/* NOTHING IN THIS SECTION USES `revert`. It rolls the cascade back to the USER-AGENT
   origin, DISCARDING the module's authored value rather than restoring it — measured:
   `aspect-ratio:revert-layer` left the cohort photo at `auto`, losing its 4/3 entirely, and
   `padding-top:revert` was dead anyway, tying the compression rule on specificity and losing
   on source order. */

/* MEASURE IN THE STATE THE CARD ACTUALLY RENDERS IN. This rule releases the height floor
   and NOTHING else, and that restraint is the whole point.

   It used to also set `display:block`, `padding:revert` and `overflow:visible`, on the
   reasoning that a measurement wants the element as bare as possible. Every one of those
   changed the answer:

     display:block   .module--memories is a grid whose two children BOTH sit at
                     grid-area:1/1 — the copy overlaps the slideshow. As a block they
                     stack instead, so the measured height was roughly the SUM of the two
                     rather than the larger of them. Wildly over-measured.
     padding:revert  restores the module's own 110–140px, where a pinned card gets the
                     compressed ~44px. Around 180px of phantom height per card.
     overflow        changes what scrollHeight counts.

   Because a tall card is positioned by `100svh - (its height)`, an over-measured height
   pushes it too far UP: its top is cut off under the nav and its bottom stops short of the
   viewport bottom, leaving ground below. That is exactly what shipped.

   `min-height` is the only thing that has to go, so that scrollHeight reports the content's
   own height rather than the floor the pin gives it. `position:sticky` does not affect an
   element's height at all, so it does not even need unpinning. */
.una200 [data-stack-on="true"] > [data-stack-panel][data-stack-fit="measuring"]{
  height:auto; min-height:0;   /* release the frame, and NOTHING else */
}

/* A CARD THAT HAS FULLY WASHED OUT IS TAKEN OUT OF THE TAB ORDER.
   Every card is pinned in the viewport region at once, so without this the keyboard walks
   all 31 links across all seven cards regardless of which one is showing — and at opacity 0
   those are invisible focus targets. `visibility:hidden` removes an element from the tab
   order AND the accessibility tree while leaving its layout alone, so the stack geometry is
   untouched. Set by script.js once the card is completely covered, lifted on the way back. */
.una200 [data-stack-on="true"] > [data-stack-panel][data-stack-gone]{
  visibility:hidden;
}

/* THE LIFT, cast both ways so a card reads as floating whether it is arriving or being
   covered. */
.una200 [data-stack-on="true"] > [data-stack-panel]{
  box-shadow:
    0 -18px 40px -18px rgba(0,0,0,.55),
    0 34px 70px -28px rgba(0,0,0,.75);
}

/* THE TIMELINE DWELLS FOR ITS FIVE MOMENTS. script.js measures this as the gap between this
   card's bottom and the next card's top, then divides by the number of stops it finds — so
   the count here is the only thing tying the two together. Change the number of moments and
   change this 5. */
.una200 [data-stack-on="true"] > [data-stack-timeline]{
  margin-bottom:calc(var(--stack-moment) * 5);
}

/* The last card has nothing following it to roll over it, so its dwell would just be dead
   scroll before the footer. */
.una200 [data-stack-on="true"] > [data-stack-panel]:last-child{ margin-bottom:0; }

/* ---- fitting the modules into one card ---- */
/* The modules were built to breathe down a long page: 110px to 140px of section padding
   each. In a card that padding is the difference between fitting and being read by scrolling
   instead of pinning. These overrides apply only inside a live stack, so the modules keep
   their original spacing everywhere else — including on this same page whenever it is off. */
/* MORE ROOM AT TOP AND BOTTOM. The cards were hugging their edges — this is the one
   dimension that was too tight rather than too generous, and it is worth paying for out of
   the height scale below. */
.una200 [data-stack-on="true"] > [data-stack-panel].module,
.una200 [data-stack-on="true"] > [data-stack-panel].hero{
  padding-top:clamp(32px,5vh,74px);
  padding-bottom:clamp(32px,5vh,74px);
}
  /* the hero sets its own min-height:100vh, which would make it taller than its card */
.una200 [data-stack-on="true"] > [data-stack-panel].hero{
  min-height:0;
  height:calc(100svh - var(--nav-h));
}
.una200 [data-stack-on="true"] > [data-stack-panel] .module__grid{ margin-block:0; }

/* THE GAP BELOW A HEAD IS THE GRID'S margin-top, NOT THE HEAD'S margin-bottom.
   Compressing `.cohort__head{margin-bottom}` did nothing at all: adjacent sibling margins
   COLLAPSE, and the grid's own 64px top margin is the larger of the two, so it won every
   time. Measured in the browser — the head's compressed 23.6px was simply never applied.
   Compress the property that actually produces the gap. */
.una200 [data-stack-on="true"] > [data-stack-panel] .cohort__grid,
.una200 [data-stack-on="true"] > [data-stack-panel] .events__list,
.una200 [data-stack-on="true"] > [data-stack-panel] .give__media,
.una200 [data-stack-on="true"] > [data-stack-panel] .news__grid{
  /* The gap between a head and its content grid. Every module was overrunning its card by
     about five pixels, all three by the same amount, which is the signature of one shared
     value rather than three separate problems — this is it. */
  margin-top:clamp(6px,1.1vh,16px);
}
.una200 [data-stack-on="true"] > [data-stack-panel] .cohort__cta,
.una200 [data-stack-on="true"] > [data-stack-panel] .events__cta,
.una200 [data-stack-on="true"] > [data-stack-panel] .give__cta,
.una200 [data-stack-on="true"] > [data-stack-panel] .news__cta,
.una200 [data-stack-on="true"] > [data-stack-panel] .timeline__cta{
  margin-top:clamp(10px,1.4vh,18px);
}

/* SHORTER PICTURES WHILE STACKED, and only while stacked. These three modules were still
   107, 42 and 170 pixels over the card at a 982px viewport after every margin had been
   trimmed, and the remainder is all photograph. A card that does not fit rests with its top
   clipped under the nav, which is what these were doing — the eyebrow and half the title
   gone. Losing some picture height is the cheaper trade.

   Landscape rather than squat: 16/9 and 2.35/1 both read as deliberate crops. */
.una200 [data-stack-on="true"] > [data-stack-panel] .cohort__photo img{
  aspect-ratio:16/9;
}
.una200 [data-stack-on="true"] > [data-stack-panel] .news__lead-media{
  aspect-ratio:2.5/1;
}
/* the lead's third line is the one that pushes News over on a laptop */
.una200 [data-stack-on="true"] > [data-stack-panel] .news__lead-excerpt{
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  line-clamp:2; overflow:hidden;
}

/* the scroll cue is an instruction to do the thing the reader is already doing by the time
   the stack matters, and it is the first thing to sacrifice for height */
.una200 [data-stack-on="true"] > .hero .hero__scrollcue{ margin-top:clamp(18px,3vh,44px); }

/* =================================================================
   MODULES THAT FIT THE CARD BY CONSTRUCTION
   ================================================================= */
/* THIS IS THE ANSWER TO "WHY IS IT DIFFERENT ON EVERY SCREEN".

   The stack used to measure each module and ask whether it happened to fit the card. It
   usually did not, because a module's height came from its CONTENT — so the answer changed
   with viewport height, viewport width (text re-wraps), webfont loading, browser zoom, and
   with whatever WordPress returns from a feed that day. Every one of those varies across a
   university's audience, so the effect appeared on some machines and not others, and worse,
   on SOME MODULES AND NOT OTHERS on the same page. Four of seven rolling over reads as
   broken; the patchwork was the real bug, not the threshold.

   So the module no longer gets to decide its own height. Each one is laid out as

       head        auto     — eyebrow, title, lede
       middle      1fr      — the photographs and lists, which absorb whatever is left
       cta         auto

   inside a card that is exactly `100svh - nav`. The middle is `minmax(0,1fr)`, which is the
   part that matters: a plain `1fr` refuses to shrink below its content's minimum, and the
   photographs would push the card open again. Everything elastic also needs `min-height:0`
   for the same reason, at every level of the chain.

   The photographs lose their aspect-ratio while stacked and fill their box with
   `object-fit:cover` instead. A crop is the one thing here that can absorb an arbitrary
   number of pixels without pushing anything else around.

   The result is deterministic: the module is one card tall on a 1512x863 laptop and on a
   1998x982 monitor alike, so every module rolls over, on every screen above the breakpoint,
   without anything being measured. The measurement in script.js stays as a guard that should
   now never fire — see the note there. */

/* 1. the card hands its whole height to the module (the hero keeps its own centring) */
.una200 [data-stack-on="true"] > .module{
  align-content:stretch;
}

/* 2. every wrapper between the card and the elastic row has to pass the height down rather
      than shrink-wrap its content */
.una200 [data-stack-on="true"] > .module .module__grid,
.una200 [data-stack-on="true"] > .module .module__grid > *{
  min-height:0;
  height:100%;
}

/* 3. the three-row layout */
.una200 [data-stack-on="true"] > .module > .cohort,
.una200 [data-stack-on="true"] > .module > .events,
.una200 [data-stack-on="true"] > .module > .give,
.una200 [data-stack-on="true"] > .module > .news,
.una200 [data-stack-on="true"] > .module .timeline{
  display:grid;
  grid-template-rows:auto minmax(0,1fr);
  min-height:0;
}

/* 4. the elastic middles, and the items inside them */
/* These four are grids in their own right, and their single row is auto-sized — so they
   FILL the space they are given but cannot compress inside it. News gave the game away:
   its lead photograph stayed 253px tall in a row with only 278px to spend, and the module
   spilled 122px. Declaring the row as minmax(0,1fr) is what lets the squeeze reach the
   photographs at the bottom of the chain. */
.una200 [data-stack-on="true"] > .module .cohort__grid,
.una200 [data-stack-on="true"] > .module .events__list,
.una200 [data-stack-on="true"] > .module .give__media,
.una200 [data-stack-on="true"] > .module .news__grid,
.una200 [data-stack-on="true"] > .module .timeline__stage{
  display:grid;
  grid-template-rows:minmax(0,1fr);
  /* AND THE CHILDREN MUST BE STRETCHED INTO IT. .news__grid authors `align-items:start`,
     which leaves its children at their own content height — so the lead article sat 494px
     tall inside a 255px row and simply overflowed, no matter how compressible everything
     below it was made. A row that can shrink is no use if nothing is pressing on it. */
  align-items:stretch;
}
.una200 [data-stack-on="true"] > .module .cohort__grid,
.una200 [data-stack-on="true"] > .module .events__list,
.una200 [data-stack-on="true"] > .module .give__media,
.una200 [data-stack-on="true"] > .module .news__grid,
.una200 [data-stack-on="true"] > .module .timeline__stage,
.una200 [data-stack-on="true"] > .module .cohort__grid > *,
.una200 [data-stack-on="true"] > .module .events__list > *,
.una200 [data-stack-on="true"] > .module .news__grid > *,
.una200 [data-stack-on="true"] > .module .timeline__panel{
  min-height:0;
}

/* 5. THE PICTURES KEEP THEIR SHAPE. The variation is absorbed by type and spacing instead.

   An earlier pass had this exactly backwards: the photographs were the elastic part, so they
   collapsed to their floor while the type around them kept its size. Measured at 1512x863 the
   result was a cohort photograph at 4.27:1, a news lead at 3.91:1 and a timeline photograph at
   6.67:1 — slivers. A photograph has a shape; it is not the slack in a layout.

   So the ratio is declared and honoured, and everything else gives way: see the height scale
   below, which drives the module's type and gaps from viewport height. `object-fit:cover`
   still crops the file to the box, which is fine — cropping is not distorting. */
.una200 [data-stack-on="true"] > .module .cohort__photo,
.una200 [data-stack-on="true"] > .module .events__media,
.una200 [data-stack-on="true"] > .module .news__lead-media{
  aspect-ratio:16/9;
}
/* The timeline sits beside its copy rather than above it, so it has room to be squarer.

   SIZED BY HEIGHT, NOT WIDTH. The other three take their width from the column and derive a
   height from the ratio, which is fine because they sit in rows that grow. This one sits in
   the stage row — the `1fr` that absorbs whatever the head, the controls and the call to
   action leave — so a width-derived height ignores the row and overflows it. It did: 432px of
   photograph in a 316px row, spilling 48px upward across the lede.

   Taking the height from the row and the width from the ratio inverts that. The photograph
   keeps its 4:3 and simply gets smaller when the row does. */
.una200 [data-stack-on="true"] > .module .timeline__photo{
  aspect-ratio:4/3;
  height:100%;
  width:auto;
  max-width:100%;
  justify-self:center;
}
/* Give's single photograph is the same case for the same reason: it is the whole of the
   elastic row, so a width-derived height would ignore the row entirely. The cap and the
   auto margins that centre it while unstacked are released here — the row centres it. */
.una200 [data-stack-on="true"] > .module .give__media{
  margin-top:0;
  max-width:none;
}
.una200 [data-stack-on="true"] > .module .give__photo{
  aspect-ratio:4/3;
  height:100%;
  width:auto;
  max-width:100%;
  justify-self:center;
  position:relative;
  min-height:0;
  overflow:hidden;
}
.una200 [data-stack-on="true"] > .module .cohort__photo,
.una200 [data-stack-on="true"] > .module .events__media,
.una200 [data-stack-on="true"] > .module .news__lead-media{
  position:relative;
  height:auto;
  min-height:0;
  overflow:hidden;
  flex:none;
}
/* the timeline photograph shares all of this EXCEPT height:auto, which would undo the
   row-driven sizing above — listed separately rather than quietly re-broken */
.una200 [data-stack-on="true"] > .module .timeline__photo{
  position:relative;
  min-height:0;
  overflow:hidden;
  flex:none;
}
/* The image is still taken out of flow. Its box now has a shape of its own, so the image must
   not be able to argue with it — in flow, an image with its own intrinsic ratio re-opens the
   box from the inside, which is the bug that cost 142px in the News module. */
.una200 [data-stack-on="true"] > .module .cohort__photo > img,
.una200 [data-stack-on="true"] > .module .events__media > img,
.una200 [data-stack-on="true"] > .module .give__photo > img,
.una200 [data-stack-on="true"] > .module .news__lead-media > img,
.una200 [data-stack-on="true"] > .module .timeline__photo > img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  max-width:none;
  aspect-ratio:auto;
  object-fit:cover;
  margin:0;
}

/* Memories is a full-bleed slideshow rather than a framed photograph — it IS the card.
   It therefore has to ignore the card's padding: laid out in flow it stopped 31px short at
   top and bottom, and the card's own ground showed through as a hard horizontal seam across
   the picture. Taking it out of flow and pinning it to the card's edges removes the seam and
   makes it independent of whatever the padding grows to. The card's overflow:hidden still
   clips it to the rounded leading edge, and .memories__copy keeps its z-index above. */
.una200 [data-stack-on="true"] > .module--memories .memories__media{
  position:absolute;
  /* GRID-AREA:AUTO IS LOAD-BEARING. For an absolutely positioned child of a grid container,
     the containing block is its GRID AREA when it has a definite one — and this element is
     placed at `grid-area:1/1`, so `inset:0` resolved to the CONTENT box and left exactly one
     padding's worth of gap at the top. Releasing the placement makes the containing block the
     grid container's padding box, which is the card edge, which is what full-bleed means. */
  grid-area:auto;
  inset:0;
  width:auto;
  height:auto;
  min-height:0;
  aspect-ratio:auto;
}
.una200 [data-stack-on="true"] > .module--memories .memories__media .showcase{
  aspect-ratio:auto;
  height:100%;
  min-height:0;
  position:relative;
  overflow:hidden;
}

/* THE NEWS LEAD: the PHOTOGRAPH yields, the type does not.
   With the picture on an `auto` row it took its full ratio height and left nothing — at a
   720px window the headline, date and excerpt were all squeezed to zero and the lead showed
   as a bare photograph above a button. Putting the photograph on the `1fr` row and the four
   rows of type on `auto` inverts that: the type takes what it needs and the picture takes
   what is left.

   `height:100%` with `width:auto` keeps the ratio while doing it — the row decides the
   height, the ratio decides the width — so a short window gets a smaller photograph rather
   than a squashed one. `justify-self:center` keeps it centred when it is narrower than the
   column.

   NOTE FOR THE NEXT CHECK: a text row squeezed to zero height is exactly this failure, and a
   detector that skips zero-height elements cannot see it. This one was found by looking. */
.una200 [data-stack-on="true"] > .module .news__lead{
  display:grid;
  grid-template-rows:minmax(0,1fr) auto auto auto auto;
  min-height:0;
}
.una200 [data-stack-on="true"] > .module .news__lead-media{
  height:100%;
  width:auto;
  max-width:100%;
  justify-self:center;
  min-height:0;
}

/* ONE TAGGED RELEASE, AND IT TAKES THE WHOLE CARD.
   The index column is dropped when only one release matches, which left the lead in a
   1.12fr column with an empty 0.88fr beside it — a card that looks like it failed to load
   the other half. That is the normal state early on and again during quiet stretches, so it
   has to look deliberate rather than broken.

   Turning the lead on its side is what uses the width: the photograph takes a column and
   spans the full height, the copy takes the other. Same move as the Events cards, and for
   the same reason — there is a great deal of width going spare in a full-bleed card and
   almost no height. */
.una200 [data-stack-on="true"] > .module .news__grid[data-news-layout="solo"]{
  grid-template-columns:minmax(0,1fr);
}
.una200 [data-stack-on="true"] > .module [data-news-layout="solo"] .news__lead{
  grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);
  /* meta, title, excerpt, link — the excerpt takes the slack so the other three sit tight */
  grid-template-rows:auto auto minmax(0,1fr) auto;
  column-gap:clamp(28px,4vw,64px);
  align-content:center;
}
.una200 [data-stack-on="true"] > .module [data-news-layout="solo"] .news__lead-media{
  grid-column:1;
  grid-row:1 / -1;
  align-self:center;
}
.una200 [data-stack-on="true"] > .module [data-news-layout="solo"] .news__meta{
  grid-column:2; grid-row:1;
}
.una200 [data-stack-on="true"] > .module [data-news-layout="solo"] .news__lead-title{
  grid-column:2; grid-row:2;
}
.una200 [data-stack-on="true"] > .module [data-news-layout="solo"] .news__lead-excerpt{
  grid-column:2; grid-row:3;
}
.una200 [data-stack-on="true"] > .module [data-news-layout="solo"] .news__lead .link-more{
  grid-column:2; grid-row:4; align-self:start;
}
/* A solo lead has room for more of the dek than a lead sharing the card with an index, and
   the feed's description is an editor-written one worth showing. */
.una200 [data-stack-on="true"] > .module [data-news-layout="solo"] .news__lead-title{
  -webkit-line-clamp:4;
  line-clamp:4;
}

/* REAL PRESSROOM HEADLINES ARE LONG, and the sample ones were not. The samples here ran six
   to eight words; the live Cascade feed's median is twelve, seven of twenty are fifteen or
   more, and one is twenty-eight. Unclamped, a nineteen-word headline took four lines and
   squeezed the lead photograph to 196x110 — a thumbnail in the slot the layout treats as
   its lead image. The twenty-eight-word one would have taken six lines and left almost
   nothing.

   The arithmetic is direct: the title sits on an `auto` row and the photograph on the `1fr`
   beneath it, so every line the headline takes comes straight out of the picture. Three
   lines is where the photograph still reads as a photograph.

   This only showed up once the feed was live. It is the clearest argument in the build for
   not judging a layout on sample content you wrote yourself. */
.una200 [data-stack-on="true"] > .module .news__lead-title{
  -webkit-line-clamp:3;
  -webkit-box-orient:vertical;
  line-clamp:3;
  overflow:hidden;
  /* display last, per the clamp note further down this file */
  display:-webkit-box;
}

/* ---- THE HEIGHT SCALE: type and spacing give way, in that order ----
   The photographs now hold their shape, so this is what absorbs the difference between a
   700px window and a 1200px one. Everything here is driven by `vh`, so a module shrinks
   continuously with the viewport rather than snapping at a breakpoint — one idea, applied
   in one place, instead of a rule per module.

   THE ORDER OF SACRIFICE IS DELIBERATE:
     1  the gaps between things
     2  the lede under each title, which restates the heading
     3  the display type, which is far larger than it needs to be at 800px of screen
     4  the body type, last and least, and never below 15px
   Nothing here touches a photograph's shape, and nothing clamps a line of body copy away.

   ACCESSIBILITY FLOOR: every clamp() below has a px minimum at or above 15px for body copy
   and 0.8rem for the small caps, so the page never scales text under the size it uses
   everywhere else on the site. The clamps are also all `rem`-based at the top end, so a
   reader who raises their browser font size still gets it. */
/* 1. the gaps */
.una200 [data-stack-on="true"] > .module .cohort__head,
.una200 [data-stack-on="true"] > .module .events__head,
.una200 [data-stack-on="true"] > .module .give__head,
.una200 [data-stack-on="true"] > .module .news__head,
.una200 [data-stack-on="true"] > .module .timeline__head{
  margin-bottom:clamp(6px,1.1vh,20px);
}
.una200 [data-stack-on="true"] > .module .cohort__cta,
.una200 [data-stack-on="true"] > .module .events__cta,
.una200 [data-stack-on="true"] > .module .give__cta,
.una200 [data-stack-on="true"] > .module .news__cta,
.una200 [data-stack-on="true"] > .module .timeline__cta{
  margin-top:clamp(4px,0.8vh,14px);
}
.una200 [data-stack-on="true"] > .module .cohort__grid,
.una200 [data-stack-on="true"] > .module .events__list,
.una200 [data-stack-on="true"] > .module .news__grid{
  gap:clamp(12px,1.6vw,28px);
}

/* 2. the lede: two lines, and smaller with the window */
.una200 [data-stack-on="true"] > .module .cohort__lede,
.una200 [data-stack-on="true"] > .module .events__lede,
.una200 [data-stack-on="true"] > .module .news__lede,
.una200 [data-stack-on="true"] > .module .timeline__lede,
.una200 [data-stack-on="true"] > .module .cohort__head > p,
.una200 [data-stack-on="true"] > .module .events__head > p,
.una200 [data-stack-on="true"] > .module .news__head > p{
  font-size:clamp(0.95rem,1.6vh,1.15rem);
  margin-top:clamp(6px,1vh,14px);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  line-clamp:2; overflow:hidden;
}

/* GIVE'S LEDE IS NOT CLAMPED, and that is deliberate. The clamp above works for the other
   four because their ledes restate the heading in a line or two — losing the tail of one
   costs nothing. This one is the ask itself, ~56 words, and two lines of it would cut the
   sentence about scholarships off mid-clause. It sits on the `auto` row and takes its
   natural height; the photograph below is on the `1fr` and absorbs the difference, which is
   exactly the trade this architecture exists to make. It still shrinks with the window. */
.una200 [data-stack-on="true"] > .module .give__lede{
  font-size:clamp(0.95rem,1.55vh,1.1rem);
  margin-top:clamp(6px,1vh,14px);
}

/* 3. the display type */
.una200 [data-stack-on="true"] > .module .module__title,
.una200 [data-stack-on="true"] > .module .timeline__title{
  font-size:clamp(1.6rem,4.2vh,3.1rem);
  line-height:1.08;
}

/* 4. the body type, last, and floored at 15px */
.una200 [data-stack-on="true"] > .module .cohort__body,
.una200 [data-stack-on="true"] > .module .events__body,
.una200 [data-stack-on="true"] > .module .news__lead,
.una200 [data-stack-on="true"] > .module .timeline__copy{
  font-size:clamp(0.94rem,1.55vh,1.05rem);
}
.una200 [data-stack-on="true"] > .module .cohort__body,
.una200 [data-stack-on="true"] > .module .events__body{
  padding:clamp(12px,1.8vh,24px) clamp(14px,1.4vw,22px);
  gap:clamp(5px,0.8vh,12px);
}

/* ---- EVENTS ON THE HOMEPAGE: HORIZONTAL CARDS ----
   Events was the binding constraint at every short window. A vertical card stacks a
   photograph ON TOP of its copy, so the two heights add up and the card needs whatever they
   come to; three of them across a card meant the module wanted more height than a laptop has.
   Turning the card on its side puts the photograph BESIDE the copy, so the card is only as
   tall as the taller of the two rather than their sum — and there is a great deal of width
   going spare in a full-bleed card.

   The photograph is sized by HEIGHT here, not width: `height:100%` with `width:auto` and a
   ratio, so the row decides how tall it is and the ratio decides how wide. That resolves
   because the chain above it is definite all the way up to the card's fixed height. It is the
   same `height:100%` that silently failed in the vertical layout — the difference is that
   there the row was being sized BY the photograph, and here it is not.

   The subpage keeps its own three-across layout; this is scoped to a live stack. */
.una200 [data-stack-on="true"] > .module .events__list{
  grid-template-columns:minmax(0,1fr);
  grid-template-rows:repeat(3, minmax(0,1fr));
  gap:clamp(10px,1.5vh,22px);
  max-width:none;
}
/* FEWER EVENTS THAN ROWS. The three rows above are the authored maximum, not a promise —
   filtered to the bicentennial tag there may be one or two events on the calendar, and
   three equal rows left them stranded above a row of nothing. script.js writes the real
   count and the rows follow it.

   THEY FILL THE SPACE RATHER THAN SITTING IN THE MIDDLE OF IT. An earlier pass sized these
   rows to their content and centred the result, which was tidy and wrong: two cards came
   out 108px tall in a 455px row, so the photograph was a sliver and the description got a
   single clamped line while most of the card stayed empty. Two cards should be two tall
   cards, not two small ones adrift.

   ONE card is the exception and is capped. At a full 455px its 16:9 photograph would be
   809px wide — two thirds of the card, with the copy squeezed into what is left. A banner,
   not a card. */
.una200 [data-stack-on="true"] > .module .events__list[data-events-filled="1"]{
  grid-template-rows:minmax(0, 300px);
  align-content:center;
}
.una200 [data-stack-on="true"] > .module .events__list[data-events-filled="2"]{
  grid-template-rows:repeat(2, minmax(0,1fr));
}

/* A TALL CARD HAS ROOM FOR MORE SENTENCE. The shared rule gives two lines, cut to one below
   1200px, because three narrow cards are short. One or two cards are not: at ~221px each
   there is room for three lines and still space under them.

   The attribute in the selector carries this past both of those rules on specificity, so it
   does not depend on sitting after them. */
.una200 [data-stack-on="true"] > .module .events__list[data-events-filled="1"] .events__desc,
.una200 [data-stack-on="true"] > .module .events__list[data-events-filled="2"] .events__desc{
  -webkit-line-clamp:3;
  line-clamp:3;
  display:-webkit-box;
}
.una200 [data-stack-on="true"] > .module .events__card{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  align-items:stretch;
  min-height:0;
}
.una200 [data-stack-on="true"] > .module .events__media{
  /* 16:9 rather than 4:3 here. A horizontal card is short, so a squarer picture comes out
     narrow — 182px at this height — and the date plate, which is 70px wide, then sits on
     nearly a quarter of it. The wider crop gives the plate room to read as an overlay on a
     photograph rather than as most of one. */
  aspect-ratio:16/9;
  height:100%;
  width:auto;
  flex:none;
  min-height:0;
}
/* THE COPY: title, description, then time and place on one footer line.

   CSS ONLY, DELIBERATELY. The Events feed rebuilds these cards wholesale from the API, so
   anything done in the markup would have to be done again in script.js and kept in step. The
   category chip is hidden and the time is moved onto the venue line without either element
   moving in the DOM.

   `display:contents` on .events__meta is what allows that: it makes the meta wrapper vanish
   from layout so its children become direct grid items of the body, which is the only way a
   grandchild can be placed in the body's grid. The chip then goes, and the time it was
   sitting beside joins the venue on the footer row — recovering a whole row of card height,
   which is what buys the description its second line. */
.una200 [data-stack-on="true"] > .module .events__meta{
  display:contents;
}
.una200 [data-stack-on="true"] > .module .events__cat{
  display:none;
}
.una200 [data-stack-on="true"] > .module .events__body{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  grid-template-rows:auto auto auto;
  align-content:center;
  column-gap:0;
}
/* placed explicitly, because auto-placement would put the time above the title — it comes
   first in the DOM, and that is exactly what `display:contents` exposes */
.una200 [data-stack-on="true"] > .module .events__title{
  grid-row:1; grid-column:1 / -1;
}
.una200 [data-stack-on="true"] > .module .events__desc{
  grid-row:2; grid-column:1 / -1;
}
.una200 [data-stack-on="true"] > .module .events__when{
  grid-row:3; grid-column:1;
}
.una200 [data-stack-on="true"] > .module .events__venue{
  grid-row:3; grid-column:2;
}
/* NO PLACE, NO DIVIDER. Many events in this feed carry no venue, and the rule below is a
   pseudo-element on the TIME, so it renders regardless of what follows — the card read
   "ALL DAY |" with nothing after the bar. script.js flags the card when the venue comes
   back empty, because a pseudo-element cannot be removed from JavaScript. */
.una200 [data-stack-on="true"] > .module .events__card[data-event-no-venue] .events__when::after{
  display:none;
}
/* the rule between them, as a pseudo-element so it is never read aloud as a character */
.una200 [data-stack-on="true"] > .module .events__when::after{
  content:"";
  display:inline-block;
  width:1px;
  height:0.85em;
  margin:0 0.7em -0.1em;
  background:currentColor;
  opacity:.4;
}
/* a horizontal card is short, so its copy needs far less room above and below than the
   vertical one did — the padding there was sized for a card three times this tall */
/* One line of description: the card is wide now, so a line carries far more of the sentence
   than it did in a 370px column.

   THE WHOLE TRIPLET, NOT JUST THE LINE COUNT. `-webkit-line-clamp` does nothing unless the
   element is `display:-webkit-box`, and this one computes to `flow-root` — so setting only
   the count left the clamp inert while the flex column squeezed the paragraph to a 15px
   sliver against a 25px line. It rendered as an empty gap between the rule and the venue,
   which looks like a layout bug rather than missing text. `flex:none` stops the column
   shrinking it again. */
/* the clamp itself is set further down, AFTER the shared three-line rule it has to beat */
.una200 [data-stack-on="true"] > .module .events__desc{
  flex:none;
}

/* ---- THE CARDS INSIDE A MODULE, which clip their own content ----
   These were losing text silently and a panel-level overflow check could never have seen it:
   an events card carries `overflow:hidden` for its rounded corners, so its body was cut off
   INSIDE the card and the panel reported a clean fit. Measured at 1512x863 before this block:
   75px of copy gone from each of the three cards, mid-sentence.

   The lesson for any future check here: a card that hides its own overflow will always look
   like it fits from the outside. Ask the element that does the hiding. */
/* `.cohort__quote`, NOT `.cohort__excerpt` — an earlier pass clamped a class that does not
   exist, so nothing happened and the cards ran to whatever length their copy wanted. One card
   took four lines while its neighbours took three, the body outgrew the card, and because the
   card does not hide its overflow the "Read their story" link ESCAPED out of the bottom at a
   different height on each card. That is the visual break in the screenshot.

   A class that matches nothing fails silently, which is why the browser check below now looks
   for content that escapes as well as content that is clipped. */
/* AN ELLIPSIS THAT REFLOWS WITH THE VIEWPORT — and it is CSS, no JavaScript. `line-clamp`
   cuts at a whole line and appends the ellipsis itself, and because it counts LINES rather
   than characters it re-cuts on its own as the column narrows.

   TWO THINGS BREAK IT, AND BOTH ARE SILENT:

   1. DECLARATION ORDER. `line-clamp` is the modern shorthand and it RESETS `display` — so
      `display:-webkit-box` followed by `line-clamp:3` leaves the element at `flow-root` with
      the legacy clamp inert. Hence `display` last here, where nothing can reset it. That was
      the bug: the Class of 2030 excerpts were not clamped at all, and `overflow:hidden` cut
      them through the middle of line four with no ellipsis.
   2. A BOX SHORTER THAN THE CLAMP. A clamp only ellipsizes at ITS OWN boundary. If a flex
      column squeezes the box below the clamped number of lines, the overflow is simply
      sliced instead — which looks identical to having no clamp. `flex:none` below is what
      stops that, and the line count has to be one the card can actually give. */
.una200 [data-stack-on="true"] > .module .events__desc,
/* FOUR LINES, UP FROM THREE. Removing the program line gave the card back about 44px and
   left it as dead space under the link. The student's own words are the substance of this
   module, so the space goes to the excerpt rather than staying empty. */
.una200 [data-stack-on="true"] > .module .cohort__quote{
  -webkit-line-clamp:4;
  line-clamp:4;
  -webkit-box-orient:vertical;
  overflow:hidden;
  min-height:0;
  flex:none;
  display:-webkit-box;
}
/* 32px of rule and margin above three lines of quotation is spacing for a page, not a card */
/* Gaps first, per the height scale's order of sacrifice. These two were clamp(6px,1vh,14px)
   and gave back the last few pixels the card needed once real content arrived. The card
   also used to carry a program line under the name, which wrapped to two lines on a real
   college name; that line has since been removed — see the note in script.js — so there is
   more room here now than when this was tightened. Worth revisiting if the card ever feels
   cramped again. */
.una200 [data-stack-on="true"] > .module .cohort__quote{
  margin-top:clamp(4px,0.7vh,10px);
  padding-top:clamp(4px,0.7vh,10px);
}
/* and the body has to be allowed to shrink, or the clamp has nothing to shrink into */
.una200 [data-stack-on="true"] > .module .cohort__body,
.una200 [data-stack-on="true"] > .module .events__body{
  min-height:0;
}
.una200 [data-stack-on="true"] > .module .events__body,
.una200 [data-stack-on="true"] > .module .cohort__body{
  padding:clamp(8px,1.3vh,18px) clamp(12px,1.2vw,20px);
  gap:clamp(3px,0.6vh,9px);
}
.una200 [data-stack-on="true"] > .module .cohort__body .link-more{
  margin-top:clamp(4px,0.8vh,12px);
  /* THE BASE RULE'S 18px padding-top IS FOR THE LONG PAGE, and it was never trimmed here.
     Inside a fixed-height card it is 18px the body does not have: measured, the card's
     content needed 229.5px against 207.6px available and the link hung 10px out of the
     bottom of its own card. The margin-top above already provides the separation, so the
     padding is pure overflow. (It escaped the panel spill check because it overflows the
     CARD, not the section — the "escaped" mode in check-content-loss.js.) */
  padding-top:0;
}

/* THE PROGRAM LINE IS NOT CLAMPED, and clamping it was tried and reverted. Real college
   names are long — "Sanders College of Business and Technology" takes two lines where the
   sample's "Declared Major" took one — so a two-line clamp looks like the obvious saving.
   It is not: measured, the clamp box came out 34.2px against 38px of text, which bought
   3.8px and shaved the descenders off "Engineering". The height comes from spacing
   instead, below. Truncating a student's college to save four pixels is the wrong trade. */

/* THE EVENTS CARD CARRIES TWO LINKS TO THE SAME PLACE — its title, and a "Details" repeat
   below the venue. Now that the whole card is a link (see .events__title a::after), the
   repeat is redundant everywhere rather than just where the card was short of room: the
   destination is not lost because the title still goes there, and a screen reader stops
   hearing the same URL announced twice under different names. `display:none` rather than
   visibility, so it leaves the accessibility tree and the tab order too.

   NOT scoped to the stack any more. Left scoped, the repeat came back below 901px — where
   the stretched overlay sits on top of it, so it looked like a link, did not hover like one,
   and led where the card already led.

   WHAT THIS COSTS ON TOUCH: no hover, so the arrow was the one explicit "this is tappable"
   cue. The card still reads as tappable by convention, but if that cue is wanted back the
   clean way is a decorative arrow rather than a second link — one link per card is the
   property worth keeping.

   The Class of 2030 cards keep theirs — checked, and there the "Read their story" link is
   the ONLY link in the card. Removing it there would strip the destination entirely. */
.una200 .events__card .link-more{
  display:none;
}
/* the date plate and venue line are small already; the meta row's own gap is not */
.una200 [data-stack-on="true"] > .module .events__meta{
  margin-bottom:clamp(2px,0.5vh,8px);
}

/* AFTER the shared card rule above, not before it: that one sets `padding` as a shorthand,
   so it was overwriting the horizontal card's `padding-block` no matter how the values were
   tuned. A horizontal card is a third the height of a vertical one and does not need the
   same room above and below its copy. */
.una200 [data-stack-on="true"] > .module .events__body{
  padding-block:clamp(4px,0.6vh,10px);
  gap:clamp(1px,0.3vh,5px);
}

/* The rule above the description carries 14px of margin AND 14px of padding, which is 28px of
   separator for a single line of text. That is well judged in a tall card where the
   description is a paragraph; in a horizontal card it is most of the height the line itself
   gets. Same separator, a third of the room. */
.una200 [data-stack-on="true"] > .module .events__desc{
  margin-top:clamp(3px,0.5vh,8px);
  padding-top:clamp(3px,0.5vh,8px);
  /* TWO lines here, not the three a vertical card gets — and this has to sit after that rule
     to win, since both are the same specificity. Left in the earlier block it was inert, and
     the third card quietly ran to two lines while its neighbours ran to one. It was one line
     until the category chip was removed; that row is what pays for the second.
     `display` repeated last, for the same reason as above: `line-clamp` resets it. */
  -webkit-line-clamp:2;
  line-clamp:2;
  display:-webkit-box;
}
/* the title's 14px margin is spacing for a tall card; here it is a tenth of the card */
.una200 [data-stack-on="true"] > .module .events__title{
  margin-top:clamp(4px,0.7vh,10px);
}

/* A NARROWER WINDOW NEEDS FEWER LINES, not a shorter box. The horizontal card's height comes
   from the module, but how much SENTENCE fits on a line comes from the width — so at 1100px
   the two-line description no longer fit the row it was given and was sliced through line
   two. Dropping to one line is the fix; squeezing the box is what produced the slice.

   Width, not height, because that is the dimension that actually changed. */
@media (max-width:1200px){
  .una200 [data-stack-on="true"] > .module .events__desc{
    -webkit-line-clamp:1;
    line-clamp:1;
    display:-webkit-box;
  }
}
.una200 [data-stack-on="true"] > .module .events__venue{
  margin-top:clamp(3px,0.5vh,8px);
}

/* One line of title too. A horizontal card is a fixed height, so a two-line title on one
   event and a one-line title on the next would make the three cards disagree — and the long
   one overflows. There is plenty of width here for a title to say its piece on one line. */
.una200 [data-stack-on="true"] > .module .events__title{
  display:-webkit-box;
  -webkit-line-clamp:1;
  -webkit-box-orient:vertical;
  line-clamp:1;
  overflow:hidden;
  /* `flex:none` OR THE TITLE IS SLICED IN HALF. Clamping sets how many lines may show; it
     does nothing to stop a flex column shrinking the box below one line's height. The card
     is a fixed height, so the column had to take the shortfall from somewhere and took it
     here — the titles rendered cut through the middle, and the third card's vanished
     altogether. Every clamped line in this card needs to refuse to shrink. */
  flex:none;
}
.una200 [data-stack-on="true"] > .module .events__meta,
.una200 [data-stack-on="true"] > .module .events__venue{
  flex:none;
}

/* ---- SHORT WINDOWS: one more pass over the furniture ----
   A breakpoint rather than a measurement, and it spends the page's least valuable pixels
   first. At 800px of viewport the Events cards were still 44px short with 16:9 photographs
   and body type already at its 15px floor; this is where that 44px comes from. The
   photographs are not touched — keeping their shape is the point of the whole pass. */
@media (max-height:860px){
  /* the lede restates the heading directly above it, so it is the first thing to go */
  .una200 [data-stack-on="true"] > .module .cohort__lede,
  .una200 [data-stack-on="true"] > .module .events__lede,
  .una200 [data-stack-on="true"] > .module .news__lede,
  .una200 [data-stack-on="true"] > .module .timeline__lede,
  .una200 [data-stack-on="true"] > .module .cohort__head > p,
  .una200 [data-stack-on="true"] > .module .events__head > p,
  .una200 [data-stack-on="true"] > .module .news__head > p{
    -webkit-line-clamp:1;
    line-clamp:1;
  }
  /* and the module's own button, which is generously padded for a full-page layout */
  .una200 [data-stack-on="true"] > .module .cohort__cta .btn,
  .una200 [data-stack-on="true"] > .module .events__cta .btn,
  .una200 [data-stack-on="true"] > .module .give__cta .btn,
  .una200 [data-stack-on="true"] > .module .news__cta .btn,
  .una200 [data-stack-on="true"] > .module .timeline__cta .btn{
    padding-top:clamp(10px,1.4vh,16px);
    padding-bottom:clamp(10px,1.4vh,16px);
  }
  /* and the inner cards' own breathing room, last, because it is the thing a reader is
     actually looking at when they read one */
  .una200 [data-stack-on="true"] > .module .events__body,
  .una200 [data-stack-on="true"] > .module .cohort__body{
    padding-top:clamp(6px,1vh,12px);
    padding-bottom:clamp(6px,1vh,12px);
    gap:clamp(2px,0.45vh,7px);
  }
  /* the horizontal card's internal rules and margins, which are sized for a tall card and
     are most of the height of a short one */
  .una200 [data-stack-on="true"] > .module .events__title{
    margin-top:clamp(2px,0.4vh,6px);
  }
  .una200 [data-stack-on="true"] > .module .events__desc{
    margin-top:clamp(2px,0.3vh,5px);
    padding-top:clamp(2px,0.3vh,5px);
  }
  .una200 [data-stack-on="true"] > .module .events__venue{
    margin-top:clamp(2px,0.3vh,5px);
  }
  /* the category chip's own padding makes the meta row 28px tall for 18px of text */
  .una200 [data-stack-on="true"] > .module .events__cat{
    padding-block:1px;
  }
}

/* =================================================================
   LIGHT-GROUND OVERRIDES — .on-paper
   ================================================================= */
/* EVERY module type style on this site assumes a dark panel, because until the Memories
   form there was no light section carrying any. On --paper they measure:
       .module__title   #fff        1.07:1   invisible
       .module__eyebrow --gold      1.60:1   invisible
       .chalk::after    --gold      1.60:1   invisible
       .btn / bullets   --gold      1.60:1   invisible
   So a light section needs all of them retuned together. They live here as one utility
   rather than being patched per page — the alternative is rediscovering this list every
   time a light panel is added, which is exactly how the white-on-cream heading shipped.

   Put `on-paper` on any section whose ground is --paper or --paper-dim. */
.una200 .on-paper .module__title{ color:var(--purple-deep); }        /* 12.57:1 */
.una200 .on-paper .module__eyebrow{ color:var(--gold-deep); }        /*  5.54:1 */
.una200 .on-paper .chalk::after{ background-color:var(--gold-deep); }/*  5.54:1 */
/* The site-wide gold focus ring measures 1.60:1 here — far under the 3:1 SC 1.4.11 asks of
   a focus indicator. Purple measures 9.18:1. */
.una200 .on-paper :focus-visible{ outline-color:var(--purple); }

/* intentional global — see the scoping note at the top of this file */
html{ scroll-behavior:smooth; }

body.una200{
  margin:0;
  font-family:var(--font);
  font-weight:400;
  background:var(--paper);
  color:var(--ink);
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
  /* `clip`, NOT `hidden`, and this is load-bearing for the scroll stack. `overflow-x:hidden`
     forces the used value of overflow-y to `auto`, which makes <body> a scroll container —
     and `position:sticky` then resolves against BODY instead of the viewport, so every
     pinned panel in the stack silently stops pinning. `overflow-x:clip` clips without
     creating a scroll container, which is exactly what was wanted here in the first place.
     Where `clip` is unsupported the declaration is dropped and nothing is clipped, which is
     a cosmetic regression rather than a broken page. */
  overflow-x:clip;

}
/* scroll lock while the mobile nav panel is open */
body.una200.is-nav-open{ overflow:hidden; }
.una200 img{ max-width:100%; display:block; }
.una200 a{ color:inherit; }
.una200 h1,.una200 h2,.una200 h3,.una200 p,.una200 figure,.una200 blockquote{ margin:0; }
.una200 :focus-visible{ outline:3px solid var(--gold); outline-offset:3px; border-radius:2px; }

.una200 .skip-link{
  position:absolute; left:1rem; top:-4rem; z-index:100;
  background:var(--ink); color:#fff; padding:.75rem 1.25rem; border-radius:4px;
  font-weight:600; transition:top .2s ease;
}
.una200 .skip-link:focus{ top:1rem; }

/* ---------- scroll progress ---------- */
.una200 .scroll-progress{
  position:fixed; top:0; left:0; right:0; height:3px; z-index:70;
  background:transparent; pointer-events:none;
}
.una200 .scroll-progress span{
  display:block; height:100%; width:0%;
  background:var(--gold);
  box-shadow:0 0 10px rgba(255,185,42,.6);
}

/* ---------- nav ---------- */
/* Bar height is logo height + 2x vertical padding, and it stays constant between the
   loose and stuck states — only the background changes — so sticking doesn't reflow. */
.una200 .site-nav{
  position:fixed; top:0; left:0; right:0; z-index:60;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:clamp(16px,3vw,40px);
  /* 17.5 + logo 48 + 17.5 + 1px border = 84px, i.e. 15% up from the previous 73px */
  padding:17.5px clamp(18px,5vw,7rem);
  background:linear-gradient(180deg, rgba(23,11,39,.85), rgba(23,11,39,0));
  border-bottom:1px solid transparent;
  transition:background .35s ease, backdrop-filter .35s ease, border-color .35s ease;
}
.una200 .site-nav.is-stuck{
  background:rgba(23,11,39,.92);
  backdrop-filter:blur(10px);
  border-bottom-color:var(--line-on-dark);
}
.una200 .site-nav__brand{
  display:flex; align-items:center; gap:clamp(14px,1.8vw,24px);
  min-width:0;
}
.una200 .site-nav__logo{ display:block; line-height:0; flex:none; }
.una200 .site-nav__logo img{ height:3rem; width:auto; margin:0; }

/* The microsite's own identity, sitting to the right of the university's. The divider and
   its padding live here rather than on the est line, because the est line hides below 1200px
   and the rule that draws the seam has to outlast it. */
.una200 .site-nav__site{
  display:flex; align-items:center; gap:clamp(9px,1.1vw,14px);
  border-left:1px solid rgba(241,230,178,.28);
  padding-left:clamp(14px,1.8vw,24px);
  min-width:0;
  text-decoration:none;
}

/* THE MARK SETS THE CEILING FOR THE WHOLE BAR. The brand row is as tall as its tallest
   child, the bar is that plus its padding, and --nav-h is hard-coded at 84px — which the
   scroll stack reads twice, to pin cards and to size them. 44px keeps this under the 48px
   logo, so the logo stays the thing that decides the height and the bar does not move.
   Raise this past 48px and every card on the homepage slides under the bar. */
.una200 .site-nav__mark{
  height:44px; width:auto; flex:none; display:block; margin:0;
}

/* HIDDEN ON THE HOMEPAGE, where the hero already carries this mark at full size. `home` is
   the class WordPress puts on the front page by itself, so this survives the port untouched
   and the nav markup stays byte-identical across all six pages. */
body.una200.home .site-nav__mark{ display:none; }

/* the two lines of type, stacked beside the mark */
.una200 .site-nav__lockup{
  display:flex; flex-direction:column; gap:3px; min-width:0;
}

/* THE WAY BACK TO index.html. The logo cannot do this job — it goes to una.edu, which is a
   different site — so without this link a visitor on a subpage has no header-level route
   home at all, only the eyebrow link part-way down the page.

   Styled as a sub-brand rather than as a sixth nav link: heavier and larger than the row on
   the right, and outside .site-nav__links, so it reads as "the name of this site" (which
   happens to be clickable) instead of another destination competing with four others. It
   still takes the same chalk underline on hover as the nav links, which is what tells a
   visitor it is a link at all. */
/* The site name. A <span> now, not the anchor — the whole lockup is the link, so the word no
   longer needs padding to reach a 24px target and the padding/negative-margin pair that used
   to buy that is gone. `align-self:start` keeps the box hugging the word so its chalk streak
   does not run the width of the column. */
.una200 .site-nav__home{
  font-size:.86rem; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:#fff; white-space:nowrap;
  transition:color .25s ease;
  align-self:start;
}

/* the establishment line that used to be the hero eyebrow.
   LEFT AT .68rem in the 2026-10-16 type pass, which raised every other small label to a
   .72rem floor. The nav row is measured to the pixel (see .site-nav__links) and this line
   is supplementary — the same date is in the page title and the hero. Re-measure the row
   at 1200px before raising it. */
.una200 .site-nav__est{
  font-size:.68rem; letter-spacing:.16em; text-transform:uppercase;
  color:var(--gold); font-weight:600; white-space:nowrap;
}

.una200 .site-nav__right{
  display:flex; align-items:center; gap:clamp(14px,2.5vw,32px);
  min-width:0;
}
.una200 .site-nav__links{
  display:flex; align-items:center; gap:clamp(6px,1.2vw,18px);
  margin:0; padding:0; list-style:none;
}
.una200 .site-nav__links a{
  display:inline-block;
  /* padding is the tap target, not decoration — keep it generous */
  padding:11px 8px;
  font-size:.8rem; font-weight:600; letter-spacing:.12em; text-transform:uppercase;
  color:rgba(255,255,255,.82); text-decoration:none; white-space:nowrap;
  transition:color .25s ease;
}
.una200 .site-nav__links a:hover{ color:#fff; }

/* GIVE IS THE PRIORITY ACTION, AND GOLD TEXT WAS THE WRONG WAY TO SAY SO — gold is also what
   marks the current page, so a permanently gold link read as "you are here" on every page.
   A bordered pill says priority through SHAPE instead, which frees colour to mean position:
   Give is the only boxed item in the row, the current page is the only gold one. */
.una200 .site-nav__links a.site-nav__give{
  color:#fff;
  border:1px solid var(--gold);
  border-radius:4px;
  /* the border needs horizontal room the bare links did not. Vertical padding stays at 11px
     so every link in the row keeps the same tap height and the same optical baseline. */
  padding:11px 18px;
  transition:color .25s ease, background-color .25s ease;
}
/* fills on hover, so it behaves like the button it now looks like */
.una200 .site-nav__links a.site-nav__give:hover{
  color:var(--ink);
  background-color:var(--gold);
}
/* No chalk streak inside the pill: the box already carries the emphasis, and the streak sits
   at bottom:-7px, which would draw a second gold line a few pixels off the bottom border. */
.una200 .site-nav__links a.site-nav__give > span::after{ content:none; }

/* Chalk underline, same tapered streak as the hero's "character".
   The taper lives in a mask so the streak can be painted with currentColor and pick up
   whichever colour the link is; the reveal is a clip-path wipe, exactly as in the hero.
   (One clip-path per element, hence taper-by-mask rather than taper-by-clip.) */
/* TWO NAMED OWNERS, not one structural selector. This was briefly keyed on
   `.site-nav a > span`, which was neat while every nav anchor held exactly one span. It stops
   being safe the moment the brand link holds a two-line lockup: `.site-nav__lockup` is also a
   span child of an anchor, so it would take a streak running under BOTH lines. Naming the two
   things that get an underline is duller and cannot be tripped by structure. */
.una200 .site-nav__links a > span,
.una200 .site-nav__home{ position:relative; display:inline-block; }
.una200 .site-nav__links a > span::after,
.una200 .site-nav__home::after{
  content:"";
  position:absolute; left:-3%; right:-3%; bottom:-7px;
  height:8px;
  background-color:currentColor;
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 26' preserveAspectRatio='none'%3E%3Cpath d='M6,9 C110,9 210,10.5 294,13 C210,15.5 110,17 6,17 Z' fill='%23000'/%3E%3C/svg%3E");
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 26' preserveAspectRatio='none'%3E%3Cpath d='M6,9 C110,9 210,10.5 294,13 C210,15.5 110,17 6,17 Z' fill='%23000'/%3E%3C/svg%3E");
  mask-size:100% 100%; -webkit-mask-size:100% 100%;
  mask-repeat:no-repeat; -webkit-mask-repeat:no-repeat;
  clip-path:inset(0 100% 0 0);
  transition:clip-path .45s cubic-bezier(.3,.7,.4,1);
  pointer-events:none;
}
.una200 .site-nav__links a:hover > span::after,
.una200 .site-nav__links a:focus-visible > span::after,
/* the brand streak is revealed by hovering the whole lockup, mark included — the word is no
   longer the hover target, the anchor around it is */
.una200 .site-nav__site:hover .site-nav__home::after,
.una200 .site-nav__site:focus-visible .site-nav__home::after{ clip-path:inset(0 0 0 0); }

/* THE CURRENT PAGE: gold name, gold chalk streak, drawn from the start.
   Nothing here sets the streak's colour. It paints with currentColor, so colouring the link
   colours its underline to match — including the taller streak the mobile panel gives it.
   The streak does not animate in on load, because a transition never runs against an
   element's first computed style.

   THIS BLOCK MUST STAY AFTER THE :hover RULES ABOVE. `a:hover` and `a[aria-current]` carry
   identical specificity, so source order alone is what keeps the current page gold while the
   pointer is over it — reordering would blank the only "you are here" signal at exactly the
   moment someone reaches for it. Markup side is already in place: every subpage carries
   aria-current="page" on its own link, which is the accessible name for this state and the
   reason it is keyed off the attribute rather than a class.

   Scoped to .site-nav rather than .site-nav__links so the homepage marks itself too: the
   home link is the one nav element that represents index.html, so on that page it is the
   thing that should be gold. Every page now carries exactly one current marker. */
.una200 .site-nav__links a[aria-current="page"],
.una200 .site-nav__links a[aria-current="page"]:hover,
/* the homepage marks the brand link, so the gold lands on the word inside it */
.una200 .site-nav__site[aria-current="page"] .site-nav__home{ color:var(--gold); }
.una200 .site-nav__links a[aria-current="page"] > span::after,
.una200 .site-nav__site[aria-current="page"] .site-nav__home::after{ clip-path:inset(0 0 0 0); }

/* ---------- menu toggle (hidden above 900px) ---------- */
/* 44x44 so it clears WCAG 2.5.5's target size, which also makes it the tallest thing in
   the bar on mobile — it is what sets nav height there now that the links have moved out. */
.una200 .site-nav__toggle{
  display:none;
  flex:none; width:44px; height:44px;
  place-items:center;
  background:transparent; border:0; padding:0; margin:0;
  color:var(--accent-cream-1);
  cursor:pointer;
}
.una200 .site-nav__toggle-label{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap;
}
/* three bars from one element plus its two pseudo-elements, so there is no icon file and
   nothing for WordPress to strip */
.una200 .site-nav__toggle-bars,
.una200 .site-nav__toggle-bars::before,
.una200 .site-nav__toggle-bars::after{
  content:"";
  display:block; width:24px; height:2px; border-radius:2px;
  background:currentColor;
  transition:transform .3s cubic-bezier(.3,.7,.4,1), opacity .2s ease;
}
.una200 .site-nav__toggle-bars::before{ transform:translateY(-7px); }
.una200 .site-nav__toggle-bars::after{ transform:translateY(5px); }
/* the middle bar fades and the outer two fold into an X on its position */
.una200 .site-nav.is-open .site-nav__toggle-bars{ background:transparent; }
.una200 .site-nav.is-open .site-nav__toggle-bars::before{ transform:translateY(0) rotate(45deg); }
.una200 .site-nav.is-open .site-nav__toggle-bars::after{ transform:translateY(-2px) rotate(-45deg); }

/* ---------- nav breakpoint ladder ---------- */
/* Rebuilt when the nav went from three destinations to five. The row carries roughly 470px
   of links instead of 220px, so the old 1040/820 ladder no longer held: shed the est line
   first, being the longest single item, then hand the links to a panel.
   These widths are calculated from type metrics, not measured in a browser — re-check if
   a sixth destination is ever added.

   The ladder used to have a middle rung that hid the "1830-2030" badge at 1000px. The badge
   was removed from the site on 2026-09-22, which takes a rung out and leaves the row with
   more slack at every width than these figures assume. */
@media (max-width:1200px){
  .una200 .site-nav__est{ display:none; }
}

/* SEVEN DESTINATIONS DO NOT FIT THE ROW UNAIDED once the est line has gone. Measured at
   905px, just above the panel breakpoint: the row wanted 62px more than it had.

   Spacing gives way, not type. The labels stay at .8rem — the size every other small label
   on the site uses — because a nav that shrinks its own text at one width is harder to read
   precisely where the window is already tight. Six gaps and twelve side paddings between
   them carry more than enough slack to find 62px.

   Give keeps more padding than the rest: it is the only bordered item, and a border 4px from
   its label reads as a box round a word rather than as a button. */
/* RAISED FROM 1030px TO 1320px WHEN ENGAGE MADE IT EIGHT ITEMS.

   The note on the nav markup has said since the seventh was added: re-measure the row
   before adding an eighth. Measured on paper rather than in a browser, because none was
   available — at the roomy spacing the eight labels plus their padding and gaps come to
   about 782px, and with the brand lockup and the nav's own side padding the row wants
   roughly 1310px of viewport. The old rung did not start tightening until 1030, which left
   a band from about 1030 to 1310 where the row had run out of space and nothing had given
   way yet.

   The tightened spacing buys about 194px, taking the requirement down to roughly 1010px,
   and the mobile panel takes over at 900. So the rung has to span 900 to 1320 to leave no
   gap, which is what it now does.

   THESE ARE ESTIMATES. The advance-width model behind them is approximate and the brand
   lockup was taken as ~300px rather than measured. Check the row at 1280 and 1100 in a
   browser; if it is still tight, the next thing to give is "Class of 2030", which is nearly
   twice the width of any other label. */
@media (max-width:1320px){
  .una200 .site-nav__links{ gap:4px; }
  .una200 .site-nav__links a{ padding-left:4px; padding-right:4px; }
  .una200 .site-nav__links a.site-nav__give{ padding-left:12px; padding-right:12px; }
}

/* ---------- mobile: the links list becomes a full-screen panel ---------- */
@media (max-width:900px){
  .una200 .site-nav__toggle{ display:grid; }

  /* the bar goes solid while the panel is open, so the two read as one surface */
  .una200 .site-nav.is-open{ background:rgba(23,11,39,.97); border-bottom-color:transparent; }

  /* position:absolute, NOT fixed. .site-nav.is-stuck carries backdrop-filter, which makes
     it a containing block for fixed descendants — a fixed panel would be trapped inside
     the 84px bar the moment the page scrolled. Absolute is also self-deriving: top:100%
     is the bottom of the bar at every breakpoint, so there is no nav-height variable to
     keep in sync (the height differs per breakpoint, and the governing element differs
     with it). The panel overhangs the viewport bottom by the bar's height, which is
     invisible because opening the menu locks body scroll. */
  .una200 .site-nav__links{
    position:absolute; top:100%; left:0; right:0;
    height:100vh; height:100dvh;
    flex-direction:column; justify-content:center; align-items:center;
    gap:clamp(2px,1.4vh,12px);
    padding:24px;
    background:rgba(23,11,39,.97);
    backdrop-filter:blur(14px);
    /* visibility, not display: it still animates, and it keeps the closed panel out of
       both the tab order and the accessibility tree */
    opacity:0; visibility:hidden;
    transition:opacity .32s ease, visibility .32s ease;
  }
  .una200 .site-nav.is-open .site-nav__links{ opacity:1; visibility:visible; }

  .una200 .site-nav__links a{
    font-size:1.3rem; letter-spacing:.06em; padding:14px 16px;
  }
  /* DELIBERATELY still .site-nav__links, unlike every other streak rule. This scales the
     streak up to match the 1.3rem type in the full-screen panel. The home link is not in the
     panel — it stays in the bar at .72rem — so widening this selector to .site-nav would hang
     an 11px streak under a small word. Leave it scoped. */
  .una200 .site-nav__links a > span::after{ height:11px; bottom:-9px; }
}

@media (max-width:560px){
  .una200 .site-nav{ padding:12px 16px; }
  .una200 .site-nav__logo img{ height:2.25rem; }
  .una200 .site-nav__links a{ font-size:1.15rem; }
  /* The home link stays — this is the width with no visible nav row at all, so it is the one
     place a header route back matters most. It only gets smaller: the bar has to hold the
     logo, this, and the Menu toggle at 360px. */
  .una200 .site-nav__home{ font-size:.72rem; letter-spacing:.08em; }
  /* the logo drops to 36px here, so the mark comes down with it to stay the shorter of the
     two and leave the logo setting the bar height */
  .una200 .site-nav__mark{ height:30px; }
  .una200 .site-nav__site{ padding-left:12px; gap:8px; }
}

/* =================================================================
   HERO
   ================================================================= */
.una200 .hero{
  /* archival campus photo sits behind the purple wash — nudge these to reposition it */
  --photo-x:50%;
  --photo-y:32%;

  position:relative;
  min-height:100vh;
  display:flex; align-items:center; justify-content:center;
  text-align:center;
  padding:140px 24px 90px;
  background:
    radial-gradient(120% 85% at 50% 0%, rgba(62,29,99,.85) 0%, rgba(23,11,39,.93) 62%),
    /* the 1280 variant, not the 2344px original: this sits under a 85–93% purple wash, so
       almost none of its detail survives to be seen, and it is on the critical path */
    url('images/old-una-1280.webp') var(--photo-x) var(--photo-y) / cover no-repeat,
    var(--ink);
  color:#fff;
  overflow:hidden;
}
.una200 .hero__field{
  position:absolute; inset:0;
  background-image:radial-gradient(circle at 1px 1px, rgba(241,230,178,.4) 1px, transparent 0);
  background-size:44px 44px;
  opacity:.12;
  mask-image:radial-gradient(65% 55% at 50% 35%, #000 0%, transparent 75%);
  -webkit-mask-image:radial-gradient(65% 55% at 50% 35%, #000 0%, transparent 75%);
}
.una200 .hero__inner{ position:relative; z-index:1; max-width:760px; margin:0 auto; }

.una200 .hero-rise{
  opacity:0; transform:translateY(18px);
  animation:hero-rise .9s cubic-bezier(.2,.7,.2,1) forwards;
  animation-delay:var(--d,0s);
}
@keyframes hero-rise{ to{ opacity:1; transform:none; } }

.una200 .hero-drop{
  opacity:0; transform:translateY(-26px);
  animation:hero-drop .9s cubic-bezier(.2,.7,.2,1) forwards;
  animation-delay:var(--d,0s);
}
@keyframes hero-drop{ to{ opacity:1; transform:none; } }

/* .hero__eyebrow, .hero__two-hundred and .hero__lede were removed — the eyebrow moved
   to .site-nav__est, and the "200" / lede paragraph were dropped. */

.una200 .hero__mark{ margin:0 auto; width:15em; }
.una200 .hero__mark-img{
  width:100%; filter:drop-shadow(0 12px 30px rgba(0,0,0,.45));
}
@keyframes mark-breathe{
  0%,100%{ transform:scale(1); opacity:1; }
  50%{ transform:scale(1.045); opacity:.92; }
}

.una200 .hero__title{
  margin-top:38px;
  font-weight:700;
  font-size:clamp(2.1rem,6.2vw,4rem);
  line-height:1.12;
  letter-spacing:-.01em;
}

.una200 .chalk-word{ position:relative; display:inline-block; color:var(--gold); }
.una200 .chalk-word__svg{
  position:absolute; left:-2%; right:-2%; bottom:-.16em;
  width:104%; height:.34em;
  overflow:visible;
  clip-path:inset(0 100% 0 0);
  animation:chalk-draw 1.1s cubic-bezier(.3,.7,.4,1) forwards;
  /* the title now enters at .35s, so draw as it settles rather than after */
  animation-delay:1.05s;
  filter:drop-shadow(0 2px 3px rgba(0,0,0,.28));
}
.una200 .chalk-word__mark{ fill:#fff; opacity:.95; }
@keyframes chalk-draw{ to{ clip-path:inset(0 0 0 0); } }

.una200 .hero__sub{
  margin-top:26px; max-width:48ch; margin-left:auto; margin-right:auto;
  font-size:1.35rem; font-weight:400; color:rgba(255,255,255,.68);
}

.una200 .hero__scrollcue{
  margin-top:56px;
  display:flex; flex-direction:column; align-items:center; gap:10px;
  font-size:.75rem; letter-spacing:.28em; text-transform:uppercase;
  color:rgba(255,255,255,.5); font-weight:600;
}
.una200 .hero__scrollcue-line{
  width:1px; height:38px;
  background:linear-gradient(var(--gold), transparent);
  animation:cue-pulse 2.2s ease infinite;
}
@keyframes cue-pulse{ 0%,100%{ opacity:.25; } 50%{ opacity:1; } }

/* =================================================================
   MODULES — alternating photo / dotted connector / description
   ================================================================= */

/* ---- reveal utility (used by module__photo, module__desc, module__dots, and every
     feature module's head, media and cta) ---- */
.una200 [data-reveal]{
  opacity:0;
  transition:opacity .8s ease, transform .9s cubic-bezier(.2,.7,.2,1);
  will-change:opacity, transform;
}
.una200 [data-reveal="pop"]{ transform:scale(.9) translateY(30px); }
.una200 [data-reveal="fade"]{ transform:translateY(34px); }
.una200 [data-reveal="up"]{ transform:translateY(20px); }
.una200 [data-reveal].is-visible{ opacity:1; transform:none; }

/* the dots reveal themselves one at a time (see .module__dots circle) — the
   svg box itself must stay put, so opt it out of the box-level fade/slide */
.una200 [data-reveal="line"]{ opacity:1; transform:none; transition:none; }

/* Same self-contained idea as "line": the container must not fade as one box, because the
   point is that its CHILDREN arrive one after another. One observer on the container is
   deliberate — separate observers on side-by-side children would fire at slightly
   different moments and the stagger would read as jitter rather than a cascade.
   Generic on `> *` so any module can use it: Class of 2030 cards and Events rows both do.
   Per-child delay comes from :nth-child, never an inline style. */
.una200 [data-reveal="cascade"]{ opacity:1; transform:none; transition:none; }

/* ANIMATION, NOT TRANSITION — and that distinction is the whole fix.
   The children of a cascade are cards, and .card sets its own `transition` for the hover
   lift. Both rules are (0,2,0) and .card is declared later, so .card's transition list won
   outright: it names transform and border-color but NOT opacity, so the slide animated
   while the fade snapped on instantly. The cards appeared to roll in with no fade at all.
   Driving the entrance with an animation instead means the two never compete for the same
   property. Don't convert this back to transitions — the collision comes back silently and
   looks like a missing style rather than a losing one.

   FILL MODE IS `backwards`, NOT `forwards`/`both`, and that is load-bearing. A filling
   animation keeps ownership of the properties it animates, so a `forwards` fill would pin
   transform to `none` for good and the hover lift on .events__card would simply stop
   working. `backwards` fills only during the stagger delay — which is exactly what is
   needed to hold each card hidden until its turn — and hands transform back afterwards.
   The end state is therefore declared on .is-visible below, not left to the fill. */
.una200 [data-reveal="cascade"] > *{ opacity:0; transform:translateY(28px); }
.una200 [data-reveal="cascade"].is-visible > *{
  opacity:1; transform:none;
  animation:cascade-rise .8s cubic-bezier(.2,.7,.2,1) backwards;
}
@keyframes cascade-rise{
  from{ opacity:0; transform:translateY(28px); }
  to{ opacity:1; transform:none; }
}

/* the left-to-right stagger. Written out because CSS cannot derive a delay from an index —
   eight covers any module here without needing new rules. */
.una200 [data-reveal="cascade"].is-visible > *:nth-child(1){ animation-delay:.05s; }
.una200 [data-reveal="cascade"].is-visible > *:nth-child(2){ animation-delay:.17s; }
.una200 [data-reveal="cascade"].is-visible > *:nth-child(3){ animation-delay:.29s; }
.una200 [data-reveal="cascade"].is-visible > *:nth-child(4){ animation-delay:.41s; }
.una200 [data-reveal="cascade"].is-visible > *:nth-child(5){ animation-delay:.53s; }
.una200 [data-reveal="cascade"].is-visible > *:nth-child(6){ animation-delay:.65s; }
.una200 [data-reveal="cascade"].is-visible > *:nth-child(7){ animation-delay:.77s; }
.una200 [data-reveal="cascade"].is-visible > *:nth-child(8){ animation-delay:.89s; }

@media (prefers-reduced-motion:reduce){
  .una200 [data-reveal]{ opacity:1; transform:none; transition:none; }
  .una200 .hero-rise, .una200 .hero-drop{ animation:none; opacity:1; transform:none; }
  .una200 .chalk-word__svg{ animation:none; clip-path:none; }
  .una200 .chalk::after{ transition:none; clip-path:none; }
  /* the nav chalk underline still appears on hover, just without the wipe */
  /* BOTH streak owners, or the brand link's underline keeps animating for someone who asked
     for no motion. Mirrors the pair the streak itself is defined on — if one gains a third
     owner, so does this. */
  .una200 .site-nav__links a > span::after,
  .una200 .site-nav__home::after{ transition:none; }
  .una200 .site-nav__links{ transition:none; }
  .una200 .site-nav__toggle-bars,
  .una200 .site-nav__toggle-bars::before,
  .una200 .site-nav__toggle-bars::after{ transition:none; }
  .una200 .hero__mark-img{ animation:none; }
  .una200 .module__dots circle{ transition:none; opacity:1; transform:none; }
  /* the cascade collapses to "already there" — children are opacity:0 by default, so they
     need explicitly restoring here, exactly like the dots above */
  .una200 [data-reveal="cascade"] > *,
  .una200 [data-reveal="cascade"].is-visible > *{ animation:none; opacity:1; transform:none; }
  .una200 .link-more span{ transition:none; }
  .una200 .card{ transition:none; }
  .una200 .events__media img{ transition:none; }
  /* the lift and the image push are hover affordances, not information — drop them
     entirely rather than leaving a jump */
  .una200 .events__card:hover,
  .una200 .events__card:focus-within{ transform:none; }
  .una200 .events__card:hover .events__media img{ transform:none; }
  /* same for the cohort portraits — the colour change on the link survives, the push does not */
  .una200 .cohort__photo img{ transition:none; }
  .una200 .cohort__card:hover .cohort__photo img{ transform:none; }
  /* the ticker never auto-advances under reduced motion — script.js sets stopped=true, so
     these only cover the control affordances */
  .una200 .ticker__arrow,
  .una200 .ticker__dot{ transition:none; }
  .una200 .pill__link,
  .una200 .pill__arrow{ transition:none; }
  .una200 .pill__link:hover,
  .una200 .pill__link:focus-visible{ transform:none; }
  .una200 .pill__link:hover .pill__arrow{ transform:none; }
  .una200 .ticker__arrow:hover{ transform:none; }
  .una200 .ticker__dot[aria-current="true"]{ transform:none; }
  /* no push, and no timer at all — script.js also sets stopped=true, so nothing
     auto-advances and a static bar would just be a decorative line */
  .una200 .ticker__slide{ transition:none; }
  .una200 .ticker__slide.is-active{ animation:none; }
  .una200 .ticker__timer{ display:none; }
  /* script.js also stops the auto-advance, leaving one static photo */
  .una200 .showcase__slide{ transition:none; }
  .una200 .showcase__slide.is-animating .showcase__img{ animation:none; }
  /* the timeline stays fully interactive, it just swaps and slides without easing */
  .una200 .timeline__panel,
  .una200 .timeline__photo,
  .una200 .timeline__photo img,
  .una200 .timeline__copy > *{ transition:none; transform:none; }
  .una200 .timeline__rail{ transition:none; }
  .una200 .timeline__stop-dot,
  .una200 .timeline__stop-date{ transition:none; }
  html{ scroll-behavior:auto; }
}

/* =================================================================
   SHARED COMPONENTS — used across modules, so they carry no module prefix
   ================================================================= */

/* ---- gold button ---- */
/* Was .cta__btn. Promoted because all four feature modules end in a CTA, and a copied
   button style is the first thing to drift out of sync. */
.una200 .btn{
  display:inline-block; margin-top:32px;
  background:var(--gold); color:var(--ink); text-decoration:none;
  font-weight:700; font-size:.85rem; letter-spacing:.06em; text-transform:uppercase;
  padding:16px 30px; transition:background .25s ease, transform .25s ease;
}
.una200 .btn:hover{ background:#fff; transform:translateY(-2px); }

/* ---- card surface ---- */
/* The panel shared by the feature modules. Surface only — each module keeps its own class
   for layout and spacing.

   Softened 2026-09-04: the first version was square-cornered with a cool white-alpha wash
   and a hard hairline, which read as "tech dashboard". Three changes move it toward
   academic and approachable, and they are all deliberate:
     · a real corner radius — square corners are the single biggest contributor to the
       hard, technical feel
     · the wash is tinted with --accent-cream-1 instead of white, so the surface reads warm
       parchment over the purple rather than cold grey
     · a wide, very soft drop shadow does the work the hard border used to, so the card
       sits on the panel instead of being cut into it
   Applied to .card, so the Class of 2030 cards soften with the Events cards — the two sit
   two sections apart and would clash if only one were rounded. */
.una200 .card{
  display:flex; flex-direction:column;
  border-radius:14px;
  background:linear-gradient(180deg, rgba(241,230,178,.075), rgba(241,230,178,.025));
  border:1px solid rgba(241,230,178,.16);
  box-shadow:0 26px 55px -34px rgba(0,0,0,.75);
  transition:border-color .35s ease, transform .45s cubic-bezier(.2,.7,.2,1),
             box-shadow .45s ease;
}

/* ---- inline "more" link with a nudging arrow ---- */
/* The quiet counterpart to .btn: used inside cards and rows, where a second gold button
   would compete with the module's single primary action. Class of 2030 and Events both
   use it; keep it shared rather than re-deriving per module. */
.una200 .link-more{
  font-size:.78rem; letter-spacing:.1em; text-transform:uppercase; font-weight:700;
  color:var(--gold); text-decoration:none;
  transition:color .25s ease;
}
.una200 .link-more:hover{ color:#fff; }
.una200 .link-more span{ display:inline-block; transition:transform .25s ease; }
.una200 .link-more:hover span{ transform:translateX(4px); }

/* ---- chalk streak ---- */
/* The hero draws its streak with an inline <svg>. This one paints the same tapered shape
   from a mask-image data URI instead, so it can be used inside page content: the governing
   rule bans new inline SVG in <main>, because WordPress strips <svg> from post content for
   anyone without unfiltered_html. Any future streak uses this; the hero's should migrate
   here eventually. Same path data as the nav's underline. */
.una200 .chalk{ position:relative; display:inline-block; white-space:nowrap; }
.una200 .chalk::after{
  content:"";
  position:absolute; left:-2%; right:-2%; bottom:-.18em;
  height:.13em;
  background-color:var(--gold);
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 26' preserveAspectRatio='none'%3E%3Cpath d='M6,9 C110,9 210,10.5 294,13 C210,15.5 110,17 6,17 Z' fill='%23000'/%3E%3C/svg%3E");
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 26' preserveAspectRatio='none'%3E%3Cpath d='M6,9 C110,9 210,10.5 294,13 C210,15.5 110,17 6,17 Z' fill='%23000'/%3E%3C/svg%3E");
  mask-size:100% 100%; -webkit-mask-size:100% 100%;
  mask-repeat:no-repeat; -webkit-mask-repeat:no-repeat;
  /* drawn by the same clip-path wipe as the hero, triggered by the reveal observer */
  clip-path:inset(0 100% 0 0);
  transition:clip-path .85s cubic-bezier(.3,.7,.4,1) .35s;
  pointer-events:none;
}
.una200 [data-reveal].is-visible .chalk::after{ clip-path:inset(0 0 0 0); }

/* =================================================================
   MODULES — alternating photo / dotted connector / description
   ================================================================= */
.una200 .module{
  position:relative;
  background:
    radial-gradient(90% 70% at 50% 0%, var(--purple-deep) 0%, var(--ink) 68%),
    var(--ink);
  color:#fff;
  padding:110px 24px;
}

.una200 .module__grid{
  max-width:var(--wrap); margin:0 auto;
  min-height:56vh;
  display:grid;
  grid-template-columns:minmax(0,1fr) 140px minmax(0,1fr);
  align-items:center;
  gap:0;
}

.una200 .module__photo img{
  width:100%; aspect-ratio:4/3; object-fit:cover; display:block;
  border:6px solid #fff;
  box-shadow:0 30px 70px -25px rgba(0,0,0,.6);
}

.una200 .module__connector{ display:flex; align-items:center; justify-content:center; height:100%; }
.una200 .module__dots{ overflow:visible; }
.una200 .module__dots--h{ display:block; width:70px; height:14px; }
.una200 .module__dots--v{ display:none; }

/* the auto margin wins over justify-content, parking the dots in the
   half of the gutter nearest that module's photo */
.una200 .module__connector--to-left  .module__dots--h{ margin-right:auto; }
.una200 .module__connector--to-right .module__dots--h{ margin-left:auto; }

/* each dot pops in on its own delay, in DOM order (center -> photo).
   NOTE: don't go back to stroke-dasharray/dashoffset for this — a dotted
   dash pattern is periodic, so an offset only slides the dots along the
   path instead of hiding them. Discrete circles are what make them
   actually start hidden and reveal one by one. */
.una200 .module__dots circle{
  fill:var(--gold);
  opacity:0;
  transform:scale(.35);
  transform-box:fill-box; transform-origin:center;
  transition:opacity .32s ease, transform .42s cubic-bezier(.2,.8,.3,1.35);
}
.una200 .module__dots.is-visible circle{ opacity:1; transform:scale(1); }
.una200 .module__dots circle:nth-child(1){ transition-delay:.15s; }
.una200 .module__dots circle:nth-child(2){ transition-delay:.24s; }
.una200 .module__dots circle:nth-child(3){ transition-delay:.33s; }
.una200 .module__dots circle:nth-child(4){ transition-delay:.42s; }
.una200 .module__dots circle:nth-child(5){ transition-delay:.51s; }

.una200 .module__desc{ padding:0 clamp(0px,3vw,32px); }
.una200 .module__eyebrow{
  display:block; font-size:.76rem; letter-spacing:.32em; text-transform:uppercase;
  color:var(--gold); font-weight:600;
}
.una200 .module__title{
  margin-top:14px; font-weight:700; color:#fff;
  font-size:clamp(1.8rem,4.4vw,2.8rem); line-height:1.1;
}
.una200 .module__years{
  display:block; margin-top:10px; font-size:.9rem; letter-spacing:.08em;
  color:rgba(255,255,255,.55); font-weight:500;
}
.una200 .module__desc p{
  margin-top:20px; font-size:1.05rem; line-height:1.7;
  color:rgba(255,255,255,.72); max-width:46ch;
}

@media (max-width:900px){
  .una200 .module__grid{ grid-template-columns:1fr; gap:28px; text-align:center; }
  /* stacked: the grid collapses in DOM order, so "toward the photo" becomes
     up for photo-first modules and down for description-first ones */
  .una200 .module__connector{ height:140px; flex-direction:column; }
  .una200 .module__dots--h{ display:none; }
  .una200 .module__dots--v{ display:block; width:14px; height:70px; }
  .una200 .module__connector--to-left  .module__dots--v{ margin-bottom:auto; }
  .una200 .module__connector--to-right .module__dots--v{ margin-top:auto; }
  .una200 .module__desc p{ margin-left:auto; margin-right:auto; }
}

/* =================================================================
   TIMELINE — interactive horizontal stepper (module 1)
   ================================================================= */
/* this module is one full-width piece, not the 3-column photo/connector/copy grid */
.una200 .module__grid--timeline{ display:block; min-height:0; }

/* Panels and the nav share one column template, which is what lines the rail up
   directly under the photo. Change the split here and both follow. */
.una200 .timeline{
  --tl-cols:1.05fr 1fr;
  --tl-gap:clamp(26px,4vw,56px);

  /* The dash line, the dots and the arrow centres are all derived from these, so the
     three stay on one horizontal axis. Change a value and everything re-aligns. */
  --tl-dot:12px;    /* inactive dot diameter */
  --tl-arrow:44px;  /* arrow button diameter */
  --tl-pad:20px;    /* window headroom, so the scaled active dot isn't clipped */
}

.una200 .timeline__head{ text-align:center; margin-bottom:clamp(30px,4vw,48px); }
.una200 .timeline__head .module__title{ margin-top:10px; }
/* the era's own standfirst, above its five milestones */
.una200 .timeline__lede{
  margin:16px auto 0; max-width:64ch;
  font-size:1.02rem; line-height:1.65;
  color:rgba(255,255,255,.66);
}

/* All panels share one grid cell, so the stage is as tall as the tallest panel and
   nothing reflows when stepping between years. */
.una200 .timeline__stage{ display:grid; }
.una200 .timeline__panel{
  grid-area:1/1;
  display:grid;
  grid-template-columns:var(--tl-cols);
  gap:var(--tl-gap);
  align-items:center;
  opacity:0; visibility:hidden;
  transition:opacity .3s ease, visibility 0s linear .3s;
}
.una200 .timeline__panel.is-active{
  opacity:1; visibility:visible;
  transition:opacity .3s ease, visibility 0s;
}

/* THE FRAME IS THE FIGURE, NOT THE IMAGE, and that move is what makes the reveal below
   possible. The white border and drop shadow used to sit on the <img>, which filled the
   figure exactly — so any `overflow:hidden` or `clip-path` needed to scale the image inside
   its frame would have clipped the shadow away completely. On the figure, the shadow sits
   outside the clip and survives. */
.una200 .timeline__photo{
  overflow:hidden;
  border:6px solid #fff;
  box-shadow:0 30px 70px -25px rgba(0,0,0,.6);
}
.una200 .timeline__photo img{
  width:100%; aspect-ratio:4/3; object-fit:cover; display:block;
}
/* Crop variants. Written when 25 milestones shared a pool of 3 archival scans and each needed
   a different framing; since 2026-10-16 every moment has its own photograph and none uses
   one. Kept because they are the "focal point" select on the WordPress milestone block — a
   photograph whose subject sits off-centre still needs one, and the governing rule bars an
   inline object-position. */
.una200 .timeline__photo--top img{ object-position:50% 20%; }
.una200 .timeline__photo--low img{ object-position:50% 80%; }
.una200 .timeline__photo--left img{ object-position:20% 50%; }
.una200 .timeline__photo--right img{ object-position:80% 50%; }
/* the photo settles first... */
/* COUNTER-MOTION REVEAL — the christchurchnz.com idea, which is that a module should read
   as OPENING rather than fading in. The frame scales UP from .94 while the photograph inside
   scales DOWN from 1.12, over a longer duration, so the picture is still settling after the
   frame has arrived. That disagreement between the two is the entire effect: give them the
   same scale direction, or the same duration, and it collapses back into a plain fade.

   The long tail on the easing (.16,.84,.28,1) is deliberate too — it front-loads most of
   the movement and then drifts, which is what stops five sequential panel swaps feeling
   mechanical. */
.una200 .timeline__photo{
  opacity:0;
  transform:scale(.94) translateY(16px);
  transition:opacity .45s ease, transform .95s cubic-bezier(.16,.84,.28,1);
}
.una200 .timeline__panel.is-active .timeline__photo{ opacity:1; transform:none; }
.una200 .timeline__photo img{
  transform:scale(1.12);
  transition:transform 1.3s cubic-bezier(.16,.84,.28,1);
}
.una200 .timeline__panel.is-active .timeline__photo img{ transform:none; }

/* ...then the copy staggers in behind it, so the paragraph reads as arriving after */
.una200 .timeline__copy > *{
  opacity:0; transform:translateY(14px);
  transition:opacity .55s ease, transform .6s cubic-bezier(.2,.7,.2,1);
}
.una200 .timeline__panel.is-active .timeline__copy > *{ opacity:1; transform:none; }
/* FOUR CHILDREN, NOT TWO. The panels now carry an era label above the title, and the last
   one also carries the link to the full history — so the copy block runs era, title,
   paragraph, link. A child with no delay rule fires at 0 and arrives BEFORE the title,
   which is exactly what happened when the link was first added. Tightened from .30/.42 so
   four steps do not outlast the photograph's own 1.3s settle. */
.una200 .timeline__panel.is-active .timeline__copy > *:nth-child(1){ transition-delay:.26s; }
.una200 .timeline__panel.is-active .timeline__copy > *:nth-child(2){ transition-delay:.36s; }
.una200 .timeline__panel.is-active .timeline__copy > *:nth-child(3){ transition-delay:.46s; }
.una200 .timeline__panel.is-active .timeline__copy > *:nth-child(4){ transition-delay:.56s; }

/* Carries the era each moment belongs to. Before the five timelines were merged this came
   from the section heading; without it the era structure would be gone from the homepage. */
.una200 .timeline__era{
  display:block;
  font-size:.8rem; letter-spacing:.18em; text-transform:uppercase; font-weight:600;
  color:var(--accent-cream-1);
}
/* The handoff to the full history. It was a text link inside the fifth panel's copy, which
   meant the reader only met it after walking all five moments — and it was the one module on
   the page whose call to action was not a button. Now a module-level CTA, matching
   .events__cta / .news__cta / .cohort__cta, so it reads the same and stays put as the moments
   advance. */
.una200 .timeline__cta{ margin-top:clamp(34px,4vw,50px); text-align:center; }
.una200 .timeline__title{
  margin-top:12px; font-weight:700; color:#fff;
  font-size:clamp(1.3rem,2.6vw,1.9rem); line-height:1.2;
}
.una200 .timeline__copy p{
  margin-top:16px; font-size:1.05rem; line-height:1.7;
  color:rgba(255,255,255,.72); max-width:44ch;
}

/* ---- navigation: a dashed rail that slides the active date to centre ---- */
/* same column template as the panels, so the controls sit under the photo only */
.una200 .timeline__nav{
  display:grid; grid-template-columns:var(--tl-cols); gap:var(--tl-gap);
  margin-top:clamp(26px,3vw,38px);
}
/* flex-start, not center: centring would align the arrows to the middle of the whole
   dot + date stack, which sits below the dash line */
.una200 .timeline__controls{
  display:flex; align-items:flex-start; gap:clamp(8px,1.4vw,16px);
  min-width:0;
}

.una200 .timeline__arrow{
  flex:none; width:var(--tl-arrow); height:var(--tl-arrow);
  display:grid; place-items:center;
  font-size:1.05rem; color:var(--gold);
  background:transparent;
  /* NOT --line-on-dark (.22), which is only 1.78:1 on the ink background. This is a
     control boundary, so WCAG 2.1 SC 1.4.11 wants 3:1 — .45 measures 3.79:1.
     The token stays at .22 for decorative rules like the nav underline. */
  border:1px solid rgba(241,230,178,.45);
  border-radius:50%;
  cursor:pointer;
  /* drop the arrow so its centre lands on the dash line. translate, not margin, so it
     has no effect on layout. */
  transform:translateY(calc(var(--tl-pad) + var(--tl-dot) / 2 - var(--tl-arrow) / 2));
  transition:background .25s ease, border-color .25s ease, color .25s ease, opacity .25s ease;
}
.una200 .timeline__arrow:hover:not(:disabled){ background:var(--gold); border-color:var(--gold); color:var(--ink); }
.una200 .timeline__arrow:disabled{ opacity:.28; cursor:default; }

/* the viewport. Only a slice of the rail shows, and it feathers out at both edges so
   the dashes read as running off into the rest of the century. */
.una200 .timeline__window{
  position:relative; flex:1 1 auto; min-width:0;
  overflow:hidden;
  padding:var(--tl-pad) 0 0;
  /* Feather widened from 16%/84%: the unfeathered band is what a long centred date has
     to fit inside, and at 390px the old 68% band left "January 11, 1830" fading at both
     ends. 76% clears it while still reading as dashes running off into the century. */
  mask-image:linear-gradient(90deg, transparent 0, #000 12%, #000 88%, transparent 100%);
  -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 12%, #000 88%, transparent 100%);
}

/* The dashed runway. 50vw of padding each side guarantees dashes still fill the window
   at the first and last stop. script.js sets translateX so the active stop sits dead
   centre — moving this whole element is what makes the dashes travel. */
.una200 .timeline__rail{
  position:relative;
  width:max-content;
  display:flex; align-items:flex-start;
  padding:0 50vw;
  background-image:linear-gradient(90deg, var(--gold) 0 9px, transparent 9px);
  background-size:22px 3px;
  background-repeat:repeat-x;
  /* the 3px line, centred on the dots rather than sitting below them */
  background-position:0 calc(var(--tl-dot) / 2 - 1.5px);
  transition:transform .62s cubic-bezier(.3,.75,.2,1);
  will-change:transform;
}

/* --tl-step is the dot rhythm, and it is deliberately independent of how long the date
   label is: the stop keeps this fixed width (flex:none + width), so the label overflows
   the box instead of widening it. That keeps the dots evenly spaced and centreRail()'s
   offsetWidth arithmetic stable.

   TIGHTENED from clamp(160px,20vw,210px) on 2026-10-16. That width existed to keep
   "January 11, 1830" clear of its neighbours; with years only it put each neighbour right
   on the window's feathered edge, where its year read as a clipped "187". At this step the
   neighbouring years sit inside the unfeathered band on desktop. */
.una200 .timeline__stop{
  --tl-step:clamp(110px,10vw,140px);
  flex:none; width:var(--tl-step);
  display:flex; flex-direction:column; align-items:center; gap:14px;
  background:none; border:0; padding:0; cursor:pointer;
  font-family:inherit;
}
.una200 .timeline__stop-dot{
  width:var(--tl-dot); height:var(--tl-dot); border-radius:50%;
  background:var(--ink); border:2px solid rgba(255,185,42,.55);
  /* punches a hole in the dashed rail behind, so a dash never butts against a dot */
  box-shadow:0 0 0 5px var(--ink);
  transition:transform .45s cubic-bezier(.2,.8,.3,1.35), background .35s ease, border-color .35s ease, box-shadow .35s ease;
}
.una200 .timeline__stop:hover .timeline__stop-dot{ border-color:var(--gold); }

/* YEARS ONLY, SO THE YEAR CAN BE BIG (2026-10-16). The label used to have to hold
   "January 11, 1830" inside the window's unfeathered band at 390px, which held it down to
   1.05-1.75rem and meant only the centred stop could show its date at all — the others were
   opacity:0, a row of unlabelled dots. A stop is now always a single four-digit year, so:

   - the centred year is display size, gold (10.6:1 on the ink);
   - every other year is VISIBLE, in tan (11.2:1) at 60% scale, so the rail reads as a
     sequence of years and each tab has a visible label, not just an announced one.

   The neighbours shrink by transform, not font-size, so the stop's box — and the rail
   height the scroll stack measures — is the same whichever stop is selected.
   white-space:nowrap stays as a guard against anyone putting a range back in. */
.una200 .timeline__stop-date{
  font-weight:800; line-height:1; letter-spacing:.01em;
  font-size:clamp(1.6rem,3.2vw,2.6rem);
  font-variant-numeric:lining-nums tabular-nums;
  white-space:nowrap;
  color:var(--accent-tan);
  transform:scale(.6); transform-origin:50% 0;
  transition:color .35s ease, transform .45s cubic-bezier(.2,.8,.3,1);
}
.una200 .timeline__stop:hover .timeline__stop-date{ color:var(--accent-cream-1); }

.una200 .timeline__stop[aria-selected="true"] .timeline__stop-dot{
  background:var(--gold); border-color:var(--gold);
  transform:scale(2);
  /* ink ring masks the dashes behind; the second shadow is blurred so it reads as a
     glow instead of a hard ring. Spreads scale with the transform, hence small values. */
  box-shadow:0 0 0 3px var(--ink), 0 0 8px 1px rgba(255,185,42,.35);
}
.una200 .timeline__stop[aria-selected="true"] .timeline__stop-date{
  color:var(--gold); transform:none; transition-delay:.12s;
}

@media (max-width:900px){
  .una200 .timeline__panel{ grid-template-columns:1fr; text-align:center; }
  .una200 .timeline__copy p{ margin-left:auto; margin-right:auto; }
  /* stacked, so the rail spans the single column under the photo */
  .una200 .timeline__nav{ grid-template-columns:1fr; }
  .una200 .timeline__controls{ gap:8px; }
  /* set the variable, not width/height — the arrow's translate is derived from it */
  .una200 .timeline{ --tl-arrow:40px; }
}
/* A phone's window is ~260px: the neighbouring dots fit, their years do not, and a year
   half-faded at the window's edge reads worse than none. The centred year stays. */
@media (max-width:560px){
  .una200 .timeline__stop-date{ opacity:0; transition:opacity .3s ease, color .35s ease, transform .45s cubic-bezier(.2,.8,.3,1); }
  .una200 .timeline__stop[aria-selected="true"] .timeline__stop-date{ opacity:1; }
}

/* =================================================================
   SHOWCASE — full-height Ken Burns slideshow (module 3)
   ================================================================= */
/* a little more breathing room than the other modules, so the frame isn't cramped */
.una200 .module--showcase{ padding-top:130px; padding-bottom:130px; }

/* This module breaks out past the shared 1180px wrap so the slideshow can read big.
   The extra width is taken from the viewport, not the copy column.
   Scoped to min-width:901px on purpose — `.module--showcase .module__grid` outranks
   the single-column `.module__grid` rule in the max-width:900px block, so leaving it
   unscoped would keep three columns on mobile. */
@media (min-width:901px){
  .una200 .module--showcase .module__grid{
    max-width:min(1600px, 92vw);
    grid-template-columns:minmax(0,1fr) 140px minmax(0,1.45fr);
  }
}

.una200 .showcase{
  position:relative;
  aspect-ratio:4/3; /* matches the framed photos in modules 1 and 2 */
  overflow:hidden;
  background:var(--ink);
}
.una200 .showcase__slide{
  position:absolute; inset:0;
  opacity:0;
  transition:opacity 1.6s ease; /* keep in sync with FADE in script.js */
}
.una200 .showcase__slide.is-active{ opacity:1; }
.una200 .showcase__img{
  width:100%; height:100%; object-fit:cover;
  transform:scale(1.08); /* resting overscale, so a drift never exposes an edge */
}

/* .is-animating lands with .is-active but is held through the crossfade, so the
   outgoing photo keeps drifting instead of snapping back to its start mid-fade.
   Removing the class is also what lets the animation restart on the next cycle. */
.una200 .showcase__slide.is-animating .showcase__img{
  animation:kb-a 9s ease-out forwards;
}
.una200 .showcase__slide:nth-child(4n+2).is-animating .showcase__img{ animation-name:kb-b; }
.una200 .showcase__slide:nth-child(4n+3).is-animating .showcase__img{ animation-name:kb-c; }
.una200 .showcase__slide:nth-child(4n+4).is-animating .showcase__img{ animation-name:kb-d; }

/* ---- soft-edged variant (Memories) ---- */
/* No frame and no hard crop: every edge dissolves into the panel behind it. The framed,
   white-bordered photos elsewhere are the archive; this one is memory, so it has no
   border by design — don't add one back. A single radial mask rather than composited
   linear ones: mask-composite still needs two different keywords across engines, and the
   ellipse softens the corners too, which two crossed linears would not. Same technique as
   .hero__field. */
.una200 .showcase--soft{
  aspect-ratio:auto;
  height:clamp(540px,60vw,780px);
  background:transparent;
  /* A soft-edged RECTANGLE, not an ellipse. A radial mask is elliptical by definition, so
     it cut the photo into a lens shape and threw the corners away — visible as an oval.
     Two crossed linear gradients intersected give straight feathered edges instead, so the
     frame stays a rectangle with soft borders and the image reaches close to the corners.
     Only the corners themselves round off, because intersect multiplies the two alphas.

     Each edge eases with a SMOOTHSTEP (3t²-2t³) ramp, six stops per side. That matters:
     the obvious "transparent 0%, #000 13%, #000 87%, transparent 100%" form turns a corner
     where the ramp meets the flat core, and a sudden change in slope is a Mach band — the
     eye reads it as a hard line even though the values are continuous. That was the ring
     reported on 2026-09-02. Smoothstep has zero slope at both ends of the ramp, so it
     meets the flat core with matching slope and there is no edge to find.
     Don't simplify these back to two stops per side.

     ONE FEATHER WIDTH, 19%, ON BOTH AXES. An earlier pass matched the two in *pixels*
     (~240px all round) on the theory that equal pixel softness looks uniform. It doesn't:
     the band is far wider than tall, so 240px is only 17% of the width but 31% of the
     height, and the top and bottom fades visibly ate the frame while the sides sat close
     to the edge. Matching percentages is what actually reads as even.

     That costs smoothness and the trade is real, not incidental. 19% of the height is far
     fewer pixels than 19% of the width, so the vertical ramp is intrinsically steeper —
     measured ~2-3x the second-difference of the pixel-matched version, and adding stops
     does NOT fix it (the figure plateaus around N=18, so what remains is curvature, not
     quantisation). The dither below carries that instead, which is what dither is for.
     If banding shows on the top or bottom edge, raise the noise before widening this.

     TWENTY-THREE STOPS PER SIDE. CSS interpolates linearly between colour stops, so a
     coarse sampling of a smooth curve is really a piecewise-linear ramp with a slope change
     at every stop — a row of little Mach bands. Six stops measured 3.6x worse than the
     ellipse this replaced. Don't thin the list.

     Both axes share one stop list, which is also why there is no longer a mobile override:
     percentages scale with the box, so the same values hold at every size. */
  --soft-stops:
    rgba(0,0,0,0) 0%, rgba(0,0,0,.001) 0.86%, rgba(0,0,0,.007) 1.73%, rgba(0,0,0,.02) 2.59%, rgba(0,0,0,.045) 3.45%,
    rgba(0,0,0,.081) 4.32%, rgba(0,0,0,.129) 5.18%, rgba(0,0,0,.188) 6.05%, rgba(0,0,0,.257) 6.91%, rgba(0,0,0,.333) 7.77%,
    rgba(0,0,0,.415) 8.64%, rgba(0,0,0,.5) 9.5%, rgba(0,0,0,.585) 10.36%, rgba(0,0,0,.667) 11.23%, rgba(0,0,0,.743) 12.09%,
    rgba(0,0,0,.812) 12.95%, rgba(0,0,0,.871) 13.82%, rgba(0,0,0,.919) 14.68%, rgba(0,0,0,.955) 15.55%, rgba(0,0,0,.98) 16.41%,
    rgba(0,0,0,.993) 17.27%, rgba(0,0,0,.999) 18.14%, #000 19%, #000 81%, rgba(0,0,0,.999) 81.86%,
    rgba(0,0,0,.993) 82.73%, rgba(0,0,0,.98) 83.59%, rgba(0,0,0,.955) 84.45%, rgba(0,0,0,.919) 85.32%, rgba(0,0,0,.871) 86.18%,
    rgba(0,0,0,.812) 87.05%, rgba(0,0,0,.743) 87.91%, rgba(0,0,0,.667) 88.77%, rgba(0,0,0,.585) 89.64%, rgba(0,0,0,.5) 90.5%,
    rgba(0,0,0,.415) 91.36%, rgba(0,0,0,.333) 92.23%, rgba(0,0,0,.257) 93.09%, rgba(0,0,0,.188) 93.95%, rgba(0,0,0,.129) 94.82%,
    rgba(0,0,0,.081) 95.68%, rgba(0,0,0,.045) 96.55%, rgba(0,0,0,.02) 97.41%, rgba(0,0,0,.007) 98.27%, rgba(0,0,0,.001) 99.14%,
    rgba(0,0,0,0) 100%;
  --soft-mask-h:linear-gradient(90deg, var(--soft-stops));
  --soft-mask-v:linear-gradient(180deg, var(--soft-stops));
  mask-image:var(--soft-mask-h), var(--soft-mask-v);
  -webkit-mask-image:var(--soft-mask-h), var(--soft-mask-v);
  mask-repeat:no-repeat; -webkit-mask-repeat:no-repeat;
  /* legacy WebKit keyword first, then the standard one — browsers that understand both
     apply mask-composite. Without either, the default composite is union, which degrades
     to an unfeathered rectangle rather than to something broken. */
  -webkit-mask-composite:source-in;
  mask-composite:intersect;
}
/* Crop further in. Done by oversizing the slide rather than adding scale() to the image,
   because .is-animating replaces the image's transform outright — the Ken Burns keyframes
   would wipe any static scale the moment a slide became active. Sliding the whole slide
   out past the frame leaves those keyframes, and their "translate under (scale-1)/2"
   budget, completely untouched. */
.una200 .showcase--soft .showcase__slide{ inset:-14%; }

/* The darkening wash, and the reason text can sit on top of this thing.
   Lives INSIDE .showcase--soft so the mask above feathers the scrim along with the photo;
   as a sibling it would be a hard-edged rectangle over a soft-edged image.

   THREE THINGS MAKE THE TRANSITION INVISIBLE. The first version had a visible elliptical
   ring, and each of these fixes one cause:

   1. NO KNEE. It used two stops — flat to 44%, then straight down. A sudden change in
      *slope* is a Mach band: the eye reads it as a hard edge even though the values are
      continuous. This is a ten-stop eased curve (roughly 1 - r³), so the slope changes
      gradually and there is no edge to find. Never collapse it back to two stops.
   2. GEOMETRY MATCHED TO THE MASK. The old pool used its own ellipse (78%/64%) while the
      mask used 50%/50%, so the scrim was still at full strength in places the photo had
      already faded — that mismatch is what drew the ring. At 65%/65% the scrim thins on
      roughly the same curve the photo does, and since both converge on the same --ink
      behind them, there is no boundary between them to see.
   3. DITHER. Even a perfect ramp bands in 8-bit across ~700px. The noise tile breaks the
      quantisation steps up. Raised to 6% when the feathers went to equal percentages,
      because the vertical ramp got steeper and the dither is what absorbs that. This is
      the dial to turn first if banding reappears on the top or bottom edge. If it ever
      reads as visible grain, delete that one background layer and its size/repeat
      entries; nothing else depends on it.

   Peak stays at ~76% behind the copy because contrast requires it, not for looks: white
   type over the requested 15% wash alone measures 1.38:1 on a pure-white photo pixel, and
   this set really has them (bright sky, white commencement regalia). Gold at eyebrow size
   is the binding case and needs ~72%. Re-run the numbers before thinning any of it. */
.una200 .showcase--soft::after{
  content:"";
  position:absolute; inset:0; z-index:1;
  pointer-events:none;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.75' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.06'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 65% 65% at 50% 47%,
      rgba(23,11,39,.760) 0%,
      rgba(23,11,39,.757) 15%,
      rgba(23,11,39,.748) 25%,
      rgba(23,11,39,.731) 35%,
      rgba(23,11,39,.702) 45%,
      rgba(23,11,39,.657) 55%,
      rgba(23,11,39,.594) 65%,
      rgba(23,11,39,.505) 75%,
      rgba(23,11,39,.371) 85%,
      rgba(23,11,39,.205) 93%,
      rgba(23,11,39,0) 100%),
    linear-gradient(180deg, rgba(23,11,39,.15) 0%, rgba(23,11,39,.02) 100%);
  background-repeat:repeat, no-repeat, no-repeat;
  background-size:140px 140px, auto, auto;
}

/* Every translate stays under (scale - 1) / 2 so no edge of the photo can show.
   Raise a translate and you must raise its paired scale too. */
@keyframes kb-a{ from{ transform:scale(1.08) translate(0,0);       } to{ transform:scale(1.26) translate(-2%,-1.5%);  } }
@keyframes kb-b{ from{ transform:scale(1.24) translate(1.5%,1%);   } to{ transform:scale(1.08) translate(-1%,-.5%);   } }
@keyframes kb-c{ from{ transform:scale(1.10) translate(-2%,1.5%);  } to{ transform:scale(1.26) translate(1.5%,-1%);   } }
@keyframes kb-d{ from{ transform:scale(1.26) translate(-1.5%,-1%); } to{ transform:scale(1.10) translate(2%,1.5%);    } }

/* =================================================================
   GIVE — closing feature module
   ================================================================= */
/* Replaced the full-bleed `.cta` panel, which put the copy on a horizontal gradient over the
   photograph. That design was fine on its own and wrong in company: it was the sixth panel in
   a row of cards and the only one that did not read as one, so the page ended by changing
   shape rather than by making its ask. The component is gone — nothing referenced `.cta` or
   `.cta__*` on any of the six pages once this module landed.

   So Give is now the same three-part card as Class of 2030, Events and News — head, one
   elastic row, one button — and inherits their whole fitting story for free. The only thing
   it does differently is its lede, which is a blurb rather than a one-line restatement of the
   title; see the stacked rule for why that is not clamped. */
.una200 .module--give{ padding-top:clamp(80px,9vw,130px); padding-bottom:clamp(80px,9vw,130px); }

.una200 .give{ max-width:var(--wrap); margin:0 auto; }

/* 68ch rather than the 60ch the other heads use. The lede here is ~56 words, and a narrower
   measure only turns that into more lines — which is height this card has to find somewhere. */
.una200 .give__head{ max-width:68ch; margin:0 auto; text-align:center; }
.una200 .give__lede{
  margin:20px auto 0; max-width:64ch;
  font-size:1.06rem; line-height:1.7;
  color:rgba(255,255,255,.7);
}

/* Capped and centred rather than run to the full wrap width. At `--wrap` a 4:3 photograph is
   885px tall, which is taller than the copy above it and reads as a second page rather than
   as part of the card. */
.una200 .give__media{
  margin-top:clamp(42px,5vw,64px);
  max-width:min(100%, 760px);
  margin-inline:auto;
}
.una200 .give__photo{ margin:0; }
/* the white frame is the .module__photo language, kept so the one photograph in this card
   belongs to the same set as the timeline's */
/* `height:auto` IS LOAD-BEARING, and its absence is silent. The <img> carries width="1280"
   height="960" so the browser can reserve the box before the file arrives, and those two
   attributes map to CSS width/height as PRESENTATIONAL HINTS. A hint has specificity 0, so
   `width:100%` here beats the width — but nothing was overriding the height, which left the
   element with two definite sides and made `aspect-ratio` inert. Measured at 834px: a 760px
   column rendering the picture 760x960, a portrait crop of a landscape group shot, at 0.79
   instead of 1.33. The ratio computed as `4 / 3` the whole time; it simply had no say. */
.una200 .give__photo img{
  display:block; width:100%; height:auto; aspect-ratio:4/3; object-fit:cover;
  border:6px solid #fff;
  box-shadow:0 30px 70px -25px rgba(0,0,0,.6);
}

.una200 .give__cta{ margin-top:clamp(34px,4vw,50px); text-align:center; }

@media (max-width:900px){
  .una200 .give__lede{ font-size:1rem; }
  /* the frame is 12px of the width at phone size, which is a lot of white around a group
     shot; halve it rather than lose it, because it is what separates photo from panel */
  .una200 .give__photo img{ border-width:4px; }
}
/* =================================================================
   MEMORIES — feature module in the first slot, after Era One
   ================================================================= */
/* The one module that deliberately isn't the photo / connector / copy grid. Centred copy,
   then the slideshow full width beneath it, dissolving on every edge. Reads as a breath
   between two eras rather than another framed exhibit. */
/* Copy and slideshow share one grid cell, so the text sits over the photos. Horizontal
   padding is zeroed and the media runs the full width of the section — the dissolve is
   what ends the image now, not a container edge, so there is nothing to inset from. */
.una200 .module--memories{
  display:grid;
  padding-top:clamp(70px,8vw,120px); padding-bottom:clamp(70px,8vw,120px);
  padding-left:0; padding-right:0;
}
.una200 .memories__media{ grid-area:1/1; width:100%; }

.una200 .memories__copy{
  grid-area:1/1; z-index:1;
  align-self:center; justify-self:center;
  width:min(64ch,100%);
  padding:0 clamp(18px,5vw,7rem);
  text-align:center;
  /* perceptual only — WCAG gives shadows no credit, so the scrim above carries the
     actual contrast. This just keeps type crisp against a moving backdrop. */
  text-shadow:0 2px 30px rgba(23,11,39,.6);
}
.una200 .memories__copy p{
  margin:22px auto 0; max-width:54ch;
  font-size:1.08rem; line-height:1.75;
  color:rgba(255,255,255,.72);
}

@media (max-width:900px){
  .una200 .memories__copy p{ font-size:1rem; }
  /* taller relative to width on phones: the copy has to fit inside the band now that it
     sits on top of it, and it wraps to far more lines down here */
  .una200 .showcase--soft{ height:clamp(560px,125vw,680px); }
  .una200 .showcase--soft .showcase__slide{ inset:-8%; }
  /* no mask override needed here any more — the feather is one percentage on both axes,
     so it scales with the band at every size */
}

/* =================================================================
   COHORT — Class of 2030 feature module (second slot, after Era Two)
   ================================================================= */
/* Three student cards. The frame follows the "Student Success Stories" pattern on una.edu
   — portrait, name, hairline, excerpt, read-more — but rebuilt for a dark panel: the
   translucent bordered surface is the shared .card language, and the gold hairline under the
   name replaces that reference's grey one. (This used to cite .cta__card, which was the
   "1830 -> 2030" panel; that has been replaced by the giving module.) */
.una200 .module--cohort{ padding-top:clamp(80px,9vw,130px); padding-bottom:clamp(80px,9vw,130px); }

.una200 .cohort{ max-width:var(--wrap); margin:0 auto; }

.una200 .cohort__head{ max-width:60ch; margin:0 auto; text-align:center; }
.una200 .cohort__lede{
  margin:20px auto 0; max-width:56ch;
  font-size:1.06rem; line-height:1.7;
  color:rgba(255,255,255,.7);
}

.una200 .cohort__grid{
  list-style:none; margin:clamp(42px,5vw,64px) 0 0; padding:0;
  display:grid; gap:clamp(20px,2.4vw,34px);
  grid-template-columns:repeat(3, minmax(0,1fr));
}

/* surface comes from the shared .card component; the left-to-right cascade comes from
   [data-reveal="cascade"] on the <ul>. Nothing card-specific is needed here. */

.una200 .cohort__photo img{
  width:100%; aspect-ratio:4/3; object-fit:cover;
  /* portraits, so bias the crop upward — a centred 4/3 cut lands on the chin */
  object-position:50% 22%;
}

.una200 .cohort__body{
  display:flex; flex-direction:column; flex:1 1 auto;
  padding:clamp(20px,2vw,28px);
}
.una200 .cohort__name{
  font-weight:700; color:#fff; font-size:clamp(1.15rem,1.7vw,1.35rem); line-height:1.25;
}
.una200 .cohort__quote{
  margin-top:16px; padding-top:16px;
  border-top:1px solid var(--line-on-dark);
  font-size:.97rem; line-height:1.7; color:rgba(255,255,255,.7);
}
/* .link-more carries the styling; this only positions it. margin-top:auto pushes it to the
   bottom so the links line up across cards of unequal copy length. */
.una200 .cohort__card .link-more{ margin-top:auto; padding-top:18px; align-self:flex-start; }

/* THE WHOLE CARD IS THE LINK, PHOTOGRAPH INCLUDED. The portrait is the most obviously
   clickable thing on the card and was the one part that did nothing.

   An overlay on the existing link rather than a second <a> around the image. Wrapping the
   photograph would put two links to the same post in one card, and a screen reader would
   announce that URL twice under different names — the same reasoning that removed the
   Events card's duplicate "Details" link. This way the accessibility tree still holds one
   link, named "Read their story", and the mouse target is the whole card.

   THE TRADE-OFF, stated: an overlay across the card means the excerpt cannot be selected
   with the mouse. Same cost the Events cards accepted, and the right one for a card whose
   whole purpose is to be clicked. */
.una200 .cohort__card{ position:relative; }
.una200 .cohort__card .link-more::after{
  content:"";
  position:absolute;
  inset:0;
  z-index:2;
}
/* the link answers a hover anywhere on the card, so the pointer has something to point at,
   and the portrait lifts the way the Events photographs do */
.una200 .cohort__card:hover .link-more,
.una200 .cohort__card:focus-within .link-more{ color:#fff; }
.una200 .cohort__photo{ overflow:hidden; }
.una200 .cohort__photo img{ transition:transform .45s cubic-bezier(.2,.7,.2,1); }
.una200 .cohort__card:hover .cohort__photo img{ transform:scale(1.05); }

/* FEWER STUDENTS THAN CARDS IS THE NORMAL EARLY STATE, not an error. The grid is authored
   with three columns and there are two tagged posts today; left alone that puts two cards on
   the left and a hole on the right, which reads as a card that failed to load. script.js
   writes the real count onto the list and the grid matches it, so two students centre as a
   pair and one centres alone. Capped at the authored three. */
/* ABOVE THE PHONE BREAKPOINT ONLY, and the media query is load-bearing. The stacked phone
   rule below is `.una200 .cohort__grid{grid-template-columns:1fr}` at specificity (0,2,0);
   an attribute selector here is (0,3,0) and would beat it regardless of source order,
   putting two columns on a 390px screen. Specificity does not care which rule is inside a
   media query, so the guard has to be explicit. */
@media (min-width:901px){
  .una200 .cohort__grid[data-cohort-filled="1"]{
    grid-template-columns:minmax(0,26rem);
    justify-content:center;
  }
  .una200 .cohort__grid[data-cohort-filled="2"]{
    grid-template-columns:repeat(2, minmax(0,24rem));
    justify-content:center;
  }
}

/* The failure notice and the nothing-tagged-yet notice read differently and look the same,
   for the same reason as the Pressroom's pair: neither is something the reader can act on. */
.una200 .cohort__fallback,
.una200 .cohort__empty{
  margin:clamp(30px,4vw,44px) auto 0; max-width:52ch; text-align:center;
  font-size:1rem; color:rgba(255,255,255,.66);
}
.una200 [data-stack-on="true"] > .module .cohort__fallback,
.una200 [data-stack-on="true"] > .module .cohort__empty{
  margin-block:auto;
  align-self:center;
}

.una200 .cohort__cta{ text-align:center; }

@media (max-width:900px){
  .una200 .cohort__grid{ grid-template-columns:1fr; max-width:26rem; margin-inline:auto; }
  /* stacked, so the cascade becomes top-to-bottom for free */
  .una200 .cohort__lede{ font-size:1rem; }
}

/* =================================================================
   EVENTS — feature module (third slot, after Era Three)
   ================================================================= */
/* A row listing, not cards — the Class of 2030 module owns the card shape, so this one
   reads as a schedule. The tear-off date plate deliberately reuses the timeline's big gold
   date typography: the timeline runs 1830 to 2030 and this module is its leading edge, so
   past dates and upcoming dates are set the same way on purpose. */
.una200 .module--events{ padding-top:clamp(80px,9vw,130px); padding-bottom:clamp(80px,9vw,130px); }

.una200 .events{ max-width:var(--wrap); margin:0 auto; }
/* There is no .events--onpanel any more: the Events subpage puts its upcoming list in the
   left column as .pills, so the full-width card grid below the panel is gone entirely. */
.una200 .events__head{ max-width:60ch; margin:0 auto; text-align:center; }
.una200 .events__lede{
  margin:20px auto 0; max-width:56ch;
  font-size:1.06rem; line-height:1.7; color:rgba(255,255,255,.7);
}

.una200 .events__list{
  list-style:none; margin:clamp(40px,5vw,60px) 0 0; padding:0;
  display:grid; gap:clamp(20px,2.4vw,34px);
  grid-template-columns:repeat(3, minmax(0,1fr));
}

/* Surface and the cascade come from .card and [data-reveal="cascade"]; this only handles
   what is specific to an event. Kept visually distinct from the Class of 2030 cards by the
   date plate, the gold category chip and a wider media crop — same system, different object. */
/* Held back until a feed settles — see settleFeed() in script.js. Sample content is a
   prototype and the no-JS fallback; showing it and then swapping it out reads as the page
   reloading itself. opacity, not display, so the space stays reserved and nothing shifts
   when the real content lands. Generic on [data-feed-state] so Events and News share it.
   The descendant selector is (0,3,0), which is what lets it out-rank
   [data-reveal="cascade"].is-visible > * — that would otherwise reveal the samples the
   moment the observer fired. */
.una200 [data-feed-state="loading"] [data-reveal="cascade"] > *,
.una200 [data-feed-state="loading"] [data-reveal] { opacity:0; }

.una200 .events__card{ overflow:hidden; }
.una200 .events__card:hover,
.una200 .events__card:focus-within{
  border-color:rgba(255,185,42,.45);
  transform:translateY(-5px);
  box-shadow:0 34px 65px -34px rgba(0,0,0,.8);
}

/* ---- media, and the date plate that sits on it ---- */
/* The figure is ALWAYS present, even with no photo. The API returns image:false for most
   events, so if the plate lived inside a figure that got removed, most cards would lose
   their date. Instead script.js drops only the <img> and marks the figure empty, and the
   fallback below keeps every card the same silhouette. */
.una200 .events__media{
  position:relative;
  aspect-ratio:16/10; overflow:hidden;
  background:
    radial-gradient(circle at 1px 1px, rgba(241,230,178,.5) 1px, transparent 0) 0 0/22px 22px,
    linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .events__media img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .6s cubic-bezier(.2,.7,.2,1);
}
.una200 .events__card:hover .events__media img{ transform:scale(1.05); }
/* the photo dissolves into the card instead of butting against the body with a hard
   horizontal cut — the same soft-edge idea as the Memories band, at a much smaller scale */
.una200 .events__media::after{
  content:""; position:absolute; inset:auto 0 0 0; height:38%;
  background:linear-gradient(180deg, rgba(23,11,39,0), rgba(23,11,39,.55));
  pointer-events:none;
}

/* Inset from the corner and rounded, rather than flush and square: a panel jammed into the
   corner is what made this read as a UI chip rather than something placed on a page.
   Opaque ink, NOT translucent — the plate sits over an unpredictable photo, and a
   see-through panel would make the gold numeral's contrast depend on the picture behind it.
   Opaque keeps gold-on-ink at a measured 10.9:1 whatever the image is. */
.una200 .events__plate{
  position:absolute; left:14px; bottom:14px; z-index:1;
  min-width:4.4rem;
  display:grid; justify-items:center; align-content:center; gap:3px;
  padding:11px 14px;
  border-radius:12px;
  background:var(--ink);
  border:1px solid rgba(241,230,178,.32);
  box-shadow:0 14px 30px -16px rgba(0,0,0,.9);
}
.una200 .events__dow,
.una200 .events__mon{
  font-size:.72rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--white); font-weight:600; line-height:1;
}
.una200 .events__day{
  font-size:clamp(1.55rem,2.3vw,1.9rem); font-weight:700; line-height:1;
  color:var(--white); letter-spacing:.01em;
}

.una200 .events__body{
  display:flex; flex-direction:column; flex:1 1 auto;
  padding:clamp(18px,1.8vw,24px);
}
.una200 .events__meta{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  font-size:.75rem; letter-spacing:.14em; text-transform:uppercase; font-weight:600;
}
/* A soft outlined pill, not a solid block. A filled gold rectangle is the loudest, most
   "badge"-like element on the card; a warm translucent wash with gold text says the same
   thing quietly, which is the whole brief. Contrast measured, not assumed — see the
   colour-contrast section. */
.una200 .events__cat{
  background:rgba(255,185,42,.14);
  color:var(--gold);
  border:1px solid rgba(255,185,42,.34);
  border-radius:999px;
  padding:4px 11px; font-weight:700;
}
.una200 .events__when{ color:rgba(255,255,255,.62); }
.una200 .events__title{
  margin-top:14px;
  /* a touch larger and looser than the first pass — tight leading on a short heading is
     what makes type feel clipped and technical rather than composed */
  font-size:clamp(1.15rem,1.6vw,1.38rem); font-weight:700; line-height:1.32;
}
.una200 .events__title a{ color:#fff; text-decoration:none; transition:color .25s ease; }
.una200 .events__title a:hover{ color:var(--gold); }

/* THE WHOLE CARD IS THE LINK, and it already looked like one before it was.
   The card lifts, borders gold and casts a shadow on hover — but only the title text was
   clickable, so a 1180x140 card invited a click everywhere and answered it in one small
   place. That is worse than no hover state at all.

   A STRETCHED PSEUDO-ELEMENT, not a wrapper. Wrapping the card in an <a> would fold the
   date, category, description and venue into the link's accessible name — a screen reader
   would announce the whole card as one enormous link title. This keeps exactly one link,
   named by the event, and simply extends its hit area to the card's edges. Keyboard focus
   still lands on the title, and the focus ring still draws around the words rather than
   around the card.

   `position:relative` on the card is what the overlay resolves against; without it the
   inset would resolve against the page. The z-index clears .events__plate, which sits at 1
   over the photograph — otherwise the date chip would be a dead patch in the middle of an
   otherwise clickable card.

   THE TRADE-OFF, stated: an overlay across the card means the description cannot be
   selected with the mouse. That is the accepted cost of this pattern, and the right one for
   a card whose whole purpose is to be clicked. */
.una200 .events__card{ position:relative; }
.una200 .events__title a::after{
  content:"";
  position:absolute;
  inset:0;
  z-index:2;
}
/* and the title answers a hover anywhere on the card, not just over its own few words, so
   the lift has something to point at */
.una200 .events__card:hover .events__title a,
.una200 .events__card:focus-within .events__title a{ color:var(--gold); }
/* warm hairline between heading and blurb, echoing the rule under the name on the
   una.edu success-stories cards and the one on the Class of 2030 cards */
.una200 .events__desc{
  margin-top:14px; padding-top:14px;
  border-top:1px solid rgba(241,230,178,.14);
  font-size:.94rem; line-height:1.7;
  color:rgba(255,255,255,.68);
}
.una200 .events__venue{
  margin-top:12px; font-size:.8rem; color:rgba(255,255,255,.55);
}
/* margin-top:auto pins it to the bottom so the links line up across cards whose blurbs
   wrap to different depths — same trick as the Class of 2030 cards */
.una200 .events__card .link-more{ margin-top:auto; padding-top:16px; align-self:flex-start; }

.una200 .events__cta{ margin-top:clamp(34px,4vw,50px); text-align:center; }

/* ---- the "no events" card: one design for both events notices ----
   .events__empty (reached, nothing tagged) and .events__fallback (not reached) were plain
   grey lines, and on the Events subpage the ticker kept its arrows and dots around nothing.
   Review asked for something presentable, so both are this card on every page that has an
   events feed. They differ only in their words — see the markup.

   The ground is the ticker's own placeholder for an event with no photograph (.ticker__media),
   dots dimmed from .5 to .3 so they sit behind text rather than compete with it. It reads as
   the space where an event would be, which is exactly what it is. */
.una200 .events-none{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:6px; text-align:center;
  margin:clamp(30px,4vw,44px) auto 0; max-width:30rem;
  padding:clamp(28px,3.4vw,40px) clamp(22px,3vw,36px);
  border:1px solid rgba(241,230,178,.18); border-radius:14px;
  background:
    radial-gradient(circle at 1px 1px, rgba(241,230,178,.3) 1px, transparent 0) 0 0/22px 22px,
    linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
/* display:flex above would otherwise beat the UA's [hidden] rule and show both cards at once */
.una200 .events-none[hidden]{ display:none; }
.una200 .events-none__icon{
  width:30px; height:30px; margin-bottom:8px;
  fill:none; stroke:var(--gold); stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round;
}
/* ---- the loading gate ---- */
/* SHOWN BY [data-gate], NOT BY [hidden]. script.js sets that attribute on .page-head__grid
   before the feeds settle and flips it to "ready" when every one of them has. With
   JavaScript off it is never set, so the gate stays hidden and the hand-authored sample
   rows remain the no-JS fallback. */
.una200 [data-events-gate]{ display:none; }
.una200 [data-gate="loading"] > [data-events-gate]{
  display:flex;
  grid-column:1 / -1; grid-row:2;      /* row 2 spans the width the feeds are about to fill */
  margin-top:0;
}

/* THE FEEDS COME OUT OF THE LAYOUT, not just down to opacity:0. The generic loading rule
   further up keeps samples invisible-but-spacing, which is right when nothing replaces
   them; here the gate does, and an invisible ticker holding 500px open above it looks
   broken. Child combinator so only these two containers are touched — the homepage module
   has no gate and is unaffected. */
.una200 [data-gate="loading"] > .ticker,
.una200 [data-gate="loading"] > .upnext{ display:none; }

/* Both arrive at once, which is the whole point of the gate; the short fade is what makes
   that read as deliberate rather than as a jump. */
.una200 [data-gate="ready"] > .ticker,
.una200 [data-gate="ready"] > .upnext{ animation:events-gate-in .45s ease both; }
@keyframes events-gate-in{ from{ opacity:0; } to{ opacity:1; } }

/* Same 30px box as .events-none__icon, so the cards line up between states. */
.una200 .events-none__spinner{
  width:30px; height:30px; margin-bottom:8px;
  border-radius:50%;
  border:2.5px solid rgba(241,230,178,.22);
  border-top-color:var(--gold);
  animation:events-spin .9s linear infinite;
}
@keyframes events-spin{ to{ transform:rotate(360deg); } }
/* SLOWED, NOT STOPPED. A spinner conveys status rather than decoration, and a frozen ring
   reads as a hung page — the opposite of what someone who asked for less motion needs to
   know. The reveal is motionless for them, since that one is only decoration. */
@media (prefers-reduced-motion:reduce){
  .una200 .events-none__spinner{ animation-duration:2.6s; }
  .una200 [data-gate="ready"] > .ticker,
  .una200 [data-gate="ready"] > .upnext{ animation:none; }
}

.una200 .events-none__title{
  font-size:clamp(1.1rem,1.6vw,1.3rem); font-weight:700; line-height:1.3; color:#fff;
}
.una200 .events-none__line{ font-size:.95rem; color:var(--accent-cream-1); }

@media (max-width:900px){
  .una200 .events__lede{ font-size:1rem; }
  /* single column, matching the Class of 2030 cards so the two feature modules collapse
     the same way; the cascade becomes top-to-bottom for free */
  .una200 .events__list{ grid-template-columns:1fr; max-width:26rem; margin-inline:auto; }
}

/* =================================================================
   NEWS — feature module (fourth slot, after Era Four)
   ================================================================= */
/* Deliberately NOT a third card grid. Class of 2030 and Events both use three equal cards,
   so a third would make the lower half of the page read as one long deck. This is the
   pressroom idiom instead: one lead story carrying the weight, and a hairline-ruled index
   of the rest beside it. Asymmetric, no card surfaces, hierarchy doing the work. */
.una200 .module--news{ padding-top:clamp(80px,9vw,130px); padding-bottom:clamp(80px,9vw,130px); }

.una200 .news{ max-width:var(--wrap); margin:0 auto; }
.una200 .news__head{ max-width:60ch; margin:0 auto; text-align:center; }
.una200 .news__lede{
  margin:20px auto 0; max-width:56ch;
  font-size:1.06rem; line-height:1.7; color:rgba(255,255,255,.7);
}

.una200 .news__grid{
  margin-top:clamp(42px,5vw,64px);
  display:grid; gap:clamp(28px,3.4vw,52px);
  grid-template-columns:1.12fr .88fr;
  align-items:start;
}

/* ---- the lead story ---- */
.una200 .news__lead-media{
  display:block; overflow:hidden; border-radius:14px;
  aspect-ratio:16/9;
  background:linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .news__lead-media img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .6s cubic-bezier(.2,.7,.2,1);
}
.una200 .news__lead:hover .news__lead-media img{ transform:scale(1.04); }

.una200 .news__meta{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  font-size:.75rem; letter-spacing:.16em; text-transform:uppercase; font-weight:600;
  color:var(--accent-cream-1);
}
.una200 .news__meta-sep{ color:rgba(241,230,178,.4); }
.una200 .news__lead .news__meta{ margin-top:22px; }

.una200 .news__lead-title{
  margin-top:12px;
  font-size:clamp(1.5rem,2.6vw,2.1rem); font-weight:700; line-height:1.2;
}
.una200 .news__lead-title a{ color:#fff; text-decoration:none; transition:color .25s ease; }
.una200 .news__lead-title a:hover{ color:var(--gold); }
.una200 .news__lead-excerpt{
  margin-top:14px; max-width:52ch;
  font-size:1rem; line-height:1.75; color:rgba(255,255,255,.7);
}
.una200 .news__lead .link-more{ display:inline-block; margin-top:18px; }

/* ---- the index of everything else ---- */
/* Hairlines rather than boxes: an index should read as a list, and a bordered surface here
   would pull it back toward the card language this module exists to avoid. */
.una200 .news__more{ list-style:none; margin:0; padding:0; }
.una200 .news__item{
  display:grid; grid-template-columns:72px minmax(0,1fr); gap:16px; align-items:center;
  padding:18px 0;
  border-top:1px solid rgba(241,230,178,.14);
}
.una200 .news__more > li:last-child{ border-bottom:1px solid rgba(241,230,178,.14); }
.una200 .news__item-media{
  display:block;
  width:72px; aspect-ratio:1; overflow:hidden; border-radius:10px;
  background:linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .news__item-media img{ width:100%; height:100%; object-fit:cover; display:block; }
.una200 .news__item-title{
  margin-top:6px;
  font-size:1rem; font-weight:600; line-height:1.4;
}
.una200 .news__item-title a{ color:#fff; text-decoration:none; transition:color .25s ease; }
.una200 .news__item-title a:hover{ color:var(--gold); }
.una200 .news__item .news__meta{ font-size:.72rem; letter-spacing:.14em; }

.una200 .news__cta{ margin-top:clamp(34px,4vw,50px); text-align:center; }
/* The failure notice and the nothing-tagged-yet notice look identical and read very
   differently. Same treatment on purpose: neither is an error state the reader can act on,
   so neither should be dressed as a warning. */
.una200 .news__fallback,
.una200 .news__empty{
  margin:clamp(30px,4vw,44px) auto 0; max-width:52ch; text-align:center;
  font-size:1rem; color:rgba(255,255,255,.66);
}
/* Inside a stacked card the notice is the whole of the elastic row rather than a line under
   a grid, so it centres in the space the stories would have filled instead of sitting at the
   top of an otherwise empty card. */
.una200 [data-stack-on="true"] > .module .news__fallback,
.una200 [data-stack-on="true"] > .module .news__empty{
  margin-block:auto;
  align-self:center;
}

@media (max-width:900px){
  .una200 .news__lede{ font-size:1rem; }
  /* the lead keeps its prominence stacked above the index rather than shrinking into it */
  .una200 .news__grid{ grid-template-columns:1fr; }
  .una200 .news__lead-media{ aspect-ratio:16/10; }
}

/* =================================================================
   PAGE HEAD — shared subpage header (Events, Memories, Class of 2030, News)
   ================================================================= */
/* Subpages get this instead of the homepage's full-viewport hero. The top padding is not
   decorative: .site-nav is position:fixed, so without it the nav sits on top of the
   heading. Any new subpage uses this — don't re-derive it per page. */
.una200 .page-head{
  /* Backdrop photo, set per page by a modifier below — never inline, per the governing
     rule. `none` is a valid background-image, so a subpage without a photo just paints the
     wash over --ink. --photo-x/--photo-y move the crop; nudge those, not the gradient. */
  --page-photo:none;
  --photo-x:50%;
  --photo-y:38%;
  /* `cover` on a panel that holds an entire page crops the photo to a narrow vertical
     strip. Sizing it to the width instead keeps its proportions and lets it end partway
     down, dissolving into the gradient and --ink below — which is what makes one tall
     panel work without a second colour block. */
  --photo-size:100% auto;

  position:relative;
  padding:clamp(140px,17vh,200px) clamp(18px,5vw,7rem) clamp(70px,8vw,110px);
  text-align:center;
  color:#fff;
  /* ONE gradient for the whole opening block. On the Events page this section also holds
     the featured ticker — as two stacked sections they each restarted this radial at
     `50% 0%` and the second purple bloom read as a seam across the page.

     Wash opacities are measured, not chosen: worst case is a pure-white photo pixel, and
     .86 purple / .94 ink is what keeps the gold eyebrow at 5.18:1 and the 72% body copy at
     5.52:1. That is the same 85–93% range as the homepage hero, for the same reason.
     Thinning it puts small text under 4.5:1 — re-measure before you do. */
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(62,29,99,.86) 0%, rgba(23,11,39,.94) 70%),
    var(--page-photo) var(--photo-x) var(--photo-y) / var(--photo-size) no-repeat,
    var(--ink);
}
/* One modifier per subpage — the only thing it sets is which photo and where to crop it.
   Plain url(), NOT image-set(): a custom property that substitutes an unsupported function
   makes the whole `background` shorthand invalid at computed-value time, which drops the
   wash AND the --ink fallback together and leaves white text on the paper body colour.
   That failure is unreadable rather than merely ugly, so it is not worth a 2x source. The
   1280 variant is plenty — it sits under an 86–94% wash, same call as the hero. */
.una200 .page-head--events{
  --page-photo:url('images/events/20260819_SGT_luckydip_0095-1280.webp');
  --photo-y:0%;
  /* wider than the panel so the crop reads as a backdrop rather than a full photo */
  --photo-size:120% auto;
}
/* Spring commencement — the class in this page's title is the one that will be in this
   photo in 2030. Same geometry as Events because it is the same job: 120% width anchored to
   the top, so the crop reads as a backdrop and dissolves into --ink before the panel ends. */
.una200 .page-head--class{
  --page-photo:url('images/class-2030/20260509_SGT_springgrad_0083-1280.webp');
  --photo-y:0%;
  --photo-size:120% auto;
}
/* The University archives — the subject of the featured release, and the only photo in the
   set that reads as records rather than people. Same geometry as the other two. */
.una200 .page-head--news{
  --page-photo:url('images/ss/20251111_samthigpen_history_0096-1280.webp');
  --photo-y:0%;
  --photo-size:120% auto;
}
.una200 .page-head__inner{ max-width:64ch; margin:0 auto; }

/* Intro BESIDE the featured ticker. .page-head centres its text for the other subpages, so
   the grid resets to left-aligned and drops the inner block's auto-centring. */
.una200 .page-head__grid{
  max-width:var(--wrap); margin:0 auto;
  display:grid; grid-template-columns:.9fr 1.1fr;
  column-gap:clamp(30px,4vw,64px);
  row-gap:clamp(28px,3.4vw,46px);
  /* start, not center: the left column carries the intro AND the pill list, so it is
     usually the taller of the two and centring would float the ticker oddly */
  align-items:start;
  text-align:left;
}
.una200 .page-head__grid .page-head__inner{ max-width:none; margin:0; }

/* DOM ORDER IS THE MOBILE ORDER — intro, featured ticker, coming up — and desktop reaches
   its two-column layout by PLACEMENT, not by `order`. That is deliberate: `order` moves
   boxes without moving the tab sequence, so using it to hoist the ticker on mobile would
   leave keyboard focus running intro -> pills -> ticker while the eye reads
   intro -> ticker -> pills. Placement leaves the sequence alone. The cost is that on
   desktop the tab order goes intro -> ticker (right column) -> pills (below the intro),
   which is a spatial jump but a sensible reading order: featured content before the list. */
.una200 .page-head__grid > .page-head__inner{ grid-column:1; grid-row:1; }
.una200 .page-head__grid > .upnext{ grid-column:1; grid-row:2; }
.una200 .page-head__grid > .ticker{ grid-column:2; grid-row:1 / span 2; }
.una200 .page-head__lede{
  margin:20px auto 0; max-width:56ch;
  font-size:1.08rem; line-height:1.75; color:rgba(255,255,255,.72);
}
/* the trail back up the hierarchy — small, quiet, above the title.
   .gallery__history, the Memories page's arrow link to History, joins these lists rather than
   copying them, so the two cannot drift apart. Only the spacing below the title is not shared. */
.una200 .page-head__back,
.una200 .gallery__history{
  display:inline-block;
  font-size:.72rem; letter-spacing:.16em; text-transform:uppercase; font-weight:600;
  color:var(--accent-cream-1); text-decoration:none;
  transition:color .25s ease;
}
.una200 .page-head__back{ margin-bottom:18px; }
.una200 .page-head__back:hover,
.una200 .gallery__history:hover{ color:var(--gold); }

/* =================================================================
   UPCOMING PILLS — the Events subpage's list, in the left column
   ================================================================= */
/* Replaces what was a full-width card grid in its own coloured section. A compact rounded
   list reads as "what else is on" without competing with the featured ticker beside it,
   and it keeps the page to one panel.

   Each pill carries only the hooks it needs — date, title, link. buildRow skips any hook it
   cannot find, which is exactly what lets one fill function serve cards, ticker slides and
   pills with no per-view branch. */
.una200 .upnext{ min-width:0; }
.una200 .upnext__title{
  font-size:.72rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--accent-cream-1); font-weight:600;
  padding-bottom:12px; margin-bottom:14px;
  border-bottom:1px solid rgba(241,230,178,.18);
}

.una200 .pills{ list-style:none; margin:0; padding:0; display:grid; gap:10px; }

.una200 .pill__link{
  display:grid; grid-template-columns:auto minmax(0,1fr) auto;
  gap:14px; align-items:center;
  padding:10px 14px 10px 10px;
  /* 14px, matching .ticker__media and .card — the fully-rounded 999px version read as a
     UI chip rather than part of the same family as the slideshow's images */
  border-radius:14px;
  /* INK tint, not the cream wash used on .card. A light tint over a photographic backdrop
     lightens it, and gold on that measured 4.41:1 against the 4.5:1 that 16.8px bold text
     needs. Deepening to 35% ink takes gold to 7.04:1 and reads as a chip sitting ON the
     photo rather than a washed-out panel. Don't lighten it back. */
  background:rgba(23,11,39,.35);
  border:1px solid rgba(241,230,178,.22);
  color:#fff; text-decoration:none;
  transition:background .25s ease, border-color .25s ease, transform .25s ease;
}
.una200 .pill__link:hover,
.una200 .pill__link:focus-visible{
  /* the gold wash goes UNDER the ink tint, so hover warms the chip without lifting it back
     into the failing range — gold stays at 6.05:1 */
  background:linear-gradient(rgba(23,11,39,.35), rgba(23,11,39,.35)), rgba(255,185,42,.14);
  border-color:rgba(255,185,42,.55);
  transform:translateX(4px);
}

/* Always present, even with no photo: script.js drops only the <img> and marks the figure
   empty, so the patterned block keeps every row the same shape. 10px inside a 14px
   container, which is the usual concentric-radius relationship. */
.una200 .pill__media{
  flex:none; width:76px; aspect-ratio:1;
  margin:0; overflow:hidden; border-radius:10px;
  background:
    radial-gradient(circle at 1px 1px, rgba(241,230,178,.45) 1px, transparent 0) 0 0/14px 14px,
    linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .pill__media img{ width:100%; height:100%; object-fit:cover; display:block; }

.una200 .pill__body{ display:grid; gap:3px; min-width:0; }
.una200 .pill__meta{
  display:flex; align-items:baseline; gap:7px; flex-wrap:wrap;
  font-size:.72rem; letter-spacing:.14em; text-transform:uppercase; font-weight:600;
}
.una200 .pill__date{ color:var(--gold); }
.una200 .pill__sep{ color:rgba(241,230,178,.45); }
.una200 .pill__when{ color:var(--accent-cream-1); }
.una200 .pill__title{
  font-size:.97rem; font-weight:600; line-height:1.3;
  /* two lines at most, so a long title can't stretch the row out of the set */
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.una200 .pill__venue{
  font-size:.76rem; line-height:1.35; color:rgba(255,255,255,.72);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.una200 .pill__arrow{
  flex:none; color:var(--gold); font-size:.9rem;
  transition:transform .25s ease;
}
.una200 .pill__link:hover .pill__arrow{ transform:translateX(3px); }

.una200 .upnext__all{ display:inline-block; margin-top:18px; }

/* =================================================================
   TICKER — featured events slider (Events subpage)
   ================================================================= */
/* One slide visible at a time with real controls, rather than the .showcase crossfade:
   these slides hold links and dates, so they have to be operable, not decorative.
   There is no .module--ticker: the ticker is part of .page-head, which owns the padding. */
/* Sits in the right-hand column of .page-head__grid, so it needs no width cap or margin of
   its own — the grid places it beside the intro. */
.una200 .ticker{
  min-width:0;
  /* ONE source of truth for the media's shape. The timer overlay below shares it so the
     bar's bottom edge lands exactly on the image's bottom edge; changing one without the
     other would float the bar off the picture. */
  --ticker-ratio:16/10;
}

/* the box the timer overlay measures itself against */
.una200 .ticker__stage{ position:relative; }

/* All slides share one grid cell, so the track is as tall as the tallest and stepping never
   reflows the page — the same trick as .timeline__stage. Crucially the inactive ones are
   visibility:hidden rather than display:none: that keeps them IN the grid (so the height
   stays put mid-animation) while still removing their links from the tab order and the
   accessibility tree. */
.una200 .ticker__track{
  list-style:none; margin:0; padding:0;
  display:grid;
}
/* One column: image on top, the event's own details directly beneath it. The details are
   part of the slide rather than the static intro, so they change with the slide and stay
   inside one animatable element. */
.una200 .ticker__slide{
  grid-area:1/1;
  display:grid; grid-template-columns:1fr;
  gap:clamp(16px,2vw,24px); align-content:start;
  opacity:0; visibility:hidden;
  /* visibility waits out the fade, so the outgoing slide dissolves instead of snapping */
  transition:opacity .34s ease, visibility 0s linear .34s;
}
.una200 .ticker__slide.is-active{
  opacity:1; visibility:visible;
  transition:opacity .45s ease, visibility 0s;
  /* The incoming slide is pushed in; the outgoing only fades. Animating both directions
     would need two resting positions per slide (waiting-right AND exited-left), which one
     class cannot express — and the incoming motion is what the eye follows anyway.
     No fill-mode: after it ends the declared .is-active values apply, which leaves
     transform free rather than owned by a filling animation. */
  animation:ticker-in .5s cubic-bezier(.2,.7,.2,1);
}
@keyframes ticker-in{
  from{ opacity:0; transform:translateX(34px); }
  to{ opacity:1; transform:none; }
}
/* going backwards, it arrives from the other side */
.una200 .ticker__track.is-back .ticker__slide.is-active{ animation-name:ticker-in-back; }
@keyframes ticker-in-back{
  from{ opacity:0; transform:translateX(-34px); }
  to{ opacity:1; transform:none; }
}

.una200 .ticker__media{
  position:relative; display:block; overflow:hidden; border-radius:14px;
  aspect-ratio:var(--ticker-ratio);
  background:
    radial-gradient(circle at 1px 1px, rgba(241,230,178,.5) 1px, transparent 0) 0 0/22px 22px,
    linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .ticker__media img{ width:100%; height:100%; object-fit:cover; display:block; }

.una200 .ticker__body{ min-width:0; }
.una200 .ticker__title{
  margin-top:10px;
  font-size:clamp(1.25rem,2vw,1.7rem); font-weight:700; line-height:1.24;
}
.una200 .ticker__title a{ color:#fff; text-decoration:none; transition:color .25s ease; }
.una200 .ticker__title a:hover{ color:var(--gold); }
.una200 .ticker__desc{
  margin-top:14px; max-width:52ch;
  font-size:1rem; line-height:1.75; color:rgba(255,255,255,.72);
}
/* 72%, not the 55% used on a card surface: this text sits over a photographic backdrop, and
   at 55% it measured 3.33:1 against the 4.5:1 small text needs. Scoped to the ticker so the
   homepage cards keep their quieter treatment. */
.una200 .ticker__venue{ margin-top:12px; font-size:.84rem; color:rgba(255,255,255,.72); }
.una200 .ticker .events__when{ color:rgba(255,255,255,.72); }
.una200 .ticker__slide .link-more{ display:inline-block; margin-top:18px; }

/* ---- the 6s line timer, sitting on the featured image ---- */
/* Same visual language as the page's scroll-progress sliver: a 3px gold line running left
   to right. It is also the clock — script.js advances on its `animationend`, so the bar and
   the advance cannot drift apart, and pausing is animation-play-state rather than a
   cancelled timeout, which means it resumes from where it stopped.

   The wrap is an overlay sharing --ticker-ratio with .ticker__media, so its bottom edge is
   the image's bottom edge whatever the width. Clipped to the same 14px corners, and
   pointer-events:none so taps still reach the image link underneath. */
.una200 .ticker__timerwrap{
  position:absolute; top:0; left:0; right:0;
  aspect-ratio:var(--ticker-ratio);
  border-radius:14px; overflow:hidden;
  display:flex; align-items:flex-end;
  pointer-events:none;
}
.una200 .ticker__timer{
  width:100%; height:4px;
  /* OPAQUE ink, not a tint. The bar sits over an unpredictable photograph, and at 55% the
     gold fill measured 2.45:1 against its own track on a bright frame — under the 3:1 that
     SC 1.4.11 wants. Opaque pins gold-to-track at 10.98:1 whatever the image is, the same
     call already made for the events date plate. */
  background:var(--ink);
  overflow:hidden;
}
.una200 .ticker__timer span{
  display:block; height:100%; width:100%;
  background:var(--gold);
  box-shadow:0 0 10px rgba(255,185,42,.6);
  transform:scaleX(0); transform-origin:left center;
}
.una200 .ticker__timer.is-running span{ animation:ticker-timer 6s linear; }
@keyframes ticker-timer{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }
/* hover, touch-hold or focus inside the ticker — hold the bar where it is */
.una200 .ticker.is-held .ticker__timer span{ animation-play-state:paused; }

/* ---- controls ---- */
.una200 .ticker__controls{
  display:flex; align-items:center; justify-content:center; gap:clamp(14px,2vw,22px);
  margin-top:clamp(20px,2.4vw,30px);
}
/* Bigger and bolder than the timeline's arrows on purpose: these are the primary way
   through the featured set, and they are now also the WCAG 2.2.2 stop mechanism, so they
   need to read as the main control rather than a quiet nudge. */
.una200 .ticker__arrow{
  flex:none; width:56px; height:56px;
  display:grid; place-items:center;
  font-size:1.5rem; font-weight:700; line-height:1;
  color:var(--gold);
  background:transparent;
  /* 2px at .55 alpha — a control boundary needs 3:1 per SC 1.4.11, and --line-on-dark's
     .22 measures 1.78:1 on ink */
  border:2px solid rgba(241,230,178,.55);
  border-radius:50%;
  cursor:pointer;
  transition:background .25s ease, border-color .25s ease, color .25s ease, transform .25s ease;
}
.una200 .ticker__arrow:hover{
  background:var(--gold); border-color:var(--gold); color:var(--ink);
  transform:scale(1.06);
}

.una200 .ticker__dots{ display:flex; align-items:center; gap:10px; }
.una200 .ticker__dot{
  width:11px; height:11px; padding:0; border-radius:50%;
  background:transparent; border:2px solid rgba(255,185,42,.55);
  cursor:pointer;
  transition:background .25s ease, transform .25s ease, border-color .25s ease;
}
.una200 .ticker__dot[aria-current="true"]{
  background:var(--gold); border-color:var(--gold); transform:scale(1.3);
}

/* ---- when the ticker has nothing to show ---- */
/* NOTHING TO SLIDE. settleFeed hides the list, but the stage (and its timer) and the arrows
   and dots are outside it — measured, both arrows and two dots stayed on screen with no
   slides behind them. The card takes the whole slot instead, at the featured image's own
   ratio, so the right-hand column keeps its shape. */
.una200 .ticker:is([data-feed-state="empty"], [data-feed-state="fallback"])
  :is(.ticker__stage, .ticker__controls){ display:none; }
.una200 .ticker .events-none{ margin:0; max-width:none; aspect-ratio:var(--ticker-ratio); }
/* Coming Up is a column, not a centred band: the card fills it like the pills would. */
.una200 .upnext .events-none{ margin:0; max-width:none; }

/* ONE CARD, NOT TWO. Both Events subpage feeds read the same tag, so when nothing carries it
   both settle empty together, and the page said so twice, side by side. The ticker's card is
   the larger, so it speaks for the page; Coming Up drops its card and its heading, which
   would otherwise head nothing, and keeps the link to the full calendar. Same for a failure.
   Without :has() (older browsers) both cards show — redundant, never wrong. */
.una200 .page-head__grid:has(> .ticker:is([data-feed-state="empty"], [data-feed-state="fallback"]))
  > .upnext:is([data-feed-state="empty"], [data-feed-state="fallback"])
  :is(.upnext__title, .events-none){ display:none; }

/* =================================================================
   STORIES — the Class of 2030 subpage's lead + index
   ================================================================= */
/* Same lead-plus-index idiom as the pressroom module, and it shares that module's JS and its
   .news__meta rule, but it is a SEPARATE block rather than a modifier on .news. Two reasons:
   .news is a homepage module with its own padding and centred head, and the lead here is a
   square portrait frame instead of a 16:9 news photo. Sharing the wiring is the win; sharing
   the CSS would have meant a modifier overriding half of it.

   No panel background of its own — it sits inside .page-head, which owns the wash, the
   backdrop photo and the padding. */
.una200 .stories{
  max-width:var(--wrap);
  margin:clamp(44px,5.5vw,72px) auto 0;
  /* .page-head centres its text for the intro; the columns below read left */
  text-align:left;
}
.una200 .stories__grid{
  display:grid;
  grid-template-columns:1.06fr .94fr;
  column-gap:clamp(30px,4vw,64px);
  row-gap:clamp(34px,4vw,54px);
  align-items:start;
}

/* ---- the lead story ---- */
/* SQUARE, not 16:9 — this page is about people, and a 1:1 frame reads as a portrait rather
   than a news photo. The sample sources are all landscape, so object-fit crops ~40% of the
   width away; centre is the fair default. Once real post images land, some will want their
   own object-position. Set it per image with an inline style ONLY as a last resort — the
   governing rule keeps presentation out of the markup, and in WordPress the crop belongs in
   the featured-image size registered by the plugin, not on the element. */
.una200 .stories__lead-media{
  display:block; overflow:hidden; border-radius:16px;
  aspect-ratio:1;
  background:linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .stories__lead-media img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .6s cubic-bezier(.2,.7,.2,1);
}
.una200 .stories__lead:hover .stories__lead-media img{ transform:scale(1.04); }

.una200 .stories__lead .news__meta{ margin-top:24px; }
.una200 .stories__lead-title{
  margin-top:12px;
  font-size:clamp(1.5rem,2.7vw,2.15rem); font-weight:700; line-height:1.2;
}
.una200 .stories__lead-title a{ color:#fff; text-decoration:none; transition:color .25s ease; }
.una200 .stories__lead-title a:hover{ color:var(--gold); }
.una200 .stories__lead-excerpt{
  margin-top:14px; max-width:52ch;
  /* .72 over the panel's lightest possible pixel measures 5.31:1 — the same call, and the
     same margin, as .page-head__lede */
  font-size:1.02rem; line-height:1.75; color:rgba(255,255,255,.72);
}
.una200 .stories__lead .link-more{ display:inline-block; margin-top:20px; }

/* ---- the index of everything else ---- */
/* Hairlines, not cards. The lead is the only surface on this page; boxing the index would
   put two competing card languages in one panel. */
.una200 .stories__side-title{
  font-size:.72rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--accent-cream-1); font-weight:600;
  padding-bottom:12px; margin-bottom:4px;
  border-bottom:1px solid rgba(241,230,178,.18);
}
.una200 .stories__list{ list-style:none; margin:0; padding:0; }
.una200 .stories__item{
  /* `auto`, not a fixed 84px: the thumb's own width sizes the column, so the mobile size-up
     below is a one-line override instead of two rules that have to be kept in step */
  display:grid; grid-template-columns:auto minmax(0,1fr) auto;
  gap:16px; align-items:center;
  padding:18px 0;
  border-bottom:1px solid rgba(241,230,178,.14);
}
.una200 .stories__item-media{
  display:block;
  width:84px; aspect-ratio:1; overflow:hidden;
  /* matches the 16px on the lead's frame, scaled down — the pills' lesson: a thumb with the
     same radius as a big frame reads as a different shape, not a smaller one */
  border-radius:10px;
  background:linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .stories__item-media img{ width:100%; height:100%; object-fit:cover; display:block; }
.una200 .stories__item-body{ min-width:0; }
.una200 .stories__item .news__meta{ font-size:.72rem; letter-spacing:.14em; }
.una200 .stories__item-title{
  margin-top:6px;
  font-size:1.02rem; font-weight:600; line-height:1.4;
}
.una200 .stories__item-title a{ color:#fff; text-decoration:none; transition:color .25s ease; }
.una200 .stories__item-title a:hover{ color:var(--gold); }
/* Nudges on hover of the whole row, so the affordance is not limited to the 84px thumb or
   the headline. Decorative and aria-hidden — the link text is what announces the row. */
.una200 .stories__item-arrow{
  flex:none; color:var(--gold); font-size:1.1rem; line-height:1;
  opacity:.55; transition:opacity .25s ease, transform .25s ease;
}
.una200 .stories__item:hover .stories__item-arrow{ opacity:1; transform:translateX(4px); }

/* =================================================================
   CHRON — the Full History page's long-form chronology
   ================================================================= */
/* The counterpart to the homepage timeline, and deliberately NOT the same component. The
   tab strip exists to fit a lot of history into a small space; this page's job is the
   opposite — everything present at once, scrollable, no state to lose. So it is a plain
   vertical chronology: no arrows, no panels, no JavaScript beyond the shared reveals.

   No ground of its own: it continues .page-head's panel, the same way the News wire does. */
.una200 .page-head--history{
  --page-photo:url('images/la-grange-1280.webp');
  --photo-y:0%;
  --photo-size:120% auto;
  padding-bottom:clamp(34px,4vw,54px);
}
.una200 .chron-panel{
  background:var(--ink);
  color:#fff;
  padding:0 clamp(18px,5vw,7rem) clamp(70px,8vw,110px);
}
/* --era-gap IS THE ONE RHYTHM BETWEEN ERAS. Every era — the first included — opens with the
   same hairline, the same space above it and the same space below it, and the footer note
   closes on the same gap. Before this the first era had its rule and padding removed while
   the others kept theirs, and the gallery added its own margin, so the space between eras
   depended on what the previous era happened to end with. One token, used three times.

   --year-col is the width of the year column on the spine layout. */
.una200 .chron{
  max-width:var(--wrap); margin:0 auto;
  --era-gap:clamp(56px,7vw,104px);
  --year-col:clamp(5.75rem,10vw,8.5rem);
  --spine:2px solid rgba(241,230,178,.28);
}
/* The jump nav needs an accessible name for the region it introduces, but a visible "The
   five eras" heading above five era headings would just be noise. */
.una200 .chron__srtitle{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

/* ---- jump links ---- */
.una200 .chron__jump{
  display:grid; grid-template-columns:repeat(5,minmax(0,1fr));
  gap:10px;
}
.una200 .chron__jumplink{
  /* THE YEAR SITS ON THE CARD FLOOR. `align-content:start` packed both rows to the top,
     which was invisible while every label was "Era One".."Era Five" and one line tall. The
     labels are now the era names and wrap to one, two or three lines, so the years were
     landing at three different heights across the row and the strip read as ragged.
     Giving the name row 1fr pushes the year to the bottom of every card instead, and the
     cards are already equal height because they are grid siblings. */
  display:grid; grid-template-rows:1fr auto; gap:6px;
  padding:14px 16px;
  text-decoration:none;
  background:rgba(241,230,178,.05);
  /* .45, and the figure matters: these read as buttons, so the border is what identifies
     the control and SC 1.4.11 wants 3:1 against the ink behind it. .3 measures 2.33:1 and
     FAILS — an earlier comment here claimed 3.13:1, which was simply wrong. .45 measures
     3.79:1. (--line-on-dark's .22 is 1.78:1, which is why that token is not used here.) */
  border:1px solid rgba(241,230,178,.45);
  border-radius:12px;
  transition:background .25s ease, border-color .25s ease, transform .25s ease;
}
.una200 .chron__jumplink:hover{
  background:rgba(241,230,178,.12);
  border-color:var(--gold);
  transform:translateY(-2px);
}
/* .8rem floor for the small uppercase labels, up from .68rem (10.9px) */
.una200 .chron__jumpera{
  font-size:.8rem; letter-spacing:.16em; text-transform:uppercase; font-weight:600;
  color:var(--gold);
}
/* The era's opening year, now a single year rather than a range, so it can be large */
.una200 .chron__jumpyears{
  font-size:clamp(1.35rem,2.2vw,1.7rem); font-weight:800; line-height:1;
  color:#fff; font-variant-numeric:lining-nums tabular-nums;
}

/* ---- the nine names ---- */
.una200 .chron__names{
  margin-top:clamp(28px,3.4vw,44px);
  padding:clamp(22px,3vw,34px);
  border:1px solid var(--line-on-dark);
  border-radius:16px;
  background:rgba(241,230,178,.04);
}
.una200 .chron__names-title{
  font-size:.8rem; letter-spacing:.2em; text-transform:uppercase; font-weight:600;
  color:var(--gold);
}
.una200 .chron__names-list{
  list-style:none; margin:14px 0 0; padding:0;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(min(16rem,100%),1fr));
  column-gap:clamp(20px,3vw,40px);
}
.una200 .chron__names-list a{
  display:flex; align-items:baseline; gap:14px;
  padding:10px 0;
  border-bottom:1px solid var(--line-on-dark);
  font-size:1rem; line-height:1.4; color:rgba(255,255,255,.9);
  text-decoration:none;
  transition:color .2s ease;
}
.una200 .chron__names-list a:hover{ color:var(--gold); }
.una200 .chron__names-year{
  flex:none; min-width:3.1em;
  font-size:1.1rem; font-weight:800; color:var(--gold);
  font-variant-numeric:lining-nums tabular-nums;
}

/* ---- one era ---- */
.una200 .chron__era{
  margin-top:var(--era-gap);
  padding-top:var(--era-gap);
  border-top:1px solid rgba(241,230,178,.14);
  /* the fixed nav must not cover the heading when a jump link lands on it */
  scroll-margin-top:clamp(56px,8vh,96px);
}

/* Head beside the era's lead photograph, chronology full width beneath. The photo is beside
   the era's introduction rather than above the entries so it reads as illustrating the era,
   not the first year in it. */
.una200 .chron__intro{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,34rem);
  gap:clamp(28px,4vw,60px);
  align-items:center;
}
.una200 .chron__head{ max-width:60ch; }
.una200 .chron__head .module__title{ margin-top:10px; }
/* "From 1830": the era's opening year, at display size. The word stays small so the year
   carries the weight. */
.una200 .chron__start{
  margin-top:18px;
  font-size:.8rem; letter-spacing:.2em; text-transform:uppercase; font-weight:600;
  color:var(--accent-cream-1);
}
.una200 .chron__start span{
  display:block; margin-top:6px;
  font-size:clamp(2.6rem,6vw,4.25rem); font-weight:800; line-height:1; letter-spacing:-.01em;
  color:var(--gold); text-transform:none;
  font-variant-numeric:lining-nums tabular-nums;
}
.una200 .chron__lede{
  margin-top:18px;
  font-size:1.05rem; line-height:1.7; color:rgba(255,255,255,.8);
}
/* The frame is on the <img>, not the <figure>, because the lead photographs now carry a
   caption: framed on the figure, the caption sat inside the white border and was clipped by
   its overflow. */
.una200 .chron__fig{ margin:0; }
.una200 .chron__fig img{
  width:100%; height:auto; aspect-ratio:4/3; object-fit:cover; display:block;
  border:6px solid #fff; border-radius:14px; background:#fff;
  box-shadow:0 30px 70px -25px rgba(0,0,0,.6);
}
/* LETS A PANORAMA BE A PANORAMA. The rule above forces every era photograph to 4:3 and
   crops with object-fit:cover, which is right for the archive scans — they arrive at every
   ratio and a ragged column of figures would be worse than losing a little edge. The
   Wesleyan then-and-now montage is the exception: it is 2:1 because the composition IS the
   two halves meeting, and cropping to 4:3 cuts the ends off the very thing being shown.
   This keeps its own ratio, which the width/height attributes already declare. */
.una200 .chron__fig--wide img{ aspect-ratio:auto; object-fit:contain; }

.una200 .chron__fig figcaption{
  margin-top:12px;
  font-size:.92rem; line-height:1.5; color:rgba(255,255,255,.76);
}

/* ---- the chronology itself ---- */
/* The spine is a border on .chron__body, not a separate element: adjacent list items make
   their borders meet, so the line is continuous for free and cannot fall out of step with
   the number of years. */
.una200 .chron__list{
  list-style:none;
  margin:clamp(40px,5vw,64px) 0 0; padding:0;
}
.una200 .chron__item{
  display:grid;
  grid-template-columns:var(--year-col) minmax(0,1fr);
  gap:clamp(18px,2.4vw,34px);
}
/* THE YEAR IS THE PAGE'S ORGANISING DEVICE, so it is set large and in gold (10.6:1 on the
   ink) rather than as the small uppercase label it used to be — that label was .76rem
   because it had to hold "1854–1855" and "1980s–1990s". Single years only now, so the
   column can afford display type.

   Sticky within its own item: in a year with several entries and a photograph the year
   stays beside them as they scroll past, then leaves with its last line. It is sticky to the
   <li>, so it can never overlap the next year. */
.una200 .chron__year{
  position:sticky; top:calc(var(--nav-h) + 16px);
  align-self:start;
  margin:0;
  text-align:right;
  font-size:clamp(1.7rem,2.9vw,2.4rem); font-weight:800; line-height:1; letter-spacing:-.01em;
  color:var(--gold);
  font-variant-numeric:lining-nums tabular-nums;
}
.una200 .chron__body{
  position:relative;
  padding:.2rem 0 clamp(34px,4vw,52px) clamp(22px,2.4vw,32px);
  border-left:var(--spine);
}
.una200 .chron__item:last-child .chron__body{ padding-bottom:0; border-left-color:transparent; }
/* Marker on the spine. Decorative — the <ol> already conveys the sequence — so it is drawn
   rather than announced. -6px centres a 10px dot on the 2px border. */
.una200 .chron__body::before{
  content:'';
  position:absolute; left:-6px; top:.75rem;
  width:10px; height:10px; border-radius:50%;
  background:var(--gold);
  box-shadow:0 0 0 4px var(--ink);
}
.una200 .chron__item:last-child .chron__body::before{ box-shadow:0 0 0 4px var(--ink), 0 0 0 8px rgba(255,185,42,.18); }

.una200 .chron__facts{ list-style:none; margin:0; padding:0; display:grid; gap:12px; }
/* .82 white is 12.1:1 on the ink, up from .72 (9.9:1) — not a contrast fix, both pass, but
   this page is long-form reading and the extra weight makes it noticeably easier going. */
.una200 .chron__fact{
  position:relative; padding-left:20px; max-width:68ch;
  font-size:1.04rem; line-height:1.65; color:rgba(255,255,255,.82);
}
.una200 .chron__fact::before{
  content:'';
  position:absolute; left:2px; top:.72em;
  width:6px; height:6px; border-radius:50%;
  background:var(--accent-tan);
}
/* The turning points — the name changes, integration, Leo — read heavier, with a gold dash
   for a marker. Emphasis only, so it carries no extra semantics. */
.una200 .chron__fact--key{ color:#fff; font-weight:600; }
.una200 .chron__fact--key::before{ left:0; top:.78em; width:12px; height:3px; border-radius:2px; background:var(--gold); }
.una200 .chron__sub{
  list-style:none; margin:8px 0 0; padding:0;
  display:grid; gap:4px;
  font-weight:400; color:rgba(255,255,255,.76);
}
.una200 .chron__sub li{ position:relative; padding-left:18px; }
.una200 .chron__sub li::before{
  content:''; position:absolute; left:0; top:.8em;
  width:9px; height:1px; background:var(--accent-tan);
}
.una200 .chron__fact cite{ font-style:italic; }

/* Photographs beside their year. Shown WHOLE, at their own proportions, with no crop: these
   are archive prints and postcards, and cropping one to a uniform ratio cuts off the very
   detail (a sign, a caption, a tower) that makes it worth showing. width/height on the <img>
   reserve the space before it loads. */
.una200 .chron__photo{ margin:clamp(18px,2vw,26px) 0 0; max-width:30rem; }
.una200 .chron__photo.is-portrait{ max-width:18rem; }
.una200 .chron__photos{
  display:grid; grid-template-columns:repeat(2,minmax(0,15rem));
  gap:clamp(14px,2vw,22px); align-items:start;
  margin-top:clamp(18px,2vw,26px);
}
.una200 .chron__photos .chron__photo{ margin:0; max-width:none; }
.una200 .chron__photo img,
.una200 .chron__shot img{
  display:block; width:100%; height:auto;
  border:5px solid #fff; border-radius:10px;
  background:#fff;
  box-shadow:0 24px 50px -24px rgba(0,0,0,.7);
}
.una200 .chron__photo figcaption,
.una200 .chron__shot figcaption{
  margin-top:10px;
  font-size:.92rem; line-height:1.5; color:rgba(255,255,255,.76);
}

/* ---- campus life: photographs with no single year to stand beside ---- */
/* Columns rather than a grid so each photograph keeps its own height — the set mixes
   portraits, landscapes and a near-square, and a grid would crop them all to one shape. */
.una200 .chron__gallery{ margin-top:clamp(48px,6vw,80px); }
.una200 .chron__gallery-title{
  font-size:.8rem; letter-spacing:.2em; text-transform:uppercase; font-weight:600;
  color:var(--gold);
}
.una200 .chron__gallery-grid{
  margin-top:20px;
  columns:3 15rem; column-gap:clamp(16px,2vw,26px);
}
.una200 .chron__shot{ break-inside:avoid; margin:0 0 clamp(18px,2vw,26px); }

/* A SHOT THAT LEADS RATHER THAN QUEUES. The Wendell Gunn diptych is the reason that box
   exists — one photograph holding 1963 and 2023 on the same steps — so it runs at the
   card's full measure above the grid instead of taking its turn as one tile among three.
   Capped at 52rem because beyond that the two halves get far enough apart that the eye
   stops reading them as a pair. */
.una200 .chron__shot--feature{
  max-width:52rem;
  margin-inline:auto;
  margin-bottom:clamp(24px,3vw,38px);
}
.una200 .chron__shot--feature img{
  width:100%; height:auto; display:block;
  border-radius:12px;
}

/* ---- Lion Country: a break between Eras Four and Five ---- */
/* The live lions span three eras' worth of years but are one story, and scattered through
   Era Five their photographs read as curiosities. Gathered here, between the era that ends
   in 1973 and the one that opens with Leo I in 1974.

   IT KEEPS THE ERA RHYTHM. Same hairline, same --era-gap above and below, so the break does
   not become the one place on the page where the spacing changes. What marks it as a break
   is the card: a deep purple ground (cream 10.6:1, gold 8.1:1 on it), not extra space. */
.una200 .chron__break{
  margin-top:var(--era-gap);
  padding-top:var(--era-gap);
  border-top:1px solid rgba(241,230,178,.14);
  scroll-margin-top:clamp(56px,8vh,96px);
}
.una200 .chron__break-card{
  padding:clamp(26px,4vw,56px);
  border-radius:20px;
  border:1px solid rgba(255,185,42,.28);
  background:linear-gradient(160deg, var(--purple-deep) 0%, var(--ink-soft) 100%);
  box-shadow:0 40px 90px -40px rgba(0,0,0,.7);
}
.una200 .chron__break-intro{
  display:grid; grid-template-columns:minmax(0,1fr) auto;
  gap:clamp(24px,4vw,56px); align-items:center;
}
.una200 .chron__break-head{ max-width:56ch; }
.una200 .chron__break-head .module__title{ margin-top:10px; }
.una200 .chron__break-extras{
  display:grid; grid-template-columns:repeat(2,minmax(0,12rem));
  gap:clamp(14px,2vw,22px); align-items:start;
}
.una200 .chron__break-extras .chron__shot{ margin:0; }

.una200 .chron__lions{
  list-style:none; margin:clamp(32px,4vw,52px) 0 0; padding:0;
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(18px,2.6vw,36px);
}
/* One crop for all three, so the row reads as a set; Leo II is a portrait, so his crop
   holds to the top of the frame where his face is. */
.una200 .chron__lion-photo{ margin:0; }
.una200 .chron__lion-photo img{
  display:block; width:100%; height:auto; aspect-ratio:4/3; object-fit:cover;
  border:5px solid #fff; border-radius:10px; background:#fff;
  box-shadow:0 24px 50px -24px rgba(0,0,0,.7);
}
.una200 .chron__lion--top .chron__lion-photo img{ object-position:50% 22%; }
.una200 .chron__lion-name{
  margin-top:18px;
  font-size:clamp(1.3rem,2vw,1.6rem); font-weight:700; line-height:1.2; color:#fff;
}
/* single years, labelled, never a range — the page's rule holds here too */
/* NOT UPPERCASE. The lioness is called Una and the University is UNA, and
   `text-transform:uppercase` collapsed the difference: "Una until 2020" rendered as
   "UNA UNTIL 2020", which a reviewer read as a statement about the institution closing
   rather than about the lioness. Sentence case is the only thing that distinguishes the
   two names, so this row cannot have the treatment the rest of the eyebrows get. The
   letter-spacing goes with it — it was there to make the caps legible. */
.una200 .chron__lion-years{
  margin-top:8px;
  display:flex; flex-wrap:wrap; gap:4px 18px; align-items:baseline;
  font-size:.82rem; letter-spacing:.01em; font-weight:600;
  color:var(--accent-cream-1);
}
.una200 .chron__lion-years b{
  margin-left:4px;
  font-size:1.3rem; font-weight:800; letter-spacing:0; color:var(--gold);
  font-variant-numeric:lining-nums tabular-nums;
}
.una200 .chron__lion-text{
  margin-top:10px;
  font-size:1rem; line-height:1.6; color:rgba(255,255,255,.82);
}

.una200 .chron__foot{
  margin-top:var(--era-gap); padding-top:clamp(26px,3vw,38px);
  border-top:1px solid rgba(241,230,178,.14);
  font-size:1rem; color:rgba(255,255,255,.75);
}
.una200 .chron__foot a{ color:var(--gold); }

/* ---- tablet: the intro stacks, the spine layout stays ---- */
@media (max-width:900px){
  .una200 .chron__intro{ grid-template-columns:1fr; }
  .una200 .chron__fig{ max-width:40rem; }
  .una200 .chron__break-intro{ grid-template-columns:1fr; }
  /* Five jump links in a row are 120px each here, too tight for "Era Three". Three across,
     and the last two share the second row. */
  .una200 .chron__jump{ grid-template-columns:repeat(6,minmax(0,1fr)); }
  .una200 .chron__jumplink{ grid-column:span 2; }
  .una200 .chron__jumplink:nth-child(n+4){ grid-column:span 3; }
}

/* ---- phone: the year moves above its entries ---- */
/* A year column at phone width leaves the entries about 20 characters, so the year moves
   above them and the spine moves to the far left so it still runs down the page. */
@media (max-width:560px){
  .una200 .chron__jump{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .una200 .chron__jumplink,
  .una200 .chron__jumplink:nth-child(n+4){ grid-column:auto; }
  .una200 .chron__jumplink:last-child{ grid-column:1 / -1; }

  .una200 .chron__list{ border-left:var(--spine); }
  .una200 .chron__item{ grid-template-columns:1fr; gap:0; }
  .una200 .chron__year{
    position:relative; top:auto;
    text-align:left;
    padding-left:clamp(22px,6vw,32px);
    margin-bottom:12px;
    font-size:clamp(1.7rem,8vw,2.1rem);
  }
  /* the marker moves to the year, which now leads each item */
  .una200 .chron__year::before{
    content:'';
    position:absolute; left:-6px; top:50%; margin-top:-5px;
    width:10px; height:10px; border-radius:50%;
    background:var(--gold);
    box-shadow:0 0 0 4px var(--ink);
  }
  .una200 .chron__body,
  .una200 .chron__item:last-child .chron__body{ border-left:0; padding-left:clamp(22px,6vw,32px); }
  .una200 .chron__body::before{ content:none; }
  .una200 .chron__photo,
  .una200 .chron__photo.is-portrait{ max-width:100%; }
  .una200 .chron__photo.is-portrait,
  .una200 .chron__shot.is-portrait{ width:78%; }
  .una200 .chron__photos{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .una200 .chron__break-extras{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .una200 .chron__lions{ grid-template-columns:1fr; }
}

/* =================================================================
   WIRE — the News subpage's featured release + archive river
   ================================================================= */
/* Third index on the site, and it has to read differently from the other two. The homepage
   module and the Class of 2030 subpage are both a STACKED lead over a compact hairline list,
   because both are teasers inside a larger page. This is the archive itself, so the lead
   goes horizontal, the rows carry an excerpt, and each row leads with a two-line date plate.

   Still hairlines, not cards: Events and Class of 2030 already own the card language, and a
   third deck would make the whole site one long row of boxes.

   No ground of its own — it sits inside .page-head, which owns the wash and the padding. */
.una200 .wire{
  max-width:var(--wrap);
  margin:clamp(44px,5.5vw,72px) auto 0;
  text-align:left;               /* .page-head centres the intro above */
}

/* ---- the featured release ---- */
.una200 .wire__lead{
  display:grid;
  grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);
  gap:clamp(24px,3.2vw,48px);
  align-items:center;
  padding-bottom:clamp(34px,4vw,52px);
  /* a heavier rule than the rows use, so the featured slot reads as separate from the
     river rather than as its first entry */
  border-bottom:1px solid rgba(241,230,178,.3);
}
.una200 .wire__lead-media{
  display:block; overflow:hidden; border-radius:16px;
  aspect-ratio:16/10;
  background:linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .wire__lead-media img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .6s cubic-bezier(.2,.7,.2,1);
}
.una200 .wire__lead:hover .wire__lead-media img{ transform:scale(1.04); }
.una200 .wire__lead-body{ min-width:0; }
/* Gold pill on the meta line — the one badge on the page, marking which release is the
   featured one. Ink on gold measures 10.98:1. */
.una200 .wire__flag{
  display:inline-block;
  padding:3px 9px; border-radius:999px;
  background:var(--gold); color:var(--ink);
  font-size:.72rem; letter-spacing:.14em; text-transform:uppercase; font-weight:700;
}
.una200 .wire__lead-title{
  margin-top:12px;
  font-size:clamp(1.55rem,2.9vw,2.3rem); font-weight:700; line-height:1.18;
}
.una200 .wire__lead-title a{ color:#fff; text-decoration:none; transition:color .25s ease; }
.una200 .wire__lead-title a:hover{ color:var(--gold); }
.una200 .wire__lead-excerpt{
  margin-top:14px; max-width:54ch;
  font-size:1.02rem; line-height:1.75; color:rgba(255,255,255,.72);
}
.una200 .wire__lead .link-more{ display:inline-block; margin-top:20px; }

/* ---- the archive river ---- */
.una200 .wire__list{ list-style:none; margin:0; padding:0; }
.una200 .wire__row{
  display:grid;
  /* plate | copy | thumb. The thumb column is `auto` so the image's own width sizes it,
     which makes the mobile size-up a one-line override. */
  grid-template-columns:5.5rem minmax(0,1fr) auto;
  gap:clamp(18px,2.4vw,30px);
  align-items:center;
  padding:clamp(20px,2.4vw,28px) 0;
  border-bottom:1px solid rgba(241,230,178,.14);
}
.una200 .wire__row:hover{ background:rgba(241,230,178,.04); }

/* The date plate. Month over day, right-aligned against the copy so the numerals form a
   column down the page — that alignment is the whole point of the device. */
.una200 .wire__plate{
  display:grid; gap:2px; text-align:right;
  padding-right:clamp(4px,1vw,14px);
  border-right:1px solid rgba(241,230,178,.18);
}
.una200 .wire__mon{
  font-size:.75rem; letter-spacing:.16em; text-transform:uppercase; font-weight:600;
  color:var(--accent-cream-1);
}
.una200 .wire__day{
  font-size:1.6rem; font-weight:700; line-height:1; color:#fff;
  font-variant-numeric:tabular-nums;   /* so 2 and 28 share a width */
}

.una200 .wire__body{ min-width:0; }
.una200 .wire__cat{
  font-size:.72rem; letter-spacing:.16em; text-transform:uppercase; font-weight:600;
  color:var(--accent-cream-1);
}
.una200 .wire__cat:empty{ display:none; }
.una200 .wire__title{
  margin-top:6px;
  font-size:clamp(1.05rem,1.5vw,1.22rem); font-weight:600; line-height:1.35;
}
.una200 .wire__title a{ color:#fff; text-decoration:none; transition:color .25s ease; }
.una200 .wire__title a:hover{ color:var(--gold); }
/* Two lines, clamped in CSS rather than by a second character budget in script.js: the
   reader already trims to 190 characters for the lead, and one clamp is easier to reason
   about than two that have to stay in proportion. */
.una200 .wire__excerpt{
  margin-top:7px; max-width:70ch;
  font-size:.95rem; line-height:1.6; color:rgba(255,255,255,.72);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  line-clamp:2; overflow:hidden;
}

.una200 .wire__media{
  display:block; width:168px; aspect-ratio:16/10;
  overflow:hidden; border-radius:12px;
  background:linear-gradient(150deg, var(--purple-deep), var(--ink) 70%);
}
.una200 .wire__media img{ width:100%; height:100%; object-fit:cover; display:block; }

.una200 .wire__all{ display:inline-block; margin-top:clamp(26px,3vw,38px); }
.una200 .wire__fallback,
.una200 .wire__empty{
  margin:clamp(30px,4vw,44px) auto 0; max-width:52ch; text-align:center;
  font-size:1rem; color:rgba(255,255,255,.66);
}
.una200 .wire__empty a{ color:var(--gold); }

/* The remaining notice, matched to the others. Every one of these pairs a failure
   message with a nothing-yet message and gives them identical treatment on purpose: neither
   is an error the reader can act on, so neither should be dressed as a warning.
   (.events__empty left this list on 2026-10-22 for the .events-none card; the principle
   still holds there — empty and failure share one design, and neither looks like an error.) */
.una200 .stories__empty{
  margin:clamp(30px,4vw,44px) auto 0; max-width:52ch; text-align:center;
  font-size:1rem; color:rgba(255,255,255,.66);
}
.una200 .stories__empty a{ color:var(--gold); }
/* inside a stacked card the notice is the whole of the elastic row, so it centres in the
   space the cards would have filled rather than sitting at the top of an empty card */
.una200 [data-stack-on="true"] > .module .events-none{
  margin-block:auto;
  align-self:center;
}

/* =================================================================
   MEMORIES SUBPAGE — gallery, sub-gallery tabs, and the Slate form
   ================================================================= */
/* The one subpage with TWO grounds. The gallery wants photographs with nothing over them;
   the form wants a calm light reading surface. Those are opposite requirements, so the
   one-block rule the other subpages follow does not apply here — see memories.html.

   No --page-photo on this page's .page-head: the gallery is the photography. The bottom
   padding is trimmed because the gallery follows immediately and supplies its own. */
.una200 .page-head--memories{ padding-bottom:clamp(34px,4vw,54px); }

/* Continues the panel above rather than starting a new one: .page-head's wash lands on
   rgba(23,11,39,.94) over --ink by 70%, which is within a hair of flat --ink, so there is
   no visible seam between them. Do NOT give this its own gradient — that is exactly what
   made the earlier Events revisions read as banded strips. */
.una200 .gallery-panel{
  background:var(--ink);
  padding:0 clamp(14px,3vw,32px) clamp(70px,8vw,110px);
  color:#fff;
}
/* position:relative is what the tab bar below is placed against — see .galtabs */
.una200 .gallery{ position:relative; max-width:min(1600px, 96vw); margin:0 auto; }

/* Title, then the History link on its own row at the left, directly above the stage. A
   column at every width rather than a wrapping row: the link belongs on the left, and in a
   space-between row it would sit at the right whenever it fitted beside the title. */
.una200 .gallery__head{
  display:flex; flex-direction:column; align-items:flex-start;
  gap:clamp(10px,1.2vw,14px);
  padding:0 0 clamp(20px,2.6vw,30px);
}
.una200 .gallery__title{
  font-size:clamp(1.25rem,2.2vw,1.7rem); font-weight:700; line-height:1.25;
}
/* .gallery__history takes its whole look from the .page-head__back rules it joins, further up. */

/* ---- sub-gallery tabs, over the bottom of the stage ---- */
/* Real <button role="tab">s with a roving tabindex, not links: nothing navigates, so a link
   would lie about what happens. Arrow-key handling is in script.js.

   PLACED AGAINST .gallery, NOT INSIDE A STAGE. One tablist drives four panels, so it cannot
   live in any one .showcase. It stays first in source, in the head, where a screen reader
   expects it, and is lifted over the photograph from there. This works because the panels
   are .gallery's LAST children and the inactive ones are display:none, so the bottom of
   .gallery is the bottom of whichever stage is showing. Put anything after the panels and
   the bar moves off the photograph with it.

   One translucent ink bar rather than four outlined pills: over an arbitrary photograph a
   bare outline has no ground to read against. Measured over a pure-white pixel, the worst
   case: 6.13:1 for the cream labels on 72% ink, 5.64:1 for the white hover label, and
   10.98:1 for ink on the selected gold tab. */
.una200 .galtabs{
  position:absolute; z-index:2;
  inset-inline:0; bottom:clamp(12px,1.6vw,20px);
  width:max-content; max-width:calc(100% - 24px);
  margin-inline:auto;
  /* Wraps rather than scrolls if it ever runs out of room (large text, heavy zoom): a
     scrolling bar clips the focus ring and hides tabs. 18px is a full pill at one row's
     height and a rounded block at two. */
  display:flex; flex-wrap:wrap; justify-content:center; gap:2px; padding:4px;
  background:rgba(23,11,39,.72);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border:1px solid rgba(241,230,178,.18);
  border-radius:18px;
}
.una200 .galtab{
  font:inherit; font-size:.66rem; font-weight:600; line-height:1.2;
  letter-spacing:.12em; text-transform:uppercase; white-space:nowrap;
  /* 27px tall here and 26px on phones (measured), over WCAG 2.2 SC 2.5.8's 24px minimum */
  padding:7px 13px;
  color:var(--accent-cream-1);
  background:transparent;
  border:0;
  border-radius:999px;
  cursor:pointer;
  transition:background .25s ease, color .25s ease;
}
.una200 .galtab:hover{ background:rgba(241,230,178,.14); color:#fff; }
/* aria-selected is the single source of truth for the selected look — no parallel
   .is-active class to fall out of step with the attribute screen readers announce */
.una200 .galtab[aria-selected="true"]{ background:var(--gold); color:var(--ink); }

/* ---- the stage ---- */
/* Overrides .showcase's 4/3. A fixed height rather than an aspect-ratio because the stage
   should not grow unboundedly on a wide monitor, and aspect-ratio plus max-height fight
   each other: the height clamps while the width does not, so the declared ratio is simply
   not honoured and the crop shifts. */
.una200 .gallery__stage{
  aspect-ratio:auto;
  height:clamp(440px,64vh,720px);
  border-radius:18px;
  /* .showcase already sets position:relative and overflow:hidden */
}
/* No mask and no scrim: this is plain .showcase, not .showcase--soft. The photographs are
   the content on this page and nothing is laid over them except the tab bar.

   THE CAPTION CARD WAS REMOVED 2026-10-22 — review asked for the photographs alone, with the
   gallery's name over them instead of the submitter's words. Its reasoning (why a caption
   must live inside its own slide, and the 82% ink it needed) is kept in context.md. */

.una200 .gallery__panel[hidden]{ display:none; }

/* The bottom PADDING clears the tab bar. When a feed fails, the stage is hidden and this is
   all the panel holds, so the bar would otherwise sit on top of the message. Padding, not
   margin: a bottom margin collapses straight out through the panel and .gallery, so the
   edge the bar is placed against never moves — measured, the bar landed on the text. */
/* One treatment for both notices: the only difference between them is the words, and a
   reader never sees the two together. Grouped rather than duplicated so they cannot drift
   apart the way the Events pair nearly did. */
.una200 .gallery__fallback,
.una200 .gallery__empty{
  margin:clamp(24px,3vw,36px) auto 0; padding-bottom:64px;
  max-width:52ch; text-align:center;
  font-size:1rem; color:rgba(255,255,255,.66);
}

/* =================================================================
   SUBMISSION FORM PANEL — the one light surface with controls on it
   ================================================================= */
/* body.una200 is already --paper on --ink text, so this panel needs no background of its
   own; it simply stops painting dark. That is also a robustness choice: the Slate form
   arrives from a third party and its own defaults are dark-on-white, so if our styling ever
   fails to reach a field type we have not seen, it degrades to legible rather than to
   invisible. On a dark panel the same failure would be white-on-white. */
/* The panel now paints its own ground. It used to have none and simply let body's --paper
   show through, which is why "get rid of the cream" was not a one-line change: the cream
   was the page, not the panel. */
.una200 .form-panel{
  padding:clamp(70px,8vw,110px) clamp(18px,5vw,7rem);
  background:var(--paper-cool);
}
.una200 .form-panel__grid{
  max-width:var(--wrap); margin:0 auto;
  display:grid; grid-template-columns:.85fr 1.15fr;
  column-gap:clamp(34px,4.5vw,72px); row-gap:clamp(28px,3.4vw,44px);
  align-items:start;
}
.una200 .form-panel__lede{
  margin-top:18px; max-width:46ch;
  font-size:1.06rem; line-height:1.72; color:rgba(23,11,39,.78);
}
.una200 .form-panel__notes{
  margin:22px 0 0; padding:0; list-style:none;
  display:grid; gap:12px;
  font-size:.95rem; line-height:1.6; color:rgba(23,11,39,.72);
}
.una200 .form-panel__notes li{ padding-left:20px; position:relative; }
.una200 .form-panel__notes li::before{
  content:''; position:absolute; left:0; top:.6em;
  /* --gold is 1.60:1 here. A list marker is decorative so SC 1.4.11 does not bind, but a
     bullet nobody can see is not worth drawing. */
  width:8px; height:8px; border-radius:50%; background:var(--gold-deep);
}

/* ---- the Slate embed ---- */
/* Styling a form that is not in the DOM at parse time. Everything here is CSS-only for that
   reason: there is no load event to hook and no promise to await, so any JS that measured
   or decorated these fields would have to poll. CSS simply applies when they arrive.

   slate-form-snippet.md carries these same rules packaged for Slate's own form builder,
   which is what makes the form look right when it is previewed or embedded OUTSIDE this
   page. THIS FILE IS THE SOURCE OF TRUTH — regenerate that one, never hand-edit it. */
.una200 .slate-form{ min-width:0; }
.una200 .slate-form__noscript{
  padding:18px 20px; border-radius:12px;
  background:var(--paper-dim); border:1px solid var(--line-on-light);
  font-size:.98rem;
}

/* Slate reads all of its own colours through --fw-* custom properties defined on :root.
   Redefining them here is far better than fighting its selectors: custom properties
   inherit, so every rule inside the embed picks these up, including states we never see in
   a sample render. The pink/yellow validation grounds are the important ones — Slate paints
   a whole question's background on error and its stock #fcc looks like a broken page. */
.una200 .slate-form .form_container,
.una200 .slate-form [id^="form_"]{
  --fw-body-text:#170B27;
  --fw-application-background:#EFEDF4;
  --fw-link-text:#592B8A;
  --fw-error-text:#8B1A1A;
  --fw-error-background:#F7E2E2;
  --fw-error-accent:#8B1A1A;
  --fw-warning-background:#F8EFD4;
  --fw-warning-accent:#A66E10;
  --fw-success-text:#1F6B2F;
  --fw-success-background:#E6F0E2;
  --fw-success-accent:#1F6B2F;
  --fw-input-border:rgba(89,43,138,.34);
  --fw-muted-text:rgba(23,11,39,.62);
  --fw-table-row-border:rgba(89,43,138,.14);
}

.una200 .slate-form .form_question{ margin:0 0 clamp(16px,1.8vw,22px); }
/* Slate ships `div.form_question .form_label,div.form_question label{display:block}` and
   nothing else on the label, so weight/size/colour are ours to set with no fight. */
.una200 .slate-form .form_label{
  font-size:.78rem; font-weight:600;
  letter-spacing:.1em; text-transform:uppercase;
  color:rgba(23,11,39,.8);
  margin-bottom:8px;
}
.una200 .slate-form .form_responses{
  /* Slate sets white-space:pre-wrap on this, which turns the newlines in its own generated
     markup into visible gaps between a field and its neighbours */
  white-space:normal;
}
.una200 .slate-form input[type="text"],
.una200 .slate-form input[type="email"],
.una200 .slate-form input[type="tel"],
.una200 .slate-form input[type="number"],
.una200 .slate-form input[type="password"],
.una200 .slate-form textarea,
.una200 .slate-form select{
  font:inherit; font-size:1rem;
  width:100%; max-width:100%; box-sizing:border-box;
  padding:12px 14px;
  color:var(--ink);
  background:#fff;
  /* .58, not the lighter tint a form like this usually gets: an input's border is the only
     thing identifying the control, so SC 1.4.11 wants 3:1 against the field's own white.
     .34 measures 1.88:1 and fails; .58 measures 3.24:1. Do not lighten it back. */
  border:1px solid rgba(89,43,138,.58);
  border-radius:10px;
  transition:border-color .2s ease, box-shadow .2s ease;
}
.una200 .slate-form textarea{ min-height:9.5em; line-height:1.6; resize:vertical; }
.una200 .slate-form input:hover,
.una200 .slate-form textarea:hover,
.una200 .slate-form select:hover{ border-color:rgba(89,43,138,.8); }
.una200 .slate-form input:focus,
.una200 .slate-form textarea:focus,
.una200 .slate-form select:focus{
  outline:none;
  border-color:var(--purple);
  box-shadow:0 0 0 3px rgba(89,43,138,.18);
}
/* The Graduation Year question is Slate's `dateym` type: two selects side by side whose
   only accessible names are their own aria-labels, because Slate hides the fieldset legend
   with `display:none!important` and shows a sibling div as the visible label. Sizing them
   to content stops two 100%-wide selects stacking into a column. */
.una200 .slate-form .form_dateym select{ width:auto; min-width:8.5em; margin-right:10px; }

/* The file input is Slate's `plugin:material` question. NOTE the escaped colon: the class
   really is `form_plugin:material`, and an unescaped `:` there parses as a pseudo-class and
   silently kills the whole selector. */
.una200 .slate-form .form_plugin\:material .form_response_material_upload{
  padding:16px 18px;
  background:var(--paper-dim);
  border:1px dashed rgba(89,43,138,.5);
  border-radius:12px;
}
.una200 .slate-form input[type="file"]{ font:inherit; font-size:.95rem; max-width:100%; }

/* Slate's own button styling is scoped under `.dialog`, which our embed has no ancestor of,
   so this submit button arrives completely unstyled — there is nothing to override. */
.una200 .slate-form .form_action_submit{ margin-top:clamp(20px,2.4vw,28px); }
.una200 .slate-form .form_button_submit{
  font:inherit; font-weight:700; font-size:.85rem;
  letter-spacing:.06em; text-transform:uppercase;
  padding:16px 34px;
  color:#fff;                     /* 9.83:1 on --purple */
  background:var(--purple);
  border:0; border-radius:0;      /* matches .btn, which is square by design */
  cursor:pointer;
  transition:background .25s ease, transform .25s ease;
}
.una200 .slate-form .form_button_submit:hover{ background:var(--purple-deep); transform:translateY(-2px); }
.una200 .slate-form .form_button_submit:disabled{ opacity:.55; cursor:default; transform:none; }

/* Validation and status. Slate adds .required to the QUESTION and paints its background. */
.una200 .slate-form .form_question.required,
.una200 .slate-form .form_question.selected{
  background:#F7E2E2;
  border-radius:10px;
  /* Slate's own rule is `padding:0 0 .5em 0!important`, so a plain padding here loses */
  padding:14px 16px 10px!important;
}
.una200 .slate-form .error,
.una200 .slate-form .success,
.una200 .slate-form .alert{
  border-radius:10px; padding:14px 16px;
  font-size:.95rem;
}
.una200 .slate-form .validate_error{ border-color:#8B1A1A; }
.una200 .slate-form .full_message{ color:#8B1A1A; font-size:.9rem; }

.una200 .stories__all{ display:inline-block; margin-top:22px; }
.una200 .stories__fallback{
  margin:clamp(30px,4vw,44px) auto 0; max-width:52ch; text-align:center;
  font-size:1rem; color:rgba(255,255,255,.66);
}

@media (max-width:900px){
  /* One column, and the placements above are released so everything simply flows in DOM
     order: header + blurb, then the featured event, then coming up. */
  .una200 .page-head__grid{ grid-template-columns:1fr; text-align:center; }
  .una200 .page-head__grid > .page-head__inner,
  .una200 .page-head__grid > .upnext,
  .una200 .page-head__grid > .ticker{ grid-column:1; grid-row:auto; }
  .una200 .page-head__grid .page-head__inner{ max-width:64ch; margin:0 auto; }
  /* Both content blocks realign left and fill the panel — the centred intro is the only
     centred thing, because prose is the only thing that wants a measure. An earlier 34rem
     cap here left the pill rows inset with wide empty gutters and squeezed their thumbs. */
  .una200 .ticker,
  .una200 .upnext{ text-align:left; max-width:none; }
  .una200 .ticker__controls{ gap:10px; }

  /* The list gets the reclaimed width: a bigger thumbnail and a size up on the text. */
  .una200 .pill__link{ gap:16px; padding:12px 16px 12px 12px; }
  .una200 .pill__media{ width:clamp(88px,26vw,132px); }
  .una200 .pill__meta{ font-size:.72rem; }
  .una200 .pill__title{ font-size:1.05rem; }
  /* the wider row can afford two lines, so the venue stops truncating mid-word */
  .una200 .pill__venue{
    font-size:.82rem; white-space:normal; text-overflow:clip;
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  }
  .una200 .upnext__title{ font-size:.76rem; }

  /* ---- Class of 2030 subpage ---- */
  /* One column, lead then index, straight down the DOM — no `order`, same reason as the
     Events grid above: it would move the boxes without moving the tab sequence. */
  .una200 .stories__grid{ grid-template-columns:1fr; }
  /* .page-head centres its text at this width for the intro; the columns still read left */
  .una200 .stories{ text-align:left; }
  /* The index gets the full panel width, so the thumb grows with it. Capped at 140px: past
     that it starts eating the headline's column on a mid-size tablet. */
  .una200 .stories__item{ gap:18px; padding:20px 0; }
  .una200 .stories__item-media{ width:clamp(96px,28vw,140px); border-radius:12px; }
  .una200 .stories__item .news__meta{ font-size:.72rem; }
  .una200 .stories__item-title{ font-size:1.08rem; }
  .una200 .stories__side-title{ font-size:.76rem; }

  /* ---- Memories subpage ---- */
  .una200 .gallery__stage{ height:clamp(400px,58vh,560px); border-radius:14px; }
  /* Four tabs stay on one line over the photograph — two rows would cover twice the picture.
     Tightened in steps; the two phone steps are after this block, so they win. */
  .una200 .galtabs{ bottom:10px; }
  .una200 .galtab{ letter-spacing:.07em; padding:7px 10px; }

  .una200 .form-panel__grid{ grid-template-columns:1fr; }
  .una200 .form-panel__lede{ max-width:none; }
  .una200 .slate-form .form_dateym select{ width:auto; min-width:7.5em; }

  /* NO SCROLL STACK BELOW THIS BREAKPOINT, so there is nothing to size for it here.
     script.js gates the whole effect on 901px — the same number as this media query — so
     none of the card treatment, the pinning, the wash or the dwells ever applies on a phone.
     An earlier revision set --nav-h, --stack-r and both dwell tokens here; they were dead
     the moment the gate moved, and a dead token is worse than none, because the next person
     to read it will assume the effect runs.

     What DOES still run on mobile is the in-module reveal animation, from the shared
     IntersectionObserver — with the stack off, ownership of those reverts to it. */

  /* ---- Full History page: see the CHRON section, which keeps its own breakpoints ---- */

  /* ---- News subpage ---- */
  .una200 .wire{ text-align:left; }
  /* the lead stacks, photo first, and keeps its prominence rather than shrinking */
  .una200 .wire__lead{ grid-template-columns:1fr; align-items:start; }
  .una200 .wire__lead-media{ aspect-ratio:16/9; }

  /* The river drops to TWO columns and the thumb moves under the copy. The date plate stays
     in its own column: it is the page's organising device and putting it inline would turn
     every row back into the compact index this page exists to differ from. */
  .una200 .wire__row{
    grid-template-columns:4.25rem minmax(0,1fr);
    column-gap:16px; row-gap:14px;
    align-items:start;
  }
  .una200 .wire__plate{ padding-top:4px; }
  .una200 .wire__day{ font-size:1.4rem; }
  /* second column, second row — under the copy, not beside the plate */
  .una200 .wire__media{ grid-column:2; width:100%; max-width:none; aspect-ratio:16/9; }
  .una200 .wire__excerpt{ -webkit-line-clamp:3; line-clamp:3; }
}

/* Memories tab bar on phones — the 900px block's spacing no longer fits four tabs on one
   line. Measured in Chrome, natural bar width against the room the stage leaves: 294px of
   316 at 360px with the first step, 268px of 276 at 320px with the second. */
@media (max-width:400px){
  .una200 .galtabs{ max-width:calc(100% - 16px); }
  .una200 .galtab{ font-size:.62rem; letter-spacing:.04em; padding:7px 8px; }
}
@media (max-width:340px){
  .una200 .galtabs{ gap:0; padding:3px; }
  .una200 .galtab{ font-size:.6rem; letter-spacing:.03em; padding:7px; }
}

@media (max-width:560px){
  .una200 .ticker__arrow{ width:48px; height:48px; font-size:1.3rem; }
}

/* =================================================================
   FOOTER
   ================================================================= */
.una200 .site-foot{
  background:var(--ink); color:rgba(255,255,255,.7);
  text-align:center; padding:80px 24px 56px;
}
.una200 .site-foot__mark{ display:inline-block; width:52px; margin-bottom:22px; }
.una200 .site-foot__mark img{ width:100%; }
.una200 .site-foot__line{ font-size:.92rem; margin-bottom:10px; }
.una200 .site-foot__link{
  display:inline-block; font-weight:600; font-size:.86rem; letter-spacing:.04em;
  color:var(--gold); border-bottom:1px solid transparent; transition:border-color .2s;
}
.una200 .site-foot__link:hover{ border-color:var(--gold); }

/* =================================================================
   RESPONSIVE — tablet / mobile
   ================================================================= */
@media (max-width:640px){
  .una200 .hero{ padding:120px 18px 70px; }
}

/* =================================================================
   ERA RAIL — the History page's table of contents
   ================================================================= */
/* A fixed column of era start years against the left edge, widening on hover or keyboard
   focus to show the era names. The History page runs to about sixteen hundred lines of
   chronology; before this there was no way to see where you were in it or to jump.

   COLLAPSED IT IS A COLUMN OF YEARS, which is the whole reason it can live on screen
   permanently. The years are the index — 1830, 1866, 1929, 1957, 1974 — and they are
   legible at a glance without reading anything. The names arrive only when asked for.

   The widening is `max-width` on the name, not `display`, so the link keeps one accessible
   name whether the rail is open or shut and a screen reader never sees it change.

   Everything below 1200px is in the breakpoint block at the end: there is no room for a
   fixed rail beside the copy at tablet width, so it becomes a toggle and a panel. */
.una200 .erarail{
  position:fixed;
  /* centred in the space BELOW the nav, not in the viewport, or it rides up under the bar */
  top:calc(var(--nav-h) + (100vh - var(--nav-h)) / 2);
  transform:translateY(-50%);
  left:0;
  /* 50, DELIBERATELY BELOW THE NAV'S 60. Matching it would not be neutral: at equal
     z-index the later element in the DOM paints on top, and the rail is inside <main>
     while the nav is above it — so the rail would cover the fixed bar. It cannot reach
     that far at a comfortable window, but a short one with the list at full height can. */
  z-index:50;
  max-height:calc(100vh - var(--nav-h) - 32px);
  display:flex;
  overflow:visible;
}
.una200 .erarail__srtitle{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

/* IT HAS TO READ AS A THING ON TOP OF THE PAGE, not part of it. The first pass used --ink
   at 93% over a page whose own background is --ink, so the rail was the same colour as
   what it sat on and only a hairline separated them — it disappeared.

   THE FILL IS NOT WHAT FIXES THAT, and it is worth being exact about which part does.
   Measured against the page: the --purple-deep surface is 1.40:1, the border 3.46:1 and
   the gold rule down the open edge 10.98:1. So the EDGE carries the separation; the fill
   only stops the type sitting on open page. Brightening the surface further was tried on
   paper — brand --purple reaches just 1.92:1 — which is not worth turning a deliberately
   quiet rail into a slab for. If it ever needs to shout, widen the gold rule rather than
   lighten the fill: that is the lever with the range in it. */
.una200 .erarail__list{
  list-style:none; margin:0; padding:10px 0;
  display:flex; flex-direction:column; gap:2px;
  background:linear-gradient(100deg, var(--purple-deep) 0%, #2A1247 100%);
  border:1px solid rgba(241,230,178,.34);
  border-left:0;
  border-right:2px solid var(--gold);
  border-radius:0 14px 14px 0;
  box-shadow:0 30px 60px -28px rgba(0,0,0,.9), 0 0 0 1px rgba(23,11,39,.6);
  backdrop-filter:blur(8px);
}

.una200 .erarail__link{
  display:flex; align-items:center; gap:12px;
  padding:9px 14px 9px 16px;
  text-decoration:none;
  /* .62 was legible on near-black and is not on the lifted surface; the years are the whole
     of the collapsed rail, so they are the last thing that should be washing out */
  color:rgba(255,255,255,.82);
  transition:color .25s ease, background-color .25s ease;
}
.una200 .erarail__year{
  font-size:.82rem; font-weight:700; letter-spacing:.06em;
  font-variant-numeric:tabular-nums;   /* so the column of years aligns */
  white-space:nowrap;
}
/* THE NAME IS WHAT EXPANDS. max-width rather than display:none so the element is never
   removed from the accessibility tree — the link reads "1830 The Frontier & Antebellum
   Beginnings" to a screen reader at every width, open or shut. */
.una200 .erarail__name{
  max-width:0;
  overflow:hidden;
  white-space:nowrap;
  font-size:.86rem; line-height:1.3;
  opacity:0;
  transition:max-width .42s cubic-bezier(.2,.7,.2,1), opacity .3s ease;
}
/* 19rem fitted the institutional names the rail used to carry. The era titles are now the
   evocative ones, and the longest — "Building a Legacy as a Forward-Thinking University" —
   ran past it and was silently cut by the `overflow:hidden` that drives the open animation.
   A clipped name in a table of contents is worse than a wide panel, and this width is only
   taken while the rail is open. */
.una200 .erarail:hover .erarail__name,
.una200 .erarail:focus-within .erarail__name{
  max-width:25rem;
  opacity:1;
}

.una200 .erarail__link:hover,
.una200 .erarail__link:focus-visible{
  color:#fff;
  background:rgba(241,230,178,.16);
}
/* the current era, set by script.js as you scroll. Gold, and a bar on the leading edge so
   position is readable even while the rail is collapsed to its years. */
.una200 .erarail__link[aria-current="location"]{
  color:var(--gold);
  background:rgba(255,185,42,.18);
  box-shadow:inset 3px 0 0 var(--gold);
}

/* ---- Lion Country, nested under Era Four ----
   HIDDEN WHILE THE RAIL IS COLLAPSED. Collapsed, the rail is a column of five years; a
   yearless row in that column is either a hole or a puzzle. It opens with the names, which
   is the point at which the reader has asked for detail.

   `grid-template-rows` rather than height, because the row's natural height does not have
   to be known in advance — 0fr to 1fr animates to exactly the content's own height. It is
   timed with the width so the rail reads as one movement opening rather than two. */
.una200 .erarail__subwrap{
  display:grid;
  grid-template-rows:0fr;
  transition:grid-template-rows .42s cubic-bezier(.2,.7,.2,1);
}
/* THE WRAPPER COLLAPSES THE WHOLE LIST, NOT A ROW OF IT. This was on the <ul> itself, with
   each sub-item as a grid row — which worked for exactly as long as there was one item.
   `grid-template-rows:0fr` declares ONE track, so a second <li> is placed in an IMPLICIT
   row, and implicit rows are sized by `grid-auto-rows`, which defaults to `auto`. Lion
   Country therefore sat at full height in the collapsed rail while Wendell Gunn above it
   was squashed to nothing — the opposite of the rule's whole purpose, and silent, because
   nothing errors and the one-item case still looked right.

   `grid-auto-rows:0fr` would patch it, but its animatability is far less settled than
   `grid-template-rows`. One wrapper holding the entire list is always one row, so the
   mechanism is the proven one and the item count stops mattering. */
.una200 .erarail__subwrap > .erarail__sub{ overflow:hidden; min-height:0; }
.una200 .erarail:hover .erarail__subwrap,
.una200 .erarail:focus-within .erarail__subwrap{ grid-template-rows:1fr; }

.una200 .erarail__sub{ list-style:none; margin:0; padding:0; }

/* INDENTED WITH A CONNECTOR, NOT JUST DIMMED. The first pass set it a size smaller at 62%
   white and left it flush with the years, which made it read as a washed-out sibling that
   had lost its date rather than as something hanging off Era Four.

   Dimming was the wrong tool for "subordinate" here. It measured 6.06:1 — AA, so not a
   failure, but the only row in the rail under AAA and the hardest thing on it to read. The
   subordination is now carried by POSITION: the label starts where its parent's name
   starts, with an elbow rule drawn in from the year column. That is unambiguous at a
   glance and costs no contrast, so the text can go back up to the weight of everything
   else — 10.27:1, AAA at this size.

   The rule is a pseudo-element, so it is never announced; a screen reader hears the list
   nesting from the markup instead. */
/* SCOPED THROUGH .erarail__sub ON PURPOSE, which costs a point of specificity and buys
   immunity from source order. The indent is `padding-left`, and the base rule sets padding
   with the SHORTHAND — so at equal specificity whichever comes last wins, and in the
   breakpoint block the base happened to come second. The result was Lion Country sitting
   flush at the far left of the panel, left of even the years, with the elbow pointing at
   nothing. Desktop was fine only because the order happened to run the other way.
   Specificity settles it in both places and keeps settling it. */
.una200 .erarail__sub .erarail__link--sub{
  position:relative;
  padding-left:61px;              /* aligns with where a sibling's NAME begins */
  font-size:.86rem;               /* same as the era names, not a size down */
  color:rgba(255,255,255,.86);    /* 10.27:1 on the rail surface */
}
/* the elbow: down from the parent row, then a short tick into the label */
.una200 .erarail__sub .erarail__link--sub::before,
.una200 .erarail__sub .erarail__link--sub::after{
  content:"";
  position:absolute;
  left:30px;
  background:rgba(241,230,178,.42);
  pointer-events:none;
}
.una200 .erarail__sub .erarail__link--sub::before{ top:0; bottom:50%; width:1px; }
.una200 .erarail__sub .erarail__link--sub::after{ top:50%; width:13px; height:1px; }

/* ONE TRUNK, NOT A STUB PER ROW. The stem above stops at its own row's midpoint, which is
   right when that row is the last one — the line ends where the tick leaves it. It was
   also the only case that existed while Lion Country sat under Era Four alone, so the bug
   arrived with the second child rather than with the rule.

   With two, every row drew its own short stem and the trunk broke between them: two
   floating elbows pointing at nothing in particular, instead of one line coming down from
   Era Four and branching twice. Every row but the last now runs its stem the full height,
   and since the sub-list is a gapless grid the segments meet exactly. Holds for any
   number of children, so the next one costs nothing. */
.una200 .erarail__sub > li:not(:last-child) .erarail__link--sub::before{ bottom:0; }

.una200 .erarail__sub .erarail__link--sub .erarail__name{ font-size:inherit; }

/* the toggle only exists below the breakpoint */
.una200 .erarail__toggle{ display:none; }

@media (prefers-reduced-motion:reduce){
  .una200 .erarail__name{ transition:none; }
  .una200 .erarail__subwrap{ transition:none; }
  .una200 .erarail__link{ transition:none; }
}

/* ---- TABLET AND PHONE: a toggle and a panel ----
   1200px, not 900px. The rail is fixed to the left edge and the chronology's copy column
   starts not far from it; between about 900 and 1200 the expanded names would sit on top of
   the text rather than beside it. The breakpoint is where the overlap starts, not where the
   site's other layouts happen to change. */
@media (max-width:1200px){
  .una200 .erarail{
    top:auto;
    bottom:20px;
    left:20px;
    transform:none;
    max-height:none;
    flex-direction:column;
    align-items:flex-start;
    gap:10px;
  }

  .una200 .erarail__toggle{
    display:inline-flex; align-items:center; gap:10px;
    padding:11px 18px;
    border:1px solid var(--line-on-dark);
    border-radius:999px;
    background:rgba(23,11,39,.92);
    backdrop-filter:blur(6px);
    box-shadow:0 20px 44px -26px rgba(0,0,0,.85);
    color:#fff; font:inherit; font-size:.8rem; font-weight:600;
    letter-spacing:.14em; text-transform:uppercase;
    cursor:pointer;
  }
  /* three bars drawn in CSS, same as the site nav's — no icon asset to ship */
  .una200 .erarail__toggle-bars{
    position:relative; width:16px; height:2px;
    background:var(--gold); border-radius:2px;
  }
  .una200 .erarail__toggle-bars::before,
  .una200 .erarail__toggle-bars::after{
    content:""; position:absolute; left:0; width:16px; height:2px;
    background:var(--gold); border-radius:2px;
  }
  .una200 .erarail__toggle-bars::before{ top:-5px; }
  .una200 .erarail__toggle-bars::after{ top:5px; }

  /* ORDER MATTERS IN THE DOM, NOT ON SCREEN. The toggle is written after the heading and
     before the list, so focus runs button then panel; `order` only moves the panel above
     the button visually, because the button sits at the bottom of the screen. */
  .una200 .erarail__list{
    order:-1;
    border:1px solid var(--line-on-dark);
    border-radius:14px;
    padding:8px;
    min-width:15rem;
    max-height:min(60vh, 420px);
    overflow-y:auto;
    /* hidden until the toggle opens it. visibility, not display, so the transition runs and
       so it leaves the tab order while shut. */
    opacity:0; visibility:hidden;
    transform:translateY(8px);
    transition:opacity .25s ease, transform .3s cubic-bezier(.2,.7,.2,1), visibility .25s;
  }
  .una200 .erarail.is-open .erarail__list{
    opacity:1; visibility:visible; transform:none;
  }
  /* the names are always out on a phone — there is no hover to ask with, so the nested
     Lion Country row is open too rather than waiting for an interaction that cannot happen */
  .una200 .erarail__name{
    max-width:none; opacity:1; white-space:normal;
  }
  .una200 .erarail__subwrap{ grid-template-rows:1fr; }
  /* The year column is min-width:3.1em of its own .82rem here, so a sibling's name begins
     at 12 (link padding) + 40.7 (year) + 12 (gap) = 65px, not the 58 this first carried.
     Seven pixels out is enough for the indent to look like a near-miss rather than an
     alignment. */
  .una200 .erarail__sub .erarail__link--sub{ padding-left:65px; }
  .una200 .erarail__sub .erarail__link--sub::before,
  .una200 .erarail__sub .erarail__link--sub::after{ left:26px; }
  .una200 .erarail__link{ padding:10px 12px; border-radius:8px; }
  .una200 .erarail__year{ min-width:3.1em; }

  @media (prefers-reduced-motion:reduce){
    .una200 .erarail__list{ transition:none; transform:none; }
  }
}

/* A phone is narrow enough that the panel wants the width, and the bottom-left corner is
   where a thumb is. */
@media (max-width:640px){
  .una200 .erarail{ left:14px; right:14px; bottom:14px; }
  .una200 .erarail__list{ min-width:0; width:100%; }
}

/* =================================================================
   ENGAGE — the "how do I take part" subpage
   ================================================================= */
/* Three routes in, each a different kind of thing: read the President's letters, bring the
   bicentennial into a course, give the University an object. One .page-head carries the
   lot, same as Events and News, so the purple bloom is painted once. */
.una200 .page-head--engage{
  --page-photo:url('images/ss/DSC00439-3-1280.webp');
  --photo-y:28%;
  --photo-size:120% auto;
}

.una200 .engage{
  max-width:var(--wrap); margin:0 auto;
  display:grid; gap:clamp(44px,5.5vw,84px);
  padding-top:clamp(40px,5vw,72px);
}
/* Two abreast once there is room, with the FIRST block spanning as a centred lead.

   In the Classroom holds that spot: it is the shortest of the three and the one that reads
   as a statement rather than a list, so centring it under the page title gives the page an
   opening instead of starting straight into a column of buttons. The communiqué list and
   Gifts in Kind pair up beneath it, both of which are fine at half width — the document
   buttons are full-width rows inside their own column. */
.una200 .engage__block{ max-width:68ch; }
@media (min-width:901px){
  .una200 .engage{
    grid-template-columns:repeat(2, minmax(0,1fr));
    column-gap:clamp(36px,4.5vw,72px);
  }
  .una200 .engage__block--lead{ grid-column:1 / -1; }
}
/* The LEAD BLOCK itself is no longer held to 68ch — its banner needs the full width. The
   measure moved to .engage__copy, which is the text inside it. Centred at every width,
   not only where the grid splits: it is the page's opening statement on a phone too. */
.una200 .engage__block--lead{ max-width:none; }
.una200 .engage__copy{
  max-width:68ch;
  margin-inline:auto;
  text-align:center;
}

/* ---- the photographs ----
   `height:auto` beside `aspect-ratio` on every one of these, and it is load-bearing: each
   <img> carries width/height attributes so the box is reserved before the file lands, and
   those map to CSS as presentational hints at specificity 0. `width:100%` beats the width
   hint, but with nothing overriding the height the element has two definite sides and the
   aspect-ratio is simply ignored. That cost a day on the Give module — see context.md. */
.una200 .engage__banner,
.una200 .engage__photo{ margin:0; }
.una200 .engage__banner img,
.una200 .engage__photo img{
  display:block; width:100%; height:auto; object-fit:cover;
  border-radius:14px;
  box-shadow:0 30px 60px -34px rgba(0,0,0,.8);
}
/* The banner is the page's opening image, so it takes the full engage width rather than the
   copy's reading measure. */
.una200 .engage__banner{ margin-bottom:clamp(26px,3vw,40px); }
.una200 .engage__banner img{ aspect-ratio:16/9; }

/* The two cards share one ratio so the pair reads as a pair. The lion is natively PORTRAIT
   (5464x8192) and is cropped to landscape at +0+2800 — centred on the head, which is the
   part that has to survive at card size. If it ever wants its portrait back, the two cards
   stop being symmetrical and the row needs rethinking rather than just a new crop. */
.una200 .engage__photo{ margin-bottom:clamp(18px,2.2vw,26px); }
.una200 .engage__photo img{ aspect-ratio:3/2; }

.una200 .engage__title{
  font-weight:700; color:#fff;
  font-size:clamp(1.5rem,3vw,2.1rem); line-height:1.15;
  margin:0 0 14px;
}
.una200 .engage__lede{
  margin:0; font-size:1.06rem; line-height:1.7;
  color:rgba(255,255,255,.78);
}
.una200 .engage__note{
  margin:16px 0 22px; font-size:.98rem; line-height:1.65;
  color:rgba(255,255,255,.62);
}

/* ---- Bicentennial Moments, as a native accordion ---- */
/* This replaces the .docbtn / .engage__docs rules that styled the PDF links. Those were
   kept unused for one commit on the chance the excerpts landed in the same shape; they
   landed as this instead, so they are gone rather than left to rot. */
.una200 .engage__moments{
  display:grid; gap:12px;
  margin:26px 0 0;
}
/* LEFT, not centred. .engage__block centres its own text, which is right for a one-line
   lede and wrong for three paragraphs of history — centred prose gives every line a
   different starting point and the eye has to search for each one. The accordion opts out
   for its whole subtree. */
.una200 .moment{
  text-align:left;
  border:1px solid var(--line-on-dark);
  border-radius:12px;
  background:linear-gradient(180deg, rgba(241,230,178,.075), rgba(241,230,178,.025));
  overflow:hidden;              /* keeps the opened body inside the rounded corners */
}
.una200 .moment[open]{ border-color:rgba(255,185,42,.45); }

/* THE DEFAULT TRIANGLE NEEDS REMOVING TWICE. `list-style:none` covers Chrome and Firefox;
   Safari draws its own and only answers to ::-webkit-details-marker. Miss either and the
   browser's marker sits next to our chevron. */
.una200 .moment__head{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto auto;
  align-items:center; gap:18px;
  padding:20px 24px;
  cursor:pointer;
  list-style:none;
  transition:background-color .25s ease;
}
.una200 .moment__head::-webkit-details-marker{ display:none; }
.una200 .moment__head:hover{ background:rgba(241,230,178,.07); }
.una200 .moment__head:focus-visible{ outline:2px solid var(--gold); outline-offset:-2px; }

.una200 .moment__title{ font-weight:600; color:#fff; font-size:1.02rem; line-height:1.35; }
.una200 .moment__date{
  font-size:.82rem; letter-spacing:.04em;
  color:rgba(255,255,255,.72);
  white-space:nowrap;
}
/* the chevron is a pseudo-element on the summary, so there is no extra span in the markup
   to keep in step with the open state */
.una200 .moment__head::after{
  content:"";
  width:9px; height:9px;
  border-right:2px solid var(--gold);
  border-bottom:2px solid var(--gold);
  transform:rotate(45deg) translateY(-2px);
  transition:transform .3s cubic-bezier(.2,.7,.2,1);
}
.una200 .moment[open] .moment__head::after{ transform:rotate(-135deg) translateY(-2px); }

/* the top padding is small but not zero: the summary already has 20px below its text, and
   doubling that would read as a gap rather than as the body belonging to the heading */
.una200 .moment__body{
  padding:4px 24px 24px;
  display:grid; gap:16px;
}
.una200 .moment__body p{
  margin:0;
  font-size:1rem; line-height:1.7;
  color:rgba(255,255,255,.82);
}

/* Three columns do not fit a phone: the title is the long part and gets squeezed to two
   words. The date drops beneath it and the chevron spans both rows. */
@media (max-width:560px){
  .una200 .moment__head{
    grid-template-columns:minmax(0,1fr) auto;
    row-gap:3px; padding:17px 19px;
  }
  .una200 .moment__date{ grid-column:1; grid-row:2; }
  .una200 .moment__head::after{ grid-column:2; grid-row:1 / span 2; }
  .una200 .moment__body{ padding:4px 19px 20px; gap:14px; }
}

@media (prefers-reduced-motion:reduce){
  .una200 .moment__head,
  .una200 .moment__head::after{ transition:none; }
}

/* the shared visually-hidden utility, which several blocks were each declaring inline */
.una200 .u-sr-only{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

/* =================================================================
   MEMORIES — gallery and form side by side
   ================================================================= */
/* Stacked below 1200px, which is exactly the layout these two sections had before the
   wrapper existed: dark gallery, light form panel under it, in source order.

   1200px, not 900px. The form is the constraint, not the gallery — Slate renders real
   input fields with real labels, and below about 480px of column they start wrapping in
   ways we do not control. At 1200px the form column is roughly 500px, which is the first
   width where it is comfortable rather than merely possible. */
.una200 .memories-split{ background:var(--ink); }

@media (min-width:1200px){
  .una200 .memories-split{
    display:grid;
    /* the gallery takes the larger share: it is the reason the page exists, and the form
       needs enough and no more */
    grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
    align-items:start;
    gap:clamp(28px,3vw,56px);

    /* FULL BLEED GROUND, CENTRED CONTENT, ONE ELEMENT. `max-width` plus `margin:0 auto`
       was the obvious way to centre this and it was wrong: capping the width capped the
       BACKGROUND with it, so on anything wider than 1600px the page's cream showed down
       both sides of the dark panel in two vertical bands. The gallery never had that
       problem because it was full-bleed with the measure on an inner element.

       `max()` does both jobs from the padding instead: the gutter on a narrow screen, and
       half the overflow once the viewport passes the measure, which centres the content
       while the element itself still runs edge to edge. */
    padding-inline:max(clamp(14px,3vw,32px), calc((100% - 1600px) / 2));
    padding-bottom:clamp(70px,8vw,110px);
  }

  /* both sections drop the page-level padding and max-width they carried as full-width
     bands — the wrapper owns those now, or the columns would be inset twice */
  .una200 .memories-split > .gallery-panel{
    padding:0;
    background:none;            /* the wrapper carries the ink */
  }
  .una200 .memories-split > .gallery-panel .gallery{ max-width:none; }

  /* THE FORM BECOMES A CARD. A light panel butted against a dark one reads as a seam; a
     rounded card with its own ground reads as something placed on top, which is what it
     is. It also sticks, because the gallery is much taller than the form and a form that
     scrolls away while you are still looking at photographs is a form you will not fill
     in. */
  .una200 .memories-split > .form-panel{
    padding:clamp(30px,2.6vw,44px);
    border-radius:18px;
    box-shadow:0 40px 80px -40px rgba(0,0,0,.9);
    position:sticky;
    top:calc(var(--nav-h) + 16px);   /* same offset as .chron__year, the site's other sticky */
  }
  /* one column inside the card — the intro sits above the form rather than beside it,
     because there is no longer width for two */
  .una200 .memories-split > .form-panel .form-panel__grid{
    grid-template-columns:minmax(0,1fr);
    row-gap:clamp(22px,2.4vw,32px);
  }
}

/* A sticky element cannot be taller than the viewport or its bottom is unreachable, and
   this card holds a whole form. Released on short windows so it scrolls normally. */
@media (min-width:1200px) and (max-height:900px){
  .una200 .memories-split > .form-panel{ position:static; }
}

/* NOT released for prefers-reduced-motion, deliberately. Sticky is not animation — nothing
   moves that would not have moved anyway — and the scroll stack on the homepage and the
   sticky column further up this file both keep theirs. Releasing only this one would be
   inconsistent for no benefit. */

/* =================================================================
   LIGHTBOX — full-size viewing for the History page's photographs
   ================================================================= */
/* Every .chron__shot and .chron__lion-photo on the History page opens here. The thumbnails
   are 480-1280px variants in a grid; the photographs behind them are worth more than that,
   particularly the archive scans.

   PROGRESSIVE ENHANCEMENT. script.js turns the <img> into a <button> wrapper at runtime and
   builds this dialog; with JavaScript off the figures render exactly as they did before and
   nothing is lost but the zoom. There is no lightbox markup in history.html at all.

   <dialog> rather than a hand-rolled overlay, because showModal() brings the focus trap,
   Escape to close, the inert background and ::backdrop with it — four things that are easy
   to get wrong by hand and that a keyboard or screen-reader user needs to be right. */
.una200-lightbox{
  width:min(96vw, 1500px);
  max-width:none;
  max-height:92vh;
  padding:0;
  border:0;
  border-radius:16px;
  background:var(--ink);
  color:#fff;
  overflow:hidden;
}
.una200-lightbox::backdrop{
  background:rgba(8,3,15,.88);
  backdrop-filter:blur(4px);
}

.una200-lightbox__frame{
  display:grid;
  grid-template-rows:minmax(0,1fr) auto;
  max-height:92vh;
}
.una200-lightbox__img{
  display:block;
  width:100%; height:100%;
  max-height:calc(92vh - 5.5rem);
  object-fit:contain;      /* contain, not cover: this is the one place nothing is cropped */
  background:#000;
}
.una200-lightbox__bar{
  display:flex; align-items:center; gap:14px;
  padding:14px clamp(14px,2vw,22px);
  border-top:1px solid var(--line-on-dark);
}
.una200-lightbox__caption{
  flex:1 1 auto; min-width:0;
  font-size:.95rem; line-height:1.45;
  color:rgba(255,255,255,.82);
}
.una200-lightbox__count{
  flex:none;
  font-size:.78rem; letter-spacing:.14em;
  font-variant-numeric:tabular-nums;
  color:rgba(255,255,255,.55);
}
.una200-lightbox__btn{
  flex:none;
  display:grid; place-items:center;
  width:44px; height:44px;          /* 44px: WCAG 2.5.8 target size, not a round number */
  border:1px solid var(--line-on-dark);
  border-radius:50%;
  background:rgba(241,230,178,.06);
  color:var(--gold);
  font:inherit; font-size:1.1rem; line-height:1;
  cursor:pointer;
  transition:background-color .25s ease, border-color .25s ease;
}
.una200-lightbox__btn:hover,
.una200-lightbox__btn:focus-visible{
  background:rgba(241,230,178,.16);
  border-color:rgba(255,185,42,.55);
}
.una200-lightbox__btn[disabled]{ opacity:.3; cursor:default; }

/* ---- the documentary lightbox ---- */
/* SIZED FROM THE AVAILABLE HEIGHT, not just the width. A 16:9 player in a dialog that is
   only capped by width overflows the moment the window is short: at 1500px wide the video
   alone is 844px tall before the caption bar, which does not fit a laptop. The third term
   runs the sum backwards — take the height actually available, subtract the bar and the
   player's own 43px of chrome, and convert to the width that produces it. Whichever of the
   three is smallest wins, so the dialog fits in both directions at every window size. */
.una200-lightbox--video{
  width:min(96vw, 1500px, calc((92vh - 5.5rem - 43px) * 16 / 9));
}
/* 56.25% is 16:9; the +43px is the player's control bar, which is NOT part of the video
   and would otherwise crop the bottom of the picture. PBS's own embed snippet does the
   same thing, and the number comes from there rather than from measuring. */
.una200-lightbox__video{
  position:relative;
  width:100%;
  padding-top:calc(56.25% + 43px);
  background:#000;
}
.una200-lightbox__video iframe{
  position:absolute; inset:0;
  width:100%; height:100%;
  border:0;
}
/* fullscreen strips the dialog's own box, so the frame has to fill what it is given */
.una200-lightbox--video:fullscreen{
  width:100vw; max-width:100vw; max-height:100vh; border-radius:0;
}
.una200-lightbox--video:fullscreen .una200-lightbox__frame{ max-height:100vh; height:100vh; }
.una200-lightbox--video:fullscreen .una200-lightbox__video{
  padding-top:0;
  height:calc(100vh - 5.5rem);
}

/* ---- the trigger card in the Wendell Gunn box ---- */
/* A card rather than a poster frame: there is no still from the documentary in the repo,
   and borrowing one of the Wendellfest photographs would imply it is a frame from the
   film. The play triangle plus the kicker carry "this is a video" without pretending. */
.una200 .chron__watch{ margin:clamp(18px,3vw,28px) 0 0; }
.una200 .chron__watch-btn{
  display:flex; align-items:center; gap:clamp(14px,2vw,20px);
  width:100%;
  padding:clamp(14px,2vw,18px) clamp(16px,2.4vw,22px);
  border:1px solid rgba(241,230,178,.45);   /* 3.79:1, same token as .chron__jumplink */
  border-radius:14px;
  background:rgba(241,230,178,.05);
  color:#fff;
  text-decoration:none;
  text-align:left;
  transition:background-color .25s ease, border-color .25s ease, transform .25s ease;
}
.una200 .chron__watch-btn:hover,
.una200 .chron__watch-btn:focus-visible{
  background:rgba(241,230,178,.12);
  border-color:rgba(255,185,42,.6);
  transform:translateY(-2px);
}
.una200 .chron__watch-play{
  flex:none;
  display:grid; place-items:center;
  width:54px; height:54px;             /* comfortably past the 44px target minimum */
  border-radius:50%;
  background:var(--gold);
}
.una200 .chron__watch-play::before{
  content:"";
  width:0; height:0;
  margin-left:4px;                     /* optical centring: a triangle's mass sits left */
  border-left:16px solid var(--ink);
  border-top:10px solid transparent;
  border-bottom:10px solid transparent;
}
.una200 .chron__watch-text{ display:grid; gap:2px; min-width:0; }
.una200 .chron__watch-kicker{
  font-size:.72rem; letter-spacing:.16em; text-transform:uppercase;
  font-weight:700; color:var(--gold);
}
.una200 .chron__watch-title{ font-size:1.12rem; font-weight:700; line-height:1.25; }
.una200 .chron__watch-meta{ font-size:.86rem; color:rgba(255,255,255,.72); }

@media (prefers-reduced-motion:reduce){
  .una200 .chron__watch-btn{ transition:none; }
  .una200 .chron__watch-btn:hover,
  .una200 .chron__watch-btn:focus-visible{ transform:none; }
}

/* the trigger: the figure's own image becomes a button, so the hit area is the picture */
.una200 .chron__shot-btn{
  display:block; width:100%;
  padding:0; border:0; background:none;
  cursor:zoom-in;
  border-radius:inherit;
}
.una200 .chron__shot-btn img{ display:block; }
.una200 .chron__shot-btn:focus-visible{
  outline:3px solid var(--gold);
  outline-offset:3px;
}

@media (max-width:600px){
  .una200-lightbox__bar{ flex-wrap:wrap; }
  .una200-lightbox__caption{ order:3; flex-basis:100%; }
}

@media (prefers-reduced-motion:reduce){
  .una200-lightbox__btn{ transition:none; }
}
