/* The accessibility baseline. Every treatment loads this BEFORE its own
   stylesheet, so the properties the brief made build requirements are true
   before a single design decision is taken.

   Nothing here is a look. There is no colour on a heading, no spacing between
   sections, no type choice beyond the fallback stack. A treatment is free to
   override any of it, and the check harness re-verifies the outcome rather than
   trusting that it did not. */

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

html {
  /* Never a pixel font-size. That is the setting a person raises when they
     cannot read the page, and a px value here silently ignores it. */
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: var(--line-body);
  /* No fixed pixel layout anywhere. The page is fluid from a narrow phone to a
     wide desktop, and the only constraint is a reading measure. */
  min-height: 100vh;
  text-rendering: optimizeLegibility;
  /* An address or a URL is one long unbreakable token. At 200% text on a
     320px viewport it pushes the page sideways unless it is allowed to break,
     and sideways scroll is the loss the requirement is about. */
  overflow-wrap: break-word;
}

/* A grid or flex child defaults to min-width:auto, which means it refuses to
   shrink below its content and takes the layout with it. This is the single
   most common source of horizontal overflow in a fluid page.

   The list is every element a treatment lays out with. It started shorter and
   grew twice, both times because the gate found overflow at 320px and 200%
   text in a container that was not on it: a <header> flex row, then a <figure>
   holding a code block. Keep adding rather than narrowing. */
:where(
    main, section, article, aside, header, footer, nav,
    form, fieldset, div, dl, ul, ol, li, figure, details
  )
  > * {
  min-width: 0;
}

/* ── Reduced motion ──────────────────────────────────────────────────
   Honoured globally rather than per-animation, so a treatment cannot forget.
   Not `animation: none`: a zero-duration animation still FIRES, so anything
   that depends on an animationend event keeps working while nothing moves. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Focus ───────────────────────────────────────────────────────────
   Visible on every interactive element, and never removed. `:focus-visible`
   rather than `:focus` so a mouse click does not leave a ring behind, but the
   fallback below keeps the ring for browsers that do not know the selector.

   Two rings, not one: the outline carries the shape and the white offset keeps
   it visible when the control's own background is close to the ring colour. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: var(--radius);
}

@supports not selector(:focus-visible) {
  :where(a, button, input, textarea, select, summary, [tabindex]):focus {
    outline: 3px solid var(--focus-ring);
    outline-offset: 3px;
  }
}

/* ── Skip link ───────────────────────────────────────────────────────
   The page has no navigation, so this points at the form: the one thing a
   keyboard visitor arrives wanting to reach. Off-screen until focused, never
   `display: none`, which would take it out of the tab order entirely. */
.skip-link {
  position: absolute;
  left: var(--space-3);
  top: var(--space-3);
  z-index: 100;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-raised);
  color: var(--fg);
  border: 2px solid var(--focus-ring);
  border-radius: var(--radius);
  text-decoration: none;
  transform: translateY(calc(-100% - var(--space-4)));
}

.skip-link:focus {
  transform: translateY(0);
}

/* ── Text ────────────────────────────────────────────────────────────
   Headings inherit their weight and colour. A treatment sets those; what the
   baseline guarantees is that a long line never outruns a comfortable measure
   and that a heading never collides with the line above it at 200% zoom. */
h1,
h2,
h3 {
  line-height: var(--line-tight);
  text-wrap: balance;
  margin: 0;
}

p,
li {
  text-wrap: pretty;
  max-width: var(--measure);
}

p {
  margin: 0;
}

a {
  color: inherit;
}

/* Underlines stay. Removing them makes colour the sole carrier of "this is a
   link", which is exactly the failure the brief names. A treatment that wants
   a different affordance has to add one, not subtract this. */
main a:not(.skip-link) {
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* ── Attribution ─────────────────────────────────────────────────────
   Colour is never the only carrier. `.attr` sets the shape and reserves room
   for the label; the two modifiers add the colour on top of a distinction that
   already exists without it.

   The dot is a separate glyph per side, not one glyph tinted two ways, so the
   pair reads apart in greyscale and to anyone who cannot tell slate from
   fuchsia. */
.attr {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  /* Wraps, and this is not cosmetic. A value and its attribution label sitting
     on one nowrap line is 456px wide at 200% text, which is 216px past a
     320px viewport: the check harness failed on exactly this the first time it
     ran. The pair is what must stay together, not the line. */
  flex-wrap: wrap;
}

.attr::before {
  content: "";
  flex: none;
  align-self: center;
  width: 0.6em;
  height: 0.6em;
  border: 2px solid currentColor;
}

.attr-user {
  color: var(--attr-user-text);
}

/* A square for what the person authored. */
.attr-user::before {
  border-radius: 2px;
}

.attr-agent {
  color: var(--attr-agent-text);
}

/* A circle for what an agent proposed. */
.attr-agent::before {
  border-radius: 50%;
  border-style: dashed;
}

/* ── Form ────────────────────────────────────────────────────────────
   The form is the point of the page, so its baseline is here rather than left
   to each treatment. Every field is labelled, every control is at least 44px
   of target, and nothing depends on a placeholder to say what it is. */
label {
  display: block;
  font-weight: 600;
}

input,
textarea,
button,
select {
  font: inherit;
  color: inherit;
  /* iOS zooms the viewport when a focused field is under 16px. Inheriting the
     fluid step keeps it above that at every width. */
  max-width: 100%;
}

input[type="email"],
input[type="text"],
textarea {
  width: 100%;
  padding: 0.7em 0.8em;
  background: var(--bg-raised);
  /* A control's border is meaningful non-text and owes 3:1. --line does not
     pay it; --line-control does. */
  border: 1px solid var(--line-control);
  border-radius: var(--radius);
  min-height: 2.75rem;
}

textarea {
  resize: vertical;
  min-height: 7rem;
  line-height: var(--line-body);
}

button {
  min-height: 2.75rem;
  padding: 0.7em 1.4em;
  border-radius: var(--radius);
  border: 1px solid transparent;
  cursor: pointer;
}

/* Help and error text is associated with its field by aria-describedby, so it
   is announced rather than merely adjacent. */
.field-help {
  color: var(--fg-muted);
  font-size: var(--step--1);
}

/* ── Utility ─────────────────────────────────────────────────────────
   Visually hidden but still read aloud. This is how a label survives a design
   that has no room to show one, and it is the only sanctioned way to hide
   text that a screen reader still needs. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Forced-colours mode throws away every custom property. What has to survive
   is the shape: the focus ring and the attribution glyph. */
@media (forced-colors: active) {
  :where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
    outline: 3px solid CanvasText;
  }

  .attr::before {
    border-color: CanvasText;
  }
}

/* ── Placeholders ────────────────────────────────────────────────────
   A string the user has not supplied yet is drawn as a SLOT, not as text: mono,
   dashed, muted. At a glance it reads as "something goes here", which is the
   truth, rather than as copy that happens to look odd. `npm run check -- --ship`
   still fails on every one of them. */
.ph {
  font-family: var(--font-mono);
  font-size: 0.8em;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--fg-muted);
  padding: 0.1em 0.35em;
  border: 1px dashed var(--line-control);
  border-radius: 0.25rem;
  /* A bounded box, not a run of inline text: a long token wrapping flush to a
     320px edge otherwise pushes its own padding past it. */
  display: inline-block;
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* ── The product image ───────────────────────────────────────────────
   One document, shared/product.html, embedded by every treatment. A picture,
   so it is out of the tab order and hidden from assistive tech; the figure
   around it carries the description. Its aspect follows the crop the frame
   width selects inside it: the whole window when wide, the editor alone when
   narrow. */
.product-frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  border: 0;
  background: transparent;
}

@media (max-width: 43.6875rem) {
  .product-frame {
    aspect-ratio: 5 / 4;
  }
}
