html, body {
    height: 100%;
}

/* The whole app is drawn a fifth larger than a browser's default.

   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 panels, the toolbox and the
   splash 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 below 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%;
}

/* ImageFlex theme.
   Dark is the default (this is a tool for editing screenshots and photos, and a dark
   canvas surround is what most image editors default to); 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.

   The greys are the *Flex house style and are shared with TextFlex, JwtFlex and CronFlex
   deliberately - they should look like the same workshop. What tells them apart is the
   accent, which is the one colour a reader sees before they have read anything: TextFlex
   is teal, JwtFlex is violet, CronFlex is azure, and ImageFlex is this cyan. It is also
   the only part of the palette that must stay clear of the verdict colours below, since
   it appears on chrome that sits next to them. */
:root {
    color-scheme: dark;
    --if-bg: #0d1015;
    --if-pane: #15191f;
    --if-pane-alt: #11151a;
    --if-chrome: #0a0d11;
    --if-chrome-2: #12161c;
    --if-chrome-hover: #1e242d;
    --if-border: #262f3a;
    --if-border-soft: #1a212a;
    --if-text: #dde5ee;
    --if-muted: #8391a2;
    --if-faint: #56626f;
    --if-accent: #2fb6c9;
    --if-accent-bright: #52d0e0;
    --if-accent-ink: #03191c;
    --if-accent-dim: rgba(47, 182, 201, 0.45);
    --if-accent-wash: rgba(47, 182, 201, 0.14);

    /* What a modal lays over the workspace. Used by an element inside the dialog
       as well as by ::backdrop, which cannot see a property declared here. */
    --if-scrim: rgba(4, 7, 10, 0.62);

    /* The three verdict colours, used for status and badges - so that green always
       means the same thing on this page whichever pane it turns up in. They are
       deliberately only ever used beside a ✓, ! or ✗, because the tool accents below
       occupy the same part of the spectrum and hue alone cannot carry a verdict here. */
    --if-good: #3ecf8e;
    --if-good-wash: rgba(62, 207, 142, 0.13);
    --if-warn: #e8a33c;
    --if-warn-wash: rgba(232, 163, 60, 0.13);
    --if-danger: #f2777a;
    --if-danger-wash: rgba(242, 119, 122, 0.13);

    /* Fixed tool accents - not theme-driven, because a highlight, an annotation and a
       success tick have to read as the same colour on the canvas whichever theme the
       chrome around it is in. Yellow for highlights (the marker-pen colour every image
       editor uses), red for annotations and destructive tools (arrows, boxes, redaction),
       green for a completed/applied action. */
    --if-tool-highlight: #e0b64d;
    --if-tool-annotate: #d9534f;
    --if-tool-success: #4caf6d;

    --if-shadow: 0 1px 0 rgba(255, 255, 255, 0.03);
}

:root[data-theme="light"] {
    color-scheme: light;
    --if-bg: #e7ebf0;
    --if-pane: #ffffff;
    --if-pane-alt: #f7f9fb;
    --if-chrome: #f9fbfc;
    --if-chrome-2: #f1f4f7;
    --if-chrome-hover: #e4e9ef;
    --if-border: #ccd5df;
    --if-border-soft: #dde4eb;
    --if-text: #18202a;
    --if-muted: #5a6673;
    --if-faint: #98a3af;
    --if-accent: #0f7d8f;
    --if-accent-bright: #128fa3;
    --if-accent-ink: #ffffff;
    --if-accent-dim: rgba(15, 125, 143, 0.45);
    --if-accent-wash: rgba(15, 125, 143, 0.10);

    --if-scrim: rgba(22, 30, 40, 0.55);

    --if-good: #0f7a52;
    --if-good-wash: rgba(15, 122, 82, 0.10);
    --if-warn: #9a5c00;
    --if-warn-wash: rgba(154, 92, 0, 0.10);
    --if-danger: #c02f33;
    --if-danger-wash: rgba(192, 47, 51, 0.09);

    --if-tool-highlight: #cf9a1f;
    --if-tool-annotate: #c02f33;
    --if-tool-success: #0f7a52;

    --if-shadow: 0 1px 2px rgba(24, 32, 42, 0.06);
}

@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
        color-scheme: light;
        --if-bg: #e7ebf0;
        --if-pane: #ffffff;
        --if-pane-alt: #f7f9fb;
        --if-chrome: #f9fbfc;
        --if-chrome-2: #f1f4f7;
        --if-chrome-hover: #e4e9ef;
        --if-border: #ccd5df;
        --if-border-soft: #dde4eb;
        --if-text: #18202a;
        --if-muted: #5a6673;
        --if-faint: #98a3af;
        --if-accent: #0f7d8f;
        --if-accent-bright: #128fa3;
        --if-accent-ink: #ffffff;
        --if-accent-dim: rgba(15, 125, 143, 0.45);
        --if-accent-wash: rgba(15, 125, 143, 0.10);

        --if-scrim: rgba(22, 30, 40, 0.55);

        --if-good: #0f7a52;
        --if-good-wash: rgba(15, 122, 82, 0.10);
        --if-warn: #9a5c00;
        --if-warn-wash: rgba(154, 92, 0, 0.10);
        --if-danger: #c02f33;
        --if-danger-wash: rgba(192, 47, 51, 0.09);

        --if-tool-highlight: #cf9a1f;
        --if-tool-annotate: #c02f33;
        --if-tool-success: #0f7a52;

        --if-shadow: 0 1px 2px rgba(24, 32, 42, 0.06);
    }
}

body {
    margin: 0;
    background: var(--if-bg);
}

/* 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 would reach exactly one of them. */
#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;
}

/* ── Shared controls ──────────────────────────────────────────────────────────
   These live here rather than in a scoped stylesheet because they are used from
   several components at once, including inside render fragments that a child
   component lays out - and scoped CSS follows the file the markup was written in,
   not the component it ends up inside. One global copy is the honest way to share
   them. */

/* Small ghost button: the pane and section header bars are full of these. */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3125rem;
    padding: 0.1875rem 0.4375rem;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: var(--if-muted);
    font: inherit;
    font-size: 0.75rem;
    white-space: nowrap;
    cursor: pointer;
}

.btn:hover:not(:disabled) {
    background: var(--if-chrome-hover);
    border-color: var(--if-border);
    color: var(--if-text);
}

.btn:disabled {
    cursor: default;
    opacity: 0.5;
}

/* Buttons that put something onto the canvas rather than change how it is shown - so
   they look like buttons rather than hints. */
.btn-strong {
    border-color: var(--if-border);
    background: var(--if-pane);
    color: var(--if-text);
}

.btn-strong:hover:not(:disabled) {
    border-color: var(--if-accent-dim);
    background: var(--if-accent-wash);
    color: var(--if-text);
}

/* A two- or three-way switch drawn as one joined strip. Pressed rather than
   one-shot - the state is what the pane is showing. */
.seg {
    display: inline-flex;
    flex: none;
    overflow: hidden;
    border: 1px solid var(--if-border);
    border-radius: 5px;
}

.seg > button {
    padding: 0.125rem 0.4375rem;
    border: 0;
    background: transparent;
    color: var(--if-muted);
    font: inherit;
    font-size: 0.6875rem;
    white-space: nowrap;
    cursor: pointer;
}

.seg > button + button {
    border-left: 1px solid var(--if-border);
}

.seg > button:hover:not(.is-on) {
    background: var(--if-chrome-hover);
    color: var(--if-text);
}

.seg > button.is-on {
    background: var(--if-accent-wash);
    color: var(--if-accent);
    font-weight: 600;
}

/* A verdict, wherever one is stated. The hue comes from a --tone the caller sets,
   so one rule covers good, warning and bad - and every badge carries a mark as
   well as a colour, because the tool accents use the same part of the spectrum. */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 0.3125rem;
    flex: none;
    padding: 0.0625rem 0.4375rem 0.0625rem 0.375rem;
    border: 1px solid color-mix(in srgb, var(--tone, var(--if-muted)) 40%, transparent);
    border-radius: 999px;
    background: var(--tone-wash, transparent);
    color: var(--tone, var(--if-muted));
    font-size: 0.6875rem;
    font-weight: 600;
    white-space: nowrap;
}

.badge-good {
    --tone: var(--if-good);
    --tone-wash: var(--if-good-wash);
}

.badge-warn {
    --tone: var(--if-warn);
    --tone-wash: var(--if-warn-wash);
}

.badge-bad {
    --tone: var(--if-danger);
    --tone-wash: var(--if-danger-wash);
}

.badge-mark {
    font-size: 0.8125rem;
    line-height: 1;
}

/* A checkbox with its label. */
.opt {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    flex: none;
    color: var(--if-muted);
    font-size: 0.75rem;
    cursor: pointer;
    user-select: none;
}

.opt input {
    width: 0.875rem;
    height: 0.875rem;
    margin: 0;
    accent-color: var(--if-accent);
}

/* A small dropdown in a header bar, styled to sit beside .btn and .seg rather than to
   look like a form control. */
.pick {
    flex: none;
    padding: 0.125rem 0.25rem;
    border: 1px solid var(--if-border);
    border-radius: 5px;
    background: transparent;
    color: var(--if-muted);
    font: inherit;
    font-size: 0.6875rem;
    cursor: pointer;
}

.pick:hover {
    background: var(--if-chrome-hover);
    color: var(--if-text);
}

/* The open list's own colours. Left to themselves, Chromium draws it from the select's - a
   transparent background falls back to white under the muted text, and then changes again
   as :hover comes and goes while the pointer drags down the list. */
.pick option,
.pick optgroup {
    background-color: var(--if-pane);
    color: var(--if-text);
}

/* A tool property that opens a picker: what it is set to, over a small caret - the property
   itself is the control, as in every annotation editor's tool properties, and the caret says
   there is more to it than one click. Global because each picker is its own component. */
.if-prop-btn {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 0.1875rem;
    padding: 0.3125rem 0.3125rem 0.1875rem;
    border: 1px solid var(--if-border);
    border-radius: 5px;
    background: var(--if-pane);
    color: var(--if-muted);
    cursor: pointer;
}

.if-prop-btn:hover:not(:disabled) {
    border-color: var(--if-accent-dim);
    color: var(--if-text);
}

.if-prop-btn:focus-visible {
    outline: 2px solid var(--if-accent);
    outline-offset: 1px;
}

.if-prop-btn:disabled {
    cursor: default;
    opacity: 0.5;
}

.if-prop-caret {
    width: 0.4375rem;
    height: 0.25rem;
    fill: currentColor;
}

/* A picker opened from one: a native popover, in the top layer and placed under its button by
   js/canvas.js - so the UA's own centring (inset: 0; margin: auto) has to go. */
.if-popover {
    position: fixed;
    inset: auto;
    margin: 0;
    padding: 0.375rem;
    border: 1px solid var(--if-border);
    border-radius: 6px;
    background: var(--if-pane);
    color: var(--if-text);
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
}

/* A name for a control that shows only a glyph. Global because several components need
   it, and scoped CSS follows the file the markup was written in. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ── Panes ───────────────────────────────────────────────────────────────────
   The toolbox, canvas and properties panels share their chrome, and they are laid out
   from different components, so the chrome is global rather than scoped to any one of
   them. */
.pane {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    background: var(--if-pane);
}

/* The pane's hue, used for the hairline under its header - enough to tie a pane to
   what it is about without colouring the text the user is actually reading. */
.pane-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: none;
    min-height: 1.875rem;
    padding: 0.1875rem 0.5rem 0.1875rem 0.625rem;
    background: var(--if-pane-alt);
    border-bottom: 1px solid var(--if-border-soft);
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--pane-hue, var(--if-accent)) 35%, transparent);
}

.pane-title {
    margin: 0;
    flex: none;
    color: var(--if-text);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.11em;
    text-transform: uppercase;
}

.pane-meta {
    min-width: 0;
    overflow: hidden;
    color: var(--if-muted);
    font-size: 0.6875rem;
    font-variant-numeric: tabular-nums;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pane-spacer {
    flex: 1;
    min-width: 0.25rem;
}

.pane-actions {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    flex: none;
}

/* Stacked and scrolling with the page: nothing clips its own content, and each
   pane's header follows it down the screen so its controls stay to hand. */
@media (max-width: 57.59rem) {
    .pane {
        overflow: visible;
    }

    .pane-head {
        position: sticky;
        top: 0;
        z-index: 2;
    }
}

/* Monospace, for file names, dimensions and any other exact value that has to line up
   in a column. */
.mono {
    font-family: 'JetBrains Mono', 'Cascadia Mono', 'Consolas', ui-monospace, monospace;
    font-size: 0.8125rem;
}

/* ── Boot splash ──────────────────────────────────────────────────────────────
   What covers the page until WebAssembly is up and the workspace has something to
   show. It lives here, in the stylesheet the document already blocks on, rather than
   in a scoped component file - the markup is in index.html and is on screen long
   before ImageFlex.styles.css means anything.

   Only theme tokens are used, so a first visit gets the right palette from the
   inline theme script in index.html and never flashes the other one. */
.if-boot {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: grid;
    place-items: center;
    background: var(--if-bg);

    /* The shell's typeface, repeated: the splash sits outside `.if-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 workspace
   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. */
.if-boot-sheet {
    position: absolute;
    inset: 0;
}

.if-boot-sheet span {
    position: absolute;
    inset: 0;
    background: var(--if-bg);
}

.if-boot-sheet span:first-child {
    clip-path: polygon(0 0, calc(100% + 2px) 0, 0 calc(100% + 2px));
}

.if-boot-sheet span:last-child {
    clip-path: polygon(100% -2px, 100% 100%, -2px 100%);
}

.if-boot.is-leaving .if-boot-sheet span {
    transition: transform 560ms cubic-bezier(0.66, 0, 0.34, 1);
}

.if-boot.is-leaving .if-boot-sheet span:first-child {
    transform: translate(-100%, -100%);
}

.if-boot.is-leaving .if-boot-sheet span:last-child {
    transform: translate(100%, 100%);
}

.if-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. */
.if-boot.is-leaving .if-boot-body {
    opacity: 0;
    transform: scale(0.97);
    transition: opacity 160ms ease, transform 220ms ease;
}

.if-boot-mark {
    width: 4.5rem;
    height: 4.5rem;
    overflow: visible;
}

/* The mark is the app's own subject: a crop/focus frame. On the splash the four
   corners light up in sequence, top-left round to bottom-left - a frame drawing
   itself around the workspace that is about to appear. */
.if-boot-mark rect {
    animation: if-boot-corner 2s ease-in-out infinite;
}

@keyframes if-boot-corner {
    0%, 22%, 100% { fill: var(--if-faint); }
    8% { fill: var(--if-accent-bright); }
}

.if-boot-name {
    margin: 0.625rem 0 1.5rem;
    font-size: 1.375rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--if-text);
}

.if-boot-name span {
    color: var(--if-accent);
}

.if-boot-rail {
    position: relative;
    width: 13.75rem;
    max-width: 60vw;
    height: 3px;
    overflow: hidden;
    border-radius: 999px;
    background: var(--if-chrome-2);
    box-shadow: inset 0 0 0 1px var(--if-border-soft);
}

.if-boot-fill {
    width: 0;
    height: 100%;
    border-radius: inherit;
    background: linear-gradient(90deg, var(--if-accent-dim), var(--if-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. */
.if-boot-rail.is-waiting::after {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 35%;
    border-radius: inherit;
    background: linear-gradient(90deg, transparent, var(--if-accent-dim), transparent);
    animation: if-boot-sweep 1.4s ease-in-out infinite;
}

@keyframes if-boot-sweep {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(386%); }
}

.if-boot-status {
    margin: 0.875rem 0 0;
    font-size: 0.8125rem;
    color: var(--if-muted);
}

/* Fixed height, so the counts appearing under the rail do not shift the wordmark
   up the screen a beat after it arrived. */
.if-boot-detail {
    margin: 0.1875rem 0 0;
    min-height: 1.1rem;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--if-faint);
}

.if-boot.is-failed .if-boot-status {
    color: var(--if-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. */
.if-boot.is-failed .if-boot-fill {
    background: var(--if-danger);
    opacity: 0.5;
}

.if-boot.is-failed .if-boot-mark rect {
    animation: none;
}

@media (prefers-reduced-motion: reduce) {
    .if-boot-mark rect,
    .if-boot-rail.is-waiting::after {
        animation: none;
    }

    /* No curtain: the whole cover fades, which is the same information without the
       movement across the viewport. */
    .if-boot.is-leaving {
        opacity: 0;
        transition: opacity 200ms ease;
    }

    .if-boot.is-leaving .if-boot-sheet span,
    .if-boot.is-leaving .if-boot-body {
        transition: none;
        transform: none;
        opacity: 1;
    }
}

.blazor-error-boundary {
    background: #b32121;
    padding: 1rem;
    color: white;
}

.blazor-error-boundary::after {
    content: "An error has occurred."
}
