/* ==========================================================================
   DarkWeb Monitor — icons
   --------------------------------------------------------------------------
   Sizing, alignment and state colouring for the SVG set built by
   contrib/build-icon-sprite.php. Markup comes from DWM\Ui\Icon.

   Everything here is currentColor or a token from tokens.css. No hex values:
   an icon takes the colour of the text it sits in, so it re-themes for free
   and a state glyph can never disagree with the --state-* palette.

   One thing to know before editing. render() emits a sprite reference, and the
   shadow tree a <use> builds inherits from the host <svg class="icon"> — not
   from the sprite document. Only *inherited* SVG properties (stroke-width,
   stroke, fill, stroke-linecap, stroke-linejoin, colour) reach the geometry.
   A rule like `.icon path { … }` will do nothing. Style the .icon element.
   ========================================================================== */

/* --- base ----------------------------------------------------------------- */

.icon {
    /* Scales with the type it sits beside, which is why the default is em and
       not a pixel step. The explicit steps below opt out of that. */
    inline-size: var(--icon-size, 1.15em);
    block-size: var(--icon-size, 1.15em);
    flex: none;
    stroke-width: var(--icon-stroke, 2);

    /* The 24-unit grid is drawn with rounded ends; keeping the joins here as
       well means an inlined icon and a sprite reference render identically. */
    stroke-linecap: round;
    stroke-linejoin: round;

    /* Optical centring against the x-height. Icons are drawn on a square grid
       whose visual centre sits above the text baseline, so without this every
       icon in a run of prose rides high. */
    vertical-align: -0.185em;

    /* An icon is never the thing being clicked; the button around it is. The
       exception below is a named icon, which is content and may be carrying a
       <title> tooltip that needs a hover to fire. */
    pointer-events: none;
    overflow: visible;
}
.icon[role='img'] { pointer-events: auto; }

/* Inside anything laid out with flex or grid, baseline nudging is wrong —
   the container is doing the alignment. */
:is(.btn, .chip, .state-badge, .flash, .stat, .tab, .menu-item, .breadcrumb) > .icon,
.icon-inline > .icon {
    vertical-align: 0;
}

/* --- size scale ----------------------------------------------------------- */

/* Fixed steps for chrome that must not resize with its label: table row
   actions, the masthead, empty-state art. */
.icon--xs { --icon-size: 12px; }
.icon--sm { --icon-size: 14px; }
.icon--md { --icon-size: 16px; }
.icon--lg { --icon-size: 20px; }
.icon--xl { --icon-size: 28px; }
.icon--2xl { --icon-size: 40px; }

/* Empty states and 404-style art, where the glyph is the illustration. */
.icon--hero {
    --icon-size: 64px;
    --icon-stroke: 1.25;
    color: var(--text-faint);
    opacity: .8;
}

/* --- stroke weight -------------------------------------------------------- */

/* The set is drawn at 2. Thinner reads better large, heavier reads better at
   12px or against a saturated fill where the strokes start to close up. */
.icon--hairline { --icon-stroke: 1; }
.icon--thin     { --icon-stroke: 1.5; }
.icon--bold     { --icon-stroke: 2.5; }
.icon--heavy    { --icon-stroke: 3; }

/* Brand marks are solid fills from Simple Icons — stroke-width is meaningless
   on them and the weight modifiers must not appear to do anything. */
[class*='icon--brand-'] { --icon-stroke: 0; }

/* --- colour --------------------------------------------------------------- */

.icon--dim    { color: var(--text-dim); }
.icon--faint  { color: var(--text-faint); }
.icon--accent { color: var(--accent); }
.icon--ok     { color: var(--ok); }
.icon--warn   { color: var(--warn); }
.icon--bad    { color: var(--bad); }
.icon--info   { color: var(--info); }
.icon--brand  { color: var(--brand-red); }

/* --- site state ----------------------------------------------------------- */

/* components.css turns data-state into --state. An icon either carries the
   attribute itself or sits inside an element that does, and takes the colour
   from whichever is nearest. Shape already carries the meaning — the five
   state glyphs are a circle, an octagon, a triangle, a square and an
   hourglass — so this is reinforcement, not the signal. Contract §6. */
.icon[data-state],
[data-state] .icon--state {
    color: var(--state, var(--text-faint));
}

/* A state glyph on a coloured chip or a solid badge has to stop asserting its
   own colour and follow the text it is set in. */
.state-badge--solid .icon,
.btn--primary .icon,
.chip--solid .icon {
    color: inherit;
}

/* Enough weight that the difference between the octagon and the square still
   reads at 12px in a dense admin table. */
.icon--state-alive,
.icon--state-blocked,
.icon--state-broken,
.icon--state-dead,
.icon--state-waiting {
    --icon-stroke: 2.25;
}

/* --- an icon that failed to resolve --------------------------------------- */

/* DWM\Ui\Icon draws the fallback glyph for an unregistered name rather than
   emitting nothing. Tint it so a typo is obvious in review instead of quietly
   shipping a question mark. */
.icon--missing { color: var(--bad); }

/* --- motion --------------------------------------------------------------- */

.icon--spin { animation: icon-spin 900ms linear infinite; }

/* Slower and eased: for a re-check that is genuinely in flight rather than a
   determinate spinner. */
.icon--turning { animation: icon-spin 1.6s var(--ease) infinite; }

/* Emphasis on something that just changed, e.g. a fresh flash message. */
.icon--pulse { animation: icon-pulse 1.8s var(--ease) infinite; }

@keyframes icon-spin {
    to { transform: rotate(360deg); }
}

@keyframes icon-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: .45; }
}

/* --- reduced motion ------------------------------------------------------- */

/* A spinner that cannot spin still has to say "working", so it becomes a slow
   opacity breath rather than nothing at all. Removing the affordance outright
   would leave a user who asked for less motion with no progress feedback. */
@media (prefers-reduced-motion: reduce) {
    .icon--spin,
    .icon--turning {
        animation: icon-pulse 2.4s steps(2, jump-none) infinite;
    }
    .icon--pulse {
        animation: none;
        opacity: .7;
    }
}

/* --- composition ---------------------------------------------------------- */

/* Icon + label, the shape most buttons, chips and menu items want. */
.icon-inline {
    display: inline-flex;
    align-items: center;
    gap: .4em;
}

/* Square hit area for an icon-only control. The affordance is the box, so it
   is sized in rem and does not shrink with a small font. */
.icon-btn {
    display: inline-grid;
    place-items: center;
    inline-size: 2rem;
    block-size: 2rem;
    padding: 0;
    color: var(--text-dim);
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: color var(--speed) var(--ease),
                background-color var(--speed) var(--ease),
                border-color var(--speed) var(--ease);
}
.icon-btn:hover {
    color: var(--text);
    background: var(--surface-hover);
    border-color: var(--border);
}
.icon-btn[aria-pressed='true'] {
    color: var(--accent);
    border-color: var(--border-strong);
}
.icon-btn--danger:hover { color: var(--bad); }
.icon-btn--sm { inline-size: 1.65rem; block-size: 1.65rem; }
.icon-btn--lg { inline-size: 2.4rem; block-size: 2.4rem; }

/* A tinted disc behind an icon, for stat tiles and empty states. */
.icon-badge {
    display: inline-grid;
    place-items: center;
    inline-size: 2.25rem;
    block-size: 2.25rem;
    color: var(--accent);
    background: color-mix(in srgb, currentColor 14%, transparent);
    border: 1px solid color-mix(in srgb, currentColor 26%, transparent);
    border-radius: var(--radius-pill);
}
.icon-badge[data-state] {
    color: var(--state, var(--text-faint));
}
.icon-badge--square { border-radius: var(--radius-sm); }
.icon-badge--lg { inline-size: 3rem; block-size: 3rem; }

/* --- print ---------------------------------------------------------------- */

/* A greyscale printout loses every --state colour, which is exactly why the
   five states differ in silhouette. Force full contrast so the shape survives
   a cheap laser printer. */
@media print {
    .icon { color: var(--ink); }
    .icon--hero { opacity: 1; }
}

/* --- forced colours ------------------------------------------------------- */

@media (forced-colors: active) {
    .icon { color: CanvasText; }
    .icon--missing { color: LinkText; }
}
