﻿html, body {
    height: 100%;
}

/* The whole app is drawn a fifth larger than a browser's default, the same scale as
   TextFlex, JwtFlex and CronFlex - 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 tables 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 in this app
   is written at 1.2x the width it would otherwise be - the content is 1.2x, so it runs
   out of room 1.2x sooner. */
:root {
    font-size: 120%;
}

/* SetFlex 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;
    --sf-bg: #0d1015;
    --sf-pane: #15191f;
    --sf-pane-alt: #11151a;
    --sf-chrome: #0a0d11;
    --sf-chrome-2: #12161c;
    --sf-chrome-hover: #1e242d;
    --sf-border: #262f3a;
    --sf-border-soft: #1a212a;
    --sf-text: #dde5ee;
    --sf-muted: #8391a2;
    --sf-faint: #56626f;
    --sf-accent: #8b7cf6;
    --sf-accent-bright: #a396ff;
    --sf-accent-ink: #120a2e;
    --sf-accent-dim: rgba(139, 124, 246, 0.45);
    --sf-accent-wash: rgba(139, 124, 246, 0.14);

    --sf-danger: #f2777a;

    /* One hue per side of the comparison, and one for what comes out of it. */
    --sf-side-left: #4cb7ec;
    --sf-side-right: #e8a33c;
    --sf-side-result: #8b7cf6;

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

:root[data-theme="light"] {
    color-scheme: light;
    --sf-bg: #e7ebf0;
    --sf-pane: #ffffff;
    --sf-pane-alt: #f7f9fb;
    --sf-chrome: #f9fbfc;
    --sf-chrome-2: #f1f4f7;
    --sf-chrome-hover: #e4e9ef;
    --sf-border: #ccd5df;
    --sf-border-soft: #dde4eb;
    --sf-text: #18202a;
    --sf-muted: #5a6673;
    --sf-faint: #98a3af;
    --sf-accent: #5b48d8;
    --sf-accent-bright: #6d5ae6;
    --sf-accent-ink: #ffffff;
    --sf-accent-dim: rgba(91, 72, 216, 0.45);
    --sf-accent-wash: rgba(91, 72, 216, 0.10);

    --sf-danger: #c02f33;

    --sf-side-left: #1a86c4;
    --sf-side-right: #b3711a;
    --sf-side-result: #5b48d8;

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

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

        --sf-danger: #c02f33;

        --sf-side-left: #1a86c4;
        --sf-side-right: #b3711a;
        --sf-side-result: #5b48d8;

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

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

/* 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. */
body:has(.doc) {
    margin: 0;
    background: var(--sf-bg);
}

/* The boot splash, shared in look with the other *Flex apps: what covers the page until
   the workbench script has run and the last session is back in the panes. Module scripts
   run after parsing, and a browser is free to paint before they do, so without this the
   empty default workbench can show for a frame and then have the session pop into it.
   It lives here, in the stylesheet the document already blocks on, so it is on screen
   from the very first paint.

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

    /* The shell's typeface, repeated: the splash sits outside `.sf-shell` and so
       inherits nothing from it, and the browser default is a serif. */
    font-family: 'Inter', 'Segoe UI Variable Text', 'Segoe UI', system-ui, -apple-system, sans-serif;
}

/* 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. */
.sf-boot-sheet {
    position: absolute;
    inset: 0;
}

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

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

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

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

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

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

.sf-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. */
.sf-boot.is-leaving .sf-boot-body {
    opacity: 0;
    transform: scale(0.97);
    transition: opacity 220ms ease, transform 220ms ease;
}

.sf-boot-mark {
    width: 4.75rem;
    height: 4.75rem;
    overflow: visible;
}

/* The two rings start apart and drift in until they sit exactly where the header
   wordmark's circles do, which is when the lens between them is at full strength -
   two lists meeting, and what they have in common. */
.sf-boot-a,
.sf-boot-b {
    fill: none;
    stroke-width: 1.5;
    animation: sf-boot-meet 2.4s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

.sf-boot-a {
    stroke: var(--sf-muted);
}

.sf-boot-b {
    stroke: var(--sf-accent);
    animation-name: sf-boot-meet-back;
}

.sf-boot-lens {
    fill: var(--sf-accent);
    opacity: 0;
    animation: sf-boot-lens 2.4s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

@keyframes sf-boot-meet {
    0%, 100% { transform: translateX(0); }
    45%, 62% { transform: translateX(3px); }
}

@keyframes sf-boot-meet-back {
    0%, 100% { transform: translateX(0); }
    45%, 62% { transform: translateX(-3px); }
}

/* The lens is drawn where the rings end up, so it can only be shown once they are
   there - it fades in behind them over the last of the drift and out again as they
   part. */
@keyframes sf-boot-lens {
    0%, 32%, 100% { opacity: 0; }
    50%, 60% { opacity: 0.32; }
}

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

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

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

.sf-boot-fill {
    width: 0;
    height: 100%;
    border-radius: inherit;
    background: linear-gradient(90deg, var(--sf-accent-dim), var(--sf-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. */
.sf-boot-rail.is-waiting::after {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 35%;
    border-radius: inherit;
    background: linear-gradient(90deg, transparent, var(--sf-accent-dim), transparent);
    animation: sf-boot-sweep 1.4s ease-in-out infinite;
}

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

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

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

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

/* The files may all have arrived and the script still failed, so the rail can be
   sitting at a confident 100% under a message saying the opposite. It should not look
   like progress. */
.sf-boot.is-failed .sf-boot-fill {
    background: var(--sf-danger);
    opacity: 0.5;
}

.sf-boot.is-failed .sf-boot-mark circle {
    animation: none;
}

@media (prefers-reduced-motion: reduce) {
    .sf-boot-a,
    .sf-boot-b,
    .sf-boot-lens,
    .sf-boot-rail.is-waiting::after {
        animation: none;
    }

    /* Still the glyph rather than two rings floating apart: park the circles where
       they meet, which is the mark itself. */
    .sf-boot-a { transform: translateX(3px); }
    .sf-boot-b { transform: translateX(-3px); }
    .sf-boot-lens { opacity: 0.32; }

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

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