/* ------ ICON LIBRARY STYLES ------ */
/*
  Layer: app. The Icon Library tool: one docked bar, a docked band for the
  selected icon, and a full-width tile grid under them. Every selector is
  .icon-library-* prefixed because this file loads on every page
  (extraStylesheets, cms/docs.config.js), so an unscoped rule here reaches all
  of them.

  No .bar-container rules here: the bar's content column belongs to
  docs-site.css. Same reason as svg-cleaner.css.

  Deliberate exceptions to the tokens-only rule, all preview or export data:
  the stage's checkerboard (fixed greys, the logo tool's), the light and dark
  grounds and the swatch fills (brand hex values the script reads from the
  palette tokens at init, because an exported file cannot resolve a custom
  property), and the focus outlines (the system's own 2px convention, as in
  the segmented control). Everything else here is tokens.
*/

/* -- Docked chrome --
   Two layers dock on this page: the bar, and the selected icon's band under
   it. The shell already sums --scroll-offset for one docked bar
   (docs-site.css), so nothing here re-points it. The band is deliberately
   not in that sum: it comes and goes with a selection, and an anchor landing
   position that moved when you picked an icon would be worse than one that
   ignores it. Tiles carry the band's real measured height instead
   (--il-detail-height, written by the module).

   --il-bar-height rebuilds the component's default-density height from its
   own tokens — one control tall, its padding, its border — so re-pointing a
   bar knob moves the band with it. --bar-top is re-pointed on .bar by the
   shell, not on :root, so anything outside a bar has to name
   --top-nav-height itself. */
body:has(.bar.icon-library-bar) {
  --il-bar-height: calc(
    var(--bar-control-height) + (2 * var(--bar-padding-y)) + var(--border-s)
  );
  --il-chrome-top: calc(var(--top-nav-height) + var(--il-bar-height));
}

/* -- Layout --
   No .container-m: the grid is the page, and a set of 175 icons reads better
   across the full measure than in a reading column. .padding-global still
   holds the gutter, and it is the same --space-xl the bar uses for
   --bar-padding-x, so the first tile and the first control share a left edge.
   That alignment is also why the bar carries no data-width="tool", which
   would constrain it to the container width the page no longer has. */
.icon-library-layout {
  min-width: 0;
}

/* Empty state swaps via a state class: the grid, count and callout leave
   together and the message takes their place. The message block itself is
   the one thing toggled with [hidden], after init has settled. */
.icon-library-layout.is-empty > :not(.icon-library-empty) {
  display: none;
}

/* -- Search -- */
/* A fixed measure: the bar never wraps, so the field needs a width that
   holds its placeholder and leaves the category room, rather than the
   intrinsic width the flex item would otherwise settle on. Element-qualified
   on purpose: input[type="search"] is (0,1,1) and a bare class (0,1,0) never
   lands — docs-site.css records the same trap for the glossary's field. */
input.icon-library-search {
  width: 14rem;
}

/* -- Bar --
   The chip is the whole visible trigger: the colour is faster to read than
   its name, and the name stays as the trigger's accessible name. Export
   data, so the same forced-colours opt-out as a swatch. */
.icon-library-swatch-chip {
  display: block;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  border: var(--border-s) solid var(--text-faded);
  border-radius: var(--radius-xs);
  background-color: var(--swatch-fill, transparent);
  forced-color-adjust: none;
}

/* The unit belongs to the number beside it, not to the group's name — the
   field is already named "Export size in pixels", so this is decoration. */
.icon-library-unit {
  font-size: var(--font-xs);
  color: var(--text-faded);
}

/* Wide enough for two rows of seven swatches, which is the palette's own
   shape: six neutrals and eight accents. */
.icon-library-colour-panel {
  width: 20rem;
  max-width: calc(100vw - (2 * var(--space-l)));
  /* .dropdown-menu carries no padding of its own: every child supplies its
     own inset, and .dropdown-item / .dropdown-label do. The swatch grid and
     the Custom row are neither, so they sat flush against the panel border
     while the captions above them were inset, and the last row had nothing
     below it at all. logo-tool.css hit this first with its width panel and
     solved it the same way: the rows borrow the item inset. */
  padding-block-end: var(--space-m);
}

.icon-library-colour-panel > .icon-library-swatches,
.icon-library-colour-panel > .icon-library-colour-row {
  padding-inline: var(--space-m);
}

/* -- Grid -- */
/* auto-fill, not auto-fit: a filtered result of three icons keeps its column
   width instead of stretching three tiles across the page. The gap is wide
   enough for a tile's focus ring to sit in the gutter. The track is narrower
   than it was because a tile carries no caption now, so its width is the
   drawing's, not a word's. */
.icon-library-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
  gap: var(--space-xs);
  /* The tooltip bubble is opacity: 0, not display: none, so its box exists
     at every moment and the last column's hangs past the page edge — 9px of
     horizontal page scroll with no visible cause. clip, not hidden: it never
     creates a scroll container, and the clip margin is wide enough that an
     edge tile's tooltip is still drawn in full. */
  overflow-x: clip;
  /* The margin is what keeps an edge tile's tooltip and focus ring whole:
     the clip box is the padding box without it, and the longest name
     overhangs a 4.5rem track by roughly 40px. Generous on purpose — nothing
     scrolls, so an over-wide clip box costs nothing. Where the property is
     unsupported the fallback is the padding edge, so check a first-column
     focus ring before assuming it is honoured. */
  overflow-clip-margin: 8rem;
}

/* -- Tile --
   A bare <button> (the base reset, not .button): a tile is a picture, not a
   labelled action. Its name lives in aria-label and data-tooltip, so the
   drawing is the whole visible tile. Selection is aria-current, keyed
   directly so the class and the attribute cannot desync — pressing the
   selected tile again does nothing, which is why it is not aria-pressed.

   scroll-margin keeps a tile that focus lands on clear of the docked chrome
   above it: a tile already inside the viewport does not scroll on focus, so
   without the margin it can gain focus underneath the bar. */
.icon-library-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: var(--target-min);
  aspect-ratio: 1 / 1;
  padding: var(--space-s);
  border-radius: var(--radius-s);
  color: var(--text-primary);
  scroll-margin-top: var(--scroll-offset);
}

/* The band's real height, measured by the module and written on the layout,
   so a tile focused while the band is open clears it. Measured rather than
   guessed because the band wraps to two and three rows as the page narrows. */
.icon-library-layout[style*="--il-detail-height"] .icon-library-tile {
  scroll-margin-top: calc(var(--scroll-offset) + var(--il-detail-height, 0px));
}

/* Bigger than an inline icon, because here the drawing is the content rather
   than a mark beside a word. The wrapper's -0.175em optical trim
   (design-system.css section 9) is for an icon sitting in a text run; a tile
   centres its glyph, so the trim only nudges it off centre. */
.icon-library-tile .svg-icn {
  --icon-size: 2rem;
  margin: 0;
}

.icon-library-tile:hover {
  background-color: var(--background-faded);
}

/* Inverted, not a wash: the faded scale tops out at 1.4:1 against the page,
   so a selected tile needs the selected pair (the segmented control's
   finding, design-system.css section 12). Declared after hover so a selected
   tile stays selected under the pointer. */
.icon-library-tile[aria-current="true"] {
  background-color: var(--selected-background);
  color: var(--selected-text);
}

.icon-library-tile:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 2px;
}

/* -- Detail band --
   Docked under the bar, full bleed like it, so the selected icon is in
   view wherever in the grid it was picked. Its own display beats the UA
   [hidden] rule, so hidden is restated — the same reason .bar-overflow[hidden]
   exists. Behind the bar in z so its menus hang over it. */
.icon-library-detail {
  position: sticky;
  top: var(--il-chrome-top);
  z-index: calc(var(--bar-z) - 1);
  display: block;
  border-bottom: var(--border-s) solid var(--border-primary);
  background-color: var(--background-primary);
}

.icon-library-detail[hidden] {
  display: none;
}

/* One row on a wide page, wrapping into stacked groups as it narrows. The
   inline padding matches .padding-global and --bar-padding-x, so the band's
   contents line up with the bars above and the grid below. */
.icon-library-detail-container {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-m) var(--space-l);
  padding: var(--space-m) var(--space-xl);
}

.icon-library-detail-meta {
  display: flex;
  flex-direction: column;
  /* A tag is as wide as its word. Without this the column's default stretch
     draws it as a full-width bar. */
  align-items: flex-start;
  gap: var(--space-2xs);
  min-width: 0;
  flex: 1 1 12rem;
}

.icon-library-detail-title {
  margin: 0;
  font-size: var(--font-m);
}

/* Focus lands here on a keyboard selection, so the ring has to be visible on
   a heading that is not otherwise interactive. */
.icon-library-detail-title:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 2px;
}

.icon-library-detail-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  flex: 0 1 auto;
  min-width: 0;
}

/* Format, then the two things anyone does with it. The row is a bar's
   trailing cluster in everything but its ancestor: the same cluster appears
   in QR Code and Logo Tool, and this one showed the page's 44px form select
   and 54px page buttons beside their 36px. So it takes the bar's spacing
   grammar too — controls --bar-group-gap apart, --space-s only between the
   caption and its own control, the way .bar-group captions get it. */
.icon-library-export {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bar-group-gap);
}

/* The bar's control shape, restated because the strip is not a .bar
   (design-system.css §41, "One control height" and "Quiet chrome"). The base
   input rule's width: 100% would take the whole line; min-height: 0 lifts
   its 44px floor; the buttons drop the vertical padding that would fight a
   fixed height. */
.icon-library-export select,
.icon-library-export .button {
  height: var(--bar-control-height);
  min-height: 0;
}

.icon-library-export select {
  width: auto;
  padding-top: 0;
  padding-bottom: 0;
  --input-background: var(--background-primary);
  border-radius: var(--radius-s);
}

.icon-library-export .button {
  --button-padding-y: 0;
  --button-padding-x: var(--space-m);
}

/* The base label rule's block margin and medium weight come off for the
   same reason .bar-label removes them: in a centred row it is the margin box
   that gets centred, so the caption rode above the select's centre line. */
.icon-library-export-label {
  font-size: var(--font-xs);
  color: var(--text-faded);
  margin-bottom: 0;
  margin-inline-end: var(--space-s);
  font-weight: var(--font-weight-regular);
  white-space: nowrap;
}

/* The close is a terminal cell, the way every bar's close is: welded to the
   trailing edge, the band's full height, behind a left divider. It was a
   small icon button floating in the corner, so the one control that means
   the same thing on the bar and the strip looked like two different
   controls. The cell reaches the edge with a negative end margin that undoes
   the container's own padding — not by zeroing that padding the way
   .bar-container:has(.bar-close) does, because the strip wraps and a bar does
   not: with the padding gone, a wrapped first line would run the title and
   filename flush against the viewport edge. */
.icon-library-detail .icon-library-detail-close {
  flex: 0 0 auto;
  align-self: stretch;
  margin-block: calc(-1 * var(--space-m));
  margin-inline-end: calc(-1 * var(--space-xl));
  margin-left: auto;
  border-left: var(--border-s) solid var(--border-faded);
  border-radius: 0;
  /* Descendant-qualified to (0,2,0) deliberately. .button sets
     --button-padding-x: var(--space-xl) at (0,1,0), and a bare
     .icon-library-detail-close ties it — the tie went to .button and the cell
     came out 24px-padded against the bar close's 16px. --icon-size is restated
     because the 1.3rem it had came from [data-icon-only], which comes off the
     markup with the square. Matched to .bar-close: --space-l inline, 1.3rem. */
  --button-padding-x: var(--space-l);
  --button-padding-y: 0;
  --icon-size: 1.3rem;
  --button-bg: transparent;
  --button-border: transparent;
}

.icon-library-detail .icon-library-detail-close:hover {
  background: var(--background-faded);
}

/* -- Preview stage --
   The icon at the export colour, on the ground that colour needs. Fixed greys, not
   tokens: the checkerboard is the universal "no background here" convention
   and has to read the same in light and dark — the same values as
   .logo-tool-viewport.is-transparent. The colour arrives as --il-colour from
   the script (export hex, see the header note); the size as --il-size; the
   light and dark grounds as --il-ground-light / --il-ground-dark, the brand's
   own off-white and off-black, because the stage previews an export and
   deliberately ignores the site theme.

   A fixed square in the band: the band is chrome and cannot grow to 512px,
   so the stage shows what it can and the scale note says what it did. */
.icon-library-preview {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 5rem;
  height: 5rem;
  padding: var(--space-xs);
  border-radius: var(--radius-s);
  color: var(--il-colour, currentColor);
  background-color: var(--neutral-100);
  background-size: 16px 16px;
  overflow: hidden;
}

.icon-library-preview[data-ground="light"] {
  background-image: none;
  background-color: var(--il-ground-light, #ffffff);
}

.icon-library-preview[data-ground="dark"] {
  background-image: none;
  background-color: var(--il-ground-dark, #1f1f1f);
}

/* Width-driven, height from the wrapper's own aspect-ratio: a 512px icon in
   a 5rem stage shows at the stage's width, and the scale note says so. */
.icon-library-preview .svg-icn {
  width: min(var(--il-size, 1.5rem), 100%);
  height: auto;
  margin: 0;
}

/* Not a live region: rewritten on every render, so an <output> here would
   announce on every stepper press. */
.icon-library-scale-note {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--text-secondary);
}

.icon-library-scale-note:empty {
  display: none;
}

/* Selectable in one click — the filename is what gets pasted into a brief. */
.icon-library-filename {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--text-secondary);
  font-family: var(--font-quaternary);
  overflow-wrap: anywhere;
  user-select: all;
}

.icon-library-filename:empty {
  display: none;
}

/* -- Colour swatches --
   Each is a button carrying the colour's name; the fill is brand export data
   set by the script as --swatch-fill (the logo tool's chip does the same).
   The boundary is --text-faded, not a border token: the swatch is identified
   by its edge where the fill is near the panel colour, and no border token
   clears 3:1. Selection is a drawn boundary — a thick primary border with
   the button's own padding showing through as a gap ring, so it reads on
   any fill; the outline is reserved for focus, so the two never look alike.
   The name and hex are also written in the trigger, so the choice is never
   carried by colour alone. */
.icon-library-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.icon-library-swatch {
  width: var(--target-min);
  aspect-ratio: 1 / 1;
  padding: var(--space-xs);
  border: var(--border-s) solid var(--text-faded);
  border-radius: var(--radius-xs);
}

.icon-library-swatch-fill {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--radius-xs);
  background-color: var(--swatch-fill);
  /* A colour sample is the canonical case for opting out of forced colours:
     flattened to Canvas, every swatch would be the same empty box. */
  forced-color-adjust: none;
}

.icon-library-swatch:hover {
  border-color: var(--text-primary);
}

.icon-library-swatch[aria-current="true"] {
  border-width: var(--border-l);
  border-color: var(--text-primary);
}

.icon-library-swatch:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 2px;
}

.icon-library-colour-row {
  display: flex;
  align-items: center;
  gap: var(--space-s);
}

.icon-library-colour-row input[type="color"] {
  flex: 0 0 auto;
  width: var(--target-min);
  height: var(--target-min);
}

/* -- Notes -- */
.icon-library-note,
.icon-library-error {
  margin: 0;
  font-size: var(--font-xs);
  overflow-wrap: anywhere;
}

.icon-library-note {
  color: var(--text-faded);
}

.icon-library-error {
  color: var(--status-danger);
}

/* The alert collapses WITHOUT display:none. A region absent from the
   accessibility tree when its text is written never announces, so it is
   clipped out of flow instead (the .visually-hidden declarations, for the
   reason logo-tool.css records). :empty means no child nodes at all, and a
   single space is a text node, so the script writes `message || ''`,
   never ' '. */
.icon-library-error:empty {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* -- Empty state -- */
.icon-library-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-m);
  padding: var(--space-2xl);
  text-align: center;
}

.icon-library-empty[hidden] {
  display: none;
}

.icon-library-empty .svg-icn {
  --icon-size: 2.5rem;
  margin: 0;
  color: var(--text-faded);
}

.icon-library-empty p {
  max-width: 42ch;
  margin: 0;
  color: var(--text-secondary);
}

/* -- Narrow --
   The band is chrome, and chrome that eats half a phone screen is worse than
   no band. The stage shrinks first: the grid tile the reader just pressed is
   itself a preview. Name, filename and the exports stay, because those are
   what the band is for. */
/* -- Touch --
   The tooltip is the tile's only visible name, and it needs hover, which a
   touch screen does not have: without this a reader on a phone has no route
   to a name but tapping all 175. The caption comes back where the pointer
   cannot ask for it, and the tile gives up its square to make room. */
@media (hover: none) {
  .icon-library-tile {
    aspect-ratio: auto;
    gap: var(--space-2xs);
    font-size: var(--font-2xs);
    line-height: var(--line-height-l);
    text-align: center;
  }

  .icon-library-tile::after {
    content: attr(aria-label);
    max-width: 100%;
    overflow-wrap: anywhere;
    color: var(--text-secondary);
  }

  .icon-library-tile[aria-current="true"]::after {
    color: inherit;
  }
}

@media (max-width: 640px) {
  .icon-library-preview {
    width: 3rem;
    height: 3rem;
  }

  /* A hard ceiling, because the band is chrome and the grid is the page. If
     the exports still do not fit, the band scrolls inside itself rather than
     pushing the icons off the screen. */
  .icon-library-detail-container {
    max-height: 45dvh;
    overflow-y: auto;
  }
}

/* -- Forced colours --
   Backgrounds flatten, so the fill that carries selection disappears; the
   system's own selected pair replaces it (the segmented control's block). The
   preview keeps its own colours for the same reason the swatch fill does. */
@media (forced-colors: active) {
  .icon-library-tile[aria-current="true"] {
    forced-color-adjust: none;
    background-color: Highlight;
    color: HighlightText;
  }

  .icon-library-tile[aria-current="true"]:focus-visible {
    outline-color: CanvasText;
  }

  .icon-library-preview {
    forced-color-adjust: none;
  }
}

/* -- Page transitions --
   A sticky element re-anchors to the wrapper's scrollport the moment a Barba
   leave starts, the same jump the bar and the TOC had; the recorded fix is
   a fade while the page animates, not a change of positioning. The one
   transition this file owns, so it carries its own reduced-motion guard. */
body.is-animating .icon-library-detail {
  opacity: 0;
  transition: opacity var(--duration-xs) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  body.is-animating .icon-library-detail {
    transition: none;
  }
}
