/*
 * Styles for the Account pages.
 *
 * Those pages carry the ASP.NET Core Identity template's markup verbatim — plain <form>, <InputText>,
 * and its Bootstrap class names — because that is the shape browsers and password managers recognise:
 * a real input with a real autocomplete token inside a real form is what makes "save this password?"
 * appear. Sunjin does not ship Bootstrap, so this file supplies only the handful of classes that
 * markup uses, drawn on MudBlazor's own palette variables so the pages match the rest of the app in
 * both light and dark themes.
 *
 * Deliberately not a Bootstrap subset for general use: nothing outside Components/Features/Account
 * should reach for these.
 */

/* ── Layout helpers ────────────────────────────────────────────────────────────────────────────── */

.w-100 {
    width: 100%;
}

.text-danger {
    color: var(--mud-palette-error);
    font-size: 0.8125rem;
}

.button-row,
.identity-links,
.external-login-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    margin-top: 1rem;
}

.identity-links {
    font-size: 0.8125rem;
    color: var(--mud-palette-text-secondary);
    gap: 0.5rem;
    justify-content: center;
}

/* The recovery links are secondary text with an underline, not brand-coloured links: three red items
   in a row competed with the sign-in button for the same attention. */
.identity-links a {
    color: var(--mud-palette-text-secondary);
    text-decoration: none;
}

.identity-links a:hover {
    color: var(--mud-palette-text-primary);
    text-decoration: underline;
}

/* The one forward action on this page that is not sign-in, so it sits on its own line below the
   recovery links and keeps the brand colour they gave up. */
.identity-signup {
    margin: 1rem 0 0;
    font-size: 0.8125rem;
    text-align: center;
    color: var(--mud-palette-text-secondary);
}

.identity-signup a {
    margin-left: 0.25rem;
    font-weight: 500;
    color: var(--mud-palette-primary);
    text-decoration: none;
}

.identity-signup a:hover {
    text-decoration: underline;
}

.inline-form {
    display: inline-block;
}

.identity-title {
    font-size: 1.5rem;
    font-weight: 600;
    margin: 0 0 0.5rem;
    color: var(--mud-palette-text-primary);
}

.identity-subtitle {
    margin: 0 0 1.5rem;
    color: var(--mud-palette-text-secondary);
    font-size: 0.875rem;
}

/* ── Language switcher ─────────────────────────────────────────────────────────────────────────── */

/*
 * Plain flexbox rather than the MudStack this used to be: the row now has to style an active item
 * differently from the links, and putting that in a stylesheet the static pages already load beats
 * threading it through MudLink's parameters.
 *
 * Separated from the form by a rule: it is the only thing below the sign-in actions, and without one
 * it read as a third row of links rather than as page furniture.
 */
.identity-cultures {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    margin-top: 2rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--mud-palette-lines-default);
    font-size: 0.8125rem;
}

.identity-cultures a {
    color: var(--mud-palette-text-secondary);
    text-decoration: none;
}

.identity-cultures a:hover {
    color: var(--mud-palette-text-primary);
    text-decoration: underline;
}

/* Weight, not colour, marks the active language — the row sits directly under the sign-in button and
   a fourth accent-coloured item there would read as another action. */
.identity-cultures__current {
    font-weight: 600;
    color: var(--mud-palette-text-primary);
}

/* ── Floating-label fields ─────────────────────────────────────────────────────────────────────── */

/*
 * The label starts sitting over the input and lifts once the field has content or focus. It is done
 * with :placeholder-shown rather than a class toggled from C#, because these pages have no circuit to
 * toggle anything — which is also why every field in the markup carries placeholder=" ".
 */
.form-floating {
    position: relative;
    margin-bottom: 1rem;

    /*
     * Where the typed text sits inside the input, written once from the input's own metrics below so
     * .password-toggle can line up with it. Two values rather than a height, because the input's
     * padding is deliberately lopsided — 1.375rem above to clear the floating label against 0.5rem
     * below — so the text line is not the box's middle, and an icon centred on the box reads as
     * floating up by the label instead of belonging to the password beside it.
     *
     * Measured from the wrapper's own top edge, which the border makes 1px higher than the input's
     * content box. The wrapper is not usable as a bottom reference at all: it also holds the
     * <ValidationMessage> each page passes in, so a field showing an error grows by a line of red
     * text while the text line inside the input does not move.
     */
    --field-text-top: calc(1px + 1.375rem);
    --field-text-height: calc(0.9375rem * 1.4);
}

.form-control {
    width: 100%;
    box-sizing: border-box;
    padding: 1.375rem 0.875rem 0.5rem;
    font: inherit;
    font-size: 0.9375rem;
    /* Pinned rather than inherited, so --field-height above stays a true statement of this box. */
    line-height: 1.4;
    color: var(--mud-palette-text-primary);
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: var(--mud-default-borderradius, 2px);
    outline: none;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

/*
 * Chrome paints its own pale-blue background on an autofilled field and ignores `background`, which is
 * why the saved username and password rendered blue while every other field was surface-coloured. The
 * colour is only reachable through the internal -internal-autofill-selected token, so the fill is
 * driven by an inset shadow the size of the box instead, and the text colour by -webkit-text-fill.
 * The long transition delay is the standard trick for the brief flash before the shadow applies.
 */
.form-control:-webkit-autofill,
.form-control:-webkit-autofill:hover,
.form-control:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--mud-palette-text-primary);
    box-shadow: 0 0 0 100px var(--mud-palette-surface) inset;
    transition: background-color 600000s 0s, color 600000s 0s;
    caret-color: var(--mud-palette-text-primary);
}

/* Re-states the focus ring, which the rule above would otherwise overwrite on an autofilled field. */
.form-control:-webkit-autofill:focus {
    box-shadow: 0 0 0 100px var(--mud-palette-surface) inset, 0 0 0 1px var(--mud-palette-primary);
}

.form-control:hover:not(:disabled) {
    border-color: var(--mud-palette-text-secondary);
}

.form-control:focus {
    border-color: var(--mud-palette-primary);
    box-shadow: 0 0 0 1px var(--mud-palette-primary);
}

.form-control:disabled {
    color: var(--mud-palette-text-disabled);
    background: var(--mud-palette-background-gray);
}

.form-control.invalid {
    border-color: var(--mud-palette-error);
}

.form-floating > label {
    position: absolute;
    top: 0.375rem;
    left: 0.9375rem;
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
    pointer-events: none;
    transition: top 120ms ease, font-size 120ms ease;
}

/*
 * Rest position — label sitting over an empty field. Written as "drop it back down" rather than
 * "lift it up" so the lifted state above is the default and any field the browser fills without
 * telling us still reads correctly.
 *
 * :not(:-webkit-autofill) is what fixes the overlap on a restored login: Chrome fills the value but
 * keeps matching :placeholder-shown until the user touches the field, so without this the label
 * stayed sitting on top of the autofilled username instead of lifting clear of it.
 */
.form-floating > .form-control:placeholder-shown:not(:focus) + label {
    top: 0.9375rem;
    font-size: 0.9375rem;
}

/*
 * Split into its own rule rather than chained onto the one above, because a browser that does not
 * know :-webkit-autofill discards the entire selector it appears in. Chained, that would take the
 * rest position with it and leave every label stuck in the lifted state in Firefox; separate, an
 * unsupported browser simply ignores this block and keeps the plain behaviour.
 */
.form-floating > .form-control:-webkit-autofill + label {
    top: 0.375rem;
    font-size: 0.75rem;
}

.form-floating > .form-control:focus + label {
    color: var(--mud-palette-primary);
}

/*
 * Reveal/hide toggle for a password field, rendered by PasswordField.razor. Absolutely positioned
 * like .field-badge below — the established pattern for putting something at the right edge of a
 * .form-floating without disturbing the input/label sibling pair the floating-label rules depend on.
 *
 * Aligned to the typed text's own line rather than centred on the input box — see --field-text-top
 * above for why those are not the same place. The button keeps a comfortable touch target by
 * padding out from that line symmetrically, so what grows is the hit area, not the icon's position.
 */
.password-toggle {
    position: absolute;
    top: var(--field-text-top);
    height: var(--field-text-height);
    right: 0.375rem;
    /* Bleeds equally above and below the text line without shifting the icon's centre off it. */
    box-sizing: content-box;
    padding: 0.5rem 0.25rem;
    margin-top: -0.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--mud-palette-text-secondary);
    background: none;
    border: 0;
    border-radius: var(--mud-default-borderradius, 2px);
    cursor: pointer;
}

.password-toggle svg {
    width: 1.25rem;
    height: 1.25rem;
}

.password-toggle:hover {
    color: var(--mud-palette-text-primary);
}

.password-toggle:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* Right padding so typed text does not run under the button — 1.25rem of icon plus the 0.25rem the
   button pads out each side for its hit area, plus its 0.375rem offset from the edge and a little
   breathing room. Scoped to this modifier class rather than every .form-control, since most fields
   on these pages have no toggle to clear. */
.form-control--has-toggle {
    padding-right: 2.75rem;
}

/* The "email confirmed" tick beside the disabled address field. */
.field-badge {
    position: absolute;
    top: 0.75rem;
    right: 0.875rem;
    font-weight: 700;
}

.field-badge--ok {
    color: var(--mud-palette-success);
}

/* Slightly more room below than above: the checkbox belongs to the fields it follows, and the extra
   space is what separates that group from the submit button. */
.checkbox-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.25rem 0 1.5rem;
    font-size: 0.875rem;
    color: var(--mud-palette-text-primary);
    cursor: pointer;
    /* Only the label and its box are the hit target; a full-width row would put the cursor on empty
       space to the right of the text. */
    align-self: flex-start;
    width: fit-content;
}

.checkbox-row input[type="checkbox"] {
    accent-color: var(--mud-palette-primary);
    width: 1rem;
    height: 1rem;
    margin: 0;
}

/* ── Buttons ───────────────────────────────────────────────────────────────────────────────────── */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    font: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.5;
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: var(--mud-default-borderradius, 2px);
    cursor: pointer;
    transition: filter 120ms ease, background-color 120ms ease;
}

.btn:hover {
    filter: brightness(0.92);
}

.btn:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.btn-lg {
    padding: 0.75rem 1.25rem;
    font-size: 0.9375rem;
}

.btn-primary {
    color: #fff;
    background: var(--mud-palette-primary);
}

.btn-secondary {
    color: var(--mud-palette-text-primary);
    background: transparent;
    border-color: var(--mud-palette-lines-inputs);
}

.btn-secondary:hover {
    background: var(--mud-palette-action-default-hover);
    filter: none;
}

.btn-danger {
    color: #fff;
    background: var(--mud-palette-error);
}

.btn-link {
    padding: 0.25rem 0;
    color: var(--mud-palette-primary);
    background: none;
    text-decoration: underline;
}

/* ── Alerts ────────────────────────────────────────────────────────────────────────────────────── */

.alert {
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    font-size: 0.875rem;
    border: 1px solid transparent;
    border-radius: var(--mud-default-borderradius, 2px);
}

.alert p {
    margin: 0.375rem 0 0;
}

.alert p:first-child {
    margin-top: 0;
}

/*
 * color-mix against the surface rather than a fixed tint: the same rule then reads correctly on the
 * light and the dark palette, instead of a pale wash disappearing on a dark background.
 */
.alert-success {
    color: var(--mud-palette-success);
    background: color-mix(in srgb, var(--mud-palette-success) 12%, var(--mud-palette-surface));
    border-color: color-mix(in srgb, var(--mud-palette-success) 35%, transparent);
}

.alert-danger {
    color: var(--mud-palette-error);
    background: color-mix(in srgb, var(--mud-palette-error) 12%, var(--mud-palette-surface));
    border-color: color-mix(in srgb, var(--mud-palette-error) 35%, transparent);
}

.alert-warning {
    color: var(--mud-palette-warning);
    background: color-mix(in srgb, var(--mud-palette-warning) 12%, var(--mud-palette-surface));
    border-color: color-mix(in srgb, var(--mud-palette-warning) 35%, transparent);
}

.alert-info {
    color: var(--mud-palette-info);
    background: color-mix(in srgb, var(--mud-palette-info) 12%, var(--mud-palette-surface));
    border-color: color-mix(in srgb, var(--mud-palette-info) 35%, transparent);
}

/* ── Tables, lists, recovery codes ─────────────────────────────────────────────────────────────── */

.table {
    width: 100%;
    margin-bottom: 1rem;
    border-collapse: collapse;
    font-size: 0.875rem;
}

.table td {
    padding: 0.625rem 0.5rem;
    border-bottom: 1px solid var(--mud-palette-table-lines);
    vertical-align: middle;
}

.table td form {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
}

.setup-steps {
    padding-left: 1.25rem;
    font-size: 0.875rem;
}

.setup-steps > li {
    margin-bottom: 1rem;
}

kbd {
    padding: 0.125rem 0.375rem;
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    background: var(--mud-palette-background-gray);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius, 2px);
}

.recovery-code-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.recovery-code {
    padding: 0.375rem 0.5rem;
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    text-align: center;
    background: var(--mud-palette-background-gray);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius, 2px);
}

/* ── The "manage account" shell ────────────────────────────────────────────────────────────────── */

.identity-shell {
    min-height: 100vh;
    background: var(--mud-palette-background);
    color: var(--mud-palette-text-primary);
}

.identity-shell__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 1.5rem;
    background: var(--mud-palette-appbar-background);
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.identity-shell__brand {
    font-size: 1.125rem;
    font-weight: 700;
    color: var(--mud-palette-primary);
    text-decoration: none;
}

.identity-shell__back {
    font-size: 0.875rem;
    color: var(--mud-palette-text-secondary);
    text-decoration: none;
}

.identity-shell__back:hover {
    text-decoration: underline;
}

.identity-shell__body {
    max-width: 60rem;
    margin: 0 auto;
    padding: 1.5rem;
}

.identity-shell__title {
    font-size: 1.375rem;
    font-weight: 600;
    margin: 0 0 1.5rem;
}

.identity-shell__columns {
    display: grid;
    grid-template-columns: 14rem 1fr;
    gap: 2rem;
    align-items: start;
}

@media (max-width: 48rem) {
    .identity-shell__columns {
        grid-template-columns: 1fr;
        gap: 1rem;
    }
}

.identity-shell__content h3 {
    margin: 0 0 1rem;
    font-size: 1.125rem;
    font-weight: 600;
}

.identity-shell__content h4 {
    margin: 1.5rem 0 0.75rem;
    font-size: 1rem;
    font-weight: 600;
}

.manage-nav {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
}

.manage-nav__link {
    padding: 0.5rem 0.75rem;
    font-size: 0.875rem;
    color: var(--mud-palette-text-primary);
    text-decoration: none;
    border-radius: var(--mud-default-borderradius, 2px);
}

.manage-nav__link:hover {
    background: var(--mud-palette-action-default-hover);
}

.manage-nav__link.active {
    color: #fff;
    background: var(--mud-palette-primary);
}

/* ── Reconnect dialog ──────────────────────────────────────────────────────────────────────────── */

/*
 * Unscoped on purpose: the framework toggles the components-* classes on the dialog from its own
 * JavaScript, and a scoped stylesheet would only match markup the Razor compiler stamped, not state
 * added at runtime.
 *
 * A dropped circuit is an interruption, not an emergency — the shop floor sees enough real alarms
 * without the UI adding one. So: no hazard striping, nothing flashing, no colour that escalates to
 * scold. Sunjin red is the only accent, carried by a slim progress track and the button; everything
 * else is ordinary surface and text, and the whole thing sits on a light scrim rather than blacking
 * the screen out. The one piece of motion — the track — runs only while a reconnect is actually in
 * flight, so movement means "still trying" and stillness means "waiting for you".
 *
 * Every colour comes from a MudBlazor palette variable, so the dialog follows the light/dark theme
 * the rest of the app is using. --mud-palette-primary is Sunjin red in both palettes.
 */

/*
 * Back to 23rem now the pixel terminal is gone: the panel holds a 144px board and two lines of text,
 * and the extra width the machine needed only left the message stretched across a measure too wide to
 * read comfortably.
 *
 * 18vh rather than the 12vh the stacked layout needed — with one drawing instead of two the panel is
 * far shorter, so sitting it that high left it stranded near the top of the screen. Both still
 * collapse against the viewport on a phone.
 */
#components-reconnect-modal {
    width: min(23rem, calc(100vw - 2rem));
    padding: 0;
    margin: 18vh auto;
    overflow: hidden;
    color: var(--mud-palette-text-primary);
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius, 2px);
    box-shadow: 0 16px 40px rgb(0 0 0 / 22%);
    opacity: 0;
    animation: app-reconnect-out 0.35s both;
}

#components-reconnect-modal[open] {
    animation: app-reconnect-in 0.35s ease-out 0.2s both;
}

/* A scrim, not a blackout: the work behind stays legible, which is most of what makes this bearable. */
#components-reconnect-modal::backdrop {
    background: rgb(0 0 0 / 28%);
    backdrop-filter: blur(1.5px);
    animation: app-reconnect-fade 0.35s ease-out;
    opacity: 1;
}

/*
 * Terminal-style progress bar along the top edge — the whole of the "something is happening" signal.
 *
 * A row of discrete cells, not a continuous fill. Square corners, hard on/off, no gradients and no
 * easing anywhere: the lit cell jumps from one to the next the way a character-cell progress bar
 * does. Sixteen cells come from the markup (see TrackCells), each carrying its index in --app-cell.
 */
.app-reconnect__track {
    --app-cells: 16;
    --app-scan: 1.6s;

    display: flex;
    gap: 2px;
    padding: 4px;
    background: var(--mud-palette-background-gray);
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.app-reconnect__track > span {
    flex: 1;
    height: 6px;

    /* The unlit cell. Faint rather than invisible, so the track still reads as a bar when nothing
       is moving — which is the whole of the failed and paused states. */
    background: color-mix(in srgb, var(--mud-palette-primary) 14%, transparent);
}

/*
 * Every cell runs the same animation; the delay is what staggers them into a scanner. Each cell is
 * one step behind its neighbour, so at any moment one cell is lit and the two behind it are fading —
 * a short comet tail rather than a single dot, which is what makes the direction of travel readable.
 *
 * The delay is deliberately negative (one full sweep is subtracted). A positive delay holds each cell
 * at its unlit state until its turn first comes round, so the opening cycle fills in from the left
 * instead of scanning; starting every cell mid-animation means the bar is already sweeping the
 * instant the dialog appears. Subtracting exactly one period leaves the phase unchanged.
 */
#components-reconnect-modal.components-reconnect-show .app-reconnect__track > span,
#components-reconnect-modal.components-reconnect-retrying .app-reconnect__track > span {
    animation: app-reconnect-scan var(--app-scan) linear infinite;
    animation-delay: calc(var(--app-cell) * var(--app-scan) / var(--app-cells) - var(--app-scan));
}

/*
 * Once it has stopped trying, every cell holds lit. It reads as "this is where things stand" rather
 * than as a warning, and it keeps the red present without shouting.
 */
#components-reconnect-modal.components-reconnect-failed .app-reconnect__track > span,
#components-reconnect-modal.components-reconnect-paused .app-reconnect__track > span,
#components-reconnect-modal.components-reconnect-resume-failed .app-reconnect__track > span {
    background: var(--mud-palette-primary);
}

/*
 * The playfield — now the only drawing in the dialog.
 *
 * Hidden by default and shown only while a reconnect is in flight, for the same reason the track only
 * animates then: once the framework has given up, the panel's job is to present a retry button, and a
 * game still cheerfully playing beside it reads as the page not having noticed. The canvas keeps its
 * intrinsic 144x144 — scaling a pixel board to fit would put block edges on half-pixels, which is the
 * one thing this whole treatment is built to avoid.
 */
.app-reconnect__play {
    display: none;
    justify-content: center;
    padding: 1.5rem 1.5rem 0.75rem;
}

#components-reconnect-modal.components-reconnect-show .app-reconnect__play,
#components-reconnect-modal.components-reconnect-retrying .app-reconnect__play {
    display: flex;
}

/*
 * The design-system page shows the same board outside the dialog, so it has to opt back out of the
 * default-hidden rule above — there is no dialog state class to switch it on.
 *
 * Left-aligned rather than centred: on the reference page it sits in a normal content card among
 * other examples, and a lone centred board there reads as a mistake rather than as a panel.
 */
.app-reconnect__play--preview {
    display: block;
}

.app-reconnect__play canvas {
    display: block;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 2px;
    /* Nearest-neighbour if the browser ever does scale it — a blurred block board looks broken in a
       way a slightly-wrong-sized crisp one does not. */
    image-rendering: pixelated;
}

.app-reconnect__body {
    padding: 1.25rem 1.5rem 1.5rem;
}

/*
 * Be Vietnam Pro at heading weight, not Anton: the condensed brand face is for a launcher title, and
 * on a small interruption panel it lands as a klaxon. Text colour, not red — the accent is already
 * doing that job above.
 */
.app-reconnect__title {
    margin: 0 0 0.375rem;
    font-size: 1.0625rem;
    font-weight: 600;
    line-height: 1.3;
    color: var(--mud-palette-text-primary);
    /* Centred under the machine, which is itself centred — left-aligned text beside a centred drawing
       reads as a layout mistake rather than a choice. */
    text-align: center;
}

.app-reconnect__text {
    margin: 0;
    font-size: 0.875rem;
    line-height: 1.55;
    color: var(--mud-palette-text-secondary);
    text-align: center;
}

/* Monospace and reserved width so a two-digit countdown does not shove the sentence sideways. */
.app-reconnect__count {
    display: inline-block;
    min-width: 1.25rem;
    text-align: center;
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    font-weight: 600;
    color: var(--mud-palette-text-primary);
}

/*
 * The row holding the app's loading glyph, gated exactly like the playfield above: visible only while
 * a reconnect is in flight, so a turning glyph always means "still trying". In the failed and paused
 * states it goes with the rest of the motion and leaves the retry button to do the talking.
 *
 * Here rather than in LoadingDots.razor.css because what is styled is the row this dialog puts the
 * component in, not the component itself — and a rule scoped to that component could not see the
 * dialog's state classes in any case.
 */
.app-reconnect__status {
    display: none;
    justify-content: center;
    margin-top: 0.875rem;
}

#components-reconnect-modal.components-reconnect-show .app-reconnect__status,
#components-reconnect-modal.components-reconnect-retrying .app-reconnect__status {
    display: flex;
}

/* Full width and 2.5rem tall — comfortable for a finger on a shop-floor terminal without shouting. */
.app-reconnect__action {
    display: block;
    width: 100%;
    min-height: 2.5rem;
    margin-top: 1.25rem;
    padding: 0 1.25rem;
    font: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    color: #fff;
    background: var(--mud-palette-primary);
    border: 0;
    border-radius: var(--mud-default-borderradius, 2px);
    cursor: pointer;
    transition: filter 120ms ease;
}

.app-reconnect__action:hover {
    filter: brightness(0.92);
}

.app-reconnect__action:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* Every state's markup is in the DOM at once; these two rules are what pick one. */
.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
.components-pause-visible,
.components-resume-failed-visible {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible {
    display: block;
}

/*
 * The framework adds a state class without removing `components-reconnect-show`, so once a reconnect
 * moves past the first attempt two of the rules above match at the same time and the dialog stacks
 * two headings — "Mất kết nối" twice, one over the other. These rules re-hide the earlier state when
 * a later one is also present, so exactly one block is ever visible.
 *
 * Written as "hide the earlier one" rather than by reordering: the state classes are not mutually
 * exclusive and are set by Blazor, so a later state has to actively suppress the ones it supersedes.
 */
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-paused .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-paused .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-paused .components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-pause-visible {
    display: none;
}

/*
 * The stops are paired so each level holds flat and then switches in one frame — no interpolation, no
 * fade. That hard edge is what separates a character-cell bar from a glowing web progress bar.
 * 6% of the sweep is roughly one cell, so: one cell fully lit, two behind it dimming, the rest dark.
 */
@keyframes app-reconnect-scan {
    0%,
    6% {
        background: var(--mud-palette-primary);
    }

    6.01%,
    12% {
        background: color-mix(in srgb, var(--mud-palette-primary) 55%, transparent);
    }

    12.01%,
    18% {
        background: color-mix(in srgb, var(--mud-palette-primary) 30%, transparent);
    }

    18.01%,
    100% {
        background: color-mix(in srgb, var(--mud-palette-primary) 14%, transparent);
    }
}

@keyframes app-reconnect-in {
    0% {
        transform: translateY(8px);
        opacity: 0;
    }

    100% {
        transform: translateY(0);
        opacity: 1;
    }
}

@keyframes app-reconnect-out {
    0% {
        opacity: 1;
    }

    100% {
        opacity: 0;
    }
}

@keyframes app-reconnect-fade {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

/* Shop-floor terminals are shared and often set to reduce motion; the dialog must still make sense. */
@media (prefers-reduced-motion: reduce) {
    #components-reconnect-modal,
    #components-reconnect-modal[open],
    #components-reconnect-modal::backdrop,
    .app-reconnect__track > span {
        animation: none;
    }

    #components-reconnect-modal,
    #components-reconnect-modal[open] {
        opacity: 1;
    }

    /* Without the scanner the cells would sit at their unlit tint, so light the whole bar instead. */
    .app-reconnect__track > span {
        background: var(--mud-palette-primary);
    }

    /*
     * The game goes entirely, not still. It is continuous unprompted motion in the corner of the eye,
     * which is the exact thing this setting is asking not to happen, and it carries no information the
     * heading does not, so removing it costs the dialog nothing. Hidden rather than paused so it is
     * not a frozen board someone is left waiting on.
     *
     * reconnect.js checks the same media query before starting the loop, so nothing is being painted
     * behind this rule. Both are needed: the rule alone would leave a timer running, and the script
     * alone would leave an empty bordered box in the layout.
     */
    #components-reconnect-modal.components-reconnect-show .app-reconnect__play,
    #components-reconnect-modal.components-reconnect-retrying .app-reconnect__play {
        display: none;
    }
}

