/* Presentation mode — how an HGI page looks while Charlie presents it.
 *
 * Loaded on every presentable page and does nothing until `presenter.js` puts
 * `hgi-presenting` on the body, so a visitor reading the page directly sees
 * none of it.
 *
 * The pink is the brand kit's accent, and the kit reserves it for the one
 * active item — which is exactly what a presenter's pointer is. Everything
 * here is one item at a time for that reason.
 */

/* ---------------- embedded chrome ---------------- */

/* Keyed on `is-embedded`, which the server puts on the body for `?embed=1`,
   NOT on the class presenter.js adds once the parent says hello. The chrome
   has to be gone the instant the page paints; waiting on a handshake means a
   visitor watches the navigation disappear a second after it appears.

   Inside Charlie's panel the page keeps its content and drops everything that
   navigates: the focus-area links, the pager, and "Ask Charlie about this" —
   an offer she has already accepted on their behalf. */
.is-embedded .m-nav,
.is-embedded .m-pager,
.is-embedded .m-cta,
.is-embedded .chapters,
.is-embedded .pager,
.is-embedded .cta { display: none; }

/* Nothing here is clickable by default. This is a presentation, not a page
   someone is browsing — a link that navigates inside the panel would strand
   them in a frame with no way back, and a dialog opening over the top would
   cover the thing Charlie is pointing at. She drives it; the visitor watches. */
.is-embedded a,
.is-embedded button,
.is-embedded [role="button"],
.is-embedded [tabindex] {
  pointer-events: none;
  cursor: default;
}

.is-embedded a:focus-visible,
.is-embedded button:focus-visible,
.is-embedded [role="button"]:focus-visible { outline: none; }

/* Two exceptions, and only two: the controls the panel itself put in the
   frame to be chosen from.

   The hub's four area cards and an overview's own cards do not navigate the
   frame — each one cancels its href, posts the choice to the panel, and the
   panel decides what to load and tells Charlie. Blanket-disabling them made
   the hub Charlie raises to get back out of an area a picture of four cards
   that could not be picked, which is the one thing it exists for.

   Everything else in a framed page stays inert. These two selectors name the
   markup that carries a message handler (`.gallery-card[data-area]` in the
   lobby, `[data-dialog][data-present-label]` in an overview) rather than
   re-enabling links by class of page, so an ordinary link inside a presented
   dossier still cannot take the frame somewhere the panel does not know
   about. */
.lobby.is-embedded .gallery-card[data-area],
.is-overview [data-dialog][data-present-label] {
  pointer-events: auto;
  cursor: pointer;
}

/* A control that can be chosen has to show it has the keyboard, which the
   blanket rule above deliberately removes from everything else. */
.lobby.is-embedded .gallery-card[data-area]:focus-visible,
.is-overview [data-dialog][data-present-label]:focus-visible {
  outline: 3px solid #ff81bb;
  outline-offset: -3px;
}

/* The page's own masthead goes too. Framed inside Charlie, the DIGITAL
   wordmark and "Housing Growth Innovation" already appear twice above it —
   once in the app's masthead, once in the panel's own head, which names the
   page as well. A third copy a few pixels below the second reads as a
   duplicate panel rather than as branding, and it costs a row of the
   framework that the page would rather spend on content. */
.is-embedded .m-head,
.is-embedded .page > header { display: none; }

/* Both page shells are three-row grids — masthead, body, footer — declared as
   `auto / 1fr / auto`. Taking the masthead out with `display: none` removes it
   from the grid entirely, so every child after it slides up a row: the body
   lands in the masthead's `auto` row and the footer inherits the `1fr` that
   was meant for the body. On the seven-page shell that produced a 68px panel
   holding 28px tiles under a 717px footer; on the mission page it pushed the
   citation off the bottom edge. Placing the two survivors explicitly keeps
   each in the row it was designed for. */
.is-embedded .sheetpage > .m-body,
.is-embedded .page > main { grid-row: 2; }

.is-embedded .sheetpage > .m-bottom,
.is-embedded .page > footer { grid-row: 3; }

/* The seven-page shell reserves a fixed 68px for its masthead, so hiding it
   leaves the height behind as an empty band. Zeroed here and handed to the
   content. (The mission shell sizes that row `auto`, so it collapses on its
   own and needs nothing.) */
.is-embedded .page { grid-template-rows: 0 minmax(0, 1fr) 44px; }

/* The tile grid splits its height evenly between however many rows it has,
   which suits a full page carrying three rows of them. A focus area with six
   documents has two rows, and in a panel that gave 328px cards holding two
   lines of text at the top and nothing else. Capped so a card is the size of
   its content and the grid stacks from the top, rather than every card
   inflating to fill whatever space happens to be going. */
.is-embedded .tiles {
  grid-auto-rows: minmax(0, 200px);
  align-content: start;
}

/* The page sizes everything — type, spacing, the ring — from one unit, and
   that unit is computed from viewport width and height. Inside the panel the
   viewport IS the frame, which is around two thirds of the window, so the unit
   lands on the floor of its clamp: 8px against roughly 15px in a full window.
   The ring keeps its size because it is driven by aspect ratio, so the effect
   is a huge diagram surrounded by type at its smallest legible size — the
   whole page reads as compressed.

   Recomputed here against the frame's own dimensions so the proportions the
   page was designed with survive being embedded. Raising the unit also shrinks
   the ring, since its width subtracts a multiple of the unit — which is the
   rebalancing that was wanted. */
.is-embedded { --u: clamp(10px, 0.8vw + 0.8vh, 16px); }

/* The ring reserves a fixed multiple of the unit for everything stacked above
   and below it, so raising the unit shrinks the ring twice over — enough that
   correcting the type alone left a 166px diagram in a 540px frame. The panel
   has no page furniture to leave room for, so it reserves less and the ring
   keeps its presence: at that frame size this is the difference between a
   298px ring and a 166px one, for the same legible type.

   The column term reserves the labels' room too. `100%` on its own let the
   ring fill the column, and the phase labels stand outside the circle — so in
   a frame taller than it is wide (a 1200x820 panel) the 3 o'clock label was
   pushed past the frame's edge and the page grew a horizontal scrollbar,
   which a panel has no way to scroll. It costs nothing in the usual case,
   where the frame's height is the binding term. */
.is-embedded .m-ring {
  width: min(calc(100% - var(--u) * 23), calc(100vw - var(--u) * 23), calc(100dvh - var(--u) * 16));
}

/* Below this the frame is too short for the essay blocks and the framework
   both, and something has to go. The page already sheds these first when it
   runs out of room on its own — the ring is its subject, the essay is its
   detail — so a short panel follows the same order rather than inventing a
   new one. Without it the "Who" block and the page's own citation were being
   clipped off the bottom edge, which is worse than not showing them: nothing
   said they were there. */
@media (max-height: 620px) {
  .is-embedded .m-sections { display: none; }
  .is-embedded .m-ring {
    width: min(calc(100% - var(--u) * 23), calc(100vw - var(--u) * 23), calc(100dvh - var(--u) * 18));
  }
}

.is-embedded .sheetpage { padding-inline: clamp(14px, 1.8vw, 34px); }
.is-embedded .sheetpage,
.is-embedded .page { padding-block-start: calc(var(--u) * .7); }

/* Charlie's visual panel can be narrow, short, or both. The public pages use a
   fixed one-screen composition, but retaining that constraint in a compact
   iframe makes cards shrink below the height of their text. In presentation
   mode the page becomes the scroll surface instead: content keeps a readable
   minimum height and the parent can still move the highlighted region into
   view. Full-size frames retain the original one-screen composition. */
@media (max-width: 900px), (max-height: 760px) {
  .is-embedded {
    height: auto;
    min-height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
  }

  .is-embedded .page {
    height: auto;
    min-height: 100dvh;
    grid-template-rows: 0 auto 44px;
  }

  .is-embedded .page > main {
    min-height: 0;
    grid-template-rows: auto auto;
    overflow: visible;
  }

  .is-embedded .panel {
    min-height: 640px;
  }

  /* Policy and Impact carries three different evidence densities. Stacking
     the stages lets each use its natural height instead of compressing seven
     project groups into the height available to three podcast cards. */
  .is-embedded .loop {
    flex: none;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto;
    overflow: visible;
  }

  .is-embedded .stage {
    min-height: max-content;
    overflow: visible;
  }

  .is-embedded .stage:not(:last-of-type)::after {
    display: none;
  }

  .is-embedded .stage-episodes,
  .is-embedded .stage-groups,
  .is-embedded .stage-evidence,
  .is-embedded .stage-commitments {
    flex: none;
    overflow: visible;
  }

  .is-embedded .stage-episodes li,
  .is-embedded .stage-groups li,
  .is-embedded .stage-commitments li {
    flex: none;
    min-height: max-content;
  }
}

/* The lifecycle labels sit just outside the ring, so the ring reserves their
   outside space above — against the viewport rather than the column, since the
   page's own side padding is room the labels may legitimately use.
   Under 720px they lose their outcome and activity lines and become a name, so
   what they need there is much less. */
@media (max-width: 720px) {
  .is-embedded .m-ring {
    width: min(100%, calc(100vw - var(--u) * 12), calc(100dvh - var(--u) * 16));
  }
}

/* ---------------- the highlighted region ---------------- */

/* Everything not being pointed at steps back rather than disappearing: the
   visitor should still see the whole framework and where in it she is.

   Opacity only, deliberately. `filter: saturate()` reads better in a still
   screenshot, but it is not a compositor-only property: every region carrying
   it is repainted whenever it changes, and there are sixteen of them on the
   lifecycle ring alone. Moving the pointer re-ran that transition on the region
   she left and the one she arrived at, several times a second while she spoke,
   and the whole page visibly flickered. Opacity animates on the compositor and
   costs nothing per frame. */
.hgi-presenting [data-present] {
  transition: opacity .35s ease;
}

/* The step back has to be visible without making the rest of the page
   unreadable — a visitor still reads around the region she is pointing at.
   At .42 every de-emphasised label fell to 1.9-2.7:1; .7 is the point where
   nothing on any presented page drops below 3:1 while the spotlight still
   reads. Dimmed text cannot also hold 4.5:1 — text near that floor loses it
   the moment any dim is applied — so the region she is on carries full
   contrast and this is the deliberate trade for the rest. */
.hgi-presenting [data-present]:not(.hgi-on) {
  opacity: .7;
}

.hgi-presenting [data-present].hgi-on {
  opacity: 1;
}

/* An SVG ring segment cannot take a box outline, so the one thing it can take
   — its own fill — is pushed to the active colour instead. */
.hgi-presenting .seg.hgi-on path { fill: var(--purple); }
.hgi-presenting .seg.hgi-on .seg-n { fill: #fff; }
.hgi-presenting .m-leg.hgi-on .leg-name { color: var(--purple); }

/* ---------------- the pointer ---------------- */

.hgi-present {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s ease;
}

.hgi-present.is-live { opacity: 1; }

/* Glides between regions rather than cutting, so the eye is carried from the
   last thing she said to this one instead of having to find it again. */
.hgi-spot {
  position: absolute;
  top: 0;
  left: 0;
  border: 2px solid var(--pink);
  border-radius: 12px;
  background: color-mix(in srgb, var(--pink) 7%, transparent);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--pink) 12%, transparent);
  transition:
    transform .42s cubic-bezier(.22, .61, .36, 1),
    width .42s cubic-bezier(.22, .61, .36, 1),
    height .42s cubic-bezier(.22, .61, .36, 1);
}

.hgi-present.is-bump .hgi-spot { animation: hgi-pulse 1.4s ease-out; }

@keyframes hgi-pulse {
  0%   { box-shadow: 0 0 0 2px color-mix(in srgb, var(--pink) 46%, transparent); }
  55%  { box-shadow: 0 0 0 16px color-mix(in srgb, var(--pink) 4%, transparent); }
  100% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--pink) 12%, transparent); }
}

.hgi-pointer {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: transform .42s cubic-bezier(.22, .61, .36, 1);
}

/* The arrow is drawn pointing left. Standing to the right of a region it
   already points at it; standing to the left the whole pointer is turned
   around, which also swings the caption clear to the outside — so one glyph
   serves both sides, and only the caption's own words are turned back. */
.hgi-pointer svg {
  flex: none;
  fill: none;
  stroke: var(--pink);
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 2px 6px color-mix(in srgb, var(--blue) 22%, transparent));
}

.hgi-present.is-bump .hgi-pointer svg { animation: hgi-nudge 1.1s ease-out; }

@keyframes hgi-nudge {
  0%   { transform: translateX(14px); opacity: 0; }
  35%  { transform: translateX(-4px); opacity: 1; }
  60%  { transform: translateX(2px); }
  100% { transform: translateX(0); }
}

/* Wraps rather than truncates. The longest label on the ring is
   "04 Pre-construction & Procurement", and clipped to one line it read
   "04 PRE-C…" — which names nothing. Two short lines and a softer corner
   carry it; the width still stops it becoming a paragraph. */
.hgi-cap {
  max-width: 18ch;
  padding: 5px 10px;
  border-radius: 10px;
  /* White on the fill pink is 4.4:1, and this caption is 10px uppercase — the
     one label a visitor has to read while Charlie is talking. */
  background: var(--pink-ink, #c31b75);
  color: #fff;
  font-family: var(--font);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .07em;
  line-height: 1.25;
  text-transform: uppercase;
  text-wrap: balance;
  box-shadow: 0 6px 16px color-mix(in srgb, var(--blue) 26%, transparent);
}

/* The pointer is rotated as a whole when it stands to the left; the caption is
   turned back so the words stay the right way up. */
.hgi-present[data-side="left"] .hgi-cap { transform: rotate(180deg); }

/* Narrow frames have no room beside a region for a caption, and the outline
   alone still says which part she means. */
@media (max-width: 720px) {
  .hgi-cap { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hgi-spot,
  .hgi-pointer { transition: none; }
  .hgi-present.is-bump .hgi-spot,
  .hgi-present.is-bump .hgi-pointer svg { animation: none; }
}
