/* ---------------------------------------------------------------------------
   Liquid glass: one token set, four stylesheets.

   A glass panel is the surface colour it already had, at part strength, over
   a blurred and slightly saturated backdrop, with a hairline of light along
   its edge and a soft shadow beneath it. Nothing here paints a colour of its
   own. Every panel takes the colour of the surface it belongs to, so a
   preset or a mode change carries the glass with it and no palette is
   duplicated per surface.

   It is applied only where something floats above the page: side menus, top
   bars, tab bars, drawers, buttons, the cards that sit over a background and
   the layers that open on top of it. Tables, forms, long text and content
   areas stay flat. Glass under a paragraph is harder to read, and the whole
   point of this is the opposite of that.

   Three ways out, all of them solid, all of them decided here rather than in
   each sheet: a viewer who asks for less transparency, a browser without
   backdrop-filter or color-mix, and a viewer who asks for less motion, who
   keeps the glass and loses only the press.

   Sheets apply it by setting background-color, background-image and
   backdrop-filter from the three derived properties below. Those are the
   only switches; changing them here changes every surface at once.
--------------------------------------------------------------------------- */

:root {
    --glass-alpha: 62%;
    /* The side menu is held more solid than a floating panel. A column of
       links read top to bottom is not somewhere to spend contrast. */
    --glass-alpha-side: 92%;
    --glass-blur: 20px;
    --glass-saturate: 160%;
    --glass-radius: 18px;
    --glass-radius-lg: 24px;

    /* The lit edge. Nearly opaque white in light mode, where the panel is
       already close to white and the edge is what separates it from the page.
       Restrained in dark, where a bright hairline reads as a seam. */
    --glass-edge: rgba(255, 255, 255, .8);
    --glass-shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 10px 28px rgba(16, 24, 40, .10);

    /* Light falling from the top left, fading out before the middle, so the
       gloss reads as a highlight rather than a stripe across the panel. */
    --glass-specular: linear-gradient(135deg, rgba(255, 255, 255, .55), rgba(255, 255, 255, .10) 38%, rgba(255, 255, 255, 0) 62%);

    /* A hairline between rows in a listbox. Dark ink on a light panel,
       light ink on a dark one, at the same weight either way. */
    --row-divider: rgba(0, 0, 0, .08);

    --glass-press: .98;
    --glass-press-ms: 120ms;

    /* The safe defaults: flat, opaque, and correct on their own. Everything
       below only ever upgrades these. */
    --glass-bg: var(--surface);
    --glass-image: none;
    --glass-filter: none;
}

@media (prefers-color-scheme: dark) {
    html:not([data-mode="light"]) {
        --row-divider: rgba(255, 255, 255, .08);
        --glass-edge: rgba(255, 255, 255, .35);
        --glass-shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 12px 32px rgba(0, 0, 0, .45);
        --glass-specular: linear-gradient(135deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, .04) 40%, rgba(255, 255, 255, 0) 64%);
    }
}

html[data-mode="dark"] {
    --row-divider: rgba(255, 255, 255, .08);
    --glass-edge: rgba(255, 255, 255, .35);
    --glass-shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 12px 32px rgba(0, 0, 0, .45);
    --glass-specular: linear-gradient(135deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, .04) 40%, rgba(255, 255, 255, 0) 64%);
}

/* The upgrade, taken only where both halves of it work. color-mix is what
   turns the surface token into glass without a second palette, and an
   unsupported color-mix would otherwise compute to nothing and leave a panel
   with no background at all, which is why it is asked for here rather than
   written straight into the rule. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) and (background-color: color-mix(in srgb, red 50%, transparent)) {
    :root {
        --glass-bg: color-mix(in srgb, var(--surface) var(--glass-alpha), transparent);
        --glass-image: var(--glass-specular);
        --glass-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
    }
}

/* Asked for plainly: no translucency anywhere, whatever the browser can do.
   After the upgrade, so it wins. */
@media (prefers-reduced-transparency: reduce) {
    :root {
        --glass-bg: var(--surface);
        --glass-image: none;
        --glass-filter: none;
    }

    .sidebar { --glass-bg: var(--side-bg); }
}

/* The side menu is a surface of its own, not a window onto the page. Taking
   the page's surface colour turned a dark column white in light mode and
   left the menu's own pale ink on top of it, so the tokens are re-derived
   here from the colour the menu already has. Everything inside it follows,
   because a custom property is resolved where it is declared. */
.sidebar { --glass-bg: var(--side-bg); }

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) and (background-color: color-mix(in srgb, red 50%, transparent)) {
    .sidebar { --glass-bg: color-mix(in srgb, var(--side-bg) var(--glass-alpha-side), transparent); }
}

/* The tray a sidebar group opens onto. Inside the menu the lift is the
   menu's own raised token rather than the page surface, which at this
   depth would be a white shelf in a dark column. */
.sidebar-fold-body {
    background-color: var(--side-active);
    backdrop-filter: var(--glass-filter);
    -webkit-backdrop-filter: var(--glass-filter);
    border: 1px solid var(--glass-edge);
}

/* ---------- the press ----------

   A control dips very slightly under the finger and comes back. Only the
   things a person actually presses, and only transform and colour, so
   nothing reflows. */

.button,
.balance-chip,
.drawer-balance-chip,
.tab-bar a,
.drawer-item,
.stat-deposit {
    transition: transform var(--glass-press-ms) ease, background-color var(--glass-press-ms) ease;
}

/* A phone waits 300ms after every tap on a control to see whether a second
   one is coming. Not set on the document: zooming a page is the reader's. */
button, .button, a, summary, label, input, select, textarea { touch-action: manipulation; }

/* The flash a phone draws over what was tapped. Chrome's own is a faint black
   box: grey on a light page, lost on a dark one. The surface's colour instead:
   the shop's primary, the consoles' accent, the marketing gold. */
body { -webkit-tap-highlight-color: color-mix(in srgb, var(--primary, var(--accent, var(--gold))) 22%, transparent); }

/* One word of a heading alone on the last line reads as a mistake, and at 380
   it is most of them. */
h1, h2, h3, .card-title { text-wrap: balance; }

/* A button that is working: the label stays, and the spinner takes the icon's
   place, at the icon's size, so the box does not move. When it appears and how
   long it stays are in public/js/busy.js. */
.button-spinner {
    display: inline-block; flex: none; width: 1.125em; height: 1.125em; border-radius: 50%;
    border: 2px solid currentColor; border-top-color: transparent; opacity: .75;
    animation: button-spin .8s linear infinite;
}
@keyframes button-spin { to { transform: rotate(360deg); } }
/* Slowed, not stopped: it is the only thing saying the button is working. */
@media (prefers-reduced-motion: reduce) { .button-spinner { animation-duration: 3s; } }
.button[aria-busy="true"] { cursor: progress; }

.button:active,
.balance-chip:active,
.drawer-balance-chip:active,
.tab-bar a:active,
.drawer-item:active,
.stat-deposit:active {
    transform: scale(var(--glass-press));
}

@media (prefers-reduced-motion: reduce) {
    .button,
    .balance-chip,
    .drawer-balance-chip,
    .tab-bar a,
    .drawer-item,
    .stat-deposit {
        transition: none;
    }

    .button:active,
    .balance-chip:active,
    .drawer-balance-chip:active,
    .tab-bar a:active,
    .drawer-item:active,
    .stat-deposit:active {
        transform: none;
    }
}

/* ---------------------------------------------------------------------------
   THE SPACING SCALE. Here for the same reason the glass tokens are: this
   sheet is the one every surface loads, so a gap decided here is the same gap
   on all four.

   It was written after counting what the project actually used. The admin
   sheets alone carried twenty-six different spacing values - .3, .35, .4, .45,
   .5, .55, .6, .65, .7 - which is not a design, it is what happens when every
   rule picks a number on its own. Nothing is a quarter of a step away from
   anything any more: a gap is one of eight sizes, and a rule that wants a gap
   says which of the eight rather than inventing a ninth.

   Eight steps on a four-pixel grid, because at these sizes the eye reads the
   difference between 8 and 12 and does not read the difference between 11 and
   12.
--------------------------------------------------------------------------- */

:root {
    --space-1: .25rem;      /* 4px  - icon to its label, inside one control */
    --space-2: .5rem;       /* 8px  - label to field, field to its helper */
    --space-3: .75rem;      /* 12px - one button to the next */
    --space-4: 1rem;        /* 16px - one form row to the next */
    --space-5: 1.25rem;     /* 20px - the inside of a card */
    --space-6: 1.5rem;      /* 24px - one card to the next */
    --space-7: 2rem;        /* 32px - the page margin on a desktop */
    --space-8: 3rem;        /* 48px - under the last thing on a page */

    /* Named for the job, so a rule can say why rather than how much. These
       are the five the layout complaints were all about: buttons flush
       against text, against inputs, against each other, and cards whose
       insides were a different size on every edge. */

    /* Two controls side by side. Never smaller than this: a thumb needs to
       land on one of them and not the other. */
    --gap-inline: var(--space-3);

    /* A button, or anything pressable, after a paragraph or a field. */
    --gap-stack: var(--space-4);

    /* Label, field, helper: the three parts of one form row. */
    --gap-field: var(--space-2);

    /* One form row to the next, and one card to the next. */
    --gap-form: var(--space-4);
    --gap-cards: var(--space-6);

    /* The inside of a card, even on all four edges. A card whose bottom
       padding is smaller than its top reads as a mistake even to somebody
       who cannot say what is wrong with it. */
    --pad-card: var(--space-5);
    --pad-card-tight: var(--space-4);

    /* The page's own margin, so nothing ever touches the edge of the window
       or the edge of the thing holding it. */
    --pad-page: var(--space-4);
    --pad-page-wide: var(--space-7);
}
