﻿html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    height: 100%;
}

/* The whole app is drawn a fifth larger than a browser's default, the same scale as
   JwtFlex, CronFlex and SetFlex - the *Flex apps are drawn at one size.

   Every size in this app is in `rem`, so this one number is the app's scale and nothing
   else has to know about it - change it and the type, the panes and the doc pages all
   move together. A percentage rather than a pixel count so that a reader who has already
   set a larger default font in their browser keeps the increase.

   The media queries are the exception and cannot use it: `rem` inside a media query is
   always the browser's initial 16px, whatever this says. So every breakpoint written for
   this app is at 1.2x the width it would otherwise be - the content is 1.2x, so it runs
   out of room 1.2x sooner. Bootstrap's own breakpoints, on the doc pages, are its and
   are left alone. */
:root {
    font-size: 120%;
}

/* TextFlex workbench theme.
   Dark is the default (this is a workbench, and it is what most users of it
   want); light applies when the user picks it, or when the system asks for it
   and the user has not overridden that. Every token is defined here on bare
   `:root` so no colour exists only inside a media query. */
:root {
    color-scheme: dark;
    --tf-bg: #0d1015;
    --tf-pane: #15191f;
    --tf-pane-alt: #11151a;
    --tf-chrome: #0a0d11;
    --tf-chrome-2: #12161c;
    --tf-chrome-hover: #1e242d;
    --tf-border: #262f3a;
    --tf-border-soft: #1a212a;
    --tf-text: #dde5ee;
    --tf-muted: #8391a2;
    --tf-faint: #56626f;
    --tf-accent: #19c39a;
    --tf-accent-bright: #24dcaf;
    --tf-accent-ink: #04231c;
    --tf-accent-dim: rgba(25, 195, 154, 0.45);
    --tf-accent-wash: rgba(25, 195, 154, 0.12);

    /* Pattern errors in the status bar. */
    --tf-danger: #f2777a;

    /* What the tour lays over the workbench, with holes cut in it. Used by an element
       inside the dialog rather than by ::backdrop, which cannot see a property declared
       here - see the note on `.help::backdrop`. */
    --tf-scrim: rgba(4, 7, 10, 0.74);

    /* One hue per stage of the pipeline: data in, pattern applied, results out. */
    --tf-stage-data: #4cb7ec;
    --tf-stage-pattern: #e8a33c;
    --tf-stage-results: #19c39a;

    --tf-shadow: 0 1px 0 rgba(255, 255, 255, 0.03);
}

:root[data-theme="light"] {
    color-scheme: light;
    --tf-bg: #e7ebf0;
    --tf-pane: #ffffff;
    --tf-pane-alt: #f7f9fb;
    --tf-chrome: #f9fbfc;
    --tf-chrome-2: #f1f4f7;
    --tf-chrome-hover: #e4e9ef;
    --tf-border: #ccd5df;
    --tf-border-soft: #dde4eb;
    --tf-text: #18202a;
    --tf-muted: #5a6673;
    --tf-faint: #98a3af;
    --tf-accent: #0f9b7c;
    --tf-accent-bright: #0bab88;
    --tf-accent-ink: #ffffff;
    --tf-accent-dim: rgba(15, 155, 124, 0.45);
    --tf-accent-wash: rgba(15, 155, 124, 0.10);

    --tf-danger: #c02f33;

    --tf-scrim: rgba(22, 30, 40, 0.58);

    --tf-stage-data: #1a86c4;
    --tf-stage-pattern: #b3711a;
    --tf-stage-results: #0f9b7c;

    --tf-shadow: 0 1px 2px rgba(24, 32, 42, 0.06);
}

@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
        color-scheme: light;
        --tf-bg: #e7ebf0;
        --tf-pane: #ffffff;
        --tf-pane-alt: #f7f9fb;
        --tf-chrome: #f9fbfc;
        --tf-chrome-2: #f1f4f7;
        --tf-chrome-hover: #e4e9ef;
        --tf-border: #ccd5df;
        --tf-border-soft: #dde4eb;
        --tf-text: #18202a;
        --tf-muted: #5a6673;
        --tf-faint: #98a3af;
        --tf-accent: #0f9b7c;
        --tf-accent-bright: #0bab88;
        --tf-accent-ink: #ffffff;
        --tf-accent-dim: rgba(15, 155, 124, 0.45);
        --tf-accent-wash: rgba(15, 155, 124, 0.10);

        --tf-danger: #c02f33;

        --tf-scrim: rgba(22, 30, 40, 0.58);

        --tf-stage-data: #1a86c4;
        --tf-stage-pattern: #b3711a;
        --tf-stage-results: #0f9b7c;

        --tf-shadow: 0 1px 2px rgba(24, 32, 42, 0.06);
    }
}

/* Blazor's unhandled-error bar. Hidden until the framework sets `display: block` on it
   by hand, which is why the `display: none` here is load-bearing rather than tidiness.

   Global, not scoped: every layout renders its own copy of this div, so a rule in any one
   .razor.css file reaches exactly one of them. It lived in MainLayout.razor.css - the
   template's layout, which nothing in the workbench uses - and so the bar was unstyled
   everywhere else: in the flow, at the bottom of the document, and visible at all times.
   On the workbench that only went unnoticed because `body:has(.tf-shell)` clips it off the
   bottom of the viewport, which also meant a real error would never have been seen. */
#blazor-error-ui {
    position: fixed;
    bottom: 0;
    left: 0;
    z-index: 1000;
    display: none;
    box-sizing: border-box;
    width: 100%;
    padding: 0.6rem 1.25rem 0.7rem;
    background: lightyellow;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    /* The bar has a fixed light background, so its text and controls must not be
       re-coloured by a dark theme. */
    color-scheme: light only;
}

#blazor-error-ui .dismiss {
    position: absolute;
    top: 0.5rem;
    right: 0.75rem;
    cursor: pointer;
}

/* The boot splash: what covers the prerendered workbench until WebAssembly is up.
   It lives here, in the stylesheet the document already blocks on, rather than in
   a scoped component file - the markup is in App.razor and is on screen long
   before TextFlex.styles.css means anything.

   Only theme tokens are used, so a first visit gets the right palette from the
   inline theme script and never flashes the other one. */
.tf-boot {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: grid;
    place-items: center;
    background: var(--tf-bg);
}

/* A standing document (DocLayout) has no WebAssembly to wait for, so there is nothing
   for the splash to cover and it would be a second of curtain over a page that already
   arrived complete in the first response. Hidden in CSS rather than by script for the
   same reason the splash itself is markup: this stylesheet blocks the first paint, so
   the curtain never appears at all rather than appearing and being taken away.

   The script in App.razor still runs its course and removes the element - `waitsForApp`
   is already false for any page without a `.tf-shell`, which is exactly this case. */
body:has(.doc) .tf-boot {
    display: none;
}

/* Same reasoning, for the other case with nothing for the splash to cover: below
   tablet width the workbench is WorkbenchLayout's "too small" notice instead, which
   is plain markup and already visible in this stylesheet's own first paint. The
   script in App.razor checks the same breakpoint before it decides whether to start
   Blazor at all, so on a narrow screen the runtime is never even requested - this
   rule just keeps the splash from sitting over the notice for the one frame before
   that script runs. Keep this breakpoint in step with the one in App.razor. */
@media (max-width: 921.58px) {
    body:has(.tf-shell) .tf-boot {
        display: none;
    }
}

/* The reveal, in the manner of a stage curtain cut on the diagonal: the top-left
   and bottom-right halves slide apart along the perpendicular of the split. Each
   triangle overshoots the diagonal by a couple of pixels so no seam of workbench
   shows through the join while they are still closed.

   Taken out of flow, because an in-flow child would be a second grid item and
   `place-items` centres each item in its own row - which puts the wordmark half a
   screen below the middle. */
.tf-boot-sheet {
    position: absolute;
    inset: 0;
}

.tf-boot-sheet span {
    position: absolute;
    inset: 0;
    background: var(--tf-bg);
}

.tf-boot-sheet span:first-child {
    clip-path: polygon(0 0, calc(100% + 2px) 0, 0 calc(100% + 2px));
}

.tf-boot-sheet span:last-child {
    clip-path: polygon(100% -2px, 100% 100%, -2px 100%);
}

.tf-boot.is-leaving .tf-boot-sheet span {
    transition: transform 560ms cubic-bezier(0.66, 0, 0.34, 1);
}

.tf-boot.is-leaving .tf-boot-sheet span:first-child {
    transform: translate(-100%, -100%);
}

.tf-boot.is-leaving .tf-boot-sheet span:last-child {
    transform: translate(100%, 100%);
}

.tf-boot-body {
    position: relative;
    display: grid;
    justify-items: center;
    gap: 0;
    padding-bottom: 3vh;
    text-align: center;
}

/* The wordmark goes first and fast, so the curtain opens on an empty stage
   rather than dragging the notice off with it. */
.tf-boot.is-leaving .tf-boot-body {
    opacity: 0;
    transform: scale(0.97);
    transition: opacity 160ms ease, transform 220ms ease;
}

.tf-boot-mark {
    width: 4.25rem;
    height: 4.25rem;
    fill: none;
    stroke-linecap: round;
}

.tf-boot-bars path {
    stroke: var(--tf-muted);
    stroke-width: 2;

    /* fill-box, so `left` is the start of the bar and not the corner of the
       viewBox - the bars have to grow from the margin the way text does. */
    transform-box: fill-box;
    transform-origin: left center;
    animation: tf-boot-flex 1.9s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}

.tf-boot-bars path:nth-child(2) { animation-delay: 0.16s; }
.tf-boot-bars path:nth-child(3) { animation-delay: 0.32s; }

.tf-boot-flex {
    stroke: var(--tf-accent);
    stroke-width: 2.3;
    stroke-linejoin: round;
    transform-box: fill-box;
    transform-origin: left center;
    animation: tf-boot-push 1.9s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}

/* Each bar reflows and the chevron leans into it - the app's one idea, which is
   text going in one shape and coming out another. */
@keyframes tf-boot-flex {
    0%, 100% { transform: scaleX(1); opacity: 0.55; }
    45% { transform: scaleX(0.68); opacity: 1; }
}

@keyframes tf-boot-push {
    0%, 100% { transform: translateX(0); opacity: 0.75; }
    45% { transform: translateX(-1.2px); opacity: 1; }
}

.tf-boot-name {
    margin: 0.5rem 0 1.5rem;
    font-size: 1.375rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--tf-text);
}

.tf-boot-name span {
    color: var(--tf-accent);
}

.tf-boot-rail {
    position: relative;
    width: 13.75rem;
    max-width: 60vw;
    height: 3px;
    overflow: hidden;
    border-radius: 999px;
    background: var(--tf-chrome-2);
    box-shadow: inset 0 0 0 1px var(--tf-border-soft);
}

.tf-boot-fill {
    width: 0;
    height: 100%;
    border-radius: inherit;
    background: linear-gradient(90deg, var(--tf-accent-dim), var(--tf-accent-bright));
    transition: width 240ms ease;
}

/* Before the first file is even asked for there is no fraction to draw, and a
   rail sitting at zero is the one thing this screen exists to avoid saying. */
.tf-boot-rail.is-waiting::after {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 35%;
    border-radius: inherit;
    background: linear-gradient(90deg, transparent, var(--tf-accent-dim), transparent);
    animation: tf-boot-sweep 1.4s ease-in-out infinite;
}

@keyframes tf-boot-sweep {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(386%); }
}

.tf-boot-status {
    margin: 0.875rem 0 0;
    font-size: 0.8125rem;
    color: var(--tf-muted);
}

/* Fixed height, so the counts appearing under the rail do not shift the
   wordmark up the screen a beat after it arrived. */
.tf-boot-detail {
    margin: 0.1875rem 0 0;
    min-height: 1.1rem;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--tf-faint);
}

.tf-boot.is-failed .tf-boot-status {
    color: var(--tf-danger);
}

/* The files that were counted may all have arrived - the runtime's own imports go
   around the count - so the rail can be sitting at a confident 100% under a
   message saying the opposite. It should not look like progress. */
.tf-boot.is-failed .tf-boot-fill {
    background: var(--tf-danger);
    opacity: 0.5;
}

.tf-boot.is-failed .tf-boot-mark path {
    animation: none;
}

@media (prefers-reduced-motion: reduce) {
    .tf-boot-bars path,
    .tf-boot-flex,
    .tf-boot-rail.is-waiting::after {
        animation: none;
    }

    /* No curtain: the whole cover fades, which is the same information without
       the movement across the viewport. */
    .tf-boot.is-leaving {
        opacity: 0;
        transition: opacity 200ms ease;
    }

    .tf-boot.is-leaving .tf-boot-sheet span,
    .tf-boot.is-leaving .tf-boot-body {
        transition: none;
        transform: none;
        opacity: 1;
    }
}

/* A standing document scrolls, so the page background has to be the theme's rather than
   the browser's white - otherwise overscroll, and any gap under a short document, flashes
   white behind a dark page. Set on body rather than on the .doc element itself, because it
   is body's background that propagates to the canvas the overscroll paints.

   Deliberately not on a bare `body`: the Blazor template's leftover pages are Bootstrap
   over white, and handing them a dark canvas would break them rather than theme them. */
body:has(.doc) {
    background: var(--tf-bg);
}

/* The workbench owns the viewport; nothing outside it should scroll. */
body:has(.tf-shell) {
    overflow: hidden;
    margin: 0;
    background: var(--tf-bg);
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}