/* ---------------------------------------------------------------------------
   WannaShare: design tokens

   The same palette as wannaverse.com, Wannaverse HR, WannaLogin, WannaMail and
   Wannaverse Calendar, so that arriving here from any of them feels like
   staying inside one product rather than being handed off to a file locker:
   navy ink carries the structure, teal is the accent, white is the surface,
   and a hairline rule does the work a shadow would do elsewhere.

   Two devices belong to this product rather than to the family. A drop target,
   because the whole of this application is moving files into places, and a
   person dragging one needs to see where it will land before they let go. And
   a share row, because a link is a decision about who can read something, and
   its visibility, its password and its expiry matter more than its address.
--------------------------------------------------------------------------- */

/* Noto Sans is the company face. Self-hosted as two variable subsets rather
   than fetched from Google, because a font request is a request that says who
   is using this tool and when, and because the content-security policy this
   application sends allows no third-party origin at all. */
@font-face {
    font-family: "Noto Sans";
    src: url("/fonts/noto-sans-latin.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
        U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Latin Extended, for the names people actually have: ć, đ, š and the rest.
   Its own file with its own unicode-range, so a browser downloads the 164 KB
   only when a page contains a character that needs it. */
@font-face {
    font-family: "Noto Sans";
    src: url("/fonts/noto-sans-latin-ext.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308,
        U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
        U+2C60-2C7F, U+A720-A7FF;
}

:root {
    /* surface */
    --paper: #ffffff;
    --tint: #f6f9fc;
    --tint-deep: #eef3f8;

    /* ink */
    --ink: #0a2540;
    --ink-up: #1d3d5f;
    --slate-deep: #071a30;
    --body: #425466;
    --muted: #6b7c93;
    --line: #e3e8ee;
    --line-strong: #cdd7e2;

    /* brand accent */
    --teal: #40adc5;
    --teal-deep: #0e7c94;
    --teal-ink: #0b6a80;
    --teal-soft: #7ac8d0;
    --teal-mist: #95dce3;
    --teal-wash: #e6f4f7;
    --teal-line: #c2e4ec;

    /* status, used for account, link and upload state only, never decoration.
       Each has an ink, a wash to sit on, and the hairline between the two. */
    --green: #1f7a52;
    --green-wash: #e6f4ee;
    --green-line: #c3e3d3;
    --amber: #8a5a00;
    --amber-wash: #fdf3e0;
    --amber-line: #f0dcb4;
    --red: #b3261e;
    --red-wash: #fdecea;
    --red-line: #f3cdc9;
    /* Body copy on --red-wash. --red itself is a shade too light to read at 13px. */
    --red-ink: #7e1a14;

    --ink-on-dark: #f0f5fa;
    --body-on-dark: #b6c4d4;

    /* elevation, deliberately scarce. Flat surfaces with a 1px rule are the
       default; a shadow is reserved for something that genuinely floats. */
    --shadow-float: 0 30px 60px -22px rgb(10 37 64 / 0.32);
    --shadow-pop: 0 12px 28px -10px rgb(10 37 64 / 0.22);

    /* shape. Corners: 5, 6, 8. Never pills. */
    --radius: 6px;
    --radius-sm: 5px;
    --radius-lg: 8px;

    /* type */
    --font-body: "Noto Sans", "Segoe UI", Tahoma, ui-sans-serif, system-ui, sans-serif;
    --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

    /* layout. The rail matches the HR portal's exactly. */
    --rail: 248px;
    --container: 1440px;
    --gutter: clamp(20px, 3.5vw, 48px);

    color-scheme: light;
}

/* ------------------------------------------------------------------ reset */

*,
*::before,
*::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    background: var(--tint);
    color: var(--body);
    font-family: var(--font-body);
    font-size: 15px;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

img {
    display: block;
    max-width: 100%;
    height: auto;
}

/* An inline <svg> with a viewBox and no width defaults to 300x150, so every
   icon needs a size from somewhere. These two are the floor: the shape it is
   given wherever nothing more specific applies, such as inside a button.
   Declared on the fragment's own class rather than on `svg`, so a chart or an
   illustration added later is not silently shrunk to an icon.

   Every other rule that places an icon (`.actions svg`, `.navLink > svg`, and
   the rest) is more specific and wins. It has to be that way round: a class
   written on the <svg> tag in a template is discarded along with the host tag
   when Thymeleaf replaces it with the fragment. */
.ico {
    width: 18px;
    height: 18px;
    flex: none;
}

.mark {
    width: 22px;
    height: 27px;
    flex: none;
}

button,
input,
select,
textarea {
    font: inherit;
    color: inherit;
}

::selection {
    background: var(--teal-mist);
    color: var(--ink);
}

/* A visible focus ring on everything, always. Half the people who live in a
   file browser never touch the mouse. */
:focus-visible {
    outline: 2px solid var(--teal-deep);
    outline-offset: 2px;
    border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ------------------------------------------------------------------- type */

h1,
h2,
h3,
h4 {
    color: var(--ink);
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -0.018em;
    text-wrap: balance;
}

h1 {
    font-size: 1.75rem;
    letter-spacing: -0.022em;
}

h2 {
    font-size: 1.1875rem;
}

h3 {
    font-size: 1rem;
    font-weight: 600;
}

p {
    text-wrap: pretty;
}

a {
    color: var(--teal-ink);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.muted {
    color: var(--muted);
}

.small {
    font-size: 0.8125rem;
}

/* Names, sizes, tokens and ids. Copied and compared character by character, so
   they get a face where l and 1 differ. */
code,
.mono {
    font-family: var(--font-mono);
    font-size: 0.875em;
    /* An identifier in running text has no spaces to break at, so it has to be
       allowed to break mid-token or it decides the width of the page. */
    overflow-wrap: anywhere;
}

/* Anything that has to line up down a column: sizes, counts, dates. Tabular
   numerals are correctness here, not styling. */
.numeric {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

.hidden {
    display: none !important;
}

/* --------------------------------------------------------------- controls */

/* A bare <button> is the primary action, because that is what almost every
   button in this application is. The two modifiers are the exceptions. */
button,
.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 600;
    line-height: 1;
    padding: 0.7rem 1.05rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--ink);
    color: #fff;
    cursor: pointer;
    white-space: nowrap;
    transition:
        background-color 150ms ease,
        border-color 150ms ease,
        color 150ms ease;
}

button:hover:not(:disabled),
.button:hover {
    background: var(--ink-up);
    color: #fff;
    text-decoration: none;
}

button:disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

button.ghost {
    background: #fff;
    color: var(--ink);
    border-color: var(--line-strong);
}

button.ghost:hover:not(:disabled) {
    background: var(--tint);
    border-color: var(--muted);
    color: var(--ink);
}

/* Destructive and hard to undo. Only on actions that delete a file, remove a
   folder, or revoke a link somebody else is using. */
button.danger {
    background: #fff;
    color: var(--red);
    border-color: #f0c4c0;
}

button.danger:hover:not(:disabled) {
    background: var(--red-wash);
    border-color: var(--red);
    color: var(--red);
}

button.small,
.button.small {
    font-size: 0.8125rem;
    padding: 0.5rem 0.7rem;
}

/* A button that is really a link: sign out, clear a search. Styled as the
   sentence it sits in rather than as a control. */
.linkish {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-size: 0.875rem;
    color: var(--teal-ink);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.linkish:hover {
    background: none;
    color: var(--teal-deep);
}

/* ------------------------------------------------------------------ forms */

label {
    display: block;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--ink);
}

input,
select,
textarea {
    width: 100%;
    font-size: 0.9375rem;
    font-weight: 400;
    color: var(--ink);
    background: #fff;
    padding: 0.6rem 0.7rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    transition:
        border-color 150ms ease,
        box-shadow 150ms ease;
}

/* A label wraps its field, so the field needs its own top margin rather than
   inheriting the label's weight and size. */
label > input,
label > select,
label > textarea {
    margin-top: 0.375rem;
}

input:focus,
select:focus,
textarea:focus {
    outline: none;
    border-color: var(--teal-deep);
    box-shadow: 0 0 0 3px rgb(14 124 148 / 0.14);
}

input[aria-invalid="true"],
select[aria-invalid="true"] {
    border-color: var(--red);
}

input::placeholder,
textarea::placeholder {
    color: #9aa8ba;
}

input:disabled,
select:disabled,
textarea:disabled,
input[readonly] {
    background: var(--tint);
    color: var(--muted);
}

input:disabled,
select:disabled,
textarea:disabled {
    cursor: not-allowed;
}

textarea {
    resize: vertical;
    min-height: 5rem;
    line-height: 1.5;
}

select {
    appearance: none;
    /* Chevron drawn inline so no icon request is needed for a form control. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%236b7c93'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.5rem center;
    background-size: 20px;
    padding-right: 2rem;
}

input[type="checkbox"],
input[type="radio"] {
    width: 17px;
    height: 17px;
    flex: none;
    margin: 0;
    padding: 0;
    accent-color: var(--teal-deep);
    cursor: pointer;
}

label.checkbox {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: 0.875rem;
    font-weight: 400;
    color: var(--ink);
    cursor: pointer;
    margin-top: 0.5rem;
}

fieldset {
    border: 0;
}

/* Sixteen pixels, on anything with a finger pointing at it.
   Not a taste decision: Safari on iOS zooms the whole page in when a field with
   type smaller than 16px takes focus, and it does not zoom back out afterwards.
   Scoped to a coarse pointer so a desktop keeps the type scale it was designed
   with, and written as a floor so a field that is already larger is left alone. */
@media (pointer: coarse) {
    input,
    select,
    textarea {
        font-size: max(1em, 16px);
    }
}

/* The field-level validation message Thymeleaf renders next to an input. */
.err {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    font-weight: 400;
    color: var(--red);
}

/* A form laid out as one column of labelled fields, which is nearly all of
   them here. */
.stack {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* --------------------------------------------------------------- rows */

.row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}

.row-end {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.6rem;
    flex-wrap: wrap;
}

.inline {
    display: inline;
}

/* ===========================================================================
   The signed-in shell

   A persistent left rail plus a wide content column. A tool, not a brochure:
   navigation stays put so the current section is always visible, and the
   content takes the full remaining width rather than sitting in a narrow
   centred box. The same shape as Wannaverse HR and WannaLogin, deliberately.
   =========================================================================== */

.shell {
    /* The height of the top bar: 44px of button between 8px of padding.
       Declared here rather than on the bar itself because the drawer and the
       scrim are its siblings, and a custom property inherits down the tree and
       not sideways. On the bar it would resolve nowhere. */
    --top-bar: 60px;

    display: grid;
    grid-template-columns: var(--rail) minmax(0, 1fr);
    min-height: 100vh;
    min-height: 100dvh;
}

.rail {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--slate-deep);
    color: var(--body-on-dark);
    padding: 22px 14px 16px;
    overflow-y: auto;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 10px 22px;
    color: #fff;
}

.brand:hover {
    text-decoration: none;
}

/* The mark arrives through a Thymeleaf fragment, which replaces the host tag
   outright: a class written on the <svg> in the template does not survive it.
   So every icon here is sized by the rule for the thing it sits in. */
.brand > svg,
.topBrand > svg {
    width: 22px;
    height: 27px;
}

.brandText {
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    color: #fff;
    line-height: 1.1;
}

.brandSub {
    display: block;
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--teal-mist);
}

.nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
}

.navGroup {
    padding: 14px 10px 6px;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgb(255 255 255 / 0.38);
}

.navLink {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 9px 10px;
    border-radius: var(--radius-sm);
    color: var(--body-on-dark);
    font-size: 0.9375rem;
    font-weight: 500;
    transition:
        background-color 140ms ease,
        color 140ms ease;
}

.navLink:hover {
    background: rgb(255 255 255 / 0.07);
    color: #fff;
    text-decoration: none;
}

.navLink[aria-current="page"] {
    background: rgb(255 255 255 / 0.11);
    color: #fff;
    font-weight: 600;
    /* The teal edge marks the one thing that is currently true. The same device
       marks the folder a dragged file is about to land in. */
    box-shadow: inset 3px 0 0 var(--teal);
}

.navLink > svg {
    width: 20px;
    height: 20px;
    opacity: 0.85;
}

.navLink[aria-current="page"] > svg {
    opacity: 1;
    color: var(--teal-mist);
}

.railFoot {
    border-top: 1px solid rgb(255 255 255 / 0.1);
    padding-top: 12px;
    margin-top: 12px;
}

.who {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 10px 10px;
    min-width: 0;
}

.who > svg {
    width: 20px;
    height: 20px;
    color: var(--teal-mist);
}

.whoText {
    min-width: 0;
}

.whoName {
    display: block;
    color: #fff;
    font-size: 0.875rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.whoRole {
    display: block;
    font-size: 0.75rem;
    color: var(--teal-mist);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.signOut {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    width: 100%;
    padding: 9px 10px;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--body-on-dark);
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
}

.signOut:hover:not(:disabled) {
    background: rgb(255 255 255 / 0.07);
    color: #fff;
}

.signOut svg {
    width: 18px;
    height: 18px;
}

/* -------------------------------------------------------------------- main */

.main {
    min-width: 0;
    background: var(--tint);
    display: flex;
    flex-direction: column;
}

.content {
    flex: 1;
    padding: 34px var(--gutter) 64px;
    max-width: var(--container);
    width: 100%;
}

.foot {
    padding: 18px var(--gutter) 26px;
    font-size: 0.75rem;
    color: var(--muted);
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    align-items: center;
}

.foot a {
    color: var(--muted);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.foot a:hover {
    color: var(--ink);
}

/* ------------------------------------------- the top bar and its drawer

   Above 900px there is no top bar and the rail is simply there. Below it the
   rail becomes a drawer that slides in from the left, which is what the
   hamburger opens. A drawer costs a tap and gives back the labels, the
   grouping, and the fact that Administration is a section. */

.topBar {
    display: none;
}

.menuButton {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    flex: none;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: #fff;
    cursor: pointer;
}

.menuButton:hover:not(:disabled) {
    background: rgb(255 255 255 / 0.08);
}

.menuButton:focus-visible {
    outline: 2px solid var(--teal);
    outline-offset: 2px;
}

/* Three bars that become a cross. Transformed rather than swapped for a
   different icon, so the two states are visibly the same object and the change
   reads as the menu opening. */
.burger {
    position: relative;
    display: block;
    width: 18px;
    height: 14px;
}

.burger span {
    position: absolute;
    left: 0;
    display: block;
    width: 100%;
    height: 2px;
    border-radius: 2px;
    background: currentColor;
    transition:
        transform 220ms cubic-bezier(0.2, 0, 0, 1),
        opacity 120ms linear;
}

.burger span:nth-child(1) {
    top: 0;
}

.burger span:nth-child(2) {
    top: 6px;
}

.burger span:nth-child(3) {
    top: 12px;
}

.burger[data-open="true"] span:nth-child(1) {
    transform: translateY(6px) rotate(45deg);
}

.burger[data-open="true"] span:nth-child(2) {
    opacity: 0;
}

.burger[data-open="true"] span:nth-child(3) {
    transform: translateY(-6px) rotate(-45deg);
}

.topBrand {
    display: flex;
    align-items: center;
    gap: 9px;
    color: #fff;
    min-width: 0;
}

.topBrand:hover {
    text-decoration: none;
}

.scrim {
    display: none;
}

@media (max-width: 900px) {
    .shell {
        grid-template-columns: minmax(0, 1fr);
        /* The bar hugs its content and the page takes the rest. Stated rather
           than left to auto: a grid whose rows are auto shares the leftover
           space between them, so on a short page half of it went into the top
           bar, which read as the navigation having enormous padding on some
           screens and not others. */
        grid-template-rows: auto minmax(0, 1fr);
    }

    .topBar {
        position: sticky;
        top: 0;
        /* Above the drawer, not below it. The control that opens it is the
           control that closes it. */
        z-index: 60;
        display: flex;
        align-items: center;
        gap: 6px;
        padding: 8px var(--gutter);
        background: var(--slate-deep);
        color: #fff;
    }

    .rail {
        position: fixed;
        /* Starts where the bar ends, so the two do not overlap and the drawer
           does not need a second brand at the top of it. */
        top: var(--top-bar);
        bottom: 0;
        left: 0;
        z-index: 50;
        width: min(280px, 84vw);
        height: auto;
        padding-top: 14px;
        /* Hidden rather than only moved. Off-screen links stay in the tab order
           and a keyboard lands in a menu nobody can see; visibility takes them
           out of it, and is delayed on the way out so the drawer is still
           visible while it slides away. */
        visibility: hidden;
        transform: translateX(-100%);
        transition:
            transform 240ms cubic-bezier(0.2, 0, 0, 1),
            visibility 0s linear 240ms;
        box-shadow: 0 0 40px rgb(0 0 0 / 0.35);
    }

    .rail[data-open="true"] {
        visibility: visible;
        transform: none;
        transition:
            transform 240ms cubic-bezier(0.2, 0, 0, 1),
            visibility 0s;
    }

    .rail:focus {
        outline: none;
    }

    /* The bar above carries the brand already, and two of them stacked is just
       the word twice. */
    .brand {
        display: none;
    }

    .scrim {
        position: fixed;
        top: var(--top-bar);
        right: 0;
        bottom: 0;
        left: 0;
        z-index: 40;
        display: block;
        width: 100%;
        padding: 0;
        border: 0;
        border-radius: 0;
        background: rgb(7 26 48 / 0.55);
        opacity: 0;
        visibility: hidden;
        transition:
            opacity 240ms linear,
            visibility 0s linear 240ms;
        cursor: pointer;
    }

    /* Written with the element as well as the class so it outranks
       `button:hover:not(:disabled)`, which is (0,2,1). A bare `.scrim:hover` is
       (0,2,0) and loses, which turned the scrim navy under the pointer. */
    button.scrim:hover {
        background: rgb(7 26 48 / 0.55);
    }

    .scrim[data-open="true"] {
        opacity: 1;
        visibility: visible;
        transition:
            opacity 240ms linear,
            visibility 0s;
    }

    .content {
        padding-top: 24px;
    }
}

/* Somebody who has asked for less movement gets the drawer without the slide.
   It still appears and disappears, because that is the information. */
@media (prefers-reduced-motion: reduce) {
    .rail,
    .scrim,
    .burger span {
        transition-duration: 0s;
    }
}

/* ===========================================================================
   The signed-out shell

   Sign in, register, the two password screens, and the pages a share link
   lands on when it wants a password or has nothing to give. One centred card
   and nothing else: everybody who reaches these pages wants one thing, so the
   page is the form and the form is the page.
   =========================================================================== */

.publicPage {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--tint);
}

.publicMain {
    flex: 1;
    display: grid;
    place-items: center;
    padding: clamp(28px, 6vh, 72px) var(--gutter);
}

.publicCard {
    width: 100%;
    max-width: 26rem;
}

.publicBrand {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    margin-bottom: 26px;
    text-align: center;
}

.publicBrand > svg {
    width: 34px;
    height: 41px;
}

.brandName {
    font-size: 1.375rem;
    font-weight: 700;
    color: var(--ink);
    letter-spacing: -0.02em;
    line-height: 1.2;
}

.publicBrand .brandSub {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--teal-deep);
    margin-top: 2px;
}

/* The card itself. It floats, because it is the whole page: the only other
   shadow in this application is on a dialog, for the same reason. */
.formCard {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 26px;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

/* Small, and inside the card. A display-size heading above the card turns a
   sign-in box into a page about signing in. */
.formTitle {
    font-size: 1.0625rem;
    font-weight: 700;
    color: var(--ink);
    letter-spacing: -0.015em;
}

.formIntro {
    font-size: 0.875rem;
    color: var(--body);
    line-height: 1.5;
    /* Tucked under the title, which has its own line height to spare. */
    margin-top: -0.5rem;
}

.formSubmit {
    width: 100%;
    padding-block: 0.8rem;
}

.formLinks {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.75rem;
    font-size: 0.875rem;
}

.formLinks a {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.formNote {
    font-size: 0.8125rem;
    color: var(--muted);
    line-height: 1.5;
}

.publicFoot {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px 18px;
    padding: 18px var(--gutter) 26px;
    font-size: 0.75rem;
    color: var(--muted);
    text-align: center;
}

.publicFoot a {
    color: var(--muted);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.publicFoot a:hover {
    color: var(--ink);
}

/* ===========================================================================
   The plain shell

   A page somebody outside the organisation can be sent to: the two legal
   documents, and the content behind a share link. Wider than a card and
   narrower than the application, with the mark at the top so it is obvious
   whose service this is.
   =========================================================================== */

.plainPage {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--tint);
}

.plainBar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 14px var(--gutter);
    background: #fff;
    border-bottom: 1px solid var(--line);
}

.plainBrand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    color: var(--ink);
}

.plainBrand:hover {
    text-decoration: none;
}

.plainBrand > svg {
    width: 21px;
    height: 26px;
}

.plainBrand .brandText {
    color: var(--ink);
    font-size: 1rem;
}

.plainBrand .brandSub {
    color: var(--teal-deep);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.07em;
}

.plainMain {
    flex: 1;
    width: 100%;
    max-width: 52rem;
    margin-inline: auto;
    padding: clamp(28px, 5vh, 48px) var(--gutter) 56px;
}

/* The two legal documents: a single readable column and nothing else. */
.legal {
    max-width: 46rem;
}

.legal h1 {
    font-size: clamp(1.75rem, 3.4vw, 2.25rem);
}

.legal h2 {
    font-size: 1.25rem;
    margin-top: 36px;
    margin-bottom: 10px;
}

.legal p,
.legal li {
    font-size: 0.9375rem;
    line-height: 1.65;
}

.legal p + p {
    margin-top: 12px;
}

.legal ul {
    margin: 12px 0 0 20px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.legal .updated {
    font-size: 0.8125rem;
    color: var(--muted);
    margin-bottom: 8px;
}

/* ===========================================================================
   Page furniture

   Headers, panels, tables, states. Flat surfaces with a single hairline rule.
   No card shadows: the only things in this product that float are the sign-in
   card and a dialog, and both float because they are the whole screen.
   =========================================================================== */

.pageHead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 26px;
}

.pageLede {
    margin-top: 6px;
    font-size: 0.9375rem;
    color: var(--body);
    max-width: 62ch;
}

.pageActions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.backLink {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 0.875rem;
    color: var(--muted);
}

.backLink:hover {
    color: var(--ink);
    text-decoration: none;
}

.backLink svg {
    width: 16px;
    height: 16px;
}

/* ------------------------------------------------------------------ panel */

.panel {
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    /* A table's header row carries its own tinted background. Without this it
       paints square into the panel's rounded top corners on every screen where
       the table is the first thing in the panel. */
    overflow: hidden;
}

.panel + .panel {
    margin-top: 20px;
}

.panelHead {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
    padding: 16px 20px;
    border-bottom: 1px solid var(--line);
}

.panelTitle {
    font-size: 1rem;
    font-weight: 700;
    color: var(--ink);
    letter-spacing: -0.012em;
}

.panelNote {
    font-size: 0.8125rem;
    color: var(--muted);
    max-width: 70ch;
}

/* ------------------------------------------------------------------ table */

.table-wrap {
    overflow-x: auto;
}

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

table.files th {
    text-align: left;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--muted);
    padding: 11px 16px;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
    background: var(--tint);
}

table.files td {
    padding: 11px 16px;
    border-bottom: 1px solid var(--line);
    color: var(--body);
    vertical-align: middle;
}

table.files tbody tr:last-child td {
    border-bottom: 0;
}

table.files tbody tr:hover td {
    background: var(--tint);
}

/* Sizes and dates must never wrap: "2.1 / MB" on two lines is harder to scan
   down a column than a slightly wider cell. */
table.files td.numeric,
table.files td.size {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.alignRight {
    text-align: right;
}

/* A file or a folder and its name: the one thing in every row that somebody is
   actually looking for, so it is the one thing in ink rather than body grey. */
.item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    color: var(--ink);
    font-weight: 600;
}

.item svg {
    width: 18px;
    height: 18px;
    color: var(--muted);
}

/* The same emphasis without the flex box, for a cell whose primary text is
   plain: a name in a list of people, say. `.item` is inline-flex, which a <td>
   cannot be without taking the table layout with it. */
.primaryCell {
    color: var(--ink);
    font-weight: 600;
}

/* A folder is a link and a file is not, so the folder gets the accent and the
   underline on hover that says so. */
.item.folder {
    color: var(--teal-ink);
}

.item.folder svg {
    color: var(--teal-deep);
}

a.item:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* ---------------------------------------------------------- row actions

   Quiet until the row is under the pointer. Six controls per row at full
   contrast is a table nobody can read; the names are what somebody scans and
   the actions are what they reach for afterwards. */

.actions {
    white-space: nowrap;
}

.actions > * + * {
    margin-left: 4px;
}

.actions a,
.actions button {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 8px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--body);
    cursor: pointer;
}

.actions a:hover,
.actions button:hover:not(:disabled) {
    background: var(--tint-deep);
    border-color: var(--line);
    color: var(--ink);
    text-decoration: none;
}

.actions svg {
    width: 15px;
    height: 15px;
    opacity: 0.75;
}

.actions button.danger {
    background: transparent;
    border-color: transparent;
    color: var(--red);
}

.actions button.danger:hover:not(:disabled) {
    background: var(--red-wash);
    border-color: var(--red-line);
    color: var(--red-ink);
}

/* Inside a table cell, a form is layout, not a section. */
.actions form.inline,
td form.inline {
    display: inline;
}

@media (max-width: 720px) {
    table.files th,
    table.files td {
        padding: 9px 10px;
    }

    .actions a,
    .actions button {
        padding: 5px 6px;
    }

    /* Below this width there is no room for six words of action per row, so the
       labels go and the icons stay. */
    .actions .actionLabel {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* ------------------------------------------------------------ status pill

   Square-ish, not a lozenge: these read as labels rather than badges, which is
   what they are. Used for link, account and upload state only. */

.pill,
.status-ok,
.status-bad,
.status-warn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px;
    border: 1px solid transparent;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    text-transform: none;
}

.pill {
    background: var(--tint-deep);
    color: var(--body);
    border-color: var(--line);
}

.status-ok {
    background: var(--green-wash);
    color: var(--green);
    border-color: var(--green-line);
}

.status-warn {
    background: var(--amber-wash);
    color: var(--amber);
    border-color: var(--amber-line);
}

.status-bad {
    background: var(--red-wash);
    color: var(--red);
    border-color: var(--red-line);
}

/* --------------------------------------------------------------- messages

   The flash after a redirect and the error above a form. An icon, a wash, and
   the hairline between the two. */

.flash {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 14px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    font-size: 0.875rem;
    line-height: 1.5;
    margin-bottom: 20px;
}

.flash svg {
    width: 18px;
    height: 18px;
    flex: none;
    margin-top: 1px;
}

.flash.success {
    background: var(--green-wash);
    border-color: var(--green-line);
    color: var(--green);
}

.flash.error {
    background: var(--red-wash);
    border-color: var(--red-line);
    color: var(--red-ink);
}

.flash.info {
    background: var(--teal-wash);
    border-color: var(--teal-line);
    color: var(--teal-ink);
}

/* Inside the sign-in card the message is part of the form's stack, so it
   brings no margin of its own. */
.formCard .flash {
    margin-bottom: 0;
}

/* ----------------------------------------------------------------- states */

/* Nothing here yet, or nothing matched. Says what would fill the space rather
   than only that the space is empty. */
.state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
    padding: 54px 24px;
    color: var(--muted);
}

.state > svg {
    width: 34px;
    height: 34px;
    color: var(--line-strong);
}

.stateTitle {
    font-size: 1rem;
    font-weight: 700;
    color: var(--ink);
}

.stateBody {
    font-size: 0.875rem;
    max-width: 48ch;
}

/* ------------------------------------------------------------ stat strip */

/* One-pixel gaps over a hairline background, so the cells are divided by the
   same rule as everything else rather than by six separate borders. */
.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 1px;
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
}

.stat-card {
    background: #fff;
    padding: 18px 20px;
}

.stat-card .label {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--muted);
}

.stat-card .value {
    font-size: 1.625rem;
    font-weight: 700;
    color: var(--ink);
    letter-spacing: -0.025em;
    line-height: 1.2;
    margin-top: 2px;
    font-variant-numeric: tabular-nums;
}

.stat-card .sub {
    font-size: 0.8125rem;
    color: var(--muted);
    margin-top: 2px;
}

/* ------------------------------------------------------- panel footer */

/* A strip under a panel's content, divided from it by the same hairline that
   divides everything else: the pager under a table, or the two facts that
   decide whether a share link still works tomorrow. */
.panelFoot,
.pagination {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    padding: 14px 20px;
    border-top: 1px solid var(--line);
}

.panelFoot {
    gap: 8px;
}

.pagination a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    padding: 5px 8px;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: #fff;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--body);
    font-variant-numeric: tabular-nums;
}

.pagination a:hover {
    background: var(--tint);
    border-color: var(--line-strong);
    color: var(--ink);
    text-decoration: none;
}

.pagination a.active {
    background: var(--ink);
    border-color: var(--ink);
    color: #fff;
}

/* ===========================================================================
   The file browser

   Breadcrumbs, the quota meter, the drop target, and the drag-and-drop states
   that go with them.
   =========================================================================== */

.breadcrumbs {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 2px;
    font-size: 0.9375rem;
    min-width: 0;
}

.breadcrumbs a {
    padding: 4px 7px;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--body);
    font-weight: 500;
}

.breadcrumbs a:hover {
    background: var(--tint-deep);
    color: var(--ink);
    text-decoration: none;
}

/* The last crumb is where you are, not somewhere to go. */
.breadcrumbs span:last-child a {
    color: var(--ink);
    font-weight: 700;
}

.breadcrumbs .sep {
    color: var(--line-strong);
    user-select: none;
}

/* A crumb is also a drop target: dragging a file onto "Documents" moves it up
   two levels. The teal edge is the same device the rail uses for the section
   you are in, because both answer "this one". */
.breadcrumbs a[data-drop-folder-id].drop-over {
    background: var(--teal-wash);
    border-color: var(--teal-deep);
    color: var(--teal-ink);
}

/* -------------------------------------------------------------- the quota */

.quota {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--body);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.quota .bar {
    width: 132px;
    height: 6px;
    flex: none;
    background: var(--tint-deep);
    border-radius: 3px;
    overflow: hidden;
}

.quota .bar span {
    display: block;
    height: 100%;
    background: var(--teal-deep);
    border-radius: 3px;
}

.toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

/* ---------------------------------------------------------- the drop zone

   The table and the area around it are one target. A dashed rule rather than a
   solid one, because the boundary is an invitation and not a container: while
   nothing is being dragged it should be almost invisible. */

.dropzone {
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    transition:
        border-color 150ms ease,
        background-color 150ms ease;
}

.dz-label svg {
    width: 16px;
    height: 16px;
}

.dz-label {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 11px 16px;
    border-bottom: 1px dashed var(--line-strong);
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--muted);
}

.dropzone.dragover {
    border-color: var(--teal-deep);
    box-shadow: 0 0 0 3px rgb(14 124 148 / 0.12);
}

.dropzone.dragover .dz-label {
    background: var(--teal-wash);
    border-bottom-color: var(--teal-deep);
    color: var(--teal-ink);
}

/* --------------------------------------------------------- drag and drop */

tr.draggable {
    cursor: grab;
}

tr.draggable.dragging {
    opacity: 0.45;
}

/* The row a file is about to land in. The teal left edge is drawn on the first
   cell, because a box-shadow on a <tr> does not paint in every browser. */
tr.drop-over > td {
    background: var(--teal-wash) !important;
}

tr.drop-over > td:first-child {
    box-shadow: inset 3px 0 0 var(--teal-deep);
}

/* ------------------------------------------------------- the upload tray

   Fixed to the corner while uploads are in flight. It floats, so it is one of
   the three things in this application allowed a shadow. */

.upload-tray {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 70;
    width: min(360px, calc(100vw - 40px));
    max-height: min(52vh, 420px);
    overflow-y: auto;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-float);
}

.upload-tray h3 {
    position: sticky;
    top: 0;
    padding: 13px 16px;
    background: #fff;
    border-bottom: 1px solid var(--line);
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--muted);
}

.upload-tray ul {
    list-style: none;
}

.upload-tray li {
    padding: 12px 16px;
    border-bottom: 1px solid var(--line);
    font-size: 0.8125rem;
}

.upload-tray li:last-child {
    border-bottom: 0;
}

.upload-tray li > div:first-child {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.upload-tray strong {
    color: var(--ink);
    font-weight: 600;
    overflow-wrap: anywhere;
}

.upload-tray .status {
    flex: none;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.upload-tray .status.done {
    color: var(--green);
    font-weight: 600;
}

.upload-tray .status.err {
    color: var(--red);
    font-weight: 600;
}

.upload-tray .pbar {
    height: 4px;
    margin-top: 8px;
    background: var(--tint-deep);
    border-radius: 2px;
    overflow: hidden;
}

.upload-tray .pbar span {
    display: block;
    width: 0;
    height: 100%;
    background: var(--teal-deep);
    border-radius: 2px;
    transition: width 120ms linear;
}

/* ===========================================================================
   Share links

   A link is a decision about who can read something. So a share row leads with
   its address, because that is what gets copied, and carries its visibility,
   its password and its expiry underneath, because those are what somebody has
   to check before sending it.
   =========================================================================== */

input.share-url-input {
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    padding: 0.45rem 0.55rem;
    background: var(--tint);
    color: var(--ink);
    min-width: 0;
}

.share-dialog-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

#shareLinksList {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-height: 44vh;
    overflow-y: auto;
}

.share-link-card {
    padding: 12px;
    background: var(--tint);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}

.share-link-url-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

.share-link-url-row .share-url-input {
    flex: 1;
    background: #fff;
}

.share-link-url-row button {
    flex: none;
    padding: 0.45rem 0.6rem;
    font-size: 0.8125rem;
}

.share-link-meta {
    display: flex;
    align-items: center;
    gap: 6px 10px;
    flex-wrap: wrap;
    margin-top: 9px;
}

/* Deleting a link happens in the row rather than in a dialog on top of a
   dialog: the thing being deleted stays visible while it is being confirmed. */
.share-link-confirm-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.share-link-confirm-row span {
    flex: 1;
    min-width: 12ch;
}

.share-link-confirm-row strong {
    font-family: var(--font-mono);
    color: var(--ink);
    overflow-wrap: anywhere;
}

.share-link-confirm-row button {
    padding: 0.45rem 0.6rem;
    font-size: 0.8125rem;
}

.share-link-pill svg {
    width: 13px;
    height: 13px;
}

.share-link-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px;
    background: var(--amber-wash);
    border: 1px solid var(--amber-line);
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--amber);
    white-space: nowrap;
}

/* The header on a page reached through a share link: says what the thing is
   before it says what it is called. */
.share-hero {
    padding: 20px;
    border-bottom: 1px solid var(--line);
}

.share-hero .kind {
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--teal-deep);
    margin-bottom: 4px;
}

.share-hero h1 {
    font-size: 1.375rem;
    overflow-wrap: anywhere;
}

.share-hero p {
    margin-top: 6px;
    font-size: 0.9375rem;
    color: var(--body);
}

/* ------------------------------------------------------------ search form */

.search-form {
    padding: 20px;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin-bottom: 20px;
}

.search-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 16px;
}

.search-row label > span {
    display: block;
    margin-bottom: 0.375rem;
}

/* The name is what people search by, so it gets the width. */
.search-q {
    grid-column: span 2;
}

@media (max-width: 560px) {
    .search-q {
        grid-column: auto;
    }
}

.search-actions {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    margin-top: 20px;
    padding-top: 18px;
    border-top: 1px solid var(--line);
}

/* ===========================================================================
   Things that float: the two dialogs, the toast stack, the modal

   Everything else in this application is a flat surface with a hairline. These
   four are on top of the page, so they are the only places a shadow appears.
   =========================================================================== */

dialog {
    width: min(34rem, calc(100vw - 2rem));
    padding: 22px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: #fff;
    color: var(--body);
    box-shadow: var(--shadow-float);
}

dialog::backdrop {
    background: rgb(7 26 48 / 0.45);
}

dialog h2 {
    font-size: 1.0625rem;
    font-weight: 700;
    color: var(--ink);
}

/* ------------------------------------------------------------------ toasts */

#toastStack {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 90;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
    /* The stack is a region, not a surface: only the toasts in it take clicks. */
    pointer-events: none;
}

.toast {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    width: min(360px, calc(100vw - 40px));
    padding: 12px 13px;
    background: #fff;
    border: 1px solid var(--line);
    border-left: 3px solid var(--muted);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-pop);
    font-size: 0.875rem;
    line-height: 1.45;
    pointer-events: auto;
    animation: toastIn 180ms cubic-bezier(0.2, 0, 0.2, 1);
}

@keyframes toastIn {
    from {
        opacity: 0;
        transform: translateY(8px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.toast.leave {
    animation: toastOut 160ms ease forwards;
}

@keyframes toastOut {
    to {
        opacity: 0;
        transform: translateY(6px);
    }
}

/* The coloured edge and the dot say the same thing twice, on purpose: the edge
   is what somebody notices and the dot is what survives a colour-blind eye
   next to the words beside it. */
.toast .dot {
    width: 8px;
    height: 8px;
    flex: none;
    margin-top: 6px;
    border-radius: 50%;
    background: var(--muted);
}

.toast.ok {
    border-left-color: var(--green);
}

.toast.ok .dot {
    background: var(--green);
}

.toast.error {
    border-left-color: var(--red);
}

.toast.error .dot {
    background: var(--red);
}

.toast.info {
    border-left-color: var(--teal-deep);
}

.toast.info .dot {
    background: var(--teal-deep);
}

.toast .msg {
    flex: 1;
    min-width: 0;
    color: var(--ink);
    overflow-wrap: anywhere;
}

.toast .close {
    flex: none;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--muted);
    font-size: 1.1rem;
    line-height: 1;
}

.toast .close:hover:not(:disabled) {
    background: var(--tint-deep);
    color: var(--ink);
}

/* -------------------------------------------------------------- the modal

   Confirm, prompt and choose, drawn here rather than by the browser. A native
   confirm() cannot say which file is about to be deleted in the product's own
   voice, and cannot be dismissed by the same Escape key as everything else. */

#uiModalRoot {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: none;
    place-items: center;
    padding: var(--gutter);
    background: rgb(7 26 48 / 0.45);
}

#uiModalRoot.open {
    display: grid;
}

.ui-modal {
    width: min(30rem, 100%);
    padding: 22px;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-float);
    animation: modalIn 160ms cubic-bezier(0.2, 0, 0.2, 1);
}

@keyframes modalIn {
    from {
        opacity: 0;
        transform: translateY(8px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.ui-modal h3 {
    font-size: 1.0625rem;
    font-weight: 700;
    color: var(--ink);
    letter-spacing: -0.015em;
}

.ui-modal .body {
    margin-top: 6px;
    font-size: 0.9375rem;
    color: var(--body);
    line-height: 1.5;
    overflow-wrap: anywhere;
}

.ui-modal .body strong {
    color: var(--ink);
}

.ui-modal input,
.ui-modal select,
.ui-modal textarea {
    margin-top: 14px;
}

.ui-modal .actions-row {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 20px;
}

/* A radio list where each row is the target, not the 17 pixels of circle at
   its left edge. Used when an upload collides with a name already taken. */
.ui-modal .radio-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 16px;
}

.ui-modal .radio-list label {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    font-size: 0.9375rem;
    font-weight: 500;
    color: var(--ink);
    cursor: pointer;
}

.ui-modal .radio-list label:hover {
    background: var(--tint);
    border-color: var(--line-strong);
}

.ui-modal .radio-list label.selected {
    background: var(--teal-wash);
    border-color: var(--teal-deep);
    color: var(--teal-ink);
}

/* A hint under a field, saying what a blank or a zero will do. Grey, because it
   is guidance and not an error. */
.fieldHint {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    font-weight: 400;
    color: var(--muted);
    max-width: 66ch;
}
