/* ============================================================
   DEFEX Workflow Explorer — /workflows

   Everything is scoped under .wfx. The design's literal colours are
   bridged onto --mud-palette-* here and used as tokens below, so the
   screen tracks the live theme (light, dark, tenant-red) from one
   place instead of carrying hex values down the file.

   COLOUR STRATEGY — "five paths, five inks".

   This is a teaching surface, not a register screen, and the thing it teaches is
   that DEFEX has FIVE distinct paths through it. So colour here encodes WHICH
   PATH and HOW FAR ALONG IT — the only two facts on the screen worth encoding —
   and nothing else. Every card carries one ink: its spine, its icon tile, the
   filled run of its rail, the stage you are standing on, the stages behind you,
   the open topic and the step badges. Copy, wells, rules and the primary button
   stay register-neutral, so the ink is the only chromatic thing on a card and it
   always means the same thing.

   Three notes on why it is built the way it is:

   - The inks are declared as HUES and resolved through the same colour-mix
     recipe utilities.css uses for the status tokens: 66% hue into
     --mud-palette-text-primary for the ink itself, 12% / 30% into
     --mud-palette-surface for its tint and its line. Because both of those
     palette variables are swapped by MudThemeProvider on :root, ONE declaration
     lands correctly in light, in dark and inside the dialog provider — where no
     theme marker reaches (see the floating-window section at the foot of this
     file). Every resulting pair clears WCAG AA in both themes; re-check with a
     contrast tool before changing a hue or a ratio.
   - --wfx-accent-fg is --mud-palette-surface rather than white. On light the ink
     is dark and the surface is white; on dark the ink lifts and the surface is
     midnight. The label on a filled chip therefore inverts with the theme for
     free instead of going white-on-pale.
   - The five inks do NOT track the tenant-operator red, unlike --wfx-accent's
     house fallback. That is deliberate and it is the one place the red carve-out
     in DESIGN.md does not apply: this screen renders authored product education
     and reads NO tenant data, so there is no "whose data am I looking at" to
     signal. The chrome around it still turns red. If this screen ever grows a
     tenant-data element, that element takes the palette, not an ink.
   ============================================================ */

.wfx {
    /* Design D "Glass Cohesion" — SOLID tier. The card fill is the house card token, so this
       screen's bespoke cards read the same as every .custom-border card on the page ground:
       78% white, not the opaque --mud-palette-surface they used to paint. It stays ONE token
       because the inner plates (the chip circles, the arrow badge that has to hide the connector
       behind its glyph) are meant to be "the card's own colour" — they have to move with it. */
    --wfx-surface: var(--defex-card-bg, var(--mud-palette-surface));
    /* NOT --mud-palette-background-gray: utilities.css resolves that to `transparent` inside
       .main-container so page grounds show the Design D gradient, which left the panel and every
       step well with no fill at all. This screen needs a real filled well, so it names the token
       that IS one. */
    --wfx-well: var(--defex-inner-bg, #F4F6FA);
    --wfx-fg: var(--mud-palette-text-primary);
    --wfx-fg-2: var(--mud-palette-text-secondary);
    --wfx-line: var(--mud-palette-lines-default);
    --wfx-line-strong: var(--mud-palette-lines-inputs);
    /* Signal blue, and NOT --mud-palette-primary: DEFEX's Mud "primary" is register-ink (#0B1220),
       the near-black the filled buttons use. The cobalt accent this screen fills its rail with is
       the palette's SECONDARY. Naming primary here painted the progress line black. */
    --wfx-accent: var(--mud-palette-secondary);

    /* Resolved FROM --wfx-accent, so a .wfx-a-* block below restates only the ink itself and these
       four follow it. Ratios mirror the status tokens in utilities.css (12% fill, 30% line) so a
       tinted well on this screen sits at the same strength as a status pill everywhere else. */
    --wfx-accent-fg: var(--mud-palette-surface);
    --wfx-accent-soft: color-mix(in srgb, var(--wfx-accent) 12%, var(--mud-palette-surface));
    --wfx-accent-line: color-mix(in srgb, var(--wfx-accent) 30%, var(--mud-palette-surface));
    /* The faintest step, for a whole card head or panel — a wash, not a fill. Mixed into
       `transparent` rather than a surface so it can sit over the card's own translucent Design D
       fill without knocking the glass back to opaque. */
    --wfx-accent-wash: color-mix(in srgb, var(--wfx-accent) 6%, transparent);

    /* Two radii, not one. --wfx-radius is the INNER radius — topic buttons, the steps card, step
       wells, the note — and takes the house nested-panel value. The card itself takes the panel
       radius separately below: 18px on a 26px step badge would be a pill, so the card's radius
       cannot be the general token. 5px was the old Mud default and sat visibly squarer than the
       PageHeader above it. */
    --wfx-radius: var(--defex-radius-inner, 10px);
    /* The card's own separation: the house card shadow, deep and soft, so the card lifts off the
       blue ground rather than sitting on it with a 1px Material drop. --wfx-shadow stays the tight
       one — it is still right for the small objects (chip circles, arrow plates) ON the card. */
    --wfx-card-shadow: var(--defex-card-shadow, 0 16px 44px rgba(15, 23, 43, 0.10));
    --wfx-card-edge: var(--defex-card-edge, var(--wfx-line));
    --wfx-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .06);
    --wfx-shadow-lift: 0 3px 6px rgba(0, 0, 0, .08), 0 1px 3px rgba(0, 0, 0, .06);
    --wfx-dur: 160ms;
    --wfx-ease: cubic-bezier(0.4, 0.0, 0.2, 1);
    /* A confident arrival with no overshoot, for the one-shot entrances (cards, panel, steps).
       --wfx-ease is the standard state curve and stays on the interaction transitions; mixing the
       two roles is how a screen ends up with everything easing like everything else. */
    --wfx-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
    /* How long one connector takes to fill. Paired with the 70ms per-segment cadence the component
       writes into --wfx-d: successive segments overlap by two thirds, which is what makes the run
       read as one travelling line rather than as four separate bars filling in turn. */
    --wfx-travel: 240ms;
}

/* No max-width, unlike the mock: PageHeader above this is full-bleed, so a centred 1240px column
   would sit lopsided under it (the body's `ma-4` carries !important, so it cannot be auto-centred
   anyway). Line length is held by the elements that need it — the card blurbs, the panel copy —
   rather than by a cap on the page. */

/* ---- the five inks ---------------------------------------- */
/* One block per path. Each declares ONE hue; the tint, the line and the label colour all fall out
   of it through the tokens above, and both themes are covered by that one declaration — see the
   header. The class is written by WorkflowCatalog.Accent, which names a role ("teal") and never a
   colour, so re-inking a path is an edit here and nowhere else.

   The hues are chosen at 500/600 strength because the recipe darkens them into the page's ink on
   light and lifts them on dark; picking a hue that is already deep leaves a brown or a navy. They
   are spread round the wheel far enough to be told apart, but colour is never the only code — every
   card also carries its own name and icon, and the cards are stacked, never side by side.

   Cobalt is Signal Blue itself and belongs to Client Setup, the path everything else hangs off:
   the house colour marks the house's foundation, and the other four read as departures from it. */

.wfx-a-cobalt  { --wfx-hue: #155DFC; }   /* Client Setup — the house blue, the root of the tree */
.wfx-a-teal    { --wfx-hue: #0891B2; }   /* Routine Maintenance — cool, scheduled, uneventful */
.wfx-a-ochre   { --wfx-hue: #D97706; }   /* Defect Rectification — a fault found, warm but not alarmed */
.wfx-a-magenta { --wfx-hue: #DB2777; }   /* Reactive Work — unplanned, hot, arriving from outside */
.wfx-a-violet  { --wfx-hue: #7C3AED; }   /* Projects — long, deliberate, staged */

/* Applied once, so a new path is a hue and nothing else. 66% into text-primary is the status-token
   ratio from utilities.css: it clears AA against the surface in light AND dark from a single line,
   because text-primary is what MudThemeProvider swaps.

   THE TINT AND THE LINE ARE RESTATED HERE, and that is not duplication — it is the fix for a bug
   that is invisible until you measure it. A custom property whose value contains var() is resolved
   at the element it is DECLARED on, then inherited as a finished value. --wfx-accent-soft declared
   up on .wfx therefore bakes in .wfx's own --wfx-accent (the house blue) and keeps it all the way
   down; a card overriding --wfx-accent below does not reach back and re-derive it. Measured: every
   step badge on the screen came out pale cobalt while the chip beside it was ochre. The .wfx copies
   stay as the fallback for anything with no ink of its own; these are the ones that matter. */
.wfx-a-cobalt,
.wfx-a-teal,
.wfx-a-ochre,
.wfx-a-magenta,
.wfx-a-violet {
    --wfx-accent: color-mix(in srgb, var(--wfx-hue) 66%, var(--mud-palette-text-primary));
    --wfx-accent-soft: color-mix(in srgb, var(--wfx-accent) 12%, var(--mud-palette-surface));
    --wfx-accent-line: color-mix(in srgb, var(--wfx-accent) 30%, var(--mud-palette-surface));
    --wfx-accent-wash: color-mix(in srgb, var(--wfx-accent) 6%, transparent);
}


/* On dark the card sits on the midnight ground, so the surfaces and the rim change; the inks do
   not, because they already resolve through the swapped palette variables.

   BOTH markers, deliberately. MudThemeProvider swaps CSS variables but never stamps a class: the
   web host's MainLayout stamps `defex-theme-dark` on .main-container (the page lives there) and the
   mobile-frame preview stamps `mud-theme-dark` on its screen wrapper. Naming only the Mud one — as
   this block used to — meant none of it ever applied on the web, and the card painted a near-white
   Design D fill on the midnight ground. */
.defex-theme-dark .wfx,
.mud-theme-dark .wfx {
    /* --defex-inner-bg is a light-only literal, so the well is named outright here rather than
       inherited: DESIGN.md's midnight-section, one step below the card's surface. */
    --wfx-well: #101B30;
    /* Same reason as the well above: --defex-card-bg is a light-only literal
       (#ffffffc7 — Design D has no dark glass-alpha counterparts yet), so on dark the card goes
       back to the palette surface rather than painting near-white. Design D is light-only by
       design; dark should look unchanged, not worse. */
    --wfx-surface: var(--mud-palette-surface);
    --wfx-card-shadow: var(--wfx-shadow);
    /* And the white rim is a highlight against the BLUE ground; on a dark card it would be a
       glowing outline, so dark falls back to the ordinary line. */
    --wfx-card-edge: var(--wfx-line);
}


/* ---- workflow card ---------------------------------------- */

.wfx-stack {
    display: flex;
    flex-direction: column;
    gap: 24px;
}

/* SOLID tier. The same four declarations .custom-border resolves to in utilities.css, written out
   because this card is bespoke markup rather than a MudPaper — the white rim reads as a highlight
   along the edge against the blue ground, and the shadow (not the border) is what separates the
   card from the page. No backdrop-filter: blur belongs to the three GLASS surfaces (app bar, nav,
   page-header chrome) and is applied globally already. */
.wfx-card {
    background: var(--wfx-surface);
    border: 1px solid var(--wfx-card-edge);
    border-radius: var(--defex-radius-panel, 18px);
    /* The path's ink as a 3px spine down the left edge, riding along with the card shadow in one
       declaration. An INSET stripe rather than a thicker left border, per DESIGN.md → Shapes: a
       border change reflows the contents, and five cards whose text starts 3px further right than
       every other card on the product would be exactly the kind of drift this rule exists to stop.
       At rest, with nothing selected, this stripe and the icon tile are the whole colour story —
       five inks down the left margin, readable as five paths before a word is read. */
    box-shadow: inset 3px 0 0 var(--wfx-accent), var(--wfx-card-shadow);
    overflow: hidden;
    /* The stack is genuinely a list of five things, so it is allowed to arrive as one — 60ms apart,
       four steps of stagger, done inside a third of a second. `both` holds the from-state so the
       later cards do not flash at full opacity before their turn. --wfx-i is written on the element
       by the page; a card with no index (none today) simply arrives first. */
    animation: wfx-card-in 460ms var(--wfx-ease-out) calc(var(--wfx-i, 0) * 60ms) both;
}

.wfx-card__head {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 24px 28px 16px;
    border-bottom: 1px solid var(--wfx-well);
    /* A wash, not a fill: the ink at 6% fading out by the two-thirds mark, so the head reads as
       tinted paper near the spine and plain paper under the blurb. Mixed into `transparent` so the
       card's Design D translucency survives underneath it. */
    background: linear-gradient(100deg, var(--wfx-accent-wash), transparent 62%);
}

.wfx-card__ic {
    width: 36px;
    height: 36px;
    flex: none;
    /* The house entity-tile radius, not the inner-panel one: this is an icon tile, and it is the
       same object PageHeader draws directly above it. */
    border-radius: var(--defex-radius-tile, 12px);
    /* Was the near-black ink shared by all five cards. Now the path's own — this tile and the spine
       beside it are what a reader picks the card out by when they come back to the screen. */
    background: var(--wfx-accent);
    color: var(--wfx-accent-fg);
    display: flex;
    align-items: center;
    justify-content: center;
}

.wfx-card__ic .mud-icon-root { font-size: 20px; width: 20px; height: 20px; }

.wfx-card__headtext { flex: 1; min-width: 0; }

.wfx-card__titlerow {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.wfx-card__title {
    margin: 0;
    font-size: 20px;
    font-weight: 600;
    color: var(--wfx-fg);
}

.wfx-card__count {
    font-size: 12px;
    color: var(--wfx-fg-2);
}

.wfx-card__blurb {
    margin: 4px 0 0;
    max-width: 720px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--wfx-fg-2);
}

/* ---- the rail --------------------------------------------- */

/* The rail scrolls sideways rather than wrapping: a workflow read as two
   rows of stages stops reading as one path. */
.wfx-rail {
    display: flex;
    align-items: flex-start;
    padding: 28px 28px 24px;
    overflow-x: auto;
}

.wfx-chip {
    flex: none;
    width: 98px;
    background: transparent;
    border: 0;
    padding: 0;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    font-family: inherit;
    text-transform: none;
    color: inherit;
}

.wfx-chip__circle {
    width: 58px;
    height: 58px;
    border-radius: 999px;
    background: var(--wfx-surface);
    border: 1px solid var(--wfx-line);
    color: var(--wfx-fg);
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: var(--wfx-shadow);
    transition: transform var(--wfx-dur) var(--wfx-ease),
                box-shadow var(--wfx-dur) var(--wfx-ease),
                background var(--wfx-dur) var(--wfx-ease),
                border-color var(--wfx-dur) var(--wfx-ease),
                color var(--wfx-dur) var(--wfx-ease);
}

.wfx-chip__circle .mud-icon-root { font-size: 26px; width: 26px; height: 26px; }

.wfx-chip:hover .wfx-chip__circle,
.wfx-chip:focus-visible .wfx-chip__circle {
    border-color: var(--wfx-accent);
    transform: translateY(-4px);
    box-shadow: var(--wfx-shadow-lift);
}

.wfx-chip:focus-visible { outline: none; }
.wfx-chip:focus-visible .wfx-chip__circle { outline: 2px solid var(--wfx-accent); outline-offset: 2px; }

/* Three states on the rail, and they are three different weights of one ink rather than three
   colours: BEHIND you the chip is tinted and outlined, ON you it is filled and lifted, AHEAD it
   is the plain grey it always was. Read left to right that is a colour gradient of progress, which
   is the same thing the filled connectors say — the two have to agree or the rail reads as two
   competing highlights. */

/* And they light IN ORDER. --wfx-d is the moment the run reaches this stage, written on the chip
   by the component (ChipDelayMs) — a chip already behind the run gets 0 and changes immediately,
   a chip the run is still travelling towards waits for it. Deselecting removes the class and with
   it the delay, so closing a card is instant: you are not travelling anywhere. */
.wfx-chip.is-done .wfx-chip__circle,
.wfx-chip.is-sel .wfx-chip__circle,
.wfx-chip.is-done .wfx-chip__num,
.wfx-chip.is-done .wfx-chip__label,
.wfx-chip.is-sel .wfx-chip__label { transition-delay: var(--wfx-d, 0ms); }

.wfx-chip__num,
.wfx-chip__label { transition: color var(--wfx-dur) var(--wfx-ease); }

.wfx-chip.is-done .wfx-chip__circle {
    background: var(--wfx-accent-soft);
    border-color: var(--wfx-accent-line);
    color: var(--wfx-accent);
}

.wfx-chip.is-done .wfx-chip__num,
.wfx-chip.is-done .wfx-chip__label { color: var(--wfx-accent); }

.wfx-chip.is-sel .wfx-chip__circle {
    background: var(--wfx-accent);
    border-color: var(--wfx-accent);
    color: var(--wfx-accent-fg);
    /* The static transform is the resting state; wfx-pop's `both` overrides it for the whole
       delay-plus-duration window, which is exactly what holds the chip at rest size until the ink
       actually gets here. Without `both` the chip would jump to 1.1 on click and pop late. */
    transform: scale(1.1);
    animation: wfx-pop 340ms var(--wfx-ease) var(--wfx-d, 0ms) both;
}

.wfx-chip.is-sel .wfx-chip__label { color: var(--wfx-accent); }

.wfx-chip.is-sel:hover .wfx-chip__circle { transform: scale(1.1); }

.wfx-chip__meta {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}

.wfx-chip__num {
    font-size: 10px;
    font-weight: 500;
    letter-spacing: .1em;
    color: var(--wfx-fg-2);
}

.wfx-chip__label {
    font-size: 13px;
    font-weight: 500;
    color: var(--wfx-fg);
    text-align: center;
    line-height: 1.3;
}

.wfx-chip.is-sel .wfx-chip__label { font-weight: 600; }

/* ---- the connector ---------------------------------------- */

.wfx-link {
    flex: 1;
    min-width: 22px;
    height: 58px;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
}

.wfx-link__base,
.wfx-link__fill {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
}

.wfx-link__base { background: var(--wfx-line); }

/* THE RUN. Each segment fills left-to-right after --wfx-d, the offset the component works out from
   how far the selection actually moved (LinkDelayMs). Segments that were already lit get 0 and
   never re-run — their transform is unchanged, so the browser has nothing to transition. Un-filling
   is instant on purpose: closing a card should not play the journey backwards. */
.wfx-link__fill {
    background: var(--wfx-accent);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform var(--wfx-travel) cubic-bezier(0, 0, .2, 1) var(--wfx-d, 0ms);
}

.wfx-link.is-filled .wfx-link__fill { transform: scaleX(1); }
.wfx-link:not(.is-filled) .wfx-link__fill { transition-delay: 0ms; }

/* The light itself. Inset to the segment so translateX(100%) lands the dot exactly on the far end,
   with the dot drawn by ::after at the left edge so it rides the leading edge of the fill. It is a
   one-shot: the animation starts when .is-filled is added and never loops, so the rail is
   completely still at rest — an ambient shimmer on a teaching screen is animation debt. */
.wfx-link__spark {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    opacity: 0;
}

.wfx-link__spark::after {
    content: "";
    position: absolute;
    left: -3px;
    top: 50%;
    width: 6px;
    height: 6px;
    margin-top: -3px;
    border-radius: 999px;
    background: var(--wfx-accent);
    box-shadow: 0 0 10px 2px var(--wfx-accent);
}

.wfx-link.is-filled .wfx-link__spark {
    animation: wfx-spark var(--wfx-travel) cubic-bezier(0, 0, .2, 1) var(--wfx-d, 0ms) both;
}

/* Sits ON the line, so it needs an opaque plate the same colour as the card
   or the rule draws straight through the glyph. */
.wfx-link__arrow {
    position: relative;
    z-index: 1;
    padding: 0 2px;
    background: var(--wfx-surface);
    color: var(--wfx-line-strong);
    font-size: 18px !important;
    width: auto !important;
    height: 18px !important;
    transition: color var(--wfx-dur) var(--wfx-ease) var(--wfx-d, 0ms);
}

/* The arrow is halfway along, so it takes the hit halfway through the fill — colour and a small
   kick, timed off the same --wfx-d. It is the moment the reader's eye is told the ink is moving
   THROUGH here rather than stopping. */
.wfx-link.is-filled .wfx-link__arrow {
    color: var(--wfx-accent);
    animation: wfx-arrow-hit 320ms var(--wfx-ease) var(--wfx-d, 0ms) both;
}

.wfx-link:not(.is-filled) .wfx-link__arrow { transition-delay: 0ms; }

/* ---- the panel -------------------------------------------- */

.wfx-panel {
    /* The panel is the one region that belongs wholly to the open path, so it takes the ink on its
       edge and a breath of it in the well — enough that the open card reads as a coloured block
       from across the page, not enough to fight the copy sitting on it. */
    border-top: 1px solid var(--wfx-accent-line);
    background: linear-gradient(var(--wfx-accent-wash), var(--wfx-accent-wash)), var(--wfx-well);
    padding: 24px 28px 28px;
    display: flex;
    gap: 28px;
    flex-wrap: wrap;
    /* Opened cold, the panel UNROLLS out of the rail — a downward wipe rather than the drop-in it
       used to do. The drop said "a thing appeared above where it belongs"; the wipe says "the card
       opened", which is what actually happened, and it starts at the rail the reader just clicked.
       --wfx-d holds it a beat behind the run without ever waiting for the whole of it. */
    animation: wfx-panel-open 380ms var(--wfx-ease-out) var(--wfx-d, 0ms) both;
}

/* Moving BETWEEN stages on an open card is lateral travel, not an opening, so the panel slides
   along the rail's own axis in the direction of the move. The card's height is already right, so
   an unroll here would look like it closed and reopened. */
.wfx-panel.is-fwd { animation-name: wfx-panel-fwd; }
.wfx-panel.is-back { animation-name: wfx-panel-back; }

.wfx-panel__left { flex: 1 1 300px; min-width: 280px; }

.wfx-panel__stage {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
}

.wfx-panel__stageic { color: var(--wfx-accent); }

.wfx-panel__stagename {
    font-size: 16px;
    font-weight: 600;
    color: var(--wfx-fg);
}

.wfx-panel__blurb {
    margin: 0 0 20px;
    font-size: 13px;
    line-height: 1.6;
    color: var(--wfx-fg-2);
    text-wrap: pretty;
}

.wfx-kicker {
    margin: 0 0 10px;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--wfx-fg-2);
}

.wfx-topics {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.wfx-topic {
    display: flex;
    align-items: center;
    gap: 10px;
    text-align: left;
    padding: 11px 14px;
    border: 1px solid var(--wfx-line);
    border-radius: var(--wfx-radius);
    background: var(--wfx-surface);
    color: var(--wfx-fg);
    font-family: inherit;
    font-size: 13px;
    font-weight: 500;
    text-transform: none;
    cursor: pointer;
    transition: border-color var(--wfx-dur) var(--wfx-ease),
                background var(--wfx-dur) var(--wfx-ease),
                color var(--wfx-dur) var(--wfx-ease);
}

.wfx-topic:hover { border-color: var(--wfx-accent); }
.wfx-topic:focus-visible { outline: 2px solid var(--wfx-accent); outline-offset: 2px; }

/* The chevron is already pointing at the steps card; leaning into the pointer is the smallest
   possible way to say the row is live and where it leads. Movement is on the glyph alone — nudging
   the whole row would shift its label out of alignment with the four rows beneath it. */
.wfx-topic__ic {
    color: var(--wfx-fg-2);
    transition: transform var(--wfx-dur) var(--wfx-ease), color var(--wfx-dur) var(--wfx-ease);
}

.wfx-topic:hover .wfx-topic__ic,
.wfx-topic:focus-visible .wfx-topic__ic { transform: translateX(3px); }

.wfx-topic__label { flex: 1; }

.wfx-topic.is-sel {
    background: var(--wfx-accent);
    border-color: var(--wfx-accent);
    color: var(--wfx-accent-fg);
}

.wfx-topic.is-sel .wfx-topic__ic { color: var(--wfx-accent-fg); }

/* ---- steps ------------------------------------------------ */

.wfx-steps {
    flex: 1 1 420px;
    min-width: 320px;
    background: var(--wfx-surface);
    border: 1px solid var(--wfx-line);
    border-radius: var(--wfx-radius);
    padding: 22px 24px;
}

.wfx-steps__head {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 18px;
}

/* Re-keyed on the topic by the page, so this replays every time a "how do I…?" is picked — the
   title changing under the reader is the confirmation that the click landed, and the step list
   below re-staggers on the same beat. No movement: the heading is a fixed landmark, and sliding it
   would fight the list arriving underneath it. */
.wfx-steps__headtext {
    flex: 1;
    min-width: 0;
    animation: wfx-fade-in 220ms var(--wfx-ease-out) both;
}

.wfx-steps__headtext .wfx-kicker { margin-bottom: 4px; }

.wfx-steps__title {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--wfx-fg);
}

/* The one action on an open card, and it was arriving as MudBlazor's `filled-default` — a 6% black
   wash that measured out fainter than the step wells beneath it. Colouring the rest of the panel
   made that worse rather than exposing it: with the topic button beside it filled in the path's
   ink, an untinted CTA read as the LESS important of the two.

   So it takes the ink at full strength. That is a deliberate departure from DESIGN.md's
   Register-Ink primary, and it is scoped to this button: on a screen whose entire colour system is
   "this is the path you are on", the one thing you can do on that path is the right place for the
   loudest instance of its colour, and a near-black button sitting in an ochre panel would read as
   belonging to something else. Everywhere the education palette does NOT reach — the product
   screens this button navigates to — the house primary is untouched. */
.wfx-steps__open {
    flex: none;
    background: var(--wfx-accent);
    color: var(--wfx-accent-fg);
}

/* Mud paints the label and the start icon from the button's own colour, so both need the override
   or the glyph stays the default grey on a filled ground. */
.wfx-steps__open .mud-button-label,
.wfx-steps__open .mud-icon-root { color: var(--wfx-accent-fg); }

/* Deepened rather than lightened, so the response reads the same in both themes: text-primary is
   near-black on light and near-white on dark, which is the direction "more ink" points either way. */
.wfx-steps__open:hover,
.wfx-steps__open:focus-visible {
    background: color-mix(in srgb, var(--wfx-accent) 82%, var(--mud-palette-text-primary));
}

.wfx-steps__open:focus-visible { outline: 2px solid var(--wfx-accent); outline-offset: 2px; }

.wfx-steplist {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.wfx-step {
    display: flex;
    align-items: center;
    gap: 14px;
    animation: wfx-step-in 300ms var(--wfx-ease) both;
}

.wfx-step:nth-child(2) { animation-delay: 40ms; }
.wfx-step:nth-child(3) { animation-delay: 80ms; }
.wfx-step:nth-child(4) { animation-delay: 120ms; }
.wfx-step:nth-child(n+5) { animation-delay: 160ms; }

.wfx-step__n {
    flex: none;
    width: 26px;
    height: 26px;
    border-radius: 999px;
    /* The path's ink at fill strength, with its own ink for the numeral — a tinted disc rather
       than a filled one, so twelve of them down a list read as a column of markers and not as
       twelve buttons. */
    background: var(--wfx-accent-soft);
    color: var(--wfx-accent);
    font-size: 12px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
}

.wfx-step__text {
    flex: 1;
    border: 1px solid var(--wfx-line);
    border-radius: var(--wfx-radius);
    background: var(--wfx-well);
    color: var(--wfx-fg);
    font-size: 12.5px;
    line-height: 1.5;
    padding: 11px 14px;
}

/* A row standing in for copy nobody has written is drawn as an empty slot,
   not as an instruction: dashed edge, muted ink. */
.wfx-step__text.is-placeholder {
    border-style: dashed;
    border-color: var(--wfx-line-strong);
    color: var(--wfx-fg-2);
}

.wfx-note {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 18px;
    padding: 10px 12px;
    /* Mixed off the cobalt accent, not --mud-palette-info: DEFEX's info token is already a pale
       FILL (#DBEAFE), so mixing it into a white surface produced an all-but-invisible band. */
    background: var(--wfx-accent-soft);
    border: 1px solid var(--wfx-accent-line);
    border-radius: var(--wfx-radius);
    color: var(--wfx-fg-2);
    font-size: 12px;
}

.wfx-note .mud-icon-root { color: var(--wfx-accent); }

.wfx-foot {
    margin: 28px 0 0;
    font-size: 12px;
    color: var(--wfx-fg-2);
}

/* ---- motion ----------------------------------------------- */

/* MOTION THESIS — "the path draws itself".
   One idea, carried the length of the screen: this is a map of how work MOVES, so picking a stage
   sends the ink travelling along the rail to it rather than switching a highlight on. Everything
   else here is either that idea's supporting cast (the arrow taking the hit, the chip landing, the
   panel opening under the arrival) or plain feedback that was missing (the topic swap). Nothing
   loops, nothing runs unprompted after the cards land, and the delays are all computed from how
   far the reader actually travelled — see WorkflowExplorer.razor.cs. */

@keyframes wfx-card-in {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}

/* Opened cold: the panel unrolls downward out of the rail. clip-path rather than height so nothing
   in the animation drives layout. */
@keyframes wfx-panel-open {
    from { opacity: 0; clip-path: inset(0 0 100% 0); }
    to   { opacity: 1; clip-path: inset(0 0 0 0); }
}

@keyframes wfx-panel-fwd {
    from { opacity: 0; transform: translateX(18px); }
    to   { opacity: 1; transform: none; }
}

@keyframes wfx-panel-back {
    from { opacity: 0; transform: translateX(-18px); }
    to   { opacity: 1; transform: none; }
}

@keyframes wfx-step-in {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: none; }
}

@keyframes wfx-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Starts at rest size, not at .86: the chip has to sit still through its delay while the ink is
   still on its way, then land. See the `both` note on .wfx-chip.is-sel. */
@keyframes wfx-pop {
    0%   { transform: scale(1); }
    55%  { transform: scale(1.18); }
    100% { transform: scale(1.1); }
}

/* The travelling light. Fades in off the left end, crosses the segment, and is gone — the dot must
   not still be lit when it reaches the chip, or it reads as a second object parked on the rail
   instead of as the ink arriving. */
@keyframes wfx-spark {
    0%   { opacity: 0; transform: translateX(0); }
    18%  { opacity: 1; }
    82%  { opacity: 1; }
    100% { opacity: 0; transform: translateX(100%); }
}

@keyframes wfx-arrow-hit {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.3); }
    100% { transform: scale(1); }
}

/* Reduced motion means less movement, not less feedback. Every entrance collapses to a plain fade,
   every travel delay collapses to zero, and the two things that carry meaning — the run of ink
   along the rail and the state of each chip — still happen, just as colour arriving at once
   instead of a light crossing the screen. A reader on this setting still has to be able to see how
   far along the path they are. */
@media (prefers-reduced-motion: reduce) {
    .wfx-card,
    .wfx-panel,
    .wfx-panel.is-fwd,
    .wfx-panel.is-back,
    .wfx-step,
    .wfx-steps__headtext {
        animation-name: wfx-fade-in;
        animation-duration: 160ms;
        animation-delay: 0ms !important;
    }

    /* The chip still fills; it just stops growing to do it. */
    .wfx-chip.is-sel .wfx-chip__circle {
        animation: none;
        transform: none;
    }

    .wfx-chip:hover .wfx-chip__circle,
    .wfx-chip:focus-visible .wfx-chip__circle { transform: none; }

    .wfx-topic:hover .wfx-topic__ic,
    .wfx-topic:focus-visible .wfx-topic__ic { transform: none; }

    /* The light is pure movement, so it goes entirely. The connector still fills — as an opacity
       change rather than a line growing sideways, so the "how far along" reading survives. */
    .wfx-link__spark { display: none; }
    .wfx-link__arrow { animation: none; }

    .wfx-link__fill {
        transform: scaleX(1);
        opacity: 0;
        transition: opacity var(--wfx-dur) linear;
    }

    .wfx-link.is-filled .wfx-link__fill { opacity: 1; }

    .wfx *,
    .wfx *::before,
    .wfx *::after {
        transition-delay: 0ms !important;
    }
}

/* ---- narrow ----------------------------------------------- */

@media (max-width: 720px) {
    .wfx-card__head { padding: 20px 20px 14px; }
    .wfx-rail { padding: 24px 20px 20px; }
    .wfx-panel { padding: 20px; gap: 20px; }
    .wfx-steps { padding: 18px; }
    .wfx-steps__head { flex-wrap: wrap; }
}

/* ---- the floating Steps window ----------------------------- */
/* The Steps card after "Open in DEFEX" has flown it into the bottom-right corner: a non-modal
   MudDialog (WorkflowStepsWindow.razor) that survives the navigation, so the instructions stay on
   screen while the reader carries them out.

   These rules live in this file rather than dialogs.css because they are the Steps card's own
   styling continued — the window reuses .wfx-steplist / .wfx-step wholesale, and the two have to
   move together. The file is linked globally from index.template.html, so it is live on every
   screen the window follows the reader to.

   Geometry is written inline by workflowStepsWindow.js (place()), not declared here: dialogDrag.js
   pins with left/top, and a CSS `right` would fight it on the first drag. Width and height are the
   parts CSS owns. */

.mud-dialog.wfx-window {
    width: 380px;
    max-width: calc(100vw - 32px);
    max-height: min(420px, 60vh);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* Panel radius, and named from the :root token rather than --wfx-radius: the window renders in
       the dialog provider, OUTSIDE .wfx, so every --wfx-* token is undefined here unless the block
       below defines it — a bare var(--wfx-radius) resolves to nothing and the window renders with
       square corners. --defex-radius-panel is on :root, so it reaches. This is the panel LANGUAGE
       only; a dialog never gets backdrop-filter (it sits on Mud's scrim, not the page ground, so
       the blur would just return grey). */
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--defex-radius-panel, 18px);
    /* Deeper than the in-page card's --wfx-shadow-lift: this one is genuinely off the page and has
       to separate from whatever screen it has landed on. The card's spine comes with it, on the same 3px inset as /workflows draws it. It is the one
       piece of the education palette that lands on a live product screen, and it is what makes the
       window read as "the Client Setup steps" rather than as a generic floating panel three routes
       later. Everything else on that screen keeps the house palette — the ring this window draws
       around a control stays Signal Blue, because on a product screen blue means "act here" and
       borrowing a teaching ink for it would be the leak this boundary exists to prevent. */
    box-shadow: inset 3px 0 0 var(--wfx-accent),
                0 12px 32px rgba(15, 23, 43, .18), 0 2px 8px rgba(15, 23, 43, .10);
}

/* The workflow's name in its own ink, so the path is identified in words and in colour at once. */
.mud-dialog.wfx-window .wfx-window__bartext .wfx-kicker { color: var(--wfx-accent); }

/* The dialog renders in the dialog provider, outside .main-container, so neither the
   .mud-theme-dark nor the .defex-theme-dark marker reaches it. Every colour therefore has to come
   from a Mud palette variable, which the theme provider does swap. --wfx-well is the one token with
   no palette equivalent, so it is mixed here instead of taken from the light-only --defex-inner-bg
   the page uses. */
.mud-dialog.wfx-window {
    --wfx-well: color-mix(in srgb, var(--mud-palette-text-primary) 4%, var(--mud-palette-surface));
    /* And the rest of the set, for the same reason: a custom property declared on .wfx does not
       inherit to an element that is not inside .wfx, so every --wfx-* the window's inner markup
       reads (it reuses .wfx-steplist / .wfx-step wholesale) was resolving to nothing here —
       border-color falling back to currentColor, border-radius to 0. Each one is bound to the
       palette so the window tracks the theme wherever it has followed the reader to. */
    --wfx-fg: var(--mud-palette-text-primary);
    --wfx-fg-2: var(--mud-palette-text-secondary);
    --wfx-line: var(--mud-palette-lines-default);
    --wfx-line-strong: var(--mud-palette-lines-inputs);
    /* --wfx-accent is deliberately NOT restated here. The window carries a wfx-a-* class naming the
       path it flew out of, and that block is a single class selector — this one is two, so
       restating the accent here would win and every window would come out house-blue whatever card
       it came from. The fallback for a window opened without an accent is the .wfx declaration on
       this same element, which is all a bare window needs.

       The ink itself needs no dark variant here even though no theme marker reaches the dialog
       provider: it resolves through --mud-palette-text-primary and --mud-palette-surface, which
       MudThemeProvider swaps on :root. That is the whole reason the recipe is built that way. */
    --wfx-surface: var(--mud-palette-surface);
    --wfx-radius: var(--defex-radius-inner, 10px);
    --wfx-dur: 160ms;
    --wfx-ease: cubic-bezier(0.4, 0.0, 0.2, 1);
    background: var(--mud-palette-surface);
}

.mud-dialog.wfx-window .mud-dialog-title {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 10px 8px 10px 16px;
    margin: 0;
    border-bottom: 1px solid var(--wfx-line);
    user-select: none;
}

.wfx-window__bar {
    display: flex;
    align-items: center;
    flex: 1;
    min-width: 0;
}

.wfx-window__bartext { flex: 1 1 auto; min-width: 0; }

.wfx-window__bartext .wfx-kicker { margin: 0 0 2px; }

.wfx-window__title {
    margin: 0;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--wfx-fg);
    /* One line: the bar is the drag handle and a title that wraps changes the grab area's height
       underneath the cursor. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.wfx-window__ctl { flex: none; color: var(--wfx-fg-2); }

/* The window is a flex column with a max-height, so the content element has to be allowed to
   shrink (min-height: 0) or the body scrolls the whole dialog off the bottom instead of itself. */
.mud-dialog.wfx-window .mud-dialog-content {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.wfx-window__body {
    padding: 14px 16px 16px;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Collapsed: the window keeps its corner and its progress count, and folds to just the bar.
   Narrower too, so a parked window takes less of the corner than a live one.

   Keyed off a class on the TITLE BAR, matched back up with :has(), not off the dialog's own Class
   parameter. MudDialog hands Class to the dialog instance, which renders a frame behind this
   component: measured, the first collapse click left the window at full size and the SECOND one
   applied it — the DOM was always one state behind the button. The title bar is markup this
   component owns, so it changes in the same render as the icon and the count. */
.mud-dialog.wfx-window:has(.wfx-window__bar.is-collapsed) {
    width: 300px;
    max-height: none;
    height: auto;
}

.mud-dialog.wfx-window:has(.wfx-window__bar.is-collapsed) .mud-dialog-content { display: none; }

.mud-dialog.wfx-window:has(.wfx-window__bar.is-collapsed) .mud-dialog-title { border-bottom: none; }

/* ---- selectable steps -------------------------------------- */
/* Each row is a button in the window (a plain <li> on the page), so it needs the button reset and
   then everything .wfx-step already says about layout. Selecting a row rings its control on the
   live screen — see .wfx-spotlight below. */

.wfx-step--nav {
    width: 100%;
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

/* The page's entrance stagger is keyed on .wfx-step:nth-child, which the window's <li> wrapper
   moves out from under it — without this every row would arrive at once. */
.wfx-window__body .wfx-steplist > li:nth-child(2) .wfx-step { animation-delay: 40ms; }
.wfx-window__body .wfx-steplist > li:nth-child(3) .wfx-step { animation-delay: 80ms; }
.wfx-window__body .wfx-steplist > li:nth-child(4) .wfx-step { animation-delay: 120ms; }
.wfx-window__body .wfx-steplist > li:nth-child(n+5) .wfx-step { animation-delay: 160ms; }

.wfx-step--nav .wfx-step__text {
    transition: border-color var(--wfx-dur) var(--wfx-ease), background var(--wfx-dur) var(--wfx-ease);
}

.wfx-step--nav:hover .wfx-step__text { border-color: var(--wfx-line-strong); }

/* The active row and the ring on screen are the same statement, so they carry the same colour. */
.wfx-step--nav.is-active .wfx-step__n {
    background: var(--wfx-accent);
    /* Not a literal white. On dark the ink lifts to a pale tint and a white numeral on it would be
       unreadable; --wfx-accent-fg is the palette surface, so the numeral inverts with the theme. */
    color: var(--wfx-accent-fg);
}

.wfx-step--nav.is-active .wfx-step__text {
    border-color: var(--wfx-accent);
    background: var(--wfx-accent-soft);
    color: var(--wfx-fg);
}

/* Inactive rows step back rather than the active one shouting — the window is small, and four
   equally-weighted rows plus a highlight is a lot of competing emphasis. */
.wfx-step--nav:not(.is-active) .wfx-step__text { color: var(--wfx-fg-2); }

/* "Can't find it" note under the active row. */
.wfx-step__miss {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 6px 0 0 40px;
    font-size: 11.5px;
    color: var(--wfx-fg-2);
}

.wfx-step__miss .mud-icon-root { font-size: 14px; }

/* ---- Back / Next ------------------------------------------- */

.wfx-window__foot {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    width: 100%;
}

.wfx-window__nav { min-width: 84px; }

.mud-dialog.wfx-window .mud-dialog-actions {
    margin: 0;
    padding: 10px 16px;
    border-top: 1px solid var(--wfx-line);
    flex: none;
}

/* Collapsed hides the body; the footer has to go with it or the window folds down to a title bar
   with a stray pair of buttons under it. */
.mud-dialog.wfx-window:has(.wfx-window__bar.is-collapsed) .mud-dialog-actions { display: none; }

/* ---- the ring on the live screen --------------------------- */
/* Drawn by workflowStepsWindow.js around whatever the active step points at. Deliberately a ring
   and NOT the tutorial's spotlight: that dims the whole page behind a 9999px box-shadow, which is
   right for "watch this" and wrong for "now go and do it" — the reader is meant to be typing into
   the thing being highlighted.

   1440 sits above MudBlazor's dialog layer (1400), so a ring lands ON TOP of the modal whose field
   it is pointing at, and below the Steps window itself (1450, .defex-dialog-nonmodal) so the window
   is never obscured by its own highlight.

   THE PULSE IS NOT DEFINED HERE. The element also carries `pulse-hint` (utilities.css), which is
   the app's existing "this is the control you want" effect — the same one the mobile job screen
   puts on its next-action button. Reusing the class rather than writing another set of keyframes
   is the point: a second, subtly different blue pulse is how a design system starts to drift.
   That also fixes the colour by inheritance — do not restate it here.

   What this rule adds is the part `pulse-hint` cannot do: it positions the overlay, and it holds a
   STEADY outline between pulses. pulse-hint alone is invisible at the trough of its animation
   (`0 0 0 0`), which is fine on a button already under the reader's eye and not fine for "find this
   field", where the ring has to stay findable the whole time it is up.

   Steady frame via `outline` rather than `box-shadow` deliberately — box-shadow is what pulse-hint
   animates, so putting a static one here would fight it. Radius matches pulse-hint's 4px; the
   --wfx-* tokens are NOT available to this element, which is a bare div parked on <body> outside
   any .wfx root. */

.wfx-spotlight {
    position: fixed;
    z-index: 1440;
    pointer-events: none;
    border-radius: 4px;
    opacity: 0;
    outline: 2px solid rgba(37, 150, 255, 0.9);
    /* Easing spelled out for the same reason as the radius: --wfx-ease does not resolve out here.
       It is the value --wfx-ease holds. */
    transition: top .25s cubic-bezier(0.4, 0.0, 0.2, 1), left .25s cubic-bezier(0.4, 0.0, 0.2, 1),
                width .25s cubic-bezier(0.4, 0.0, 0.2, 1), height .25s cubic-bezier(0.4, 0.0, 0.2, 1),
                opacity .18s linear;
}

.wfx-spotlight.is-on { opacity: 1; }

/* ---- the flight -------------------------------------------- */
/* The clone workflowStepsWindow.js tweens from the card's place on the page to the window's resting
   rect. 1460 keeps it above the non-modal dialog layer (1450, dialogs.css) so it is never occluded
   half-way, and below the snackbar layer at 1500. */

/* Nothing inside the clone should re-animate: the entrance stagger would replay mid-flight and the
   card would appear to assemble itself while travelling. */
.wfx-flight-ghost * {
    animation: none !important;
    transition: none !important;
}

.wfx-flight-ghost {
    position: fixed;
    z-index: 1460;
    margin: 0;
    pointer-events: none;
    transform-origin: top left;
    box-shadow: 0 12px 32px rgba(15, 23, 43, .18);
    will-change: transform, opacity;
}

@media (prefers-reduced-motion: reduce) {
    .wfx-step--nav .wfx-step__text { transition: none; }

    /* The ring still appears, it just stops sliding between targets and stops pulsing — the steady
       outline carries it on its own. Scoped to .wfx-spotlight rather than fixing `pulse-hint`
       itself: that utility has no motion guard today, and quietly changing it would also change
       the mobile job screen's next-action hint, which is not this change's business. This file is
       linked after utilities.css, so an equal-specificity rule here wins. */
    .wfx-spotlight {
        transition: opacity .18s linear;
        animation: none;
    }
}

/* Below the corner geometry's breakpoint the window is a bottom sheet instead — a 380px card in the
   corner of a phone is most of the screen anyway. Kept in step with targetRect() in
   workflowStepsWindow.js, which uses the same 720px cut. */
@media (max-width: 720px) {
    .mud-dialog.wfx-window {
        width: calc(100vw - 16px);
        max-height: 45vh;
    }

    .mud-dialog.wfx-window:has(.wfx-window__bar.is-collapsed) { width: calc(100vw - 16px); }
}
