/* ==========================================================================
   FLOWTENT — VISUAL SYSTEM TOKENS v1.0

   Source: mockups/flowtent-design/project/assets/flowtent-tokens.css (D1).
   Reference implementation: landing/assets/landing.css — the landing page
   shipped from the same set of mockups and is already deployed; the two
   fixes made there are repeated here and documented next to the relevant
   tokens.

   This file is a dictionary of values and, with one exception, knows nothing
   about MudBlazor — the translation layer is mud-bridge.css. The exception is
   the reduced-motion block at the end: it is a policy rather than a value, it
   has to load before anything that animates, and its escape hatch names
   MudBlazor's progress classes by hand. Splitting that safety net across two
   files would let the reset ship without the hatch, freezing every loading
   indicator in the app for the people who asked for less motion.

   Dark mode: [data-theme="dark"] on <html>, set before the first paint by a
   script in App.razor.
   ========================================================================== */

/* ---- Typefaces — self-hosted ---------------------------------------------
   Public Sans is a variable font: ONE file per subset covers every weight
   100–900 that the system uses. Eight static files would have been eight
   downloads for nothing. latin-ext is not optional here — it carries the
   Polish diacritics ą ć ę ł ń ó ś ź ż.                                       */

@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/public-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/public-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/ibm-plex-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Owned here, not in the bridge: color-scheme has nothing to do with
     MudBlazor and must not die with it. It tells the browser which way to
     paint the things CSS cannot reach — scrollbars, <select> popups, date
     pickers, the overscroll canvas. Without the pair below, a user whose OS
     is light but who picks Dark gets a dark page framed in light chrome.  */
  color-scheme: light;

  /* ---- Colour — neutral base (light) -------------------------------------
     Contrast ratios in this file are quoted against --ft-surface unless the
     comment names another ground. The pairs (tint/deep) are self-describing:
     the pair IS the ground. Where a token's tightest real case is a surface
     it is not quoted against, that case is called out — see --ft-text-3.  */
  --ft-bg: #F6F7F9;
  --ft-surface: #FFFFFF;
  --ft-surface-2: #F1F3F6;
  --ft-hairline: #E3E7EC;
  --ft-text-1: #1B2733;         /* 15.2:1 */
  --ft-text-2: #46545F;         /* 7.8:1  */
  /* --ft-text-3 is #5F6C78, not the mockup's #66737F. The mockup value
     measures 4.53:1 on --ft-bg — it passes AA by three hundredths, and this
     is the colour of EVERY piece of metadata in the app. #5F6C78 buys 5.0:1
     for a shift nobody can see. The correction comes from the landing page. */
  /* 5.38:1 on --ft-surface, 5.02 on --ft-bg — and 4.84 on --ft-surface-2,
     which is the tightest case in the whole ramp and the one no comment
     used to quote. 0.34 above failing. Do not darken the surfaces without
     re-measuring this.                                                     */
  --ft-text-3: #5F6C78;
  /* NOT a text token — hence the name. SVG strokes, dashed outlines, strong
     hairlines. At 2.45:1 on --ft-surface nothing a reader has to read may use it;
     placeholders and disabled labels stop at --ft-text-3. It was called
     --ft-text-4 until the review pointed out that anyone typing "--ft-text-"
     and taking the last completion would get an unreadable colour with the
     warning three lines out of sight.

     This is the one place --ft-text-4 is named, and the mockups still write it.
     The map, for the next person reading one of them: text goes to --ft-text-3,
     decoration comes here. Do not resurrect the old name — a declaration reading
     a variable that does not exist is NOT a syntax error and is therefore silent.
     It wins the cascade and only then computes to `unset`, so `color` degenerates
     to the PARENT's colour (full-strength --ft-text-1 where a muted grey was
     meant) and `background` to `transparent` (P8 found nine such references: the
     404 numeral, the two mono lines, three micro notes, the "(You)" marker, the
     off-state switch track, and an inline override on Settings).             */
  --ft-decor: #9AA7B4;
  --ft-scrim: rgba(15, 23, 30, .44);

  /* Ink for AvatarColorHelper's hash-derived initials palette (Application
     layer, C#) — a fixed ten-colour ramp picked for contrast against white
     specifically, not against whichever ink --ft-text-1 currently resolves
     to, so it deliberately does not follow the theme. The literal elsewhere
     is the same value for the same reason, predating this token (Components/
     Shared/SideMetaPanel.razor, Components/Layout/NotificationBell.razor,
     both scoped <style> blocks); mud-bridge.css's .ft-avatar is the one
     consumer that reads it from here instead of repeating the literal.     */
  --ft-avatar-ink: #fff;

  /* ---- Colour — accent (Flow Violet) -------------------------------------
     White text NEVER on --ft-accent (4.4:1 — short of the bar for labels).
     Primary button fills are always --ft-accent-strong (5.5:1).             */
  --ft-accent: #7C5CFC;
  --ft-accent-strong: #6843FC;
  --ft-accent-text: #5025FB;    /* 7.0:1 — links and accent text */
  --ft-accent-tint: #EFEAFE;
  --ft-accent-tint-2: #DFD4FE;
  --ft-on-accent: #FFFFFF;
  /* The ring measures 1.26:1 against --ft-accent-strong, so on a primary
     button it is legible ONLY because the offset lifts it clear onto the
     surface behind. Draw it as an inset box-shadow or a border and it
     disappears on exactly the button people press most.                    */
  --ft-focus-ring: #7C5CFC;
  --ft-focus-ring-width: 2px;
  --ft-focus-ring-offset: 2px;

  /* ---- Colour — semantics (tint/deep pair, contrast measured) ------------ */
  --ft-success-tint: #E1F5EA;  --ft-success-deep: #166B3F;  /* 5.8:1 */
  --ft-warning-tint: #FCF0DA;  --ft-warning-deep: #7A5300;  /* 6.1:1 */
  --ft-danger-tint:  #FBE9E9;  --ft-danger-deep:  #AE2A24;  /* 5.7:1 */
  --ft-info-tint:    #E4F0FB;  --ft-info-deep:    #0C5A94;  /* 6.3:1 */

  /* ---- Colour — @mention ---------------------------------------------------
     A role of its own because a mention is a REFERENCE, not a problem. It was
     folded into warning when the old palette's orange pill lost its token, and
     on post detail that put an @handle mid-caption in the same colours as
     "2 of 5 media missing alt" — one object saying two things, and in dark the
     amber pill read as an alert fired by the sentence around it.

     Teal, because it is the only hue the dictionary had not spent: red is
     danger, amber warning, green success, blue info and planned, violet the
     accent and progress, magenta pending, slate idea. Pushed toward cyan
     rather than green so the pass chip on the same screen stays a different
     object — the deep sits dE 32.6 from success in light and dE 29.5 from
     info in dark, both wider than the 22.9 the status ramp already accepts
     between its closest pair. Against warning, the pair it was confused with,
     it measures dE 66.6 light and 80.4 dark.

     Hashtags stay on --ft-accent-text: the two are siblings in a caption and
     have to be told apart at a glance. Platform previews do NOT use this —
     PlatformCaptionRenderer paints mentions in each network's own link
     colour, which is the point of a preview.                                */
  --ft-mention-tint: #E0F4F2;  --ft-mention-deep: #0B6A72;  /* 5.5:1 */

  /* ---- Colour — post status scale ----------------------------------------
     A progress ramp, deliberately distinct from the semantic scale: grey →
     blue → indigo → magenta → green. Mapping "in progress" onto warning was
     the old behaviour and it made ordinary work look like a problem. The
     second signal is a progress-wheel glyph (◌ ◔ ◑ ◕ ●) and it is mandatory
     — never colour alone.                                                   */
  --ft-st-idea-tint: #ECF0F3;      --ft-st-idea-deep: #45525D;      /* 7.0:1 */
  --ft-st-planned-tint: #E2F1FD;   --ft-st-planned-deep: #0A5FA3;   /* 5.7:1 */
  --ft-st-progress-tint: #E9EAFC;  --ft-st-progress-deep: #4438C2;  /* 6.8:1 */
  --ft-st-pending-tint: #FAE7FA;   --ft-st-pending-deep: #9D1FAC;   /* 5.5:1 */
  --ft-st-published-tint: #DFF3E7; --ft-st-published-deep: #0E7244; /* 5.2:1 */

  /* ---- Colour — platform brands (platform chips and previews ONLY) -------
     The only colours in the file that are not ours. They never become UI
     colours — and they are FILLS for glyphs and chip dots, never label
     text: six of the seven fall under 4.5:1 on a light surface. Labels take
     --ft-text-1.

     Snapchat is already darkened from #FFFC00, so brand fidelity has
     already yielded to legibility once here; the dark-mode overrides below
     are the same trade, not a new principle.

     X and Snapchat are darkened again below, for a different ground: as the
     8px chip dot on --ft-surface (#FFFFFF — the chip's actual fill; see the
     comment in mud-bridge.css section 9 for why it is --ft-surface and not
     --ft-surface-2), the un-darkened values read 2.83:1 and 1.85:1 — under
     the 3:1 floor SC 1.4.11 sets for a graphical object, and low enough
     that the dot reads as a rendering fault, not a design choice. The chip
     carries a text label too, so SC 1.4.11 does not strictly bind here —
     but that is not the reason these two move. Legibility is the goal on
     its own terms; a dot nobody can see is not reinforcement, it is dead
     pixels. Do not "optimise" this back on the grounds that the label
     makes the dot redundant. (These two ratios move if the chip's ground
     token ever changes again — re-measure, do not assume.)

     Dark mode already clears 3:1 comfortably on the ORIGINAL, un-darkened
     hex (5.96:1, 9.12:1 against --ft-surface dark) — darkening here would
     only hurt it, so the dark-mode block below overrides both back to their
     original values, the same per-theme pattern already used for the three
     brands lightened there.

     Naming: --ft-brand-x matches the platform, but the enum member is still
     Platform.Twitter. Do not derive this variable name from the enum — the
     lookup in PlatformStyleExtensions is explicit for that reason.         */
  --ft-brand-instagram: #E1306C; --ft-brand-facebook: #1877F2;
  --ft-brand-linkedin: #0A66C2;  --ft-brand-youtube: #FF0000;
  --ft-brand-tiktok: #FE2C55;    --ft-brand-x: #0D8DDC;        /* darkened from #1DA1F2 — dot on --ft-surface-2 measured 2.54:1 */
  --ft-brand-snapchat: #AE8000;  /* darkened from #F4B400 (already darkened once from #FFFC00) — dot on --ft-surface-2 measured 1.66:1 */

  /* ---- Skeleton ----------------------------------------------------------
     Judge a shimmer by lightness separation, not by contrast ratio: the ratio
     is the wrong instrument here and would say the change barely happened
     (1.12 → 1.17). The obvious inference — --ft-surface-2 to --ft-hairline —
     separates by ΔL* 4.3, a sweep nobody notices. This pair runs ΔL* 6.1.  */
  --ft-skeleton-base: #E9EDF2;
  --ft-skeleton-sheen: #D6DCE4;

  /* Card lift on hover. Named because the plan and landing.css had already
     drifted to two values for one gesture.                                 */
  --ft-lift: -1px;

  /* ---- Typography ---------------------------------------------------------
     IBM Plex Mono ships only at weight 400. The Style Guide also called for
     500, but no token uses it and the landing page doesn't ship it either —
     we'll add the file when the first component actually requests it.      */
  --ft-font: 'Public Sans', -apple-system, 'Segoe UI', sans-serif;
  --ft-font-mono: 'IBM Plex Mono', ui-monospace, monospace;
  --ft-type-display: 700 44px/52px var(--ft-font);
  --ft-type-h1: 700 28px/36px var(--ft-font);
  /* Phone-width companion to --ft-type-h1 — same weight, smaller size. NOT a
     proportionally shorter line-height: 28/20 = 1.40 vs. --ft-type-h1's
     36/28 = 1.286 — looser on purpose. A page title wraps to two lines far
     more often at phone width, and the extra leading is what keeps a wrapped
     title from looking cramped. Metrically this token is identical to
     --ft-type-h2 (600 20px/28px vs. this one's 700 20px/28px) — weight is
     the only difference, and it is the reason the two exist separately. Do
     not "deduplicate" them onto one token; a future h1/h2 size split would
     have nothing left to diverge from. */
  --ft-type-h1-mobile: 700 20px/28px var(--ft-font);
  --ft-type-h2: 600 20px/28px var(--ft-font);
  --ft-type-h3: 600 16px/24px var(--ft-font);
  --ft-type-body-lg: 400 16px/26px var(--ft-font);
  --ft-type-body: 400 14px/22px var(--ft-font);
  --ft-type-small: 400 13px/20px var(--ft-font);
  --ft-type-micro: 500 12px/16px var(--ft-font);
  --ft-type-mono: 400 12.5px/20px var(--ft-font-mono);
  /* Every other token in this file is order-independent; this one is not.
     The `font` shorthand resets font-variant-numeric to `normal`, so
     --ft-tabular must be applied AFTER --ft-type-mono, e.g.:
       font: var(--ft-type-mono); font-variant-numeric: var(--ft-tabular);
     Reverse that order and the shorthand silently wins — the figures lose
     their tabular alignment and nothing points at why.                     */
  --ft-tabular: tabular-nums;

  /* ---- Spacing (4 px base) ------------------------------------------------ */
  --ft-sp-1: 4px; --ft-sp-2: 8px; --ft-sp-3: 12px; --ft-sp-4: 16px;
  --ft-sp-5: 20px; --ft-sp-6: 24px; --ft-sp-8: 32px; --ft-sp-10: 40px;
  --ft-sp-12: 48px; --ft-sp-16: 64px;
  /* Air BETWEEN sections, thrift INSIDE a row. This is a work tool: the user
     scans 30–50 rows at a time, so a list row is a card, not a tile.        */
  --ft-row-h: 76px;
  --ft-content-max: 1600px;

  /* ---- Radii / shadows / borders -------------------------------------------
     Shadows are tinted (23,36,49), never pure black — black shadows on a
     cool-grey ground read as dirt. Resting cards get hairline + sm, not an
     elevation. Never 24px radius on small elements; it ages badly.          */
  --ft-r-sm: 6px;               /* chips, badges */
  --ft-r-md: 10px;              /* buttons, inputs */
  --ft-r-lg: 14px;              /* cards */
  --ft-r-xl: 20px;              /* dialogs */
  --ft-r-full: 999px;           /* pills, avatars */
  --ft-shadow-sm: 0 1px 2px rgba(23,36,49,.06);
  --ft-shadow-md: 0 2px 8px -2px rgba(23,36,49,.10), 0 1px 2px rgba(23,36,49,.05);
  --ft-shadow-lg: 0 8px 24px -6px rgba(23,36,49,.14);
  --ft-shadow-overlay: 0 16px 48px -8px rgba(23,36,49,.22);
  --ft-border: 1px solid var(--ft-hairline);

  /* ---- Motion — CSS only --------------------------------------------------
     Every interaction in Blazor Server is a round trip over SignalR, so
     driving animation frame by frame from C# is ruled out by construction.
     No parallax, no cursor-tracking effects — they would stutter.          */
  --ft-dur-fast: 120ms;         /* hover, focus */
  --ft-dur-base: 160ms;         /* card lift */
  --ft-dur-enter: 200ms;        /* content entering, toast */
  --ft-dur-slow: 240ms;         /* dialog, drawer */
  --ft-ease: cubic-bezier(.2,.6,.2,1);
  --ft-ease-out: cubic-bezier(0,.4,.1,1);

  /* ---- Z-index ------------------------------------------------------------
     Replaces the inherited 2000–10010 scramble, where the alpha banner
     outranked the drawer that had to cover it.

     These numbers are BELOW MudBlazor's own defaults (drawer 1100 through
     tooltip 1600), so on their own they would sink under every overlay the
     library draws. They govern only because FlowtentTheme remaps
     MudTheme.ZIndex onto this same scale — the two are one system and must
     be changed together.                                                   */
  --ft-z-sticky: 100;
  --ft-z-dropdown: 200;
  --ft-z-drawer: 300;
  --ft-z-dialog: 400;
  --ft-z-snackbar: 500;
  --ft-z-tooltip: 600;
  --ft-z-system: 700;           /* SignalR failure bar — always on top */
}

/* ---- DARK MODE — a separate palette, not an inversion -------------------
   Three depths of surface, tints pulled down, the accent brightened enough
   to keep its contrast. The primary button carries DARK text on a light
   accent — the reverse of light mode, and the thing most often got wrong
   when a dark palette is generated by flipping lightness.                  */
[data-theme="dark"] {
  color-scheme: dark;
  --ft-bg: #10161B; --ft-surface: #171E24; --ft-surface-2: #1E262E;
  --ft-hairline: #2B3540;
  --ft-text-1: #E9EEF3;         /* 14.4:1 */
  --ft-text-2: #B4C0CA;         /* 9.1:1  */
  --ft-text-3: #8B99A5;         /* 5.8:1  */
  --ft-decor: #5D6B77;
  --ft-scrim: rgba(4, 8, 12, .6);
  --ft-accent: #B4A2FD;         /* 7.6:1 on surface */
  --ft-accent-strong: #AA96FD;
  --ft-accent-text: #C2B4FE;    /* 9.0:1 */
  --ft-accent-tint: #261C4E;    --ft-accent-tint-2: #312566;
  --ft-on-accent: #1A1040;      /* 7.1:1 on accent-strong */
  --ft-focus-ring: #B4A2FD;
  --ft-success-tint: #12301F; --ft-success-deep: #6FD39A;  /* 7.8:1 */
  --ft-warning-tint: #33270D; --ft-warning-deep: #E3B34C;  /* 7.5:1 */
  --ft-danger-tint: #3A1715;  --ft-danger-deep: #F08C86;   /* 6.7:1 */
  --ft-info-tint: #0F2A40;    --ft-info-deep: #6CB4EC;     /* 6.6:1 */
  --ft-mention-tint: #0E2E30; --ft-mention-deep: #5ECBD6;  /* 7.6:1 */
  --ft-st-idea-tint: #232C33;      --ft-st-idea-deep: #A6B4BF;      /* 6.7:1 */
  --ft-st-planned-tint: #0F2940;   --ft-st-planned-deep: #68B1F0;   /* 6.5:1 */
  --ft-st-progress-tint: #1D2044;  --ft-st-progress-deep: #A0A3F5;  /* 6.7:1 */
  --ft-st-pending-tint: #38102F;   --ft-st-pending-deep: #E879F9;   /* 6.7:1 */
  --ft-st-published-tint: #0F2E1E; --ft-st-published-deep: #63CE93; /* 7.5:1 */
  --ft-shadow-sm: 0 1px 2px rgba(0,0,0,.4);
  --ft-shadow-md: 0 2px 8px -2px rgba(0,0,0,.5);
  --ft-shadow-lg: 0 8px 24px -6px rgba(0,0,0,.55);
  --ft-shadow-overlay: 0 16px 48px -8px rgba(0,0,0,.65);
  /* These are --ft-surface-2 and --ft-hairline verbatim — the construction
     the light comment rejects. Deliberate: the same hex step buys more
     perceptual separation this far down (ΔL* 7.0, the widest of the three).
     Written out rather than referenced so retuning the dark surfaces cannot
     drag the skeleton along without anyone noticing.                      */
  --ft-skeleton-base: #1E262E;
  --ft-skeleton-sheen: #2B3540;

  /* Three brands are too dark to sit on --ft-surface: LinkedIn measures
     ~3.0:1 there, at or under the 3:1 floor SC 1.4.11 sets for a graphical
     object a user has to identify. Lightened just enough to clear it. The
     other four already pass on dark — TikTok, X and Snapchat pass more
     comfortably there than on light.

     X and Snapchat are overridden here for the opposite reason: the light
     block darkens both so the chip dot clears 3:1 on --ft-surface light,
     but that darkened value would only erode dark mode's existing margin
     (5.42:1, 8.29:1). These two lines hold the ORIGINAL, un-darkened brand
     hex, so dark mode is untouched by the light-mode fix.

     LinkedIn's hue is also rotated +10° here (208° to 218° — still squarely
     blue, not violet), because the lightening above was tuned only against
     the ground, never against its neighbours: at 8px, LinkedIn #4DA3F0 and
     X #1DA1F2 measured CIE76 dE 5.0 apart, decoration a colour-sighted user
     would read as one dot rendered twice, not two platforms. Facebook was
     never confused with either (dE 10.6 / 11.1 — unchanged, both untouched
     by this line). The rotation alone — no lightness or saturation change —
     takes LinkedIn-vs-X to dE 21.5 and holds LinkedIn-vs-Facebook at 11.0,
     without moving X off its canonical, universally-recognised hex. Contrast
     on --ft-surface dark drops from 6.26:1 to 4.89:1, still well clear of
     the 3:1 floor. X and Facebook were left alone deliberately: X's hex is
     the brand's own, and Facebook was never part of the collision.        */
  --ft-brand-linkedin: #4D88F0;
  --ft-brand-facebook: #4B9AF7;
  --ft-brand-instagram: #F06592;
  --ft-brand-x: #1DA1F2;
  --ft-brand-snapchat: #F4B400;
}

/* prefers-reduced-motion: the movement goes, the states stay. Nothing stops
   working — end states appear at once instead of easing in.

   1ms, not 0s, and that is the load-bearing detail: a zero duration means no
   transitionend/animationend event ever fires, so anything awaiting one
   waits forever. Do not "tidy" this to 0s.

   Delays are reset for the same reason the durations are. A staggered
   entrance with its 300ms delay intact and its animation cut to 1ms makes
   content arrive LATE for precisely the people who asked for less motion.

   scroll-behavior is neither a transition nor an animation, so it escapes
   the two resets above and has to be named separately.

   This is a policy rather than a value, which sits awkwardly in a file that
   is otherwise a dictionary. It lives here because it has to load before
   anything that animates, and this sheet is first.                        */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }

  /* WCAG 2.3.3 is about non-essential motion. Indeterminate progress is this
     app's ONLY loading indicator — there is no skeleton anywhere yet — and it
     appears on twenty-odd screens, so capping it at one iteration would leave
     every load frozen mid-arc: a partial ring that reads as a broken graphic
     rather than as "working". The hatch is !important because the reset is.

     Durations are RESTATED, not reverted. In author origin `revert` rolls
     back to the user-agent value, and animation-duration there is 0s — it
     would have killed the animation outright while looking like it restored
     it. `revert-layer` would need cascade layers this file does not use.

     Three selectors for the circular spinner because three elements animate
     independently: the root rotates, the child counter-rotates, and the
     circle's dash array grows and shrinks. Restore only the root and the
     spinner turns around a frozen arc.

     The linear indicator is two bars INSIDE the element carrying
     .mud-progress-indeterminate, told apart by position. MudBlazor 9.2 also
     ships -1-indeterminate/-2-indeterminate rules, but the component never
     emits those classes — checked against the assembly's string heap, not
     against the stylesheet — so matching on them would be dead code. The
     follow bar needs its 1.15s delay back or the two travel as one block.

     Values track MudBlazor 9.2. If a later version retimes them, the
     indicator still animates, just at ours.                               */
  .mud-progress-circular.mud-progress-indeterminate,
  .mud-progress-circular-circle.mud-progress-indeterminate,
  .mud-progress-circular-indeterminate-child {
    animation-duration: 1.4s !important;
    animation-iteration-count: infinite !important;
  }

  .mud-progress-linear.mud-progress-indeterminate .mud-progress-linear-bar {
    animation-duration: 2.1s !important;
    animation-iteration-count: infinite !important;
  }
  .mud-progress-linear.mud-progress-indeterminate .mud-progress-linear-bar:last-child {
    animation-delay: 1.15s !important;
  }

  /* The general hatch, for our own components — deliberately separate from
     the spinner rule above, which would otherwise force MudBlazor's 1.4s and
     an infinite loop onto anything wearing this class. A 400ms one-shot
     marked essential would have been silently retimed into a 1.4s loop.

     It carries no timing of its own: marking an element essential obliges it
     to state its duration locally, because the `revert` trap above means the
     original cannot be recovered. The fallbacks exist only so a bare class
     is not broken.

       .my-thing { --ft-motion-essential-duration: 400ms;
                   --ft-motion-essential-iterations: 1; }                  */
  .ft-motion-essential {
    animation-duration: var(--ft-motion-essential-duration, 1.4s) !important;
    animation-iteration-count: var(--ft-motion-essential-iterations, infinite) !important;
  }
}
