/* ------ COLOUR PAIRING STYLES ------ */

/* Loaded on every page (docs.config.js extraStylesheets), so every selector
   here is prefixed .pairing- and nothing reaches outside the tool.

   The tool paints palette primitives on purpose: they are its subject. The
   colours arrive as custom properties the script sets from the chosen tokens
   (--pairing-bg and --pairing-fg on the container, so the preview, the
   readout and the bar swatches all inherit them; --pairing-swatch-fill on
   each tile). Everything else, the chrome around the painted surfaces, uses
   semantic tokens like the rest of the site. Layout is the block, row and
   grid primitives on the elements; all this file owns is what they cannot
   express. */

/* Scoped to the tool's container, not :root: this stylesheet loads on every
   page, and its own header promises nothing reaches outside the tool. The
   container is the same element the script resolves and sets the pair on. */
[data-tool="color-pairing"] {
  --pairing-swatch-size: 1.5rem;
  --pairing-tile-height: 3rem;
  /* The floor a tile needs to read as a colour rather than a stripe. It
     carries no text now, so the floor is the swatch itself. Four columns fit
     wherever the width allows, which is where the palette still reads as one
     hue per row; below that the grid drops to three then two on its own,
     rather than on a breakpoint that cannot know the column width. */
  --pairing-tile-min: 4.5rem;
  --pairing-check-size: 1.5rem;
  --pairing-duration: var(--duration-2xs);
  /* The chosen tile's mark takes the system's selection pair, so a brand that
     moves selection colour moves it here too. Ring and disc are each other's
     inverse: one of the two reads on a white tile and on a black one. */
  --pairing-selected-ink: var(--selected-background);
  --pairing-selected-ground: var(--selected-text);
}

/* -- Specimen: the pair, always in view --
   Sits outside the section like the bar, so it runs edge to edge straight
   under it, and sticks beneath the bar so the pair stays put while the grids
   scroll. Painted in the pair itself; the fallbacks are the page's own
   text-on-background, so it is a legitimate pair before the script runs and
   never an unpainted box. The horizontal inset is .padding-global on the
   element and the column cap is .container-l inside it, the same pair the
   body below uses, so on a wide screen the specimen stops where the page
   does instead of running to the far edges. Only the vertical padding is
   this file's, because no utility owns it. Its own paint is what stops
   scrolled tiles showing through, so it needs no page ground behind it. */
.pairing-preview {
  position: sticky;
  /* Where it docks: under the bar. The shell publishes the docked total on
     body as --scroll-offset (site header, bar, breathing room); the
     breathing room comes back off, the .az-index precedent. The calc is
     written here, not as a :root token: a var() resolves where it is
     declared, and :root's --scroll-offset is the header alone. */
  top: calc(var(--scroll-offset) - var(--space-xl));
  /* Its only rivals are the tiles below; the bar keeps its own stacking
     context at --z-bar, so its menus still clear the specimen. */
  z-index: 1;
  /* Generous, because the band is the specimen: the pair needs room around
     the type to read as a surface rather than a strip of colour. Small
     viewports trade that for a compact docked row, and viewports too short
     to hold one drop the dock entirely — see the responsive section. */
  padding-top: var(--space-5xl);
  padding-bottom: var(--space-5xl);
  background-color: var(--pairing-bg, var(--background-primary));
  color: var(--pairing-fg, var(--text-primary));
  border-bottom: var(--border-s) solid var(--border-faded);
}

/* The specimen row inside the column. Wrapping is why this is not .block.row:
   no utility carries flex-wrap, and the three specimens must fall onto a
   second line before the sentence is squeezed. */
.pairing-preview-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xl);
}

/* -- Bar swatches: the pair either side of the swap, colour only --
   The group's caption is .bar-label, hidden in the bar below 768px and shown
   again when the group demotes into the overflow panel. */
.pairing-bar-swatch {
  flex: none;
  width: var(--pairing-swatch-size);
  height: var(--pairing-swatch-size);
  /* --border-control, not --border-faded: this is the boundary of a control,
     and --border-faded is 1.4:1, which leaves a white swatch on a white bar
     with no shape at all. */
  border: var(--border-s) solid var(--border-control);
}

.pairing-bar-swatch[data-slot="fg"] {
  background-color: var(--pairing-fg, var(--text-primary));
}

.pairing-bar-swatch[data-slot="bg"] {
  background-color: var(--pairing-bg, var(--background-primary));
}

/* Sizes are the thresholds WCAG names: 16px is normal text, 24px is 18pt,
   the large-text line. The display "Aa" is decoration for the eye. */
.pairing-preview-display {
  font-size: var(--font-6xl);
  line-height: var(--line-height-s);
}

.pairing-preview-text {
  flex: 1 1 16rem;
}

.pairing-preview-body {
  font-size: var(--font-s);
  color: inherit;
}

.pairing-preview-large {
  font-size: var(--font-2xl);
  line-height: var(--line-height-m);
  color: inherit;
}

/* -- The readout, on the right of the specimen --
   The number is painted in the pair like the rest of the band: it reads as
   part of the specimen rather than as a panel laid over it. The grade badge
   beside it keeps the component's own colours, so the verdict is still a
   labelled chip in page colours when the pair itself goes unreadable.

   margin-left auto pushes it to the far end of the row; the base paragraph
   margin is cleared because a flex row centres the margin box, not the text.
   Tabular figures so the number does not shuffle as it counts. */
.pairing-readout {
  margin: 0 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-s);
  color: inherit;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.pairing-readout strong {
  font-size: var(--font-3xl);
  font-weight: var(--font-weight-medium);
  line-height: var(--line-height-s);
}

/* The "nothing clears the filter" lines are live regions. A mutation inside
   a display:none region is not announced, and unhiding an already-filled one
   is announced inconsistently, so the script drives them with text alone and
   an empty one takes no space. */
.pairing-none:empty {
  display: none;
}

/* -- Checks table: thresholds sit beside their badge -- */
.pairing-verdicts td {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* -- Swatch grids --
   Bespoke rather than .grid: the palette is eight hues at four steps, so
   four fixed columns read as one hue per row without row headings, at every
   width. */
.pairing-swatches {
  display: grid;
  /* Four columns wherever four fit, fewer where they do not, and never more:
     the max() floors each track at a quarter of the row, so the palette keeps
     reading one hue per row rather than spilling to five on a wide phone. */
  grid-template-columns: repeat(auto-fit, minmax(max(var(--pairing-tile-min), (100% - 3 * var(--space-s)) / 4), 1fr));
  gap: var(--space-s);
}

/* Each tile is a label wrapping a covered radio, the chip's pattern
   (design-system.css, .chip input[type="radio"]): the input stays in the tree
   so it keeps focus and arrow-key movement, and the label is what you see. */
.pairing-swatch {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: 0;
  font-weight: var(--font-weight-regular);
  cursor: pointer;
}

.pairing-swatch input[type="radio"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  border: none;
  opacity: 0;
  cursor: pointer;
  /* Arrowing upward scrolls the focused tile flush to the viewport top,
     behind the docked bar. The shell's own scroll-margin rule covers
     headings and landmarks only, so the tiles state their own — and it goes
     on the input, not the label, because the input is what takes focus and
     therefore what gets scrolled into view. Its inset:0 box is the label's,
     so the two frame the same rectangle.

     --pairing-band is the specimen's measured height, published by the
     script on this container. It is measured rather than written down
     because the band's height moves with the viewport, the root font size
     and whether the readout's row wraps — a constant per media range held
     at 16px text and undershot by 50px once it wrapped, hiding the ring of
     the tile it had just scrolled to. The fallback is that old constant,
     sized for the tallest band, for a browser with no ResizeObserver: it
     overshoots on a phone, and overshooting only scrolls a tile lower. */
  scroll-margin-top: calc(var(--scroll-offset)
    + var(--pairing-band, calc(2 * var(--space-5xl) + 5rem))
    + var(--space-l));
  scroll-margin-bottom: var(--space-l);
}

/* The painted half. A constant faded edge gives a white tile on a white
   page a shape. Selection is a double ring, page ground then ink, which
   reads on any hue, plus the check; hover is the wash on the meta strip. */
.pairing-swatch-color {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--pairing-tile-height);
  background-color: var(--pairing-swatch-fill, var(--background-primary));
  /* --border-control clears 1.4.11 at 4.7:1 in both themes; --border-faded
     is 1.4:1 and is documented as decorative, never a control boundary. */
  border: var(--border-s) solid var(--border-control);
  transition: box-shadow var(--pairing-duration) var(--ease-out);
}

.pairing-swatch-check {
  --icon-size: 1rem;
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--pairing-check-size);
  height: var(--pairing-check-size);
  border-radius: var(--radius-pill);
  background-color: var(--pairing-selected-ground);
  color: var(--pairing-selected-ink);
}

/* Hover: the same double-ring the selection draws, one step thinner, so the
   two read as degrees of the same thing rather than two ideas. It is drawn
   outside the paint, never on it — a wash over the swatch would change the
   colour the tile exists to show. */
.pairing-swatch:hover:not(:has(input:checked)) .pairing-swatch-color {
  box-shadow:
    0 0 0 var(--border-s) var(--pairing-selected-ground),
    0 0 0 calc(var(--border-s) + var(--border-m)) var(--pairing-selected-ink);
}

/* The script mirrors checked onto .is-selected, so selection holds where
   :has() is missing; the focus ring has no such fallback and matches the chip. */
.pairing-swatch:has(input:checked) .pairing-swatch-color,
.pairing-swatch.is-selected .pairing-swatch-color {
  box-shadow: 0 0 0 var(--border-m) var(--pairing-selected-ground), 0 0 0 calc(var(--border-m) + var(--border-l)) var(--pairing-selected-ink);
}

.pairing-swatch:has(input:checked) .pairing-swatch-check,
.pairing-swatch.is-selected .pairing-swatch-check {
  display: flex;
}

.pairing-swatch:has(input[type="radio"]:focus-visible) {
  outline: 2px solid var(--text-primary);
  outline-offset: 2px;
}

/* ------ RESPONSIVE ------ */

/* -- Small viewports: the specimen docks compact --
   It keeps docking on a phone, and on a landscape phone that is wide but
   short. The ratio is the only one on the page and eighty tiles sit below
   it, so a specimen that scrolls away means scrolling back for the answer
   after every tile — the tool's own loop, broken on the device with the
   least room to spare. What changes here is not whether it docks but what
   docks: the two sample sentences come out and the display drops to the
   readout's size, so the band is one short row still painted in the pair,
   still typeset, still carrying its number. A tall surface holding half a
   short screen was the reason to unstick it, and it stops being true.

   The tiles' scroll-margin says nothing about any of this: it reads the
   band's measured height, so a compact band, a wrapped one and a band at
   200% text all arrive as the same number from the same rule. */
@media (max-width: 768px), (max-height: 600px) {
  /* Vertical only: the horizontal inset stays .padding-global's, so the
     specimen keeps its alignment with the body below at every width. */
  .pairing-preview {
    padding-top: var(--space-m);
    padding-bottom: var(--space-m);
  }

  /* Down to the readout's size, so the row reads as one line. */
  .pairing-preview-display {
    font-size: var(--font-3xl);
  }
}

/* -- Narrow only: the sentences come out --
   They are the tall part of the row and the part a narrow docked strip
   cannot afford. "Aa" stays: a band of colour with no type in it is not a
   specimen. Keyed on width alone, not on the pair above, because the short
   clause also matches a wide window at 200% browser zoom — a 1920 laptop is
   960x540 there — and a low-vision reader losing the type specimen on the
   tool whose subject is legibility is the wrong trade. Width is the
   constraint that actually crowds the row. Splitting the queries is safe
   now only because no constant has to match them: the tiles read the band's
   measured height, whichever combination applies. */
@media (max-width: 768px) {
  .pairing-preview-text {
    display: none;
  }
}

/* -- Viewports too short to dock anything --
   400% browser zoom on a laptop is a 256px-tall viewport. The bar and a
   docked band take 186px of it at default text and the whole of it once the
   text is scaled, which leaves a contrast tool with no contrast tool in
   view. Below this the specimen goes back to the flow and the page is the
   page again — the ratio scrolls away, which is the lesser loss. The tiles
   drop the band term with it, in the same block so the two cannot drift. */
@media (max-height: 400px) {
  .pairing-preview {
    position: static;
  }

  .pairing-swatch input[type="radio"] {
    scroll-margin-top: var(--scroll-offset);
  }
}

/* -- Forced colours --
   The painted surfaces opt out: a contrast tool the OS repaints measures
   nothing. Selection and focus take the system pair so state survives. */
@media (forced-colors: active) {
  .pairing-preview,
  .pairing-swatch-color,
  .pairing-bar-swatch {
    forced-color-adjust: none;
  }

  .pairing-swatch:has(input:checked) .pairing-swatch-color,
  .pairing-swatch.is-selected .pairing-swatch-color {
    box-shadow: 0 0 0 var(--border-m) Canvas, 0 0 0 calc(var(--border-m) + var(--border-l)) Highlight;
  }

  .pairing-swatch-check {
    forced-color-adjust: none;
    background-color: Canvas;
    color: CanvasText;
  }

  .pairing-swatch:has(input[type="radio"]:focus-visible) {
    outline-color: CanvasText;
  }
}
