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 and the editor 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%;
}

/* JsonFlex 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;
    --jf-bg: #0d1015;
    --jf-pane: #15191f;
    --jf-pane-alt: #11151a;
    --jf-chrome: #0a0d11;
    --jf-chrome-2: #12161c;
    --jf-chrome-hover: #1e242d;
    --jf-border: #262f3a;
    --jf-border-soft: #1a212a;
    --jf-text: #dde5ee;
    --jf-muted: #8391a2;
    --jf-faint: #56626f;
    --jf-accent: #22d3ee;
    --jf-accent-bright: #67e8f9;
    --jf-accent-ink: #062024;
    --jf-accent-dim: rgba(34, 211, 238, 0.45);
    --jf-accent-wash: rgba(34, 211, 238, 0.14);

    --jf-danger: #f2777a;

    /* Used sparingly in the tree view, to tell a string from a number from the rest.
       Keys keep a blue family of their own, shifted towards indigo so they read apart
       from the accent's cyan rather than blending into it. */
    --jf-side-left: #7c8cf8;
    --jf-side-right: #e8a33c;
    --jf-side-result: #22d3ee;

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

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

    --jf-danger: #c02f33;

    --jf-side-left: #3b4fd1;
    --jf-side-right: #b3711a;
    --jf-side-result: #0e7490;

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

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

        --jf-danger: #c02f33;

        --jf-side-left: #3b4fd1;
        --jf-side-right: #b3711a;
        --jf-side-result: #0e7490;

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

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

/* A standing document - the privacy notice - 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(--jf-bg);
}

/* The boot splash: what covers the page until the workbench script has run and the last
   session is back in the editor. Module scripts run after parsing, the session is read
   from IndexedDB after that, and a browser is free to paint in between, 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. */
.jf-boot {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: grid;
    place-items: center;
    background: var(--jf-bg);

    /* The shell's typeface, repeated: the splash sits outside `.jf-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 the editor 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. */
.jf-boot-sheet {
    position: absolute;
    inset: 0;
}

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

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

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

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

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

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

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

.jf-boot-mark {
    width: 4.75rem;
    height: 4.75rem;
    overflow: visible;
    fill: none;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

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

.jf-boot-mark-b {
    stroke: var(--jf-accent);
}

/* A slow breath rather than the Venn-diagram "meeting" SetFlex used - there are no
   two things to bring together here, just braces holding a document. */
.jf-boot-mark {
    animation: jf-boot-breathe 2.4s ease-in-out infinite;
}

@keyframes jf-boot-breathe {
    0%, 100% { opacity: 0.75; }
    50% { opacity: 1; }
}

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

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

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

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

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

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

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

.jf-boot.is-failed .jf-boot-status {
    color: var(--jf-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. */
.jf-boot.is-failed .jf-boot-fill {
    background: var(--jf-danger);
    opacity: 0.5;
}

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

@media (prefers-reduced-motion: reduce) {
    .jf-boot-mark,
    .jf-boot-rail.is-waiting::after {
        animation: none;
    }

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

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