/*
  GLOBAL stylesheet. forge-ui-shell.html §08 fixes what may live here and what
  may not:

    GLOBAL   — both token sets as custom properties (that is forge-tokens.css,
               generated), font stacks, resets, @keyframes, and the focus-ring
               mixin. NOTHING ELSE.
    ISOLATED — layout, size and state selectors, in *.razor.css, always reading
               tokens through var().

  NO HEX LITERALS. Not one, anywhere but the generated forge-tokens.css.
  UiTokenDisciplineTests fails the build on a raw colour in this tree, and the
  reason is not tidiness: a hardcoded colour does not survive the theme switch,
  so it is a defect that only shows up on somebody else's machine.
*/

/* ── reset ────────────────────────────────────────────────────────────────── */

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

html,
body {
    margin: 0;
    padding: 0;
}

body {
    background: var(--forge-color-bg);
    color: var(--forge-color-text);
    font-family: var(--forge-type-ui-family);
    font-size: var(--forge-type-ui-size);
    font-weight: var(--forge-type-ui-weight);
    line-height: var(--forge-type-ui-line-height);
    -webkit-font-smoothing: antialiased;
}

/* ── type roles ───────────────────────────────────────────────────────────── */
/*
  Five roles, no webfont, zero weight budget spent: system sans for chrome, a
  system serif for corpus prose, mono for every identifier and value. The serif
  is the notebook signal and costs nothing.
*/

.fg-display {
    font-family: var(--forge-type-display-family);
    font-size: var(--forge-type-display-size);
    font-weight: var(--forge-type-display-weight);
    line-height: var(--forge-type-display-line-height);
}

.fg-prose {
    font-family: var(--forge-type-prose-family);
    font-size: var(--forge-type-prose-size);
    font-weight: var(--forge-type-prose-weight);
    line-height: var(--forge-type-prose-line-height);
    max-width: var(--forge-type-prose-measure);
}

.fg-label {
    font-family: var(--forge-type-label-family);
    font-size: var(--forge-type-label-size);
    font-weight: var(--forge-type-label-weight);
    line-height: var(--forge-type-label-line-height);
    letter-spacing: var(--forge-type-label-tracking);
    text-transform: uppercase;
    color: var(--forge-color-text-dim);
}

.fg-data {
    font-family: var(--forge-type-data-family);
    font-size: var(--forge-type-data-size);
    font-weight: var(--forge-type-data-weight);
    line-height: var(--forge-type-data-line-height);
}

/* ── the focus ring ───────────────────────────────────────────────────────── */
/*
  2px background gap, then 2px of accent. NEVER REMOVED — forge-design-language
  §04 says so in as many words, and a primitive that drops it is a keyboard user
  losing their place, not a cosmetic preference.

  Drawn as a box-shadow rather than an outline so it follows the border radius,
  and applied on :focus-visible so a mouse click does not paint it.

  Danger and confirm keep their own hue: they set --fg-focus-hue locally, which
  is why the mixin reads a variable instead of the token directly.

  THE GAP IS "BACKGROUND", AND ON THE RAIL THE BACKGROUND IS NOT bg (#184). The
  gap token resolves to --forge-color-bg, which is paper in light and #17161A in
  dark — on the graphite rail that is a bright halo in one theme and no separation
  at all in the other. So the gap reads --fg-focus-gap first, and a region whose
  field is its own declares it: FgNavRail sets it to railBg. Same indirection, and
  the same reason, as --fg-focus-hue.
*/

.fg-focusable {
    --fg-focus-hue: var(--forge-component-focus-ring-color);
}

.fg-focusable:focus-visible {
    outline: none;
    box-shadow:
        0 0 0 var(--forge-component-focus-gap-width) var(--fg-focus-gap, var(--forge-component-focus-gap-color)),
        0 0 0 calc(var(--forge-component-focus-gap-width) + var(--forge-component-focus-ring-width)) var(--fg-focus-hue);
}

/*
  The same ring, drawn around a CONTAINER whose border is the visible control and
  whose focusable element sits inside it — FgSearchInput's 30px box is one glyph,
  one borderless field and one shortcut hint, and a ring hugging the field alone
  would read as a second control inside the first.

  A separate class rather than a second selector on .fg-focusable, because
  :focus-within is a different question from :focus-visible: it is true while a
  descendant holds focus by any means, including a mouse click. The elements it is
  used on are text fields, where a click-focused ring is correct and is what a
  native input draws anyway.
*/

.fg-focusable-within {
    --fg-focus-hue: var(--forge-component-focus-ring-color);
}

.fg-focusable-within:focus-within {
    outline: none;
    box-shadow:
        0 0 0 var(--forge-component-focus-gap-width) var(--fg-focus-gap, var(--forge-component-focus-gap-color)),
        0 0 0 calc(var(--forge-component-focus-gap-width) + var(--forge-component-focus-ring-width)) var(--fg-focus-hue);
}

/* ── keyframes ────────────────────────────────────────────────────────────── */
/*
  Shimmer is an OPACITY PULSE on skeletonBase, not a travelling gradient — the
  design calls for a pulse and a gradient would need a second colour stop that is
  not a token. Held static under prefers-reduced-motion, per §02.
*/

@keyframes fg-shimmer {
    0% { opacity: 0.55; }
    50% { opacity: 1; }
    100% { opacity: 0.55; }
}

/*
  A popover arriving. An opacity fade and nothing else: the surface is positioned
  against its anchor, so anything that moved it would be movement away from the
  thing it belongs to. Neutralised by the reduced-motion block below like every
  other animation here.
*/

@keyframes fg-popover-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

/*
  Heat, then quench: the indexing animation, 3.6s, runs only while a corpus build
  is live. This is the ONE in-app use of ember, and it is why the keyframes name
  brand variables — the element carrying it must also carry .forge-ember-licensed
  or none of these resolve.
*/

@keyframes fg-quench {
    0% { fill: var(--forge-brand-ink); }
    12% { fill: var(--forge-brand-ember-deep); }
    30% { fill: var(--forge-brand-ember); }
    46% { fill: var(--forge-brand-ember-glow); }
    62% { fill: var(--forge-brand-ember); }
    82% { fill: var(--forge-brand-ember-deep); }
    100% { fill: var(--forge-brand-ink); }
}

/*
  THE GLOW, which §06 specifies as a separate blurred copy of the aperture path —
  "never a drop shadow, never a gradient on the mass". Both of those would be
  cheaper and both would be wrong: a drop-shadow spreads from the mass as well,
  and a gradient would need colour stops that are not tokens.

  Opacity only. The blur and the fill live on the element in FgHearthMark's own
  stylesheet, so this stays a pure timing curve. 0.8 at 46% is §06's stated peak,
  and it lands on the same stop as the quench ramp's brightest frame — which is
  why frame 3 is also the one held under prefers-reduced-motion.
*/

@keyframes fg-glow {
    0%, 10% { opacity: 0; }
    46% { opacity: 0.8; }
    70% { opacity: 0.35; }
    100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
    }
}

/* ── MudBlazor ────────────────────────────────────────────────────────────── */
/*
  MudBlazor supplies the app frame and the overlay host; Forge's own primitives
  are hand-built to the design references and do not wrap Mud equivalents.

  Its palette is REMAPPED onto Forge tokens here rather than restated as a
  MudTheme in C#. meridian keeps a palette in CSS and a second one in
  MeridianTheme.cs and needs an architecture test to keep the two honest; Forge
  keeps one, generated, and this block is the only bridge to it.
*/

.mud-theme-primary,
.mud-application-layout-content {
    --mud-palette-primary: var(--forge-color-accent);
    --mud-palette-primary-text: var(--forge-color-accent-text);
    --mud-palette-surface: var(--forge-color-bg-panel);
    --mud-palette-background: var(--forge-color-bg);
    --mud-palette-text-primary: var(--forge-color-text);
    --mud-palette-text-secondary: var(--forge-color-text-mid);
    --mud-palette-lines-default: var(--forge-color-border);
    --mud-palette-success: var(--forge-color-pass);
    --mud-palette-warning: var(--forge-color-warn);
    --mud-palette-error: var(--forge-color-fail);
    --mud-palette-info: var(--forge-color-info);
}

/* ── layout helpers ───────────────────────────────────────────────────────── */
/*
  Spacing base is 4px. These exist so isolated component CSS does not each invent
  its own gap ladder.
*/

.fg-stack {
    display: flex;
    flex-direction: column;
    gap: calc(var(--forge-component-row-spacing-base) * 2);
}

.fg-row {
    display: flex;
    align-items: center;
    gap: calc(var(--forge-component-row-spacing-base) * 2);
}

/* ── asserted, not verified ───────────────────────────────────────────────── */
/*
  forge-primitives-ii.html §03. An agent-asserted fact is not a failed fact — it is
  an unconfirmed one — so it must not borrow the failure palette: a red machine name
  reads as a broken machine. The distinction is carried by FORM, never by colour,
  which also means it survives greyscale printing and both themes unchanged.

  A DOTTED UNDERLINE ON THE VALUE ITSELF, and that is why it is global rather than a
  rule in each component's isolated stylesheet. §03's own argument for it is that the
  status must TRAVEL WITH THE VALUE when a row is copied into a ticket; the corollary
  is that the mark has to be identical everywhere, and three stylesheets each drawing
  their own dotted line is exactly how "unconfirmed" would come to mean three
  slightly different things. There is one rule, in one place, and
  UiAssertedValueTests keeps it that way.

  It is not decorative and it is not only visual: the element carries its own title,
  so a reader who cannot see a 1px line can still ask what the mark means.
*/

.fg-asserted {
    border-bottom: 1px dotted var(--forge-color-border-strong);
    padding-bottom: 1px;
}
