/* Hand-written. The generated sheets are fonts.css (subset_fonts.py) and
   text.css (idml2html.py); this file owns the canvas, the screen model and
   the hotspot layer. */

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

/* The ADVENT platform derives the iframe's aspect ratio from this canvas
   (816/1056 = 0.7727), and iFrameAdapter.js scales the wrapper to fit. Nothing
   here may use viewport units or position:fixed -- both fight that transform
   and break on the platform while looking fine locally. */
body {
  width: 816px;
  height: 1056px;
  position: relative;
  overflow: hidden;
  background: #fff;
  font-family: 'Poppins', sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ---- screens ---------------------------------------------------------- */

/* Every screen is a whole page -- including the three that read as pop-ups,
   which the design already draws as complete compositions. So there is one
   mechanism and no special cases: a screen is either at rest over the canvas
   or parked one canvas-height below it, and showing or hiding it is a single
   class toggle that the transition carries. This is how the platform's own
   flipbooks work; see reference/.

   Two properties here are load-bearing:

   - The opaque background. Each screen's art is a separate SVG, and an SVG
     that has not been rasterised yet paints nothing, so a screen rising over
     another one showed its text over the page underneath for the first frame
     or two. The background covers that gap whatever the decoder is doing.
   - Never visibility:hidden. Parked screens stay rendered, so their art is
     already rasterised by the time they are asked to move. */
main {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #fff;
  transition: transform .5s cubic-bezier(.22, .61, .36, 1);
  /* A screen is one layer, and what it stacks inside itself is its own
     business. scripts.js gives every screen it shows a z-index from the depth
     of the stack, which makes that true for all of them but the one the deck
     opens on: page 1 is the floor and is never shown, so it never gets one.
     Without this its hotspots -- which carry a z-index of their own, see
     `main > .hotspots .hs` -- rose into the root stacking context and sat on
     top of every pop-up in the deck, so page 1's Next answered the click meant
     for the pop-up's own footer. Stated here rather than by giving page 1 a
     z-index at load, because the rule wanted is "a screen is a layer", not
     "one screen needs a number". */
  isolation: isolate;
}

main.hidden {
  transform: translateY(100%);
  pointer-events: none;
}

/* ---- popouts ---------------------------------------------------------- */

/* A popout is a page too -- assemble.py builds it from the same layers, in the
   same order -- but it opens onto the page it belongs to rather than replacing
   it, so moving the whole screen would slide a copy of that page up over
   itself. Instead the screen holds still and its two groups arrive separately:
   the card rises, and .behind fades in once the card has landed, which reads
   as the page dimming behind it.

   .behind is a repeat of what is already on screen, which is what lets it
   simply fade in. It is not redundant, though: the design re-sets one line and
   drops two footnotes on these pages, and those cross-fade rather than snap. */
main.popout {
  background: none;
}

main.popout.hidden {
  /* The screen itself never travels; only the card does. */
  transform: none;
}

main.popout .behind,
main.popout .card {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

main.popout .card {
  transform: translateY(0);
  transition: transform .55s cubic-bezier(.22, .61, .36, 1);
}

/* Opaque for the same reason a screen is: the page art does not paint every
   pixel, and the live page underneath would otherwise show through the gaps.
   It reads through .behind's opacity, so it fades with everything else. */
main.popout .behind { background: #fff; }

/* Starts on the click, not when the card lands. It is the first thing the
   reader sees answer them, so it has to be immediate; held back even a little,
   it read as a second, unrelated event. Shorter than the card's rise, so the
   page has settled by the time the card arrives on top of it. */
main.popout .behind {
  opacity: 1;
  transition: opacity .4s ease;
}

main.popout.hidden .card { transform: translateY(100%); }

/* On the way out the page brightens immediately, so the reader is looking at
   a live page again before the card has finished leaving. */
main.popout.hidden .behind {
  opacity: 0;
  transition: opacity .25s ease;
}

/* ---- the undimmed footer ---------------------------------------------- */

/* v2 paints the footer after the pale rectangle that dims the page, so it is
   neither the dimmed page's nor the card's: it stands at full strength while
   the card rises past it. pdf2art.py cuts the pills off the card slice and
   idml2html.py the labels, and they land here, between .behind and .card --
   the order the design paints them in, which is also the order that lets the
   card travel over the footer rather than under it.

   Deleting this layer instead (which is what the build did before) took the
   pills out of the package: the labels are white, so every pathway popout
   shipped Next and References as invisible text on bare paper. */
main.popout .chrome {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* The hotspots layer is above everything and owns the clicks; this is paint
     only, and must not take them off the card it sits under. */
  pointer-events: none;
  /* A parked popout does not travel -- .card is translated down and .behind is
     faded out instead -- so this layer needs a hidden state of its own or it
     stands over the live page below, always. It ships the same pills and the
     same labels the page underneath already draws, a pixel or two off, so what
     that looked like was page 1's Next wearing page 6's Back. Faded on
     .behind's timing, so the popout's footer and its dimmed page arrive and
     leave together and the crossfade over the page's own footer is invisible. */
  opacity: 1;
  transition: opacity .4s ease;
}

main.popout.hidden .chrome {
  opacity: 0;
  transition: opacity .25s ease;
}

main.popout .chrome .foot {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* A multi-state screen carries one footer per state, for the same reason it
   carries one card per state: the design draws the whole footer band lower on
   pages 11-25 than on pages 1-10, so a screen spanning both has to swap it to
   match either. Only the current one is painted -- without this the deck
   stacked all ten of the illustrative case's, a pixel or two apart, which
   smears the labels into unreadable bold. It moves on the cards' own timing,
   so a state change carries the two together.

   The dimming pass goes with it, and for the same reason: the design takes the
   dim down to meet the rule above the footer, so it too is drawn 47px deeper
   on pages 11-25. Shipping the screen's first dim for all ten states left the
   illustrative case's chart states with a band of bare white between the
   dimmed page and that rule (client 2026-08-22). It sits inside .behind rather
   than in .chrome because it dims the page and the card rises over it. */
/* Not a cross-fade. Fading one layer out while the other fades in puts both
   at about half strength in the middle of the move, and half of a card is not
   a card: the dimmed page shows straight through it for a frame or two, which
   is what the client saw on every event icon (2026-08-25).

   So the arriving layer fades in *over* the one it replaces, and the one it
   replaces holds at full strength until it is completely covered and then
   snaps out from underneath -- `transition: opacity 0s <fade>` is a zero-
   length change delayed by exactly the length of the fade. At no point in the
   move is the total opacity below 1, so nothing behind is ever visible.

   The z-index is what makes "over" true in both directions: the states are in
   the document in state order, so without it the arriving layer paints under
   the leaving one whenever the reader goes backwards. It lifts each layer only
   above its own siblings -- the dims are already the last thing in `.behind`
   and the feet are all `.chrome` holds -- so nothing else changes order. */
main.states .chrome .foot,
main.states .behind .dim {
  opacity: 0;
  z-index: 1;
  transition: opacity 0s .18s;
}

main.states .chrome .foot.current,
main.states .behind .dim.current {
  opacity: 1;
  z-index: 2;
  transition: opacity .18s ease;
}

@media (prefers-reduced-motion: reduce) {
  main.states .chrome .foot,
  main.states .behind .dim,
  main.states .chrome .foot.current,
  main.states .behind .dim.current { transition: none; }
}

/* ---- a popout with several states ------------------------------------- */

/* The fluctuation popout is one card the design draws four times, once per
   BEC pattern. It ships as one screen holding all four cards in place, with
   one of them current; tapping a tab makes another one current.

   Only the current card is visible and only the current card is clickable --
   each card carries its own tab strip, because the strip moves with the card,
   and a strip belonging to a hidden card must not take the click.

   The fade is what makes that legible. The design re-draws each state a few
   pixels from the last, and the tab strip with it; cross-faded that is
   invisible, where a swap would jolt. It is quick because the annotation asks
   for the highlight to shift "instantly". */
/* Held, covered and snapped rather than cross-faded -- see the footer band
   above, which moves on the same click and for the same reason. The cards are
   where it showed: a case profile card is most of the canvas, so half of one
   is a whole page of dimmed backdrop coming through.

   Keep the popout's rise. `transition: opacity` alone replaced the transform
   timing, so Key Factors (and the other state screens) faded in place instead
   of sliding up from the bottom. */
main.states .card {
  opacity: 0;
  z-index: 1;
  pointer-events: none;
  transition: opacity 0s .18s, transform .55s cubic-bezier(.22, .61, .36, 1);
}

main.states .card.current {
  opacity: 1;
  z-index: 2;
  pointer-events: auto;
  transition: opacity .18s ease, transform .55s cubic-bezier(.22, .61, .36, 1);
}

/* Only while the popout is arriving or leaving: the cards travel together, so
   the one being faded out must not paint over the page during the move -- and
   must not hold at full strength for .18s of it either, which is what the
   delay above would otherwise do. */
main.states.hidden .card,
main.states.hidden .card.current {
  transition: opacity 0s, transform .55s cubic-bezier(.22, .61, .36, 1);
}

@media (prefers-reduced-motion: reduce) {
  main.states .card,
  main.states .card.current { transition: none; }
}

/* The page art, with all text removed -- the text is live HTML on top. */
.art {
  position: absolute;
  top: 0;
  left: 0;
  width: 816px;
  height: 1056px;
  pointer-events: none;
  user-select: none;
}

/* ---- what pulses ------------------------------------------------------ */

/* Everything the reader can click pulses, so that it reads as interactive
   without a hover state having to be discovered first: the "+" icons, and the
   Next / References / Home buttons in the footer.

   The artwork is lifted out of the page art by extract_icons.py so it can
   move; each piece sits back at the exact spot it was cut from, so a static
   render is pixel-identical to the design. Only pieces with a hotspot under
   them get .live -- artwork that pulses but does nothing is worse than
   artwork that sits still. */
.lifted {
  position: absolute;
  pointer-events: none;
  user-select: none;
}

/* A button is two elements -- lifted artwork, and its label, which is live
   HTML in the text layer -- and they have to grow as one thing. assemble.py
   gives every member of a group a transform-origin pointing at the same page
   point, the artwork's centre, so the group scales together instead of each
   part scaling about itself and drifting apart. A "+" icon is a group of one,
   and that point is its own centre. */
.live {
  animation: pulse 2s ease-in-out infinite;
  transform-origin: 50% 50%;
}

/* Kept modest: this is medical education, and the icons sit on top of data. */
@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.12); }
}

/* A control whose artwork holds still and whose label moves on its own: the
   fluctuation popout's BEC tabs, where the pill is 140x34 and growing the
   whole of it read as the card underneath moving rather than as an invitation
   (client 2026-08-25). assemble.py marks the label `solo` and leaves the
   lifted pill without `.live`, so the pill still ships exactly as the design
   draws it and nothing about the pairing changes.

   6% rather than 12%, for the reason the CTA is 6%: this is a bar of words,
   and the icons' amplitude on words reads as a wobble. */
.live.solo {
  animation-name: pulse-text;
}

@keyframes pulse-text {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}

/* The hotspot is a separate element in a layer above the artwork, so the two
   cannot be linked by a CSS combinator; scripts.js pairs them by the group
   name and toggles .hot on hover, which settles the pulse into a held,
   slightly enlarged state -- responsive without a highlight box. */
.live.hot {
  animation: none;
  transform: scale(1.12);
  transition: transform .18s ease;
}

.live.solo.hot { transform: scale(1.06); }

/* Respect a user who has asked the OS for less motion. It still has to read as
   interactive, so it holds a slightly enlarged resting state rather than
   disappearing into the artwork. */
@media (prefers-reduced-motion: reduce) {
  .live { animation: none; transform: scale(1.06); }
  .live.solo { transform: scale(1.03); }
  main,
  main.popout .behind,
  main.popout .card { transition-duration: .01s; transition-delay: 0s; }
}

/* ---- hotspots --------------------------------------------------------- */

/* Hotspot rects come from the reviewer's annotation boxes, which are drawn by
   hand and are neither tight to the artwork nor the same shape as it. So the
   hotspot itself must stay completely invisible -- tinting it on hover drew a
   sloppy rectangle around the icon and read as a cutout. The affordance is the
   artwork underneath: the `+` icons pulse, and the cursor changes. */
.hs {
  position: absolute;
  display: block;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

/* A screen's own hotspots -- Back, References and the close X -- are the last
   thing in the <main>, and until 2026-08-25 that was enough to put them above
   every painted layer, because nothing on the screen carried a z-index.
   Giving the cards one for the covered swap above quietly moved them
   underneath the current card, whose text layer then ate all three clicks on
   the fluctuation pop-out and the factors accordion.

   So the stack is stated rather than implied: cards at 1 and 2, clicks at 3.
   On the z-index and not on the wrapper, because the wrapper is a 0x0 box that
   never takes a click and making it the size of the canvas would give it every
   click the cards' own hotspots need. A card's own hotspots are inside the
   card, which is its own stacking context, so they are unaffected -- and so is
   a parked pop-out, whose `main.hidden` still turns pointer events off for
   the whole screen however its layers are ordered. */
main > .hotspots .hs { z-index: 3; }

/* Keyboard focus still has to be visible -- this is an accessibility-reviewed
   platform -- but :focus-visible never fires for a mouse click, so it costs
   the pointer user nothing. */
.hs:focus-visible {
  outline: 3px solid #0098a5;
  outline-offset: 2px;
  border-radius: 6px;
}

/* ---- the two buttons the design does not draw -------------------------- */

/* Client 2026-08-22 asked for a "Take the Quiz" call to action on the case
   profile cards and a way back to the graph from the quiz. The artwork has
   neither, so these are the only things in the package that are not cut out of
   the design -- and they are still not chosen: derive_buttons.py measures the
   deck's own "Tap to explore" pill for the colour, the height and the room it
   leaves round its words, and takes the position from the note the client
   typed on the marked-up PDF. All of that arrives as custom properties on the
   element; there is not a number in this block.

   It is a hotspot that paints, rather than artwork with a hotspot over it,
   because there is no artwork: one element is the whole button. It is sized by
   its own label, so the client can change either button's wording without
   anything being re-measured, and it is placed by its middle for the same
   reason. The type class its label carries brings the family, weight, size and
   colour, so only the box is described here.

   When the agency draws these in InDesign, this block and derive_buttons.py
   both go and the pills are lifted like every other one. */
.cta {
  transform: translate(-50%, -50%) scale(2);
  height: var(--cta-h);
  line-height: var(--cta-h);
  padding: 0 var(--cta-pad);
  border-radius: calc(var(--cta-h) / 2);
  background: var(--cta-fill);
  white-space: nowrap;
  text-align: center;
  /* The client asked for the CTA to draw attention to itself. Gentler than the
     icons' pulse -- 6% rather than 12% -- because this is a bar of words
     rather than a small round icon, and the same movement on it reads as a
     wobble. Its own keyframes because it has to keep its centring transform. */
  animation: pulse-cta 2s ease-in-out infinite;
}

@keyframes pulse-cta {
  0%, 100% { transform: translate(-50%, -50%) scale(2); }
  50%      { transform: translate(-50%, -50%) scale(2.12); }
}

.cta:hover,
.cta:focus-visible {
  animation: none;
  transform: translate(-50%, -50%) scale(2.12);
  transition: transform .18s ease;
}

@media (prefers-reduced-motion: reduce) {
  .cta {
    animation: none;
    transform: translate(-50%, -50%) scale(2.06);
  }
}

/* Press "h" to reveal the hotspot rects when reviewing against the design. */
body.show-hotspots .hs {
  background: rgba(255, 0, 255, .18);
  outline: 1px dashed #f0f;
}

/* ---- text layer ------------------------------------------------------- */

/* Generated text is positioned art, not a reading surface; selecting it drags
   ragged fragments out of order, so it is left inert like the artwork. */
.page { user-select: none; }
