/* THE PAGE PLACES THE FOOTER; ARITHMETIC USED TO, AND GOT IT WRONG.
   Every region here used to carry a `min-height: calc(100vh - 4rem)` that hard-coded a footer
   strip, while the HTML frame inside it sized itself against `100dvh`. Those units are not the
   same number on a phone: `vh` is the LARGE viewport, `dvh` the current one, so with the URL bar
   showing, the document reserved ~80px more than its contents occupied and the difference rendered
   as a band of white above the footer — visible until you scrolled the bar away. The 4rem was also
   a guess about a footer that is 0 tall on a branding-removed artifact.
   A flex column needs neither number: the regions grow into whatever is left, and the footer is
   placed by flow at whatever height it actually has — including none at all. */
.aa-public-page {
  display: flex;
  min-height: 100dvh;
  flex-direction: column;
  background: var(--color-aa-bg);
}

/* Every region that can be the body of this page takes the leftover height, in ONE declaration so
   the two states cannot drift apart the way they did before — `.aa-viewer` wraps the live document,
   `.aa-viewer-terminal` replaces it, and the gate stands in front of it. That is what puts the
   footer at the bottom of a short screen instead of leaving it under the content.
   `flex-shrink: 0` throughout: content taller than the viewport must overflow into scroll, never be
   squeezed to fit. */
.aa-viewer,
.aa-viewer-terminal,
.aa-viewer-gate,
.aa-viewer-document,
.aa-viewer-content {
  flex: 1 0 auto;
}

/* The chain the frame needs in order to inherit that space: viewer → document → content → frame.
   `min-width: 0` is load-bearing on a column flex item — its default `auto` refuses to go below the
   intrinsic width of a wide code block, which would push the reading column sideways. */
.aa-viewer,
.aa-viewer-document,
.aa-viewer-content {
  display: flex;
  min-width: 0;
  min-height: 0;
  flex-direction: column;
}

/* A compact, dark status bar. It reads as the chrome AROUND a published artifact — a live dot and
   the title on the left, details and actions on the right — rather than as part of the document. */
.aa-viewer-chrome {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-aa-4);
  min-height: 3rem;
  border-bottom: 1px solid var(--color-aa-dark-line);
  padding: var(--spacing-aa-2) var(--spacing-aa-5);
  color: var(--color-aa-dark-text);
  background: color-mix(in srgb, var(--color-aa-dark-card) 92%, transparent);
  backdrop-filter: blur(12px);
}

.aa-viewer-chrome__lead {
  display: flex;
  min-width: 0;
  flex: 0 1 auto;
  align-items: center;
  gap: var(--spacing-aa-3);
}

.aa-viewer-chrome__dot {
  width: 0.5rem;
  height: 0.5rem;
  flex: 0 0 auto;
  border-radius: var(--radius-aa-full);
  background: var(--color-aa-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-aa-accent) 28%, transparent);
}

.aa-viewer-title {
  min-width: 0;
  margin: 0;
  overflow: hidden;
  color: var(--color-aa-dark-text);
  font-size: var(--text-aa-md);
  font-weight: 650;
  letter-spacing: -0.01em;
  line-height: 1.2;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.aa-viewer-chrome__end {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--spacing-aa-2);
}

.aa-viewer-chrome__meta {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-aa-2);
  white-space: nowrap;
}

.aa-viewer-byline,
.aa-viewer-updated {
  margin: 0;
  color: var(--color-aa-dark-muted);
  font-size: var(--text-aa-sm);
}

.aa-viewer-chrome__sep {
  color: var(--color-aa-dark-line);
}

.aa-viewer-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-aa-2);
}

/* Buttons and the version select sit on a dark bar, so they take a light-on-dark outline treatment
   instead of the app's light-surface default. */
.aa-viewer-chrome .aa-btn,
.aa-viewer-version-select {
  min-height: 2rem;
  color: var(--color-aa-dark-text);
  border-color: color-mix(in srgb, var(--color-aa-dark-text) 26%, transparent);
  background: transparent;
}

/* `Button` wraps its children in one plain `<span>`, and the reset renders every `svg` as a block —
   so a mark beside a word broke the line, putting the icon above "Download" and pushing the label
   out of the button's own box onto the bar behind it. The wrapper has to be a row for a button that
   carries both. */
.aa-viewer-chrome .aa-btn > span {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-aa-2);
}

.aa-viewer-chrome .aa-btn:hover {
  color: var(--color-aa-dark-text);
  border-color: var(--color-aa-dark-text);
  background: color-mix(in srgb, var(--color-aa-dark-text) 12%, transparent);
}

.aa-viewer-version-select {
  width: auto;
  min-width: 4.5rem;
  padding-right: var(--spacing-aa-8);
}

/* The chrome's marks. Sized here rather than on the elements so every icon in the bar is one
   optical size, and `flex: 0 0 auto` so a button's label cannot squeeze the glyph. */
.aa-viewer-icon {
  width: 1.125rem;
  height: 1.125rem;
  flex: 0 0 auto;
}

/* Refresh says it is working by turning. The busy state is already on the button — `viewer.js` sets
   `aria-busy` for the whole request and clears it in a `finally`, so success and failure both stop
   the spin — and this is that same state made visible. Before, the only feedback was the button
   greying out, which on a fast connection is a flicker and on a slow one is indistinguishable from
   a dead control. */
@keyframes aa-viewer-spin {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

.aa-viewer-refresh[aria-busy="true"] .aa-viewer-icon {
  animation: aa-viewer-spin 900ms linear infinite;
  transform-origin: 50% 50%;
}

/* Desktop: the wrapper is not a box at all. `display: contents` drops it from the layout so the
   meta and the actions become direct children of the bar's flex row, laid out exactly as they were
   before the panel existed. */
.aa-viewer-menu {
  display: contents;
}

.aa-viewer-menu-toggle,
/* The bar already shows the title in full at this size; a second copy would be a duplicate
   heading in the accessibility tree as well as on screen. */
.aa-viewer-menu__title {
  display: none;
}

/* The title gets the leftover width. It was `flex: 0 1 auto` — content-sized, so on a phone the
   Download label and two controls took what they wanted first and the title ellipsised into three
   words. Moving those into the menu frees the room; this is what lets the title claim it. */
.aa-viewer-chrome__lead {
  flex: 1 1 auto;
}

/* Phones: the meta, the version picker and Download move behind the ⋮ toggle, leaving the bar with
   a readable title and two 44px controls. */
@media (max-width: 560px) {
  .aa-viewer-menu {
    position: absolute;
    z-index: 30;
    top: calc(100% + var(--spacing-aa-2));
    right: var(--spacing-aa-3);
    display: grid;
    width: max-content;
    min-width: 12rem;
    max-width: calc(100vw - var(--spacing-aa-6));
    gap: var(--spacing-aa-3);
    border: 1px solid var(--color-aa-dark-line);
    border-radius: var(--radius-aa-lg);
    /* One value, all four edges. Stated as a single shorthand rather than a block/inline pair so
       the two sides cannot be given different numbers later without somebody meaning to. */
    padding: var(--spacing-aa-4);
    background: var(--color-aa-dark-card);
    box-shadow: 0 18px 40px rgb(0 0 0 / 45%);
  }

  /* Closed is `display: none` here and nowhere else: on desktop this panel is the bar's own
     contents, and hiding it there would take the version picker and Download off the page. */
  .aa-viewer-menu[data-aa-open="false"] {
    display: none;
  }

  /* The whole title, wrapping, at the top of the panel: this is the one place on a phone where a
     long title is readable, because the bar above has already had to cut it off. */
  .aa-viewer-menu__title {
    display: block;
    overflow-wrap: break-word;
    color: var(--color-aa-dark-text);
    font-size: var(--text-aa-md);
    font-weight: 650;
    letter-spacing: -0.01em;
    line-height: 1.3;
  }

  .aa-viewer-menu .aa-viewer-chrome__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-aa-1);
    white-space: normal;
  }

  /* A ROW OF CONTROLS, NOT A STACK OF BARS. These were `width: 100%` in a grid, which turned two
     small controls into two full-bleed slabs filling a panel that exists to be compact — and the
     select's own 2rem chevron inset then read as the panel being lopsided. Intrinsic widths, laid
     out in a row that wraps when it must. */
  .aa-viewer-menu .aa-viewer-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-aa-2);
    align-items: center;
  }

  .aa-viewer-menu .aa-viewer-version-select {
    width: auto;
    min-width: 4.5rem;
  }

  .aa-viewer-menu .aa-btn {
    width: auto;
  }

  .aa-viewer-menu-toggle {
    display: inline-flex;
  }
}

.aa-viewer-updated-pill {
  opacity: 0;
  transform: translateY(-2px);
  transition:
    opacity 160ms ease,
    transform 160ms ease;
}

.aa-viewer-updated-pill[data-aa-visible="true"] {
  opacity: 1;
  transform: translateY(0);
}

.aa-viewer-version-banner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-aa-2);
  align-items: center;
  justify-content: center;
  border-bottom: 1px solid var(--color-aa-accent-line);
  padding: var(--spacing-aa-3) var(--spacing-aa-6);
  color: var(--color-aa-accent-active);
  background: var(--color-aa-accent-soft);
  font-size: var(--text-aa-sm);
  font-weight: 550;
}

/* Sits directly under the chrome that holds the refresh control, because a status belongs beside
   the thing it describes. Collapses to nothing while both states are hidden. */
.aa-viewer-status {
  min-width: 0;
  /* Same column as the chrome above it and the document below it. Not filed as a defect, but it is
     the same drift: a status about this artifact reading 400px left of the artifact. */
  padding-inline: max(
    var(--spacing-aa-6),
    calc((100% - var(--width-aa-prose)) / 2 + var(--spacing-aa-6))
  );
}

.aa-viewer-status:has([data-aa-viewer-status]:not([hidden])) {
  padding-block-start: var(--spacing-aa-4);
}

/* An anchor the sanitizer disarmed is not a link, and must not look like one.
   Hostile markdown reaches this surface, the sanitizer strips the href and keeps the text, and the
   remains inherited `.aa-md a`'s accent colour — so a dead control sat in the product's own link
   colour, distinguishable from a live one only by an underline that appears on hover. A reader
   cannot tell those apart before clicking, and clicking teaches them nothing.
   Scoped to the viewer deliberately: `.aa-md a` lives in app.css, and prose inside a dashboard card
   is not this defect. The hover state is included, or the costume returns with the cursor. */
.aa-viewer-content .aa-md a:not([href]),
.aa-viewer-content .aa-md a:not([href]):hover {
  color: inherit;
  text-decoration: none;
  cursor: text;
}

/* HTML artifacts render full-screen: the frame fills whatever the chrome, the banner and the footer
   leave, so an app-style artifact is not squeezed into a short box with the page empty beneath it.
   That floor used to be spelled `calc(100dvh - 7rem)` — a guess at the height of two other elements,
   wrong the moment either changed and wrong by a whole footer on a branding-removed artifact.
   `flex-grow` asks for the same thing without naming a number.
   It still GROWS past the viewport for taller content: `flex-basis: auto` reads the inline height
   the measurement handshake sets, and `flex-shrink: 0` means a 3000px measurement stays 3000px. */
.aa-viewer-frame {
  display: block;
  width: 100%;
  flex: 1 0 auto;
  border: 0;
  background: var(--color-aa-bg);
  transition: height 160ms ease;
}

.aa-viewer-gate,
.aa-viewer-terminal {
  display: grid;
  place-items: center;
  padding: var(--spacing-aa-8) var(--spacing-aa-6);
}

.aa-viewer-gate-card,
.aa-viewer-terminal-card {
  display: grid;
  width: min(100%, 27rem);
  gap: var(--spacing-aa-4);
  justify-items: center;
  text-align: center;
}

.aa-viewer-gate-card h1,
.aa-viewer-gate-card h2,
.aa-viewer-terminal-card h1 {
  margin: 0;
  color: var(--color-aa-ink);
  font-size: var(--text-aa-2xl);
  font-weight: 650;
  letter-spacing: -0.02em;
  line-height: 1.25;
}

.aa-viewer-gate-card p,
.aa-viewer-terminal-card p {
  margin: 0;
}

/* Tone is scoped away from `.aa-error` on purpose. As a plain descendant selector this rule is
   (0,2,0) and outranks `.aa-error` (0,1,0) at any source order, so the single most
   security-relevant public error in the product — "Incorrect password." — rendered in the same
   muted grey as helper text. */
.aa-viewer-gate-card p:not(.aa-error),
.aa-viewer-terminal-card p:not(.aa-error) {
  color: var(--color-aa-muted);
}

/* Reserve the line so revealing the error cannot push the submit button down under the thumb
   that is already reaching for it. */
.aa-viewer-password-error {
  min-height: 1.25rem;
}

.aa-viewer-terminal-actions {
  margin-top: var(--spacing-aa-2);
}

.aa-viewer-password-form {
  display: grid;
  width: 100%;
  gap: var(--spacing-aa-3);
  text-align: left;
}

/* One line, and as little of the page as one line needs.
   The block padding was `--spacing-aa-6` on both edges: 24 + 24 around a 24px link, a 73px band to
   carry six words of attribution under someone else's document. At `--spacing-aa-3` it is 49px —
   still an unmistakable strip, a third less of the reader's screen. The inline padding stays
   generous so the line never touches a phone's edge. */
.aa-viewer-footer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-aa-2);
  align-items: center;
  justify-content: center;
  border-top: 1px solid var(--color-aa-line);
  padding: var(--spacing-aa-3) var(--spacing-aa-5);
  color: var(--color-aa-muted);
  font-size: var(--text-aa-sm);
}

/* This reads as a caption otherwise — same colour, same weight, no underline as the text around it.
   It is the one link on the page that says where the page came from, so it has to look like a link.

   V6-N3 — AND IT HAS TO BE HITTABLE. Nothing here declared a size, so the box was whatever
   line-height arithmetic happened to produce: 22.39px measured, failing a 24px floor by a pixel and
   a half that nobody chose. The floor is WCAG 2.5.8's 24px for STANDALONE TEXT LINKS, not our 44px
   control floor — deliberately, because a tertiary attribution line at 44 becomes a band of empty
   space, and 2.5.8's inline exemption does not cover a standalone link like this one.
   `24px` rather than `1.5rem` because the success criterion is stated in CSS pixels, and a reader
   who has shrunk their root font is exactly the reader this protects.
   `inline-flex` so the extra height centres the text instead of hanging below it. */
.aa-viewer-footer a {
  display: inline-flex;
  min-height: 24px;
  align-items: center;
  color: var(--color-aa-muted);
  text-decoration: underline;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.18em;
}

/* The mark sits inline in the byline, so it takes the line's optical size rather than the brand
   lockup's. */
.aa-viewer-footer__brand {
  display: inline-flex;
  align-items: center;
  /* Snapped off a between-steps literal: 0.35rem is 5.6px, between the 4px and 8px steps and on
     neither. It is the SAME literal r8 removed from .aa-badge in app.css — that fix was frame-limited
     to one sheet and the value survived one file over, which is the narrow-frame class paying out
     against the very commit that named it.
     Not inert, checked rather than assumed: the link is inline-flex with three items ("Made with ",
     the mark, " Agent Artifacts"), so there are TWO live gaps, and the r8 badge precedent (a gap that
     turned out to render nothing) is exactly why that needed checking before any zero-pixel claim. */
  gap: var(--spacing-aa-1);
}

.aa-viewer-footer__brand .aa-mark {
  width: 1rem;
  height: 1rem;
  color: var(--color-aa-accent);
}

.aa-viewer-footer a:hover {
  color: var(--color-aa-accent);
  text-decoration: underline;
}

@media (min-width: 760px) {
  .aa-viewer-chrome {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    /* The bar stays full-bleed — its border and blur are what make it read as chrome — but its
       contents ride the document's own column. The title appears twice on this page, in the chrome
       and as the document's heading, and at 1440 they sat 424px apart: two grids for one page.
       Derived from `--width-aa-prose` and the same `--spacing-aa-6` edge inset `.aa-prose-page`
       uses, so the two cannot drift apart; `max()` keeps the phone's margins once the viewport is
       narrower than the column. */
    padding-inline: var(--spacing-aa-6);
  }

  .aa-viewer-actions {
    justify-content: flex-end;
  }
}

/* V6-N4 — a landscape phone hands the viewer about 375px of height, and a STICKY bar spends it
   permanently: what the chrome takes is not space at the top, it is space the reader can never
   scroll away from. Stacked, it measured 180px — 48% of the viewport holding the bar ABOUT the
   artifact instead of the artifact, against a 30% bar that the dashboard headers meet at 17%.

   The chrome stacks because `grid-template-columns` is one column until 760px, and that rule is
   keyed on WIDTH — reasonably, since the reason to stack is narrowness. A landscape phone inverts
   the premise: 667px of width is plentiful and height is the scarce axis. So this borrows the wide
   layout early, on the axis that is actually short, rather than inventing a third arrangement.

   Unsticking was the alternative and was rejected: the chrome carries Download, the version picker
   and Refresh, and a reader who scrolls into a long document should not have to scroll back to the
   top to reach them. Condensing keeps the affordances and returns the space.

   Paired with `min-width` so it cannot catch a narrow-and-short viewport, where two columns would
   have nowhere to go — 560px is an edge the sheet already breaks on rather than a new number. */
@media (max-height: 450px) and (min-width: 560px) {
  .aa-viewer-chrome {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--spacing-aa-3);
    align-items: center;
    padding-block: var(--spacing-aa-2);
  }

  .aa-viewer-title {
    font-size: var(--text-aa-xl);
  }

  .aa-viewer-actions {
    justify-content: flex-end;
  }
}

@media (prefers-reduced-motion: reduce) {
  .aa-viewer-updated-pill,
  .aa-viewer-frame {
    transition: none;
  }

  /* The spin is decoration on top of a state that is already announced — `aria-busy` and the
     "Refreshing artifact" label carry it — so removing the motion removes nothing but the motion. */
  .aa-viewer-refresh[aria-busy="true"] .aa-viewer-icon {
    animation: none;
  }
}
