/*
    THE SHARED CHROME AND THE SHARED TYPE SCALE (ratified Phase 18 plan Section 3.2, slice 18A;
    the chrome itself redesigned in slice 18B, Sections 3.9, 5.4, 5.5 and 9.1).

    WHY THERE IS A SECOND STYLESHEET AT ALL, AND ONLY ONE. Section 5.99 rejects splitting public.css
    per page: each <link> is one more request on a site designed as one document. This file earns its
    request by serving a SECOND AREA, and since 18B it really does: Areas/Identity/Pages/_Layout.cshtml
    links it, so the login form, the register form and the eleven forked account pages wear the same
    header, the same footer, the same type scale and the same tokens as the marketing site. That is
    finding F6, and it is what "one set theme for all of the pages" was asked for (decision Q7).

    NOT ONE LITERAL COLOUR, exactly as public.css promises of itself. Every colour is a --forge-*
    custom property emitted by Forge.Web/Public/PublicPalette.cs, which reads AppTheme's two palettes
    in C#. Both areas now emit that same block from that same method, byte for byte, so there is no
    longer a hand-mirrored copy of the palette anywhere in this repository - which is what
    IdentityShellPaletteTests asserts since 18B, in place of the mirror-agreement check it used to
    make.

    NOTHING HERE FETCHES. No @import, no url(), no web font - the same sentence public.css makes, and
    PublicSiteNoScriptTests checks it on this file too, because that sweep discovers what App.razor
    links rather than reading one file by name.

    TWO MEDIA FEATURES AND NO MORE. LandingPageTests' allow-set rule permits exactly
    (min-width: 768px), read from ForgeWidths.TableMinimum, and (prefers-reduced-motion: reduce).
    A third one is a second place to change the layout, and the fluid steps between the matrix widths
    come from clamp() rather than from another breakpoint (Section 9.3).

    AND NO INLINE <svg> ANYWHERE, WHICH IS WHY THE MENU ICON IS DRAWN HERE. BannedApiSweepTests bans
    inline SVG in every .razor and .cs under src/Forge.Web/ with one allow-listed file, and
    Section 5.5 anticipates it: the icon is two pseudo-elements on a span and the state affordance is
    a rotated border on another. No asset, no request, no markup a sweep has to make an exception for.
*/

/* ---------------------------------------------------------------------------------------------
   The type scale (Section 5.1).

   IT REPLACES A SECOND TYPEFACE. Section 3.1's decision Q3 keeps the system font stack, because a
   web font is the one asset that would break "this page fetches nothing", and spends the effort on
   a ramp instead: a display size for a hero, a real step between a heading and its body, tighter
   tracking where a large size needs it, and a wider line-height for prose.

   SINCE 18B IT REACHES THE IDENTITY PAGES TOO, which is the deliberate consequence of linking this
   file there: an Identity heading now sits on the same ramp and takes the same text-wrap: balance as
   a marketing heading. That is Section 3.6's "one type scale across public, Identity and app", and
   it is worth stating because it is the one thing 18B changes on a surface it otherwise only re-frames.

   THE FALLBACK IS ON THE CONSUMING DECLARATION AND IT IS WORTH SAYING WHAT IT DOES AND DOES NOT
   COVER, because a comment that promises a fallback which cannot exist is the F17M-8-1 defect and
   this file will not repeat it. Every rule that reads one of these tokens writes a plain size first
   and then var(--forge-text-*, <the same plain size>). The var() fallback argument is the half that
   really fires: it covers this stylesheet failing to load at all, which is a genuine failure mode on
   a page that now links two, and it substitutes at parse time so the resulting declaration is valid.
   The plain declaration before it covers the token being defined but unreadable. Neither one rescues
   an engine that has the token and does not understand clamp(), because a custom property is stored
   unparsed and the substituted clamp() would then be invalid at computed-value time - so the honest
   answer to Section 12 R9 is that clamp() is the floor, it has been interoperable since 2020, and
   18G's walkthrough is what settles it rather than a comment here.

   THE CLAMPS ARE ARITHMETIC AGAINST THE MATRIX, not taste: 1vw is 0.2rem at 320 and 0.48rem at 768,
   so --forge-text-h1 sits at its 1.7rem floor on a phone - which is what shipped - reaches about
   2.19rem at the breakpoint, where the old fixed rule said 2.1rem, and stops at 2.6rem so a
   desktop-width heading never becomes a poster.
   --------------------------------------------------------------------------------------------- */

:root {
    --forge-text-xs: 0.8rem;
    --forge-text-sm: 0.85rem;
    --forge-text-body: 1rem;
    --forge-text-lede: clamp(1.05rem, 1rem + 0.25vw, 1.2rem);
    --forge-text-h3: clamp(1.15rem, 1.08rem + 0.35vw, 1.35rem);
    --forge-text-h2: clamp(1.15rem, 1.02rem + 0.65vw, 1.5rem);
    --forge-text-h1: clamp(1.7rem, 1.35rem + 1.75vw, 2.6rem);
    --forge-text-display: clamp(2rem, 1.5rem + 2.5vw, 3.25rem);

    --forge-tracking-tight: -0.012em;
    --forge-tracking-wide: 0.08em;
    --forge-leading-prose: 1.65;

    /* The one transition duration and easing the whole site uses (Section 5.8, tier 1). Named once
       so "everything on this site moves the same way" is a fact rather than a habit, and so the
       reduced-motion arm at the end of this file has one thing to switch off.

       --forge-motion-fast: 120ms USED TO SIT HERE AND IS DELETED, which is finding F18-A2-3's own
       class found a second time by the guard written for it: no rule in either served stylesheet
       ever read it, so it was bytes on every anonymous visit plus a name the next author would
       reach for believing it was wired. PublicTypeScaleTests closes over every custom property
       these files declare on :root, so a ninth unread one fails rather than accumulating. */
    --forge-motion: 160ms;
    --forge-motion-ease: ease-out;

    /* The chrome's own inline padding, named because the mobile navigation panel's rows carry it
       themselves: the panel is anchored to the header rather than to the measured row inside it
       (R2-1), so a row's text lines up with the wordmark above it only while the two numbers agree.
       Two literals that must stay equal are two literals that will not. */
    --forge-chrome-gutter: 16px;
}

/* A heading that wraps into a one-word last line reads as a mistake, and text-wrap: balance is the
   whole fix. An engine that does not know the value ignores the declaration and wraps as it always
   did, which is the correct degradation and needs no fallback. Tracking is applied only to h1,
   because the negative value that flatters a 2.6rem display size tightens a 1.15rem heading into
   its own letters (Section 5.1: "tighter tracking above 1.5rem"). */
h1,
h2,
h3 {
    text-wrap: balance;
}

h1 {
    letter-spacing: var(--forge-tracking-tight, normal);
}

/* ---------------------------------------------------------------------------------------------
   Header, and the disclosure that is a menu below 768 px and a link row above it.

   THE HEADER IS FULL BLEED AND ITS CONTENTS ARE MEASURED, which is the band model Section 5.2 gives
   the pages, applied to the chrome: the bar's colour and its rule run edge to edge, and the lockup
   and the navigation sit inside a container that stops growing. Before 18B the header had no measure
   at all, so on a wide display the mark and the links were pinned to opposite edges of the glass.

   THE INNER ROW IS THE ONE ELEMENT BOTH AREAS SHARE A VARIANT ON. The Identity pages lay out on
   their own 1200 px measure, so ForgeChromeHeader adds .forge-identity-bar-inner there and
   _Layout.cshtml declares that one property. Everything else about the row - the flex, the wrap, the
   gap, the alignment - is this rule, worn by both.

   AND ON A PHONE THE BAR IS ONE ROW (owner round-2 finding R2-1: "put the hamburger nav item where
   the CF is, we don't need two rows for this"). The disclosure used to be width: 100%, so it wrapped
   onto a second full-width line under the lockup and spent a whole row of a 390 px screen on the
   word "Navigation" and a chevron. It is now a shrink-to-fit control the inner row's own
   justify-content: space-between puts at the trailing edge, beside the mark - the shape every reader
   already knows - and the label it used to show stays in the accessibility tree as the control's
   name.

   WHICH IS WHY THE HEADER IS THE PANEL'S CONTAINING BLOCK. A <summary> and the panel it opens are
   the same element's children, so the only way the summary can sit on the bar while the panel spans
   the viewport is for the panel to leave the bar's flow: it is absolutely positioned against THIS
   element, which is full bleed, so it still reaches both edges exactly as the negative inline margin
   used to. Above the breakpoint that positioning is undone with the rest of the panel's treatment.
   --------------------------------------------------------------------------------------------- */

.forge-public-header {
    /* The containing block for the open navigation panel below the breakpoint. It is the header
       rather than the inner row because the panel is full bleed and the inner row is measured. */
    position: relative;
    background-color: var(--forge-header);
    border-bottom: 1px solid var(--forge-divider);
}

.forge-public-header-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    max-width: 72rem;
    margin-inline: auto;
    padding: 8px var(--forge-chrome-gutter, 16px);
}

.forge-public-mark {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    text-decoration: none;
    color: var(--forge-text);
    font-weight: 600;
}

.forge-public-monogram {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    background-color: var(--forge-primary);
    color: var(--forge-primary-contrast);
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.forge-public-wordmark {
    font-size: 1.05rem;
}

/* THE CONTROL TAKES ITS OWN WIDTH AND NEVER THE ROW'S (R2-1). width: 100% is what put the
   disclosure on a second line: a full-width flex item cannot share a line with the mark, whatever
   the row's wrap or justification says. flex: 0 0 auto says the same thing the other way round and
   says it at both breakpoints, so there is no second declaration above 768 undoing a first one. */
.forge-public-nav {
    flex: 0 0 auto;
}

.forge-public-nav-toggle {
    display: flex;
    align-items: center;
    gap: 10px;
    /* THE FLOOR IN BOTH DIRECTIONS, WHICH THE LABEL USED TO PROVIDE ONE OF (R2-1). While the word
       "Navigation" was visible the control was a wide row and only its height needed declaring;
       an icon and a caret are 35 px of content, so the width is now a floor of its own rather than
       a consequence of some text. Both are the same number the panel's rows carry. */
    min-width: 44px;
    min-height: 44px;
    padding: 0 4px;
    cursor: pointer;
    color: var(--forge-text);
    font-weight: 600;
    /* The disclosure triangle goes with list-style AND with display:flex - a summary that is not
       display:list-item draws no marker in WebKit either, which is why there is no vendor-prefixed
       ::-webkit-details-marker rule here. */
    list-style: none;
}

/* THE ICON, DRAWN RATHER THAN FETCHED OR INLINED. Three bars: the element itself is the middle one
   and the two pseudo-elements are the others, all in currentColor so the icon follows the text into
   either theme with no second token. It is aria-hidden in the markup, because the summary already
   has a label and an icon announced beside it would be the same word twice. */
.forge-public-nav-icon {
    position: relative;
    display: inline-block;
    width: 18px;
    height: 2px;
    border-radius: 2px;
    background-color: currentColor;
}

.forge-public-nav-icon::before,
.forge-public-nav-icon::after {
    content: "";
    position: absolute;
    left: 0;
    width: 18px;
    height: 2px;
    border-radius: 2px;
    background-color: currentColor;
}

.forge-public-nav-icon::before {
    top: -6px;
}

.forge-public-nav-icon::after {
    top: 6px;
}

/* THE LABEL IS THE CONTROL'S NAME AND IS NO LONGER ITS WIDTH (R2-1).

   IT IS HIDDEN FROM THE EYE AND NOT FROM THE ACCESSIBILITY TREE, which is the whole reason this is
   the clip idiom the pricing table's own reader labels use rather than display: none. A <summary>
   takes its accessible name from its text, the icon and the caret beside it are aria-hidden, and
   display: none would leave a disclosure announced as nothing at all. So the word is still in the
   markup, still in the name, and simply not spending a row of a phone.

   AND THE FONT SIZE IS GONE WITH THE VISIBLE TEXT rather than left behind: a size on a one-pixel
   clipped box is a declaration that describes nothing, and this file's own rule about consuming
   declarations is about text a reader reads. --forge-text-body is still read by the footer's
   description, so no token is orphaned by its going. */
.forge-public-nav-toggle-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* THE STATE AFFORDANCE (Section 5.5): a chevron that points down when the panel is closed and up
   when it is open, so the control says what it will do rather than only what it is. It is two
   borders on an empty span rather than a glyph, because a glyph is a font's opinion about a shape
   and this site ships no font. */
.forge-public-nav-caret {
    display: inline-block;
    width: 7px;
    height: 7px;
    margin-left: auto;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
}

.forge-public-nav[open] .forge-public-nav-caret {
    transform: rotate(225deg);
}

/* ---------------------------------------------------------------------------------------------
   The open panel, below the breakpoint (Section 5.5, decision Q8; finding F7).

   IT IS A PANEL RATHER THAN A LIST. Its own surface colour separates it from the bar above it, each
   row is 44 px with a divider under it, the rows run the full width of the header rather than
   sitting inside its gutter, and the primary call to action is pinned at the bottom where a thumb
   is. None of that changed in R2-1; what changed is how it reaches the header's edges.

   IT IS ANCHORED TO THE HEADER RATHER THAN INSET OUT OF THE ROW (R2-1). A negative inline margin
   was the right answer while the disclosure was a full-width second row and its panel was already
   as wide as the bar. Now the disclosure is a 44 px control at the trailing edge, so a panel in its
   flow would be 44 px wide: it is taken out of flow instead and pinned to both edges of
   .forge-public-header, which is full bleed, so the rendered panel is the same width it always was
   and the bar above it is one row. It overlays what follows rather than pushing it down, which is
   what a header disclosure does on a phone, and it needs a stacking order for exactly that reason -
   20 is above the page and below the Identity area's status toast at 1400.

   THE STATE IS STILL THE ELEMENT'S OWN. Nothing here shows or hides the panel: a closed <details>
   renders no content at all, which is what makes this whole design work with no script.
   --------------------------------------------------------------------------------------------- */

.forge-public-nav-links {
    position: absolute;
    top: 100%;
    right: 0;
    left: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    width: auto;
    padding: 0 0 12px;
    border-top: 1px solid var(--forge-divider);
    background-color: var(--forge-band);
}

.forge-public-nav-link {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0 var(--forge-chrome-gutter, 16px);
    border-bottom: 1px solid var(--forge-divider);
    color: var(--forge-text);
    text-decoration: none;
}

.forge-public-nav-link:hover,
.forge-public-nav-link:focus-visible {
    background-color: var(--forge-surface);
    text-decoration: underline;
}

/* .forge-public-nav-note IS DELETED (Phase 21, slice 21D). It styled the "Sign-up opens soon" note
   that stood in the primary slot of five marketing surfaces; 21C replaced three of them and 21D
   took the last two, so no element in src/ wears the class any more. A served rule nothing wears is
   bytes on every anonymous visit plus a name the next author would reach for believing it was wired
   - the class F18-A2-3 filed and ScopedStylesheetReachTests exists for - and the contrast suite said
   so on this branch's first gate, because a rule it cannot ground is a pair nobody is checking.
   This file is 21C's hotspot; the rule died of 21D's edit, so 21D buries it. */

/* The sign-out control is a <button> because signing out is a POST, and a button that lives in a
   link row has to stop looking like a button: the browser's own background, border and font are
   reset so the row reads as one list rather than as three links and a control. */
.forge-public-nav-signout {
    width: 100%;
    border: 0;
    border-bottom: 1px solid var(--forge-divider);
    background-color: transparent;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.forge-public-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    margin: 12px var(--forge-chrome-gutter, 16px) 0;
    padding: 0 16px;
    border-radius: 8px;
    background-color: var(--forge-primary);
    color: var(--forge-primary-contrast);
    font-weight: 600;
    text-decoration: none;
}

.forge-public-cta:hover,
.forge-public-cta:focus-visible {
    background-color: var(--forge-primary-strong);
}

/* The Identity area's account row. It is a list inside the same panel, so it inherits the panel's
   direction and its rows are the same .forge-public-nav-link rows; what this rule does is stop it
   being a bulleted list and keep it able to shrink. The wrap and the min-width that make a long
   address survive a 320 px viewport are declared in _Layout.cshtml, where IdentityManagePageTests
   reads them out of the served document: this file is a <link>, so its text never reaches that
   page's HTML. */
.forge-identity-nav {
    display: flex;
    flex-direction: column;
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* ---------------------------------------------------------------------------------------------
   Footer (Section 5.4, Section 9.1; findings F3, F4 and F5).

   THREE CENTRED BLOCKS AND FIVE LINES, IN ONE ORDER. Product name, one-sentence description, the
   legal link row, the copyright, the disclaimer. The centring is unconditional - text-align on the
   band, a column flex with align-items: center inside it - so there is no width at which the footer
   is left-aligned and no second breakpoint deciding when it stops being.

   THE LINK ROW IS ONE WRAPPING ROW AT EVERY WIDTH (owner's phone walkthrough, 2026-09-12, item 1).

   WHAT THE OWNER MET, AND WHAT THIS FILE USED TO SAY ABOUT IT. The rule here was flex: 1 1 14rem on
   each link, and the paragraph that stood in this place argued for it: 14 rem is 224 px, two of them
   cannot share a 288 px content box, so "every link takes its own full-width row and centres its
   text". That is exactly what a phone got - four legal links stacked one per line, four 44 px rows
   of footer for two words each - and the arithmetic was right about the behaviour and wrong about
   the want. Four short labels are a ROW that wraps when it runs out of room, not a column that is
   always four lines.

   SO THE BASIS IS GONE AND EACH LINK TAKES ITS OWN WIDTH. flex: 0 0 auto at every width, with the
   row's own flex-wrap and justify-content doing the rest: at 320 and 360 the four labels are about
   340 px of text, so they fall into two centred lines of two; at 600 and above they sit on one. No
   breakpoint decides that, and the 768 block below no longer has anything to undo - the declaration
   it carried IS this one now, which is why it is deleted there rather than left as a rule that
   repeats the default.

   THE ROW GAP IS ZERO ON PURPOSE. Every link is already a 44 px target, so a wrapped second line is
   44 px clear of the first without a gap adding to it; the 16 px is the COLUMN gap, between labels
   sharing a line. And the two lines under the links are tightened - the copyright's top margin off,
   a 1.4 line height on both - because they are the quietest lines on the page and were reading as
   two more blocks rather than as a footnote.
   --------------------------------------------------------------------------------------------- */

/* THE BOTTOM PADDING CLEARS THE BACK-TO-TOP CONTROL (owner's phone walkthrough, 2026-09-12, item
   2): 44 px of button plus its 16 px inset plus the 16 px this band already spent, so the fixed
   control never lands on the disclaimer at the end of the page. */
.forge-public-footer {
    padding: 24px var(--forge-chrome-gutter, 16px) 76px;
    border-top: 1px solid var(--forge-divider);
    background-color: var(--forge-header);
    color: var(--forge-text-muted);
    text-align: center;
}

.forge-public-footer-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    width: 100%;
    max-width: 48rem;
    margin-inline: auto;
}

.forge-public-footer-name {
    margin: 0;
    color: var(--forge-text);
    font-weight: 600;
}

.forge-public-footer-description {
    margin: 0;
    max-width: 42rem;
    font-size: 1rem;
    font-size: var(--forge-text-body, 1rem);
}

.forge-public-footer-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    width: 100%;
    gap: 0 16px;
}

.forge-public-footer-link {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 4px;
    color: var(--forge-text-muted);
}

.forge-public-footer-note {
    margin: 0;
    font-size: 0.85rem;
    font-size: var(--forge-text-sm, 0.85rem);
    line-height: 1.4;
}

/* The disclaimer is the quietest line on the page on purpose (Section 9.1: 0.85rem in
   --forge-text-muted). What it says is decided in LegalDocumentCatalog, beside the placeholders it
   must not contradict; what it looks like is decided here. */
.forge-public-footer-disclaimer {
    margin: 0;
    max-width: 46rem;
    font-size: 0.85rem;
    font-size: var(--forge-text-sm, 0.85rem);
    line-height: 1.4;
    color: var(--forge-text-muted);
}

/* ---------------------------------------------------------------------------------------------
   Back to top, with no script and no circuit (owner's phone walkthrough, 2026-09-12, item 2).

   WHAT THE APPLICATION HAS AND THESE TWO AREAS DID NOT. ScrollToTopButton wraps MudScrollToTop,
   which is a Blazor component listening to a scroll event over a circuit. A public page has no
   circuit by construction and runs no script at all - PublicSiteNoScriptTests holds that over the
   response bytes - so the application's control cannot be reused here and a copy of it would be the
   one thing this site is not allowed to ship.

   SO IT IS AN ANCHOR TO THE PAGE'S OWN TOP, AND ITS href IS THE PAGE'S OWN URL. ForgeChromeHeader
   carries id="top" on the bar itself, and ForgeChromeFooter renders the anchor with the caller's
   own path and query in front of the fragment. A bare href="#top" would be WRONG rather than
   merely terse: both shells serve <base href="/">, against which a fragment-only reference resolves
   to the HOME page's #top, so the control on /terms would have navigated a reader to the landing.
   Carrying the query matters for the same class of reason - a same-document fragment navigation is
   a scroll, and dropping ?topic= off /contact or ?returnUrl= off the login page would make it a
   fresh document load that discards what the reader had typed.

   THE REVEAL IS A SCROLL-DRIVEN ANIMATION AND ITS FALLBACK IS "ALWAYS THERE". The rule declares no
   animation-duration at all: on an engine that implements scroll-driven animations the initial
   value is auto, which means "the whole of the timeline's range", and on one that does not it is
   0s - so the fill-mode lands the element on its final step immediately and the control is simply
   always visible. That is the §12 R3 direction, and it is why the first step is written in the
   keyframe rather than as an opacity on the rule: an invisible resting state declared outside a
   keyframe is a page that can be stuck invisible, which PublicMotionTests refuses outright.

   AND THE RESTING STEP TRANSLATES AS WELL AS FADES. opacity: 0 alone leaves a fully clickable
   control sitting over the page's own content at the top of every document; 200% of its own height
   puts it outside the viewport, where it cannot be tapped by somebody who cannot see it.

   IT CLEARS THE FOOTER RATHER THAN COVERING IT. The footer's bottom padding is the button's height
   plus its inset, so at the end of the page the control sits beside the disclaimer's empty space
   instead of on top of the last line a reader might want to read.
   --------------------------------------------------------------------------------------------- */

.forge-scroll-top {
    position: fixed;
    right: var(--forge-chrome-gutter, 16px);
    bottom: 16px;
    z-index: 20;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    /* --forge-control-edge AND NOT --forge-divider. The divider is the decorative token and computes
       about 1.1 to 1.4:1 against every ground on this site; this is a control a reader operates, so
       its boundary owes WCAG 2.2 SC 1.4.11's 3:1. PublicContrastTests refused the first draft of
       this rule by name, which is the guard 18A's §3.8 repair left behind doing its job. */
    border: 1px solid var(--forge-control-edge);
    border-radius: 50%;
    background-color: var(--forge-surface);
    color: var(--forge-text);
    text-decoration: none;
    animation-name: forge-scroll-top-reveal;
    animation-fill-mode: both;
    animation-timing-function: linear;
    animation-timeline: scroll();
    animation-range: 240px 480px;
}

/* THE ARROW IS DRAWN IN CSS, for the reason the navigation icon is: BannedApiSweepTests reads every
   .razor under src/Forge.Web/ and allows exactly one inline <svg>, which is the world map. Two
   borders on a square, rotated, is an arrowhead; the element is aria-hidden and the anchor carries
   its own label. */
.forge-scroll-top-glyph {
    width: 0.6rem;
    height: 0.6rem;
    margin-top: 0.2rem;
    border-top: 2px solid currentColor;
    border-right: 2px solid currentColor;
    transform: rotate(-45deg);
}

.forge-scroll-top:hover,
.forge-scroll-top:focus-visible {
    background-color: var(--forge-band);
    color: var(--forge-text);
}

@keyframes forge-scroll-top-reveal {
    from {
        opacity: 0;
        transform: translateY(200%);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* ---------------------------------------------------------------------------------------------
   Motion, tiers 1 and 3 (Section 5.8): the chrome's own interactive states and the panel's entrance,
   and nothing else.

   ONLY PROPERTIES THAT ARE ALREADY BEING CHANGED BY A STATE RULE ABOVE ARE TRANSITIONED, so a
   transition can never be the thing that decides whether something is visible - which is the R3
   failure mode in its cheapest form. Every one of these elements renders correctly with the
   transition removed; all it does is take 160 ms to get there.
   --------------------------------------------------------------------------------------------- */

.forge-public-mark,
.forge-public-nav-toggle,
.forge-public-nav-link,
.forge-public-nav-signout,
.forge-public-cta,
.forge-public-footer-link,
.forge-scroll-top {
    transition:
        background-color var(--forge-motion, 160ms) var(--forge-motion-ease, ease-out),
        border-color var(--forge-motion, 160ms) var(--forge-motion-ease, ease-out),
        color var(--forge-motion, 160ms) var(--forge-motion-ease, ease-out);
}

.forge-public-nav-caret,
.forge-public-nav-links {
    transition: transform var(--forge-motion, 160ms) var(--forge-motion-ease, ease-out);
}

/* TIER 3, AND IT MOVES A TRANSFORM RATHER THAN AN OPACITY (Section 5.8; Section 12 R3).

   @starting-style is what gives a newly rendered element a value to transition FROM, so the panel
   slides down out of the bar instead of appearing whole. It is Baseline since August 2024 and an
   engine that does not know the at-rule skips the whole block, which is the correct degradation:
   the panel simply appears, exactly as it did before this slice.

   THE STARTING VALUE IS A TRANSLATION AND DELIBERATELY NOT opacity: 0. R3's failure mode is content
   that stays invisible because a transition did not run, and the cheapest way to be immune to it is
   never to write the invisible value at all. A panel that starts six pixels high is a panel that is
   readable in every frame, including the frame where nothing animated. */
@starting-style {
    .forge-public-nav[open] .forge-public-nav-links {
        transform: translateY(-6px);
    }
}

/* ---------------------------------------------------------------------------------------------
   768 px and up: the disclosure stops being a menu.

   THIS IS THE ONE BREAKPOINT ON THE PUBLIC SITE. Below it the nav is a closed <details> the reader
   taps open; at and above it the summary is gone, the links sit in a row, and the element is open
   whether or not the reader ever touched it - which is why the links are shown by an explicit
   display rule rather than by relying on the element's open state.

   THE PANEL'S OWN TREATMENT IS UNDONE HERE RATHER THAN CONFINED BELOW THE BREAKPOINT, which is the
   same shape the file has always had: the phone layout is the default and this block is the
   exception. The band colour, the dividers, the negative margin and the pinned action all come off,
   because above the breakpoint there is no panel - there is a row of links in a bar.
   --------------------------------------------------------------------------------------------- */

@media (min-width: 768px) {
    .forge-public-nav-toggle {
        display: none;
    }

    /* THE WIDTH RESET THAT STOOD HERE IS GONE WITH THE FULL WIDTH IT UNDID (R2-1). The base rule
       gives the disclosure flex: 0 0 auto at every width now, so this block has nothing left to
       correct - and a declaration that undoes a declaration nobody makes is the shape this file's
       own F13-10 note warns about. */

    /* THE ENTIRE DESKTOP NAVIGATION DEPENDS ON THIS ONE RULE, AND THERE IS NO SECOND HALF
       (17M finding F17M-8-1; the comment that stood here promised a fallback that cannot exist).

       At and above the breakpoint the links are still inside a CLOSED <details> - nothing opens it,
       because there is no script - so this rule is the only thing that shows them. A closed
       <details> hides its contents through the content slot that ::details-content exposes, NOT
       through the light-DOM children. So .forge-public-nav-links { display: flex } below describes
       a DIFFERENT element and cannot override it: the cascade's author-beats-user-agent rule does
       not reach across elements, and content-visibility on an ancestor is not undone by a display
       declaration on a descendant.

       WHICH MAKES THE BROWSER FLOOR REAL, AND WORTH WRITING DOWN RATHER THAN INVENTING A FALLBACK.
       ::details-content is Baseline "newly available" since September 2025 - Chrome and Edge 131,
       Safari and iOS Safari 18.4, Firefox 143 (MDN and caniuse, read 2026-09-06). An engine older
       than its own row gets NO navigation at all above 768 px, because the same block turns the
       <summary> off. If the supported matrix ever includes one, the only script-free answer is to
       render the link row a second time outside the <details> and show one per breakpoint; until
       that is decided, the desktop nav at 768 and 1024 is an explicit walkthrough item.

       PublicLayoutTests.The_desktop_nav_rests_on_the_details_content_rule asserts this rule in the
       shipped text, because bUnit evaluates no stylesheet and deleting these three lines was a
       green build across 88 tests. */
    .forge-public-nav::details-content {
        content-visibility: visible;
        block-size: auto;
    }

    .forge-public-nav-links {
        /* BACK INTO THE BAR'S OWN FLOW (R2-1). Below the breakpoint the panel is pinned to the
           header's edges; here there is no panel, there is a row of links inside a bar, and an
           absolutely positioned one would hang under the header instead of sitting in it. */
        position: static;
        z-index: auto;
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 4px 20px;
        width: auto;
        padding-bottom: 0;
        border-top: 0;
        background-color: transparent;
    }

    .forge-public-nav-link {
        padding: 0 4px;
        border-bottom: 0;
    }

    .forge-public-nav-link:hover,
    .forge-public-nav-link:focus-visible {
        background-color: transparent;
    }

    .forge-public-nav-signout {
        width: auto;
        border-bottom: 0;
    }

    .forge-identity-nav {
        flex-direction: row;
        align-items: center;
        gap: 4px 20px;
    }

    .forge-public-cta {
        margin: 0;
    }

    /* THE FOUR LEGAL LINKS' OVERRIDE IS GONE WITH THE BASIS IT UNDID (owner's phone walkthrough,
       2026-09-12, item 1). The rule that stood here stopped the footer link growing, so the four
       sat in one centred row above the breakpoint while below it each one grew into a full-width
       line of its own. The base rule stops it growing at EVERY width now, so this block has nothing
       left to correct - and a declaration that undoes a declaration nobody makes is the shape the
       nav's own note two comments up warns about. */
}

/* ---------------------------------------------------------------------------------------------
   Reduced motion (Section 3.5).

   ONE ARM PER STYLESHEET, WRITTEN BY THAT STYLESHEET'S OWNER, and this is chrome.css's. It names
   the classes THIS FILE animates rather than reaching for the universal selector, for a reason
   worth stating: a * arm satisfies any coverage check by construction, and 18E's motion guard folds
   the animated-class set out of the stylesheet and compares it against this selector list. A guard
   that cannot fail is not protecting the reader who asked their operating system to stop things
   moving; a list that has to grow with the animations is.

   IT COVERS THE PANEL'S ENTRANCE AS WELL AS THE HOVER STATES. @starting-style has no arm of its own
   - it supplies a value, not a duration - so switching the transition off is what stops the slide,
   and .forge-public-nav-links is therefore in this list even though nothing about it is a hover.

   transition-duration rather than transition: none, because a duration of nearly zero still fires
   the transitionend a later rule might depend on, where "none" removes the transition entirely.
   --------------------------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    .forge-public-mark,
    .forge-public-nav-toggle,
    .forge-public-nav-icon,
    .forge-public-nav-caret,
    .forge-public-nav-links,
    .forge-public-nav-link,
    .forge-public-nav-signout,
    .forge-public-cta,
    .forge-public-footer-link,
    .forge-scroll-top {
        animation: none;
        transition-duration: 0.01ms;
    }
}

/* ---------------------------------------------------------------------------------------------
   THE SIGN-OUT CONTROL SAYS IT IS WORKING (Round 1 audit finding F18-A1-7, Low; appended by
   18F R1, which is why it sits after the arm above rather than inside it).

   THE DEFECT. 18B gave the shared sign-out button the data-forge-pending-submit hook, and
   wwwroot/js/pending.js does its half: it adds .forge-pending, sets aria-busy="true" and disables
   the button on the next tick. The VISUAL half is deliberately left to the caller, and the Identity
   layout defines it as .btn.forge-pending::before - a .btn selector. This button is not a .btn, and
   neither stylesheet declared any :disabled or busy rule for it, so nothing matched. The
   author-declared colour on .forge-public-nav-link also beats the user agent's own disabled
   greying, so the control did not even dim: pressing sign out made it silently stop responding,
   against Working-Conventions' "command-sending controls disable while a command is in flight and
   something visibly animates".

   IT KEYS OFF aria-busy RATHER THAN OFF THE CLASS, for two reasons. The state a reader is being
   shown is the state assistive technology is already being told about, so the two cannot drift; and
   Subjects() in PublicMotionTests folds every class token out of a selector's last compound, so
   .forge-public-nav-signout.forge-pending would demand .forge-pending in a reduced-motion arm as
   well - a class this stylesheet does not own and whose meaning lives in a script.

   THE MOTION IS A PULSE AND NOT A SPIN, and that is Section 3.5 rather than taste: every keyframe on
   this site has to END where the element already was, so an engine that skips the animation renders
   a correct page. A rotation cannot (transform's initial value is none), while opacity's is 1, so
   the pulse ends fully opaque and a reader who never sees it moving still sees the ring and the
   dimmed label. The ring itself is a static ::before and carries no animation at all.

   ITS OWN REDUCED-MOTION ARM, because the arm above names .forge-public-nav-signout at (0,1,0) and
   this rule is (0,2,0) - so the arm above could not switch it off. public.css already carries two
   arms for the same append-only reason; PublicMotionTests folds their union per file.
   --------------------------------------------------------------------------------------------- */

.forge-public-nav-signout[aria-busy="true"] {
    animation: forge-signout-pending 800ms ease-in-out infinite alternate;
    cursor: progress;
}

.forge-public-nav-signout[aria-busy="true"]::before {
    content: "";
    display: inline-block;
    width: 0.75em;
    height: 0.75em;
    margin-right: 0.5rem;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
}

@keyframes forge-signout-pending {
    from { opacity: 0.45; }
    to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .forge-public-nav-signout[aria-busy="true"] {
        animation: none;
        transition-duration: 0.01ms;
    }

    .forge-public-nav-signout[aria-busy="true"]::before {
        border-right-color: currentColor;
        opacity: 0.55;
    }
}

/* ---------------------------------------------------------------------------------------------
   THE APPLICATION'S OWN NAVIGATION, DRAWN WITHOUT A CIRCUIT (Phase 20 round-2 audit, F20-O-6; the
   owner's words: "we should still have the same nav menu on the left, it should look like we are
   on the same site").

   APPENDED BELOW THE ARMS ABOVE RATHER THAN WOVEN INTO THEM, which is this file's standing rule: a
   slice owns the region it wrote, so an edit inside an earlier phase's block is a merge conflict on
   every rebase rather than a design decision.

   WHY THESE RULES ARE IN THIS FILE AND NOT IN THE IDENTITY SHELL'S INLINE BLOCK. Two guards have to
   be able to read them: PublicContrastTests computes every pair the design paints from the rules in
   the stylesheets the chrome serves, and PublicTouchTargetTests parses the floor-bearing selectors
   out of the same set. An inline block in a Razor Page is a file the first of those never opens, so
   a colour written there is a colour nobody computes. What stays in _Layout.cshtml is the Identity
   area's own measure, which is genuinely that area's.

   IT IS A VERTICAL LIST AT EVERY WIDTH, AND WHAT CHANGES IS WHERE IT LIVES (owner mobile round 3,
   M9). It shipped as a horizontally scrolling strip above the card below 768 and a rail beside it
   above - the mechanism the Manage cluster's own section nav had used since 8C - and on a 390 px
   phone that spent a whole screen on navigation before the page began and still clipped its rows:
   the owner read "Tasl" where the product says Tasks. Below the breakpoint the nav is now inside the
   header's <details> disclosure, so the card starts at the top of the document and the whole product
   appears when the hamburger beside the wordmark is opened; at 768 and above the rail is exactly
   what T6 shipped. There is still one media query, no viewport probe and no script.

   WHICH IS WHY THE ROWS ARE A COLUMN IN THE BASE RULE NOW. A horizontal row that scrolls has no
   width left to be correct at, in either position: in the panel it would clip inside a 390 px
   viewport, and in the rail it was already a column. A rule that reaches neither position is the
   F13-10 shape, so overflow-x and the row direction are deleted rather than left standing.

   THE PANEL HAS A GROUND OF ITS OWN, and that is what makes its pairs computable: the nav is
   --forge-surface wherever it is rendered, so its label colour is measured against the same ground
   in both areas and in both themes rather than against whatever it happens to sit on.

   NOT ONE LITERAL COLOUR, exactly as the top of this file promises. Every value below is a var()
   read of a name PublicPalette emits.
   --------------------------------------------------------------------------------------------- */

.forge-app-nav {
    /* Load-bearing on a flex child, for the reason .forge-manage-nav's own comment gives: without
       it this column refuses to shrink below its content and the DOCUMENT scrolls sideways instead
       of the thing inside it. */
    min-width: 0;
    padding: 8px 12px;
    border: 1px solid var(--forge-divider);
    border-radius: 8px;
    background-color: var(--forge-surface);
}

/* A LABELLED GROUP, NOT A HEADING, and the element is the component's decision rather than this
   file's: this nav is drawn BEFORE the card that holds every Identity page's h1, so a heading here
   would put an h2 in front of it. ForgeAppNav renders a paragraph the list names through
   aria-labelledby; the rule is written on the class so it does not care which element wears it.
   Quiet, because a section label is not a destination. */
.forge-app-nav-heading {
    margin: 12px 0 4px;
    font-size: var(--forge-text-xs, 0.8rem);
    font-weight: 600;
    letter-spacing: var(--forge-tracking-wide, 0.08em);
    text-transform: uppercase;
    color: var(--forge-text-muted);
}

/* A COLUMN, AND NOTHING SCROLLS SIDEWAYS (M9). A row of twenty destinations does not fit a 320 px
   viewport and never will, so the shipped rule made the LIST scroll instead of the document - which
   is a page a reader drags a finger across to read a word, and the word they read was "Tasl". Every
   row now takes its own line in both positions, so a destination is either on the screen or one
   scroll of the page away, never half of it.

   min-width: 0 stays and is still load-bearing on a flex child: without it a row's own content
   decides the column's floor and the DOCUMENT widens behind it. */
.forge-app-nav-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* A row's whole line is the target rather than its label. */
.forge-app-nav-list > li {
    flex: 1 1 auto;
}

/* 44 px tall, the touch floor from the Working-Conventions width matrix, stated here rather than
   inherited from anything: this markup is served in an area whose form framework is Bootstrap, and
   Bootstrap's own .nav-link is 38 px. */
.forge-app-nav-link {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0 12px;
    border-radius: 6px;
    color: var(--forge-text);
    text-decoration: none;
    white-space: nowrap;
}

/* HOVER HAS TO BE VISIBLE AGAINST THE PANEL IT SITS ON (audit 8D finding L8-C11, the same lesson
   the Manage nav learned). --forge-divider is the token that already means "a line you can see
   against this surface" in both themes, so the hover borrows its separation instead of inventing a
   colour, and the label stays --forge-text on it: 12.26:1 in light and 10.49:1 in dark. */
.forge-app-nav-link:hover {
    background-color: var(--forge-divider);
    text-decoration: underline;
}

/* FOCUS IS NOT HOVER. Pointing at an entry and having landed on it by keyboard are different facts,
   so they are different pixels: focus keeps the background change and adds a ring in the palette's
   own primary, drawn outside the border box so its ratio is against the panel as well as against
   the row's own fill (6.75:1 and 5.34:1 in light, 7.35:1 and 5.74:1 in dark). */
.forge-app-nav-link:focus-visible {
    background-color: var(--forge-divider);
    outline: 2px solid var(--forge-primary);
    outline-offset: 2px;
}

/* -----------------------------------------------------------------------------------------------
   THE SAME NAV, INSIDE THE HEADER'S DISCLOSURE (owner mobile round 3, M9).

   These rules describe the copy the header draws inside its <details> panel, and they are written as
   a DESCENDANT of the panel rather than as a variant class on the nav: the two copies are the same
   markup and what differs between them is genuinely where they are, which is the one thing a
   descendant selector says exactly. It also keeps the nav's own class list identical in both
   positions, so every guard that reads `<nav class="forge-app-nav"` reads both.

   THE PANEL ALREADY IS A LIST, SO THE NAV STOPS BEING A CARD. Inside the disclosure the nav's own
   border, radius, padding and surface would draw a card inside a panel; what a reader should see is
   the panel's own rows continuing. So the box comes off, each row takes the panel's gutter and the
   panel's divider, and the group labels sit in the same gutter above the rows they name.

   AND A ROW WRAPS RATHER THAN CLIPPING, which is the finding itself. The rail is 13 rem of known
   width and its labels are nowrap; a panel row is as wide as the viewport and its label has no
   length this design may assume, so white-space returns to normal and a long destination takes two
   lines instead of disappearing off the edge.
   ----------------------------------------------------------------------------------------------- */

.forge-public-nav-links .forge-app-nav {
    padding: 0;
    border: 0;
    border-radius: 0;
    background-color: transparent;
}

.forge-public-nav-links .forge-app-nav-heading {
    margin: 0;
    padding: 12px var(--forge-chrome-gutter, 16px) 4px;
}

.forge-public-nav-links .forge-app-nav-list {
    gap: 0;
}

.forge-public-nav-links .forge-app-nav-link {
    padding: 0 var(--forge-chrome-gutter, 16px);
    border-radius: 0;
    border-bottom: 1px solid var(--forge-divider);
    white-space: normal;
}

@media (min-width: 768px) {
    /* THE DISCLOSURE'S COPY IS THE PHONE'S, AND ONLY THE PHONE'S. At and above the breakpoint the
       summary is display: none and the panel is a row of links in the bar, so a nav left in it would
       be the whole product laid across the app bar. The rail beside the card is the copy that
       answers here, and _Layout.cshtml is where that half is declared, beside the measure it takes.
       Exactly one copy is displayed at any width, which is what keeps one of them in the
       accessibility tree. */
    .forge-public-nav-links .forge-app-nav {
        display: none;
    }
}

/* ---------------------------------------------------------------------------------------------
   THE OUTCOME TOAST (Phase 20 round-2 audit, F20-O-7; the owner's words: "I was able to save my
   display name, but didn't see a snackbox it just has the inline message it was saved").

   WHAT THIS IS AND WHAT IT IS NOT. The application answers a successful save with a MudBlazor
   snackbar; the Manage cluster answers it with a full page load, a redirect and a line of text at
   the top of the document, which on a phone is above the fold or not depending on how the heading
   wraps. This is the SAME string, rendered a second time by the same partial, pinned where a
   reader's eye already is. It is PURELY VISUAL: the toast carries aria-hidden, so the one live
   region on the page is still the inline banner and a screen reader is told once rather than twice.

   NOT ONE COLOUR IS DECLARED HERE, WHICH IS THE POINT OF THE DESIGN. The toast wraps the SAME
   .forge-manage-status element the page already renders inline, so its ground, its text and its
   tone accent are that rule's, in the Identity shell where they already live and are already
   correct in both themes. This block is geometry: where the box sits, how wide it may be, and what
   the dismiss control looks like. A toast with a palette of its own would be a second thing to keep
   looking like the first.

   FULL WIDTH AT THE TOP BELOW THE BREAKPOINT, A CARD IN THE CORNER ABOVE IT, which is the matrix's
   row for this element: on a phone the page IS one column, and a floating card in the corner of it
   is a card with no corner to float in.
   --------------------------------------------------------------------------------------------- */

.forge-status-toast {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    z-index: 1400;
    padding: 12px;
    transition: transform var(--forge-motion, 160ms) var(--forge-motion-ease, ease-out);
}

/* THE ROW INSIDE IT: the banner, and the dismiss control beside it. The banner is
   .forge-manage-status and brings its own ground, border and tone; its margin-bottom is cancelled
   because there is nothing under it here. */
.forge-status-toast > .forge-manage-status {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 0;
}

.forge-status-toast-text {
    flex: 1 1 auto;
    min-width: 0;
    /* A status message can carry an address, which is one unbreakable token. */
    overflow-wrap: anywhere;
}

/* THE DISMISS CONTROL IS A POINTER AFFORDANCE AND DELIBERATELY NOT A TAB STOP, WHICH IS WHY IT HAS
   NO FOCUS RULE. The whole toast is aria-hidden, and a focusable element inside an aria-hidden
   subtree is a control a keyboard can reach and a screen reader cannot describe, so the markup
   carries tabindex="-1" and :focus-visible could never match it. A rule that cannot fire is the
   F13-10 shape and is not written. The keyboard reader loses nothing: the banner this duplicates is
   still in the page, still announced, and the toast removes itself. It still clears the touch floor,
   because a thumb is exactly who it is for, and it takes its colour from the banner it sits in
   rather than declaring one. */
.forge-status-toast-dismiss {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    padding: 0 8px;
    border: 0;
    border-radius: 6px;
    background-color: transparent;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.forge-status-toast-dismiss:hover {
    text-decoration: underline;
}

/* TIER 3, AND IT MOVES A TRANSFORM RATHER THAN AN OPACITY, which is the rule the navigation panel's
   own entrance states earlier in this file: R3's failure mode is content that stays invisible
   because a transition did not run, and the cheapest way to be immune to it is never to write the
   invisible value at all. A toast that starts eight pixels high is readable in every frame,
   including the frame where nothing animated. */
@starting-style {
    .forge-status-toast {
        transform: translateY(-8px);
    }
}

@media (min-width: 768px) {
    /* The toast leaves the left edge and takes a card's width in the corner. */
    .forge-status-toast {
        left: auto;
        max-width: 26rem;
        padding: 16px;
    }
}

/* ITS OWN REDUCED-MOTION ARM, appended for the reason this file's second arm exists: the file is
   append-only per slice and an earlier arm belongs to the slice that wrote it. It names exactly the
   selector this slice animates, at the same specificity, so it can switch it off. */
@media (prefers-reduced-motion: reduce) {
    .forge-status-toast {
        animation: none;
        transition-duration: 0.01ms;
    }
}
