﻿/* =============================================================
   Wafaq ISD Management Software (WMS)
   GLOBAL STYLESHEET - loaded on every page
   -------------------------------------------------------------
   Plain, hand-written CSS. No framework, no build step: these
   files are served straight from /public.

   WHERE THINGS LIVE
     css/app.css              this file - tokens, reset, and every
                              shared component. Loaded everywhere.
     css/layout.css           the signed-in shell: sidebar, header,
                              footer, page frame.
     css/pages/auth.css       login screen only
     css/pages/dashboard.css  dashboard only
     css/pages/clients.css    client screens only
     css/pages/wallet.css     wallet screen only

   A page loads app.css plus whatever it needs, pushed onto the
   `styles` stack from the view - see resources/views/layouts/app.blade.php.

   THE RULE FOR THIS FILE
     A style belongs here when more than one page uses it, or
     plainly will. Anything used by a single screen belongs in that
     screen's file. When a page style turns out to be needed twice,
     move it here rather than copying it.

   CONTENTS
     Numbered sections are the foundation, in order:

       1. Design tokens
       2. Base / reset
       3. Logo
       4. Forms and fields
       5. Buttons
       6. Alerts
       7. Accessibility helpers
       8. Shared responsive rules

     Between 6 and 7 come the shared components, each under its
     own "--- Name ---" heading: cards, statistic tiles, content
     grids, tables, badges, filter bar, description lists,
     amounts, row form layout, tabs, grouped inputs, search box,
     pagination, month picker, empty states, links and the small
     table helpers.

   Images live under /public/images, grouped by role:
     images/logos/  images/icons/  images/backgrounds/  images/illustrations/
   User-uploaded files do NOT belong there - those go to
   storage/app/public.
   ============================================================= */

/* -------------------------------------------------------------
   1. Design tokens

   Every colour, space and radius used below is declared once here.
   The two brand colours are sampled directly from the logo file, so
   the interface and the mark cannot drift apart.
   ------------------------------------------------------------- */

:root {
    /* Brand - taken from Wafaq-ISD-Logo.png */
    --color-brand-navy: #263a90;
    --color-brand-blue: #0976bc;

    /* A wash of the brand colour, for a control that is selected
       without being the thing in focus. */
    --color-brand-blue-soft: rgba(9, 118, 188, 0.08);

    /* Navy shades, for the login panel gradient and hover states */
    --color-navy-deep: #16225c;
    --color-navy-dark: #1d2d73;
    --color-blue-dark: #075e97;
    --color-blue-light: #9dc8e4;

    /* Surfaces and text */
    --color-page: #f2f4f8;
    --color-surface: #ffffff;
    --color-border: #d5dae3;
    --color-text: #161922;
    --color-text-muted: #5f6779;

    /* Feedback */
    /* Green reads as "it worked": used by the success toast and
       the buttons that carry a save through. */
    --color-success: #15803d;
    --color-success-surface: #e7f6ec;

    /* Amber reads as "needs watching" rather than "wrong": money
       committed but not yet gone, work paused rather than stopped. */
    --color-warning: #d97706;

    --color-danger: #b42318;
    --color-danger-surface: #fef3f2;
    --color-danger-border: #f5c6c2;

    /* Focus ring, expressed as a translucent brand blue */
    --color-focus-ring: rgba(9, 118, 188, 0.28);

    /* Spacing scale */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --space-7: 48px;
    --space-8: 64px;

    /* Shape and depth */
    --radius: 8px;
    --radius-large: 14px;
    --shadow-panel: 0 1px 2px rgba(16, 24, 40, 0.06), 0 12px 32px rgba(16, 24, 40, 0.10);

    /* Typography */
    --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-size-base: 15px;
    --font-size-small: 13px;
    --line-height: 1.5;

    /* Width of the sign-in form column */
    --login-form-width: 380px;

    /* Row-form proportions: the label column, and how wide a control
       is allowed to grow. Capping the control keeps a short field
       like a postcode from stretching across the whole card. */
    --form-label-width: 180px;
    --form-control-width: 420px;

    /* Width of the sidebar when it is permanently visible.
       Note: the matching breakpoint (1024px) has to be written out
       in each @media rule - custom properties are not allowed in
       media queries. Change both together. */
    --sidebar-width: 256px;

    /* The sidebar narrowed to icons only. Wide enough for a 20px
       icon centred with comfortable room either side. */
    --sidebar-width-collapsed: 74px;
}

/* -------------------------------------------------------------
   2. Base / reset

   A deliberately small reset: only the defaults that get in the way.
   ------------------------------------------------------------- */

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

body {
    margin: 0;
    background-color: var(--color-page);
    color: var(--color-text);
    font-family: var(--font-sans);
    font-size: var(--font-size-base);
    line-height: var(--line-height);
    -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
p,
ul {
    margin: 0;
}

ul {
    padding: 0;
    list-style: none;
}

img {
    /* Never let an image overflow its column. */
    max-width: 100%;
}

/*
 * The `hidden` attribute only sets `display: none` at the lowest
 * precedence, so any later `display` rule silently defeats it. This
 * keeps hidden elements hidden - the password toggle relies on it
 * to stay invisible until the script enables it.
 */
[hidden] {
    display: none !important;
}

/*
 * Scrollbars on light surfaces - the page itself and any scrolling
 * panel such as a wide table. Slimmer and quieter than the OS
 * default, which is visually heavy next to this interface.
 *
 * The sidebar declares its own light-on-navy variant separately.
 * Note these rules only restyle the bar; they never hide it, because
 * a hidden scrollbar removes the only cue that content continues
 * out of view.
 */
html {
    scrollbar-width: thin;
    scrollbar-color: #c3c9d4 transparent;
}

::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background-color: #c8cedb;
    /* A transparent border with background-clip insets the thumb,
       leaving breathing room either side of it. */
    border: 2px solid transparent;
    background-clip: content-box;
    border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
    background-color: #aeb6c6;
}

/* Stops the two scrollbars meeting in an odd grey square. */
::-webkit-scrollbar-corner {
    background: transparent;
}

/* Inputs and buttons do not inherit font settings by default. */
input,
button,
select,
textarea {
    font: inherit;
    color: inherit;
}

/* A single, consistent focus ring for every keyboard-focusable
   element. Never remove this without providing a replacement:
   it is the only way keyboard users can see where they are. */
:focus-visible {
    outline: 2px solid var(--color-brand-blue);
    outline-offset: 2px;
}

/* -------------------------------------------------------------
   3. Logo

   Markup comes from the <x-app-logo /> Blade component. The source
   PNG is 235x186 with an opaque white background, so it must sit on
   a white or very light surface. Sizing is driven by height, with
   width left automatic so any replacement keeps its proportions.
   ------------------------------------------------------------- */

.logo {
    display: block;
    width: auto;
    height: 40px;
}

.logo--large {
    height: 118px;
}

/* -------------------------------------------------------------
   4. Forms
   ------------------------------------------------------------- */

/* Wraps one label + input + error message. */
.field {
    margin-bottom: var(--space-4);
}

.field-label {
    display: block;
    margin-bottom: var(--space-1);
    font-size: var(--font-size-small);
    font-weight: 600;
    letter-spacing: 0.01em;
}

.field-input {
    display: block;
    width: 100%;
    padding: 11px var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.field-input::placeholder {
    color: #a3aab8;
}

.field-input:hover {
    border-color: #b9c1ce;
}

.field-input:focus {
    border-color: var(--color-brand-blue);
    /* Ring drawn with box-shadow so it does not shift layout. */
    box-shadow: 0 0 0 3px var(--color-focus-ring);
    outline: none;
}

/* Applied by the Blade template when the field failed validation. */
.field-input--invalid {
    border-color: var(--color-danger);
}

/* Positioning context for a control sitting inside an input,
   such as the password show/hide toggle. */
.field-control {
    position: relative;
}

/* Reserves room so typed text never runs under the toggle button. */
.field-input--with-action {
    padding-right: 46px;
}

/* --- Password show/hide toggle ---
   Markup from <x-password-field />, behaviour from public/js/app.js.
   The button is hidden until the script reveals it. */

.password-toggle {
    position: absolute;
    top: 1px;
    right: 1px;
    bottom: 1px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0 var(--radius) var(--radius) 0;
    color: var(--color-text-muted);
    cursor: pointer;
}

.password-toggle:hover {
    color: var(--color-brand-navy);
}

.password-toggle-icon {
    width: 18px;
    height: 18px;
    display: block;
}

/*
 * Which eye shows is derived from aria-pressed, so the accessible
 * state and the visible state cannot fall out of step - the script
 * sets one attribute and both update together.
 */
.password-toggle-icon--hide {
    display: none;
}

.password-toggle[aria-pressed="true"] .password-toggle-icon--show {
    display: none;
}

.password-toggle[aria-pressed="true"] .password-toggle-icon--hide {
    display: block;
}

.field-error {
    display: block;
    margin-top: var(--space-1);
    color: var(--color-danger);
    font-size: var(--font-size-small);
}

/* A field label with a small action opposite it - "Add vendor" beside
   "Paid to". The action belongs to the field, so it sits on the
   field's own line rather than in the page header where it would read
   as being about the whole form. */
.field-label-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
}

.field-label-row .field-label {
    margin-bottom: 0;
}

/* Hidden until js/app.js adds `has-js` - see the dialog rules further
   down, which do the same for every other dialog trigger. */
.field-label-action {
    display: none;
    align-items: center;
    gap: 4px;
    padding: 0;
    background: none;
    border: none;
    color: var(--color-brand-blue);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}

.has-js .field-label-action {
    display: inline-flex;
}

.field-label-action:hover {
    text-decoration: underline;
}

.field-label-action svg {
    width: 13px;
    height: 13px;
}

.field-checkbox {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
    color: var(--color-text-muted);
    font-size: 14px;
    /* Stops the label text highlighting on a double click. */
    user-select: none;
    cursor: pointer;
}

.field-checkbox input {
    /* Tints the native control with the brand colour, avoiding a
       custom checkbox that would need JavaScript to stay accessible. */
    accent-color: var(--color-brand-blue);
    width: 15px;
    height: 15px;
}

/* The foot of a table: how many rows on the left, the pages on the
   right. Both at the bottom, where somebody who has read to the end
   of the table is already looking. */
.table-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3);
}

/* A reference typed in the middle of what surrounds it: the prefix
   and suffix are fixed - they come from Settings - and only the
   number is a box. Reads as one field rather than three. */
.reference-input {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.reference-input .field-input {
    /* Wide enough for eight digits and no wider: the box says how
       much belongs in it. */
    width: 10rem;
}

.reference-input-fixed {
    color: var(--color-text-muted);
    font-size: 14px;
    font-weight: 600;
    white-space: nowrap;
}

/* --- Rows per page ---
   A short label and a dropdown, sitting in the toolbar beside the
   search box. The Show button next to it is only for a browser
   without the script - see section 9 of js/app.js. */

.perpage {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.perpage-label {
    color: var(--color-text-muted);
    font-size: 13px;
    white-space: nowrap;
}

.perpage-select {
    padding: 8px var(--space-2);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    color: var(--color-text);
    font: inherit;
    font-size: 14px;
    cursor: pointer;
}

.perpage-select:hover {
    border-color: #b9c1ce;
}

.perpage-select:focus {
    outline: none;
    border-color: var(--color-brand-blue);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

/* The dropdown submits on its own once the script is there, so the
   button has nothing left to do. */
.has-js .perpage-go {
    display: none;
}

/* --- Searchable dropdowns ---
   A search box on the long selects, added by js/select-search.js.

   Built on <details>, like <x-multi-select> and the month picker, so
   the browser does the opening and closing and the shared data-menu
   handler does the rest. These rules follow the multiselect's
   closely on purpose: the two are the same control with a different
   number of answers, and they should not look like two designs.

   The native <select> is still what gets submitted; it is only
   hidden from view. With the script absent there is nothing here to
   see and the native dropdown is simply used. */

.selectsearch {
    position: relative;
}

/* Hidden from view but still in the form, still submitted, and still
   what the browser's own validation reads. `display: none` would take
   it out of that, so it is taken out of sight instead. */
.selectsearch-native {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    border: 0;
    opacity: 0;
    pointer-events: none;
}

.selectsearch-trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: 11px var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    cursor: pointer;
    /* Removes the default disclosure triangle in every engine. */
    list-style: none;
}

.selectsearch-trigger::-webkit-details-marker {
    display: none;
}

.selectsearch-trigger:hover {
    border-color: #b9c1ce;
}

.selectsearch[open] > .selectsearch-trigger {
    border-color: var(--color-brand-blue);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

.selectsearch-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Nothing chosen yet: read as a placeholder, not as a value. */
.selectsearch-value.is-empty {
    color: #a3aab8;
}

/* Drawn rather than an icon file, so it costs nothing to load and
   turns over with the panel. */
.selectsearch-chevron {
    flex: none;
    width: 9px;
    height: 9px;
    margin-right: 3px;
    border-right: 2px solid var(--color-text-muted);
    border-bottom: 2px solid var(--color-text-muted);
    border-radius: 1px;
    transform: translateY(-3px) rotate(45deg);
    transition: transform 0.15s ease;
}

.selectsearch[open] > .selectsearch-trigger .selectsearch-chevron {
    transform: translateY(1px) rotate(225deg);
}

.selectsearch-panel {
    position: absolute;
    top: calc(100% + var(--space-1));
    left: 0;
    right: 0;
    z-index: 40;
    padding: var(--space-2);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-large);
    box-shadow: var(--shadow-panel);
}

.selectsearch-search {
    display: block;
    width: 100%;
    padding: 8px var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font: inherit;
    font-size: 14px;
}

.selectsearch-search:focus {
    outline: none;
    border-color: var(--color-brand-blue);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

/* Tall enough to show that there is more, short enough not to cover
   the rest of the form. */
.selectsearch-list {
    max-height: 240px;
    margin-top: var(--space-2);
    overflow-y: auto;
}

.selectsearch-option {
    display: block;
    width: 100%;
    padding: 8px var(--space-3);
    background: none;
    border: none;
    border-radius: var(--radius);
    color: var(--color-text);
    font: inherit;
    font-size: 14px;
    text-align: left;
    cursor: pointer;
}

.selectsearch-option:hover,
.selectsearch-option:focus {
    outline: none;
    background-color: #eef1f6;
}

.selectsearch-option.is-selected {
    background-color: var(--color-brand-blue-soft);
    font-weight: 600;
}

.selectsearch-empty {
    padding: var(--space-3);
    color: var(--color-text-muted);
    font-size: 13px;
    text-align: center;
}
/* A short row of checkboxes reading as one answer - "Self and Sub
   Contract" - rather than as separate questions. Used where there are
   only two or three and both are worth seeing at once; a longer list
   belongs in <x-multi-select>. */

.checkbox-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.checkbox-option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 9px var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font-size: 14px;
    /* Stops the label text highlighting on a double click. */
    user-select: none;
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}

.checkbox-option:hover {
    border-color: #b9c1ce;
}

/* The whole tile lights up, not just the box - at a glance the
   answer is which tiles are blue, without reading them. */
.checkbox-option:has(input:checked) {
    background-color: var(--color-brand-blue-soft);
    border-color: var(--color-brand-blue);
}

.checkbox-option input {
    /* Tints the native control with the brand colour, avoiding a
       custom checkbox that would need JavaScript to stay accessible. */
    accent-color: var(--color-brand-blue);
    width: 15px;
    height: 15px;
}

/* A field that only applies once something above it has been chosen.
   Hidden by the class, which the server sets on the way out and
   js/projects.js takes off as the boxes are ticked - so a form that
   came back with an error opens with the field already in place.

   Without the script the class is still whatever the server wrote,
   which is the safe way round: a field that is needed is shown. */
.field-dependent.is-hidden {
    display: none;
}

/* A message written by a script while somebody is typing, rather
   than by the server after a refused save. Same red as .field-error
   so the two read as one thing; hidden until there is something to
   say, which the [hidden] attribute already does. */
.field-error--live {
    display: block;
    margin-top: calc(-1 * var(--space-3));
    margin-bottom: var(--space-5);
}

/* -------------------------------------------------------------
   5. Buttons
   ------------------------------------------------------------- */

.button {
    /* inline-flex rather than inline-block so a button can hold an
       icon beside its label without extra markup. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 11px var(--space-5);
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-weight: 600;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.05s ease;
}

/* Icons sized relative to the label they sit beside. */
.button svg {
    flex: none;
    width: 16px;
    height: 16px;
}

.button--primary {
    background-color: var(--color-brand-navy);
    border-color: var(--color-brand-navy);
    color: #ffffff;
}

.button--primary:hover {
    background-color: var(--color-navy-dark);
    border-color: var(--color-navy-dark);
}

.button--primary:active {
    /* A small nudge gives the click physical feedback. */
    transform: translateY(1px);
}

/* Low-emphasis action, e.g. "Sign out" in the top bar. */
.button--quiet {
    padding: 7px var(--space-3);
    background-color: transparent;
    border-color: var(--color-border);
    color: var(--color-text-muted);
    font-weight: 500;
}

.button--quiet:hover {
    border-color: var(--color-brand-navy);
    color: var(--color-brand-navy);
}

/*
 * Safe, affirmative actions such as Edit. Outlined to match the
 * danger button beside it, so the pair reads as one set with two
 * different meanings rather than two different kinds of control.
 */
.button--success {
    background-color: transparent;
    border-color: #b9e3c6;
    color: #15803d;
}

.button--success:hover {
    background-color: #e7f6ec;
    border-color: #15803d;
}

/*
 * Neutral state changes that undo cleanly, such as putting an
 * employee on leave. Blue rather than amber: nothing is being lost.
 */
.button--info {
    background-color: transparent;
    border-color: #c3ddf2;
    color: var(--color-blue-dark);
}

.button--info:hover {
    background-color: #e6f1fa;
    border-color: var(--color-brand-blue);
}

/*
 * Significant but reversible actions, such as marking an employee as
 * having left. Amber sits deliberately between the green Edit and the
 * red Delete: worth pausing over, but not destructive.
 */
.button--warning {
    background-color: transparent;
    border-color: #f2ddb6;
    color: #9a5b00;
}

.button--warning:hover {
    background-color: #fdf3e2;
    border-color: #b45309;
}

/*
 * Destructive actions. Outlined rather than solid red: a filled
 * button invites a click, and deleting should not.
 */
.button--danger {
    background-color: transparent;
    border-color: var(--color-danger-border);
    color: var(--color-danger);
}

.button--danger:hover {
    background-color: var(--color-danger-surface);
    border-color: var(--color-danger);
}

/* Modifier: stretch a button to its container's width. */
.button--block {
    display: flex;
    width: 100%;
}


/* Modifier: a button sitting beside a heading rather than in a form
   footer, where the full-size control would outweigh the text. */
.button--small {
    padding: 7px var(--space-3);
    font-size: 13px;
}

.button--small svg {
    width: 15px;
    height: 15px;
}

/* --- Icon-only buttons ---
   A control small enough to sit inside a card or a table row. The
   icon alone carries no meaning to a screen reader, so every one of
   these must be given an aria-label by its template.

   The hit area is 30px square rather than the icon's 16px, so it is
   still reachable with a thumb. */

.icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    background-color: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--color-text-muted);
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.icon-button svg {
    width: 16px;
    height: 16px;
}

.icon-button:hover {
    background-color: #eef2fb;
    border-color: var(--color-border);
    color: var(--color-brand-blue);
}

.icon-button:focus-visible {
    box-shadow: 0 0 0 3px var(--color-focus-ring);
    outline: none;
}

/* Destructive actions turn red on approach, not at rest: a row of
   delete buttons all shouting at once is noise. */
.icon-button--danger:hover {
    background-color: var(--color-danger-surface);
    border-color: var(--color-danger-border);
    color: var(--color-danger);
}

/* --- Dialogs ---
   The native <dialog> element: the browser supplies the modal
   behaviour, the focus trap and the Escape key, so none of that is
   reimplemented here.

   Opened by public/js/app.js from any [data-dialog-open="id"], and
   closed by any [data-dialog-close] inside it. */

.dialog {
    width: min(480px, calc(100vw - 2 * var(--space-4)));
    max-height: calc(100vh - 2 * var(--space-5));
    padding: 0;
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-large);
    box-shadow: 0 20px 50px rgba(16, 24, 40, 0.22);
    color: var(--color-text);
    overflow: auto;
}

.dialog::backdrop {
    background-color: rgba(16, 24, 40, 0.45);
}

.dialog-head {
    padding: var(--space-5) var(--space-5) 0;
}

.dialog-title {
    font-size: 17px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.dialog-description {
    margin-top: var(--space-1);
    color: var(--color-text-muted);
    font-size: 13px;
}

.dialog-body {
    padding: var(--space-5);
}

/* The last field's own margin would otherwise double up with the
   footer's padding. */
.dialog-body .field:last-child {
    margin-bottom: 0;
}

.dialog-foot {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    padding: var(--space-4) var(--space-5);
    background-color: #f8f9fc;
    border-top: 1px solid var(--color-border);
}

/* A dialog holding a whole card of fields rather than two or three
   - the vendor added from the expense form. Wide enough for the
   label-left rows inside it to read as rows. */
.dialog--wide {
    width: min(680px, calc(100vw - 2 * var(--space-4)));
}

/* The rows inside a dialog have less width to work with than a page
   does, so the label column is narrower. */
.dialog-body .form-rows .field {
    grid-template-columns: 140px minmax(0, 1fr);
}

@media (max-width: 640px) {
    /* Stacked, like every other form row at this width. */
    .dialog-body .form-rows .field {
        grid-template-columns: 1fr;
    }
}

/* --- The confirmation dialog ---
   One dialog serves every destructive action, so it is styled once
   here rather than per screen. See partials/confirm-dialog.blade.php
   and section 5 of js/app.js.

   Narrower than a dialog holding a form: this one asks a question
   and takes an answer, and a short line of text set across the full
   width of a form dialog reads as an afterthought. */

.dialog--confirm {
    width: min(420px, calc(100vw - 2 * var(--space-4)));
}

/* The document viewer.

   Much larger than a form dialog, because the point of it is to
   read a page of A4 without downloading it first. Sized against
   the viewport rather than in pixels so it fills a laptop screen
   and still fits a tablet held upright.

   overflow is hidden rather than auto: the frame inside scrolls
   the document itself, and two scrollbars on the same file is
   nobody's idea of a viewer. */

.dialog--viewer {
    width: min(900px, calc(100vw - 2 * var(--space-4)));
    height: min(90vh, 900px);
    max-height: calc(100vh - 2 * var(--space-4));
    overflow: hidden;
}

/* [open], and it matters.

   A <dialog> that is not open is hidden by the browser's own
   `display: none`, and that rule is weak enough for any class here
   to beat it. Setting `display: flex` on the dialog itself would
   therefore show it on every page it sits on, closed or not. The
   layout belongs to the open state, so it is written on the open
   state. */

.dialog--viewer[open] {
    display: flex;
    flex-direction: column;
}

.dialog--viewer .dialog-head {
    padding-bottom: var(--space-4);
}

/* Takes what is left after the head and the footer. */
.viewer-frame {
    flex: 1;
    min-height: 0;
    background-color: #f1f3f8;
    border-top: 1px solid var(--color-border);
    border-bottom: 1px solid var(--color-border);
}

.viewer-file {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Said in place of the frame, for a file a browser cannot show. */
.viewer-unviewable {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-6) var(--space-5);
    background-color: #f8f9fc;
    border-top: 1px solid var(--color-border);
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text-muted);
    font-size: 14px;
    text-align: center;
}

/* Close on the left, Download on the right: the safe one is not
   where a hand reaching for the confirming button lands. */
.dialog--viewer .dialog-foot {
    justify-content: space-between;
}

/* The download is a link rather than a button, so it needs the
   icon and label sitting on one line the way a button's do. */
.dialog--viewer .dialog-foot .button {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

.dialog--viewer .dialog-foot .button svg {
    width: 16px;
    height: 16px;
}

.confirm-head {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-5) var(--space-5) var(--space-4);
}

/* The icon carries the tone before the words are read. Neutral by
   default; the script turns it red for a deletion, which is the same
   moment the button below it turns red - so the two cannot disagree
   about how serious the answer is. */
.confirm-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 40px;
    height: 40px;
    background-color: #eef1f6;
    color: var(--color-text-muted);
    border-radius: 50%;
}

.confirm-icon svg {
    width: 20px;
    height: 20px;
}

.confirm-icon--danger {
    background-color: var(--color-danger-surface);
    color: var(--color-danger);
}

.confirm-words {
    /* Long names wrap rather than pushing the dialog wider. */
    min-width: 0;
}

/* The heading is the question itself, so it carries the weight the
   dialog's own title would. */
.dialog--confirm .dialog-title {
    line-height: 1.35;
}

.dialog--confirm .dialog-description {
    margin-top: var(--space-2);
    line-height: 1.5;
}

/* A control that opens a dialog can do nothing without the script,
   so it stays hidden until js/app.js adds `has-js`. Each kind of
   trigger restores its own display, since they are not all
   inline-flex - see .breakdown-add in css/pages/budget.css. */
[data-dialog-open] {
    display: none;
}

.has-js .button[data-dialog-open] {
    display: inline-flex;
}

.has-js .icon-button[data-dialog-open] {
    display: inline-flex;
}/* -------------------------------------------------------------
   6. Alerts and validation messages
   ------------------------------------------------------------- */

.alert {
    margin-bottom: var(--space-4);
    padding: 10px var(--space-3);
    border: 1px solid transparent;
    border-left-width: 3px;
    border-radius: var(--radius);
    font-size: 14px;
}

.alert--error {
    background-color: var(--color-danger-surface);
    border-color: var(--color-danger-border);
    border-left-color: var(--color-danger);
    color: var(--color-danger);
}

/* --- Toasts ---
   What just happened, said in the top-right corner and taken away
   after three seconds by section 8 of js/app.js.

   The markup sits at the top of the page content - see
   partials/toasts.blade.php - and only becomes a corner strip once
   the script has added `has-js`. Without it the same message reads
   as an ordinary alert above the page, which is the behaviour that
   was there before: nothing is ever announced only to vanish
   unread. */

.toasts {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
}

.has-js .toasts {
    position: fixed;
    top: var(--space-4);
    right: var(--space-4);
    z-index: 60;
    width: min(380px, calc(100vw - 2 * var(--space-4)));
    margin-bottom: 0;

    /* The strip itself is not a surface - only the messages in it
       are - so clicks pass through the gaps between them. */
    pointer-events: none;
}

.toast {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    overflow: hidden;
}

.has-js .toast {
    pointer-events: auto;
    box-shadow: 0 12px 32px rgba(16, 24, 40, 0.16);

    /* In from the side it came from, so the eye follows it rather
       than finding it already there. */
    animation: toast-in 0.22s ease-out both;
}

/* Leaving. Added by the script, which removes the element when the
   animation has run. */
.has-js .toast.is-leaving {
    animation: toast-out 0.2s ease-in forwards;
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateX(12px) scale(0.98);
    }
}

@keyframes toast-out {
    to {
        opacity: 0;
        transform: translateX(12px);
    }
}

/* Somebody who has asked for less movement gets the message without
   it sliding. It still appears and still leaves. */
@media (prefers-reduced-motion: reduce) {
    .has-js .toast,
    .has-js .toast.is-leaving {
        animation-duration: 0.01ms;
    }
}

.toast-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 28px;
    height: 28px;
    border-radius: 50%;
}

.toast-icon svg {
    width: 16px;
    height: 16px;
}

.toast-body {
    display: flex;
    flex-direction: column;
    gap: 2px;

    /* A long message wraps rather than pushing the close button off
       the end. */
    min-width: 0;
    flex: 1;
}

.toast-title {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.toast-message {
    color: var(--color-text-muted);
    font-size: 13px;
    line-height: 1.45;
}

.toast-close {
    display: none;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 24px;
    height: 24px;
    margin: -2px -6px 0 0;
    padding: 0;
    background: none;
    border: none;
    border-radius: var(--radius);
    color: var(--color-text-muted);
    cursor: pointer;
}

/* Nothing behind it without the script, so it is not offered. */
.has-js .toast-close {
    display: flex;
}

.toast-close:hover {
    background-color: #eef1f6;
    color: var(--color-text);
}

.toast-close svg {
    width: 14px;
    height: 14px;
}

/* The three seconds, running down along the foot of the message.
   Hidden without the script, where there is no countdown to show. */
.toast-timer {
    display: none;
    position: absolute;
    left: 0;
    bottom: 0;
    height: 2px;
    width: 100%;
    transform-origin: left;
}

.has-js .toast-timer {
    display: block;
    animation: toast-countdown 3s linear forwards;
}

/* Paused while the pointer is on the message: somebody reading it
   should not be timed out mid-sentence. The script holds its own
   timer back at the same moment. */
.has-js .toast:hover .toast-timer,
.has-js .toast:focus-within .toast-timer {
    animation-play-state: paused;
}

@keyframes toast-countdown {
    to {
        transform: scaleX(0);
    }
}

.toast--success {
    border-left: 3px solid var(--color-success);
}

.toast--success .toast-icon {
    background-color: var(--color-success-surface);
    color: var(--color-success);
}

.toast--success .toast-timer {
    background-color: var(--color-success);
}

.toast--error {
    border-left: 3px solid var(--color-danger);
}

.toast--error .toast-icon {
    background-color: var(--color-danger-surface);
    color: var(--color-danger);
}

.toast--error .toast-timer {
    background-color: var(--color-danger);
}

/* On a phone the corner is the whole width, under the header rather
   than over it. */
@media (max-width: 640px) {
    .has-js .toasts {
        left: var(--space-3);
        right: var(--space-3);
        width: auto;
    }
}

/* --- Section headings ---
   A run of cards under one label, optionally with a control
   opposite the heading. Used by the dashboard and the budget
   planner, so it belongs here rather than in either page's own
   stylesheet. */

.section {
    margin-bottom: var(--space-7);
}

/* A section heading with a control opposite it - used where a filter
   applies to one section rather than the whole page. */
.section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}

.section-head .section-title {
    margin-bottom: 0;
}

.section-title {
    margin-bottom: var(--space-3);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

/* --- Cards --- */

.card {
    margin-bottom: var(--space-5);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-large);
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
}

.card-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--color-border);
}

.card-title {
    font-size: 15px;
    font-weight: 600;
}

.card-description {
    margin-top: 2px;
    color: var(--color-text-muted);
    font-size: 13px;
}

.card-actions {
    flex: none;
}

.card-body {
    padding: var(--space-5);
}

/* For content that should meet the card edges, such as a table. */
.card-body--flush {
    padding: 0;
}

/* --- Statistic tiles --- */

/*
 * auto-fit + minmax gives a responsive grid with no breakpoints:
 * tiles wrap whenever they would fall below 210px wide.
 */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}

.stat {
    padding: var(--space-4) var(--space-5);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-large);
    /* Brand accent along the top edge. */
    border-top: 3px solid var(--color-brand-blue);
}

.stat-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

.stat-label {
    color: var(--color-text-muted);
    font-size: 13px;
    font-weight: 500;
}

/* Optional icon in the top-right of a tile. Used on counts, to tell
   them apart from the money figures at a glance. */
.stat-icon {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    background-color: #eef2fb;
    border-radius: var(--radius);
    color: var(--color-brand-navy);
}

.stat-icon svg {
    width: 16px;
    height: 16px;
    display: block;
}

.stat-value {
    margin-top: var(--space-1);
    font-size: 30px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.1;
}

/*
 * Money tiles run smaller than count tiles. "SAR 12,450,000.00" is
 * four times the characters of "84", so at the same size it dominates
 * the card and crowds the tile it sits in.
 */
.stat-grid--money .stat-value {
    font-size: 23px;
}

.stat-change {
    margin-top: var(--space-1);
    font-size: 12.5px;
}

/* Colour reinforces the direction; the text states it outright, so
   the meaning survives for colour-blind readers and in print. */
.stat-change--up {
    color: #15803d;
}

.stat-change--down {
    color: var(--color-danger);
}

/* Not down - down is money lost or owed. This is money spoken for:
   agreed and waiting to go out. */
.stat-change--warn {
    color: var(--color-warning);
}

.stat-change--flat {
    color: var(--color-text-muted);
}

/* --- Content grid --- */

/*
 * Two columns on wide screens, stacking on narrow ones. The minmax
 * floor stops the main column from being squeezed unreadably.
 */
.content-grid {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: var(--space-5);
    align-items: start;
}

/*
 * Modifier: both columns take the height of the taller one, and each
 * card's contents are centred in whatever space it ends up with.
 *
 * Opt-in rather than the default, because a short card stretched to
 * match a long one only looks right when its contents can sit in the
 * middle - true of a chart, not of a list that should start at the top.
 */
/*
 * Modifier: three of equal width rather than a wide column and a
 * narrow one.
 *
 * For three answers to one question - what happened to this asset -
 * where no one of them is the main event and the other two footnotes.
 */
.content-grid--thirds {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.content-grid--equal {
    align-items: stretch;
}

.content-grid--equal > .card {
    display: flex;
    flex-direction: column;
    /* The grid's own gap provides the spacing here. */
    margin-bottom: 0;
}

.content-grid--equal > .card > .card-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/* Note: the donut's own spacing inside an --equal grid is set in
   css/pages/dashboard.css, next to the donut itself. */

/*
 * The search over one asset's history.
 *
 * Ruled off from the table it narrows, so the box reads as belonging
 * to the list underneath rather than to the card heading above it.
 */
.movements-search {
    border-bottom: 1px solid var(--color-border);
}


/*
 * Horizontal bars on a shared scale.
 *
 * Moved here from the dashboard's own stylesheet once a second page
 * wanted it - a client's page compares what they were billed against
 * what has come in. Widths are set inline because they are data;
 * everything else lives here.
 */

.bar-chart {
    display: grid;
    gap: var(--space-3);
}

.bar-chart-row {
    display: grid;
    /* Label, bar, value. The bar takes whatever is left, so the
       labels and figures stay aligned down the column. */
    grid-template-columns: 130px 1fr 120px;
    align-items: center;
    gap: var(--space-3);
    font-size: 13px;
}

.bar-chart-label {
    color: var(--color-text-muted);
    font-weight: 500;
}

.bar-chart-track {
    height: 22px;
    background-color: #eef0f5;
    border-radius: 4px;
    overflow: hidden;
}

.bar-chart-fill {
    display: block;
    height: 100%;
    border-radius: 4px;
    transition: width 0.3s ease;
}

/*
 * One colour per measure: navy for sales, green for money received,
 * amber for what is still outstanding, red for what is owed out.
 */
.bar-chart-fill--sales {
    background-color: var(--color-brand-navy);
}

.bar-chart-fill--received {
    background-color: #15803d;
}

/* Red: money the division has billed for and not been paid. It is
   owed to us and the longer it sits the worse it is. */
.bar-chart-fill--balance {
    background-color: var(--color-danger);
}

/* Amber: money the division has agreed to pay out but has not paid
   yet. Committed rather than lost, so it is not shown as red. */
.bar-chart-fill--payables {
    background-color: var(--color-warning);
}

/* Grey: everything past the limit, added into one row rather than
   dropped. It is a real figure, but not one of the measures the
   colours above stand for. */
.bar-chart-fill--muted {
    background-color: #98a2b3;
}

.bar-chart-value {
    text-align: right;
}

/* The share of the whole, under the figure. Small and quiet: the
   amount is the answer, the percentage is the context. */
.bar-chart-share {
    display: block;
    color: var(--color-text-muted);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

/* Said in words rather than drawn as an empty frame, which reads as
   a chart that failed to load. */
.chart-empty {
    margin: 0;
    padding: var(--space-5);
    background-color: #f8f9fc;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius);
    color: var(--color-text-muted);
    font-size: 13px;
    text-align: center;
}

@media (max-width: 640px) {
    /* The label above the bar rather than beside it: 130px of label
       and 120px of figure leaves nothing for a bar on a phone. */
    .bar-chart-row {
        grid-template-columns: 1fr auto;
    }

    .bar-chart-label {
        grid-column: 1 / -1;
    }
}

@media (max-width: 560px) {
    /* Stack the label above the bar rather than squeezing three
       columns into a phone's width. */
    .bar-chart-row {
        grid-template-columns: 1fr auto;
    }

    .bar-chart-label {
        grid-column: 1 / -1;
    }
}

/* --- Somebody's own account --- */

/*
 * The photograph, and the initials it replaces.
 *
 * One class for both states so the circle is the same size and shape
 * either way - an avatar that changes size when a picture is added
 * makes the header jump.
 */
.avatar img,
.profile-photo img {
    width: 100%;
    height: 100%;
    border-radius: inherit;
    object-fit: cover;

    /* A face is rarely centred in the frame, and the top of a portrait
       is where the face is. */
    object-position: center top;
}

.profile-hero-body {
    display: flex;
    align-items: center;
    gap: var(--space-5);
    flex-wrap: wrap;
}

/* Large enough to be a picture of somebody rather than a thumbnail of
   one - this is the page where it is looked at. */
.profile-photo {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 96px;
    height: 96px;
    overflow: hidden;
    background-color: var(--color-brand-navy);
    border-radius: 50%;
    color: #ffffff;
    font-size: 30px;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.profile-hero-identity {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
}

.profile-hero-name {
    margin: 0;
    font-size: 20px;
    font-weight: 700;
    line-height: 1.2;
}

.profile-hero-meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    flex-wrap: wrap;
}

.profile-hero-login {
    color: var(--color-text-muted);
    font-size: 13px;
}

.profile-photo-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-1);
    flex-wrap: wrap;
}

/* The label is the button that opens the file picker, so it has to
   behave like one under the cursor. */
.profile-photo-actions label.button {
    cursor: pointer;
}

/*
 * With the script, choosing a file sends it and the Upload button is
 * redundant. Without it the button is the only way to send, so it
 * stays - see js/profile.js and the .has-js class the shell sets.
 */
.has-js [data-photo-submit] {
    display: none;
}

/* --- Tables --- */

/*
 * A wide table scrolls inside this wrapper rather than forcing the
 * whole page sideways. Marked focusable by browsers so it can be
 * scrolled with the keyboard.
 */
.table-wrap {
    overflow-x: auto;
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.table th,
.table td {
    padding: var(--space-3) var(--space-5);
    text-align: left;
    /* Keeps columns from collapsing when the table scrolls. */
    white-space: nowrap;
}

.table thead th {
    background-color: #f8f9fc;
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text-muted);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.table tbody tr + tr td {
    border-top: 1px solid var(--color-border);
}

.table tbody tr:hover {
    background-color: #fafbfd;
}

/*
   A register with a lot of columns.

   The ordinary table is built for six or seven columns and gives
   each one 24px of air on either side. That is right until a
   register grows to ten - at which point the padding alone is
   asking for the better part of five hundred pixels, and the
   table pushes itself off the side of the screen.

   Two changes, both only inside this modifier:

     * Half the side padding, which is where the width actually
       goes.

     * Words may wrap. The ordinary table forbids it so columns
       do not collapse while it scrolls sideways - but the whole
       point here is not to scroll sideways, and a name on two
       lines is better than a table nobody can see the end of.

   Figures, dates and the row of icons keep their single line:
   a date broken across two lines reads as two dates, and a
   column of money that does not line up is not a column of
   money.
*/
.table--compact th,
.table--compact td {
    padding: var(--space-3);
    white-space: normal;
}

.table--compact .tabular,
.table--compact .amount,
.table--compact .table-right {
    white-space: nowrap;
}

.table-strong {
    font-weight: 600;
}

/* --- Badges --- */

.badge {
    display: inline-block;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
}

.badge--success {
    background-color: #e7f6ec;
    color: #15803d;
}

.badge--progress {
    background-color: #e6f1fa;
    color: var(--color-blue-dark);
}

.badge--warning {
    background-color: #fdf3e2;
    color: #9a5b00;
}

.badge--neutral {
    background-color: #eef0f5;
    color: var(--color-text-muted);
}

.badge--danger {
    background-color: var(--color-danger-surface);
    color: var(--color-danger);
}

/* --- Filter bar ---
   A plain GET form above a table, so each filtered view keeps its own
   bookmarkable URL. */

.filter-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-4);

    /* One surface, so a row of controls reads as the thing that
       narrows the table below rather than as loose boxes floating on
       the page. */
    padding: var(--space-2);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.filter-label {
    color: var(--color-text-muted);
    font-size: 13px;
    font-weight: 600;
}

.filter-select {
    width: auto;
    min-width: 180px;
    height: 40px;
    padding-block: 0;
}

/* Separates the reject form from the approve button above it. */
.reject-form {
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

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

/* --- Description lists --- */

.detail-row {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) 0;
    font-size: 14px;
}

.detail-row + .detail-row {
    border-top: 1px solid var(--color-border);
}

.detail-row dt {
    color: var(--color-text-muted);
}

.detail-row dd {
    margin: 0;
    font-weight: 500;
    text-align: right;
    /* Long emails wrap instead of widening the card. */
    overflow-wrap: anywhere;
}

/* A second list under the first, in the same card. */
.detail-list--spaced {
    margin-top: var(--space-5);
}

/* And what that second list is, where the two answer different
   questions - a person's pay above, what the division is charged for
   them below. Without it the second list reads as more rows of the
   first. */
.detail-heading {
    margin-top: var(--space-5);
    margin-bottom: calc(var(--space-3) * -1);
    color: var(--color-text-muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* The summing line at the foot of a list of amounts. */
.detail-row--total {
    border-top: 2px solid var(--color-border);
    font-weight: 700;
}

.detail-row--total dt {
    color: var(--color-text);
    font-weight: 700;
}

.detail-row--total dd {
    font-size: 16px;
    font-weight: 700;
}

/* --- Amounts ---
   Money is always right-aligned with tabular figures, so columns of
   it line up on the decimal point and can be scanned down. */

.amount {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    white-space: nowrap;
}

.amount--in {
    color: #15803d;
}

.amount--out {
    color: var(--color-danger);
}

/* A figure that has gone below nothing.

   Its own class rather than --out, which means money leaving: a
   negative balance is not a payment, it is an account that has
   spent more than it ever held and has to be squared. Red
   wherever a balance is printed, so it cannot be read as one
   more small number in a column. */
.amount--negative {
    color: var(--color-danger);
}

.table-right {
    text-align: right;
}

/* Exception to the table's nowrap: descriptive columns may wrap. */
/* A cell whose contents wrap.

   Written as `.table .table-wrap-text` on purpose. `.table th,
   .table td` sets `white-space: nowrap` and is one element more
   specific than a bare class, so a plain `.table-wrap-text` rule lost
   to it and never wrapped anything - which is how a role holding
   twenty-two modules became one unbroken line and pushed the whole
   page sideways. */

.table .table-wrap-text {
    white-space: normal;
    min-width: 220px;
}

/* Several badges in one cell: a wrapping row rather than a line of
   inline blocks, so the gaps are even and the last one drops onto a
   new line instead of stretching the column. */
.badge-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-2);
}

/* --- Row form layout ---
   One field per row, label on the left and the control on the right,
   as in the reference design.

   Works with the existing field components untouched: each .field is
   turned into a two-column grid, the label takes column one and
   everything else in the field - input, hint, error, grouped controls
   - is pushed into column two. */

.form-rows .field {
    display: grid;
    grid-template-columns: var(--form-label-width) minmax(0, var(--form-control-width));
    column-gap: var(--space-4);
    align-items: start;
    margin-bottom: var(--space-3);
}

.form-rows .field-label {
    grid-column: 1;
    /* Lines the label up with the text inside the input beside it,
       rather than with the top of its border. */
    padding-top: 11px;
    margin-bottom: 0;
}

/*
 * Everything that is not the label goes in column two: the input
 * itself, and any hint or error beneath it.
 */
.form-rows .field > *:not(.field-label) {
    grid-column: 2;
}

.form-rows .field-hint,
.form-rows .field-error {
    margin-top: var(--space-1);
}

@media (max-width: 720px) {
    /* Not enough width for two columns; stack the label above. */
    .form-rows .field {
        grid-template-columns: 1fr;
    }

    .form-rows .field-label {
        padding-top: 0;
        margin-bottom: var(--space-1);
    }

    .form-rows .field > *:not(.field-label) {
        grid-column: 1;
    }
}

/* --- Tabs ---
   Markup from the client form; behaviour from public/js/app.js.
   Without JavaScript the panels render stacked, so the strip is only
   a navigation aid, never the sole way to reach a field. */

.tab-list {
    display: flex;
    gap: var(--space-1);
    margin-bottom: var(--space-5);
    border-bottom: 1px solid var(--color-border);
    /* Many tabs on a small screen scroll sideways in the strip. */
    overflow-x: auto;
}

.tab {
    padding: 10px var(--space-4);
    background: none;
    border: 0;
    /* Reserves the underline's space so activating a tab does not
       shift the row below by two pixels. */
    border-bottom: 2px solid transparent;
    color: var(--color-text-muted);
    font-size: 14px;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
}

.tab:hover {
    color: var(--color-text);
}

.tab[aria-selected="true"] {
    border-bottom-color: var(--color-brand-blue);
    color: var(--color-brand-navy);
    font-weight: 600;
}

/* Without JavaScript the strip is inert; hide it and let the panels
   stack under their own headings. */
html:not(.has-js) .tab-list {
    display: none;
}

html:not(.has-js) .tab-panel[hidden] {
    display: block;
}

.tab-panel-heading {
    margin-bottom: var(--space-4);
    font-size: 15px;
    font-weight: 600;
}

/* --- Grouped inputs on one row --- */

/* Salutation + first + last name. */
.name-group {
    display: grid;
    grid-template-columns: 130px 1fr 1fr;
    gap: var(--space-2);
}

/* Dialling code + number. */
.phone-group {
    display: grid;
    grid-template-columns: 82px 1fr;
    gap: var(--space-2);
}

/* ID type + number. */
.buyer-group {
    display: grid;
    grid-template-columns: 110px 1fr;
    gap: var(--space-2);
}

@media (max-width: 560px) {
    .name-group {
        grid-template-columns: 1fr;
    }
}

/* --- Search box ---
   A plain GET form above a table, so each search has its own URL. */

/* A row above a table: search on one side, actions on the other. */
.toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.search-box {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* Standing alone rather than in a toolbar, it needs its own spacing. */
.search-box:only-child {
    margin-bottom: var(--space-4);
}

/*
 * The Search button and Clear link are only needed when the form
 * cannot submit itself. With the script running, typing searches and
 * deleting the text clears it, so both are noise.
 */
.has-js .search-box-fallback {
    display: none;
}

/* --- List tabs ---
   Markup from <x-list-tabs />: links that split a table by state.
   Distinct from the in-form tabs above, which switch panels of one
   page; these fetch a different set of rows. */

.list-tabs {
    display: flex;
    gap: var(--space-1);
    margin-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}

.list-tab {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 10px var(--space-4);
    /* Reserves the underline's space so switching tabs does not
       shift the table by two pixels. */
    border-bottom: 2px solid transparent;
    color: var(--color-text-muted);
    font-size: 14px;
    font-weight: 500;
    text-decoration: none;
    white-space: nowrap;
}

.list-tab:hover {
    color: var(--color-text);
}

.list-tab.is-active {
    border-bottom-color: var(--color-brand-blue);
    color: var(--color-brand-navy);
    font-weight: 600;
}

/* How many rows the other list holds, without switching to it. */
.list-tab-count {
    padding: 1px 7px;
    background-color: #eef0f5;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.list-tab.is-active .list-tab-count {
    background-color: #dfe8fa;
    color: var(--color-brand-navy);
}

/* Controls that belong together at one end of a toolbar. */
.toolbar-group {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.export-buttons {
    display: flex;
    gap: var(--space-2);
}

/* --- Filter menu ---
   Markup from <x-filter-menu />: a <details> panel of links. Closing
   on an outside click comes from the shared `data-menu` handler in
   js/app.js. */

.filtermenu {
    position: relative;
}

.filtermenu-trigger {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 9px var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font-size: 13.5px;
    white-space: nowrap;
    cursor: pointer;
    /* Removes the default disclosure triangle in every engine. */
    list-style: none;
}

.filtermenu-trigger::-webkit-details-marker {
    display: none;
}

.filtermenu-trigger svg {
    flex: none;
    width: 15px;
    height: 15px;
    color: var(--color-text-muted);
}

.filtermenu-trigger:hover {
    border-color: var(--color-brand-blue);
}

/*
 * While a filter is applied the control is tinted, so a table
 * showing a subset is never mistaken for the whole set.
 */
.filtermenu-trigger.is-active {
    background-color: #eef2fb;
    border-color: var(--color-brand-blue);
    color: var(--color-brand-navy);
}

.filtermenu-trigger.is-active svg {
    color: var(--color-brand-blue);
}

.filtermenu-label {
    color: var(--color-text-muted);
}

.filtermenu-trigger.is-active .filtermenu-label {
    color: var(--color-brand-navy);
}

.filtermenu-value {
    font-weight: 600;
}

.filtermenu-chevron {
    transition: transform 0.15s ease;
}

.filtermenu[open] > .filtermenu-trigger {
    border-color: var(--color-brand-blue);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

.filtermenu[open] > .filtermenu-trigger .filtermenu-chevron {
    transform: rotate(180deg);
}

.filtermenu-panel {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 0;
    z-index: 40;
    min-width: 240px;
    padding: var(--space-2);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-large);
    box-shadow: var(--shadow-panel);
}

.filtermenu-option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 8px var(--space-2);
    border-radius: var(--radius);
    color: var(--color-text);
    font-size: 14px;
    text-decoration: none;
    white-space: nowrap;
}

.filtermenu-option:hover {
    background-color: #f4f6fa;
}

.filtermenu-option.is-active {
    background-color: #eef2fb;
    color: var(--color-brand-navy);
    font-weight: 600;
}

/* Fixed width whether or not a tick is inside it, so the labels
   line up down the panel. */
.filtermenu-tick {
    flex: none;
    width: 14px;
    height: 14px;
    color: var(--color-brand-blue);
}

.filtermenu-tick svg {
    width: 14px;
    height: 14px;
    display: block;
}

.filtermenu-option-label {
    flex: 1;
}

/* How many rows the option would give: choosing a filter that
   returns nothing is the usual frustration with a hidden count. */
.filtermenu-count {
    padding: 1px 7px;
    background-color: #eef0f5;
    border-radius: 999px;
    color: var(--color-text-muted);
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.filtermenu-count.is-empty {
    color: #b6bcc9;
}

.search-box-control {
    position: relative;
    flex: 1;
    /* Long enough to read a company name, capped so it does not run
       the whole width of a wide screen. */
    min-width: 240px;
    max-width: 460px;
}

.search-box-icon {
    position: absolute;
    top: 50%;
    left: var(--space-3);
    transform: translateY(-50%);
    width: 16px;
    height: 16px;
    color: var(--color-text-muted);
    /* Clicks pass through to the input behind it. */
    pointer-events: none;
}

.search-box-input {
    padding-left: 38px;
}

/* Chrome adds its own clear cross to type="search"; ours is the
   Clear link, so hide the duplicate. */
.search-box-input::-webkit-search-cancel-button {
    display: none;
}

/* --- Pagination ---
   Markup from resources/views/vendor/pagination/wms.blade.php. */

.pagination-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3);
}

.pagination-summary {
    color: var(--color-text-muted);
    font-size: 13px;
}

.pagination-summary strong {
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
}

.pagination {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin: 0;
    padding: 0;
    list-style: none;
}

.pagination-link {
    display: block;
    min-width: 34px;
    padding: 7px var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    color: var(--color-text);
    font-size: 13px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    text-align: center;
    text-decoration: none;
}

.pagination-link:hover {
    border-color: var(--color-brand-blue);
    color: var(--color-brand-navy);
}

.pagination-link.is-current {
    background-color: var(--color-brand-navy);
    border-color: var(--color-brand-navy);
    color: #ffffff;
    font-weight: 600;
}

/* Previous/Next at either end of the range. */
.pagination-link.is-disabled,
.pagination-link.is-disabled:hover {
    border-color: var(--color-border);
    color: #c2c7d2;
    cursor: not-allowed;
}

.pagination-gap {
    display: block;
    padding: 0 var(--space-1);
    color: var(--color-text-muted);
}

/* --- Small table helpers --- */

/* A cell with nothing to say. The dash is written in the template so
   it is read out as a dash rather than as an empty cell; this only
   keeps it from looking like data. */
.table-muted {
    color: var(--color-text-muted);
}

/* --- Clickable rows ---
   The pointer is the promise that the row does something; the row
   handler in public/js/app.js is what keeps it. */

.table-row-link {
    cursor: pointer;
}

.table tbody tr.table-row-link:hover {
    background-color: #f2f6fd;
}

/* The company name is a real link, but styled as plain text until
   hovered - a table of blue underlined rows is hard to read. */
.table-link {
    color: inherit;
    text-decoration: none;
}

.table-row-link:hover .table-link {
    color: var(--color-brand-navy);
    text-decoration: underline;
}

/* Row number column: narrow, and never wider than its digits need. */
.table-serial {
    width: 1%;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* A secondary line under the main value in a table cell. */
.table-subtext {
    display: block;
    color: var(--color-text-muted);
    font-size: 12.5px;
    font-weight: 400;
}

/*
 * The row being edited by a form elsewhere on the page.
 *
 * Tinted rather than outlined, so it still reads as one row of the
 * table and the eye can find it after scrolling back.
 */
.table tbody tr.is-selected {
    background: var(--color-page);
}

/* Digit-heavy text (phone numbers, references) aligns evenly. */
.tabular {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* --- Month picker ---
   Markup from <x-month-picker />. One compact button that opens a
   panel of years and a grid of months; every control in it is a
   submit button, so the whole thing works without JavaScript. */

.monthpicker {
    position: relative;
    display: inline-block;
}

.monthpicker-trigger {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 8px var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    /* Removes the default disclosure triangle in every engine. */
    list-style: none;
}

.monthpicker-trigger::-webkit-details-marker {
    display: none;
}

.monthpicker-trigger svg {
    width: 16px;
    height: 16px;
    color: var(--color-text-muted);
}

.monthpicker-trigger:hover {
    border-color: var(--color-brand-blue);
}

.monthpicker[open] > .monthpicker-trigger {
    border-color: var(--color-brand-blue);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

.monthpicker-chevron {
    transition: transform 0.15s ease;
}

.monthpicker[open] > .monthpicker-trigger .monthpicker-chevron {
    transform: rotate(180deg);
}

.monthpicker-panel {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 0;
    z-index: 40;
    /* Overridden below when the trigger sits on the right. */
    width: 268px;
    padding: var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-large);
    box-shadow: var(--shadow-panel);
}

/* Anchored to the trigger's right edge, so a picker on the right of
   a row opens inwards rather than off the side of the page. */
.monthpicker-panel--right {
    left: auto;
    right: 0;
}

/* Years sit in a row above the grid: three of them fit comfortably
   and it costs far less height than a stacked list. */
.monthpicker-years {
    display: flex;
    gap: var(--space-1);
    margin-bottom: var(--space-3);
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--color-border);
}

.monthpicker-year {
    flex: 1;
    padding: 6px 0;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--color-text-muted);
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

.monthpicker-year:hover:not(:disabled) {
    background-color: #f4f6fa;
    color: var(--color-text);
}

.monthpicker-year.is-selected {
    background-color: #eef2fb;
    border-color: var(--color-brand-blue);
    color: var(--color-brand-navy);
}

.monthpicker-months {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-1);
}

.monthpicker-month {
    padding: 9px 0;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
}

.monthpicker-month:hover:not(:disabled) {
    background-color: #f4f6fa;
}

/* The month being viewed. */
.monthpicker-month.is-selected {
    background-color: var(--color-brand-blue);
    border-color: var(--color-brand-blue);
    color: #ffffff;
    font-weight: 600;
}

/* The real current month, outlined so "today" stays findable while
   another month is selected. */
.monthpicker-month.is-current {
    border-color: var(--color-brand-blue);
    color: var(--color-brand-navy);
    font-weight: 600;
}

/* Nothing has happened yet in a future month. */
.monthpicker-month:disabled,
.monthpicker-year:disabled {
    color: #c2c7d2;
    cursor: not-allowed;
}

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

.empty-state {
    padding: var(--space-6) var(--space-4);
    text-align: center;
}

.empty-state-title {
    font-weight: 600;
}

.empty-state-text {
    margin-top: var(--space-1);
    color: var(--color-text-muted);
    font-size: 14px;
}

/* --- Links --- */

.link {
    color: var(--color-blue-dark);
    font-size: 14px;
    font-weight: 500;
    text-decoration: none;
}

.link:hover {
    text-decoration: underline;
}

/* --- Form layout --- */

/*
 * Two-column form grid that collapses to one column on narrow
 * screens, again without a breakpoint.
 */
.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 0 var(--space-4);
}

/* Makes a field span every column of the grid. */
.field--full {
    grid-column: 1 / -1;
}

/*
 * The buttons at the end of a table row - Edit, Delete, and whatever
 * else a register offers on one record.
 *
 * Here rather than in a page's own stylesheet because four registers
 * use it. It lived in admin.css and was called .admin-row-actions,
 * which is how the employee overview came to carry the class on a
 * page that loads no admin stylesheet: the buttons there had never
 * been laid out at all, and nothing said so.
 *
 * Ranged right, because the column is right-aligned and a row of
 * buttons that started at the left edge of a wide cell would sit
 * nowhere near the record it acts on.
 */
.row-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
}

/*
 * Fields that appear together, or not at all.
 *
 * A wrapper is needed so one line of script can show or hide the set
 * of them - the name and designation on the user form appear only for
 * a general user. `display: contents` keeps the wrapper out of the
 * layout: the fields inside sit in the form grid as though it were not
 * there, so a revealed pair lines up with the fields around it instead
 * of being crammed into one column.
 *
 * Hiding is left to the [hidden] attribute, which carries !important
 * and so wins over this. Without JavaScript nothing hides them and
 * both fields simply show - a form with two fields too many, which is
 * a great deal better than one with two fields missing.
 */
.field-pair {
    display: contents;
}

html:not(.has-js) .field-pair[hidden] {
    display: contents !important;
}

/* A line under a box saying what has been typed into it - the VAT on a
   PO value, and what it comes to. Sits between the box and the hint:
   the hint explains what the field is for and does not change, this
   answers what is in it and does. */
.field-note {
    margin: var(--space-1) 0 0;
    color: var(--color-text);
    font-size: 13px;
}

.field-hint {
    display: block;
    margin-top: var(--space-1);
    color: var(--color-text-muted);
    font-size: 12.5px;
}

/* A hint that is a caution rather than an aside - a setting that is
   accepted but will print badly. Louder than a hint, quieter than a
   validation error, because nothing has been rejected. */
.field-hint--warning {
    margin-bottom: var(--space-3);
    color: var(--color-warning);
    font-weight: 600;
}

/* Select and textarea share .field-input, so only the differences
   are declared here. */
select.field-input {
    /* Restores the dropdown arrow that appearance:none would remove;
       left as the platform default deliberately. */
    height: 42px;
}

textarea.field-input {
    min-height: 84px;
    resize: vertical;
}

.form-footer {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

/* Explanatory line above a form's fields. */
.form-lede {
    margin-bottom: var(--space-4);
    color: var(--color-text-muted);
    font-size: 14px;
}

/* Marks a field as not required, so the absence of a marker means
   required - the opposite convention leaves every label cluttered. */
/* Required, in the one colour a form uses for "you have to deal with
   this". Red on white at this size clears the contrast minimum, and
   the label still reads correctly without it - the input carries its
   own required attribute for anybody not looking at colour. */
/* Three to a row rather than two, for fields that are read together -
   the two months an asset schedule runs between and what the asset
   came to. Collapses to one per row on a phone, like the grid it is
   built on. */
.form-grid--thirds {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

@media (max-width: 900px) {
    .form-grid--thirds {
        grid-template-columns: 1fr;
    }
}

.field-required {
    margin-left: var(--space-1);
    color: var(--color-danger);
    font-size: 14px;
    font-weight: 700;
    line-height: 1;
}

.field-optional {
    margin-left: var(--space-1);
    color: var(--color-text-muted);
    font-size: 11px;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

/* --- Currency prefix inside an amount input --- */

.field-prefix {
    position: absolute;
    top: 1px;
    bottom: 1px;
    left: 1px;
    display: flex;
    align-items: center;
    padding: 0 var(--space-3);
    background-color: #f4f6fa;
    border-right: 1px solid var(--color-border);
    border-radius: var(--radius) 0 0 var(--radius);
    color: var(--color-text-muted);
    font-size: 13px;
    font-weight: 600;
    /* Clicks pass through to the input behind it. */
    pointer-events: none;
}

.field-input--with-prefix {
    padding-left: 58px;
}

.field-input--amount {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* --- Alerts: success --- */

.alert--success {
    background-color: #e7f6ec;
    border-color: #b9e3c6;
    border-left-color: #15803d;
    color: #14622f;
}

.alert--warning {
    background-color: #fdf3e2;
    border-color: #f2ddb6;
    border-left-color: #b45309;
    color: #8a5200;
}

.alert--info {
    background-color: #e6f1fa;
    border-color: #c3ddf2;
    border-left-color: var(--color-brand-blue);
    color: var(--color-blue-dark);
}

/* --- Pagination (Laravel's default markup) --- */

.pagination {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    align-items: center;
    list-style: none;
    padding: 0;
    margin: 0;
}

/* --- Multi-select ---
   Markup from <x-multi-select />: a <details> dropdown holding
   checkboxes. Closing on an outside click comes from the shared
   `data-menu` handler in js/app.js; js/forms.js keeps the collapsed
   summary in step with the boxes. */

.multiselect {
    position: relative;
}

.multiselect-trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: 11px var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    cursor: pointer;
    /* Removes the default disclosure triangle in every engine. */
    list-style: none;
}

.multiselect-trigger::-webkit-details-marker {
    display: none;
}

.multiselect-trigger:hover {
    border-color: #b9c1ce;
}

.multiselect[open] > .multiselect-trigger {
    border-color: var(--color-brand-blue);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

.multiselect-summary {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Nothing chosen yet: read as a placeholder, not as a value. */
.multiselect-summary.is-empty {
    color: #a3aab8;
}

/* What is chosen, under the control.

   The summary above truncates to one line; this wraps, so six
   projects read as six projects rather than as the first two and an
   ellipsis. Hidden by the [hidden] attribute when nothing is
   chosen - there is no empty row to explain. */

.multiselect-chosen {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-2);
    list-style: none;
}

.multiselect-chip {
    padding: 2px var(--space-2);
    background-color: #eef2fb;
    border: 1px solid #dce3f5;
    border-radius: var(--radius);
    color: var(--color-brand-navy);
    font-size: 12px;
    font-weight: 600;
}

.multiselect-chevron {
    flex: none;
    width: 16px;
    height: 16px;
    color: var(--color-text-muted);
    transition: transform 0.15s ease;
}

.multiselect[open] > .multiselect-trigger .multiselect-chevron {
    transform: rotate(180deg);
}

.multiselect-panel {
    position: absolute;
    top: calc(100% + var(--space-1));
    left: 0;
    right: 0;
    z-index: 40;
    padding: var(--space-2);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-panel);
}

.multiselect-option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 7px var(--space-2);
    border-radius: var(--radius);
    font-size: 14px;
    cursor: pointer;
    user-select: none;
}

/* "Select all", which only works with a script behind it - so it is
   hidden until js/app.js adds `has-js`. Set apart from the options it
   controls by a rule under it, because it is not one of them. */
/* ===================================================================
   Including / excluding VAT

   Two radio buttons drawn as one control. A switch would have been
   smaller and wrong: a switch has an off position, and "not including
   VAT" is a different answer rather than the absence of one.

   The radios themselves stay in the markup and keep working with no
   script - they are simply not drawn, and the label around each one
   carries the look.
   =================================================================== */

.vat-toggle {
    /*
       Flex rather than inline-flex: the control belongs to the field
       it is in, and a field in the form grid is as wide as its column.
       Inline-flex sized it to its own labels, which is how a long pair
       of them ended up sitting on the field beside it.
    */
    display: flex;
    max-width: 100%;
    padding: 3px;
    background-color: var(--color-surface-sunken, #f4f6fa);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.vat-toggle-option {
    position: relative;

    /* Half each, whatever the labels say. Equal halves are what makes
       it read as one control rather than two buttons. */
    flex: 1 1 0;
    min-width: 0;

    padding: 6px 10px;
    border-radius: calc(var(--radius) - 2px);
    font-size: 14px;
    line-height: 1.3;
    text-align: center;
    color: var(--color-text-muted);
    cursor: pointer;
    user-select: none;
}

.vat-toggle-option input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

/* The chosen half. :has() carries this in every browser this
   application supports; where it does not, both halves simply read
   the same and the radio still decides. */
.vat-toggle-option:has(input:checked) {
    background-color: #fff;
    border: 1px solid var(--color-border);
    color: var(--color-text);
    font-weight: 600;

    /* A pixel off each side, so the border it gains does not make the
       chosen half wider than the other one. */
    padding: 5px 9px;
}

/* Keyboard focus has to be visible on a control whose radio is not. */
.vat-toggle-option:has(input:focus-visible) {
    outline: 2px solid var(--color-brand-blue);
    outline-offset: 1px;
}

.multiselect-all {
    display: none;
}

.has-js /* ===================================================================
   Including / excluding VAT

   Two radio buttons drawn as one control. A switch would have been
   smaller and wrong: a switch has an off position, and "not including
   VAT" is a different answer rather than the absence of one.

   The radios themselves stay in the markup and keep working with no
   script - they are simply not drawn, and the label around each one
   carries the look.
   =================================================================== */

.vat-toggle {
    /*
       Flex rather than inline-flex: the control belongs to the field
       it is in, and a field in the form grid is as wide as its column.
       Inline-flex sized it to its own labels, which is how a long pair
       of them ended up sitting on the field beside it.
    */
    display: flex;
    max-width: 100%;
    padding: 3px;
    background-color: var(--color-surface-sunken, #f4f6fa);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.vat-toggle-option {
    position: relative;

    /* Half each, whatever the labels say. Equal halves are what makes
       it read as one control rather than two buttons. */
    flex: 1 1 0;
    min-width: 0;

    padding: 6px 10px;
    border-radius: calc(var(--radius) - 2px);
    font-size: 14px;
    line-height: 1.3;
    text-align: center;
    color: var(--color-text-muted);
    cursor: pointer;
    user-select: none;
}

.vat-toggle-option input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

/* The chosen half. :has() carries this in every browser this
   application supports; where it does not, both halves simply read
   the same and the radio still decides. */
.vat-toggle-option:has(input:checked) {
    background-color: #fff;
    border: 1px solid var(--color-border);
    color: var(--color-text);
    font-weight: 600;

    /* A pixel off each side, so the border it gains does not make the
       chosen half wider than the other one. */
    padding: 5px 9px;
}

/* Keyboard focus has to be visible on a control whose radio is not. */
.vat-toggle-option:has(input:focus-visible) {
    outline: 2px solid var(--color-brand-blue);
    outline-offset: 1px;
}

.multiselect-all {
    display: flex;
    margin-bottom: var(--space-1);
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--color-border);
    border-radius: 0;
    font-weight: 600;
}

.multiselect-option:hover {
    background-color: #f4f6fa;
}

.multiselect-option input {
    accent-color: var(--color-brand-blue);
    width: 15px;
    height: 15px;
}

/* --- Contact-person rows ---
   Shared by the client and vendor forms, and by any future record
   that keeps a list of people. Each person is a bordered block of
   two rows: seven controls on one line leaves each of them too
   narrow to read, so they wrap onto a second line. */

.contact-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
    padding: var(--space-3);
    background-color: #f8f9fc;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.contact-row-fields {
    flex: 1;
    min-width: 0;
    display: grid;
    /* Row 1: title, first, last, designation.
       Row 2: email (two columns), work phone, mobile. */
    grid-template-columns: 84px repeat(3, minmax(0, 1fr));
    gap: var(--space-2);
}

/* Just wide enough for "Mrs." plus the dropdown arrow. */
.contact-salutation {
    padding-left: var(--space-2);
    padding-right: var(--space-1);
}

.contact-email {
    grid-column: span 2;
}

.contact-row-remove {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    background: none;
    border: 0;
    border-radius: 50%;
    color: var(--color-text-muted);
    cursor: pointer;
}

.contact-row-remove svg {
    width: 14px;
    height: 14px;
}

.contact-row-remove:hover {
    background-color: var(--color-danger-surface);
    color: var(--color-danger);
}

.contact-add {
    margin-top: var(--space-2);
}

/* Rows are only ever created by the script, so without it the add
   button would do nothing - hide it rather than offer a dead control. */
html:not(.has-js) [data-repeat-add] {
    display: none;
}

/* --- Progress bar ---
   Markup from <x-progress-bar />. Used in the project table and on
   the project page. The width comes from an inline percentage
   because it is data; everything else lives here. */

.progress-bar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 130px;
}

.progress-bar-track {
    flex: 1;
    height: 7px;
    background-color: #eef0f5;
    border-radius: 999px;
    overflow: hidden;
}

.progress-bar-fill {
    height: 100%;
    border-radius: 999px;
    transition: width 0.3s ease;
}

/*
 * Colour repeats what the figure beside it already says - amber
 * before anything starts, blue while running, green once finished -
 * so it is a second reading of the same fact, never the only one.
 */
.progress-bar-fill--not-started {
    background-color: #e2e5ec;
}

.progress-bar-fill--running {
    background-image: linear-gradient(90deg, var(--color-brand-navy), var(--color-brand-blue));
}

.progress-bar-fill--complete {
    background-color: #15803d;
}

.progress-bar-value {
    min-width: 38px;
    color: var(--color-text-muted);
    font-size: 12.5px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* The version on the project page, beneath the headline figure. */
.progress-bar--large .progress-bar-track {
    height: 12px;
}

.progress-bar--large .progress-bar-value {
    display: none;
}

/* Keeps the progress column from being squeezed to nothing by the
   longer text columns beside it. */
.table-progress {
    width: 170px;
}

/* --- Project hero ---
   The headline card on a project page: the figure, a wide bar with
   today marked on it, and the money underneath. */

.project-hero .card-body {
    padding: var(--space-5) var(--space-6);
}

.project-hero-top {
    display: flex;
    align-items: flex-start;
    gap: var(--space-6);
    flex-wrap: wrap;
}

.project-hero-figure {
    flex: none;
}

.project-hero-label {
    color: var(--color-text-muted);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.project-hero-value {
    margin-top: var(--space-1);
    font-size: 46px;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.project-hero-unit {
    margin-left: 2px;
    font-size: 22px;
    font-weight: 600;
    color: var(--color-text-muted);
}

.project-hero-caption {
    margin-top: var(--space-1);
    color: var(--color-text-muted);
    font-size: 12.5px;
}

.project-hero-bar {
    flex: 1;
    min-width: 240px;
    /* Sits level with the big figure beside it. */
    padding-top: var(--space-5);
}

.project-hero-track {
    position: relative;
    height: 14px;
    background-color: #eef0f5;
    border-radius: 999px;
}

.project-hero-fill {
    height: 100%;
    border-radius: 999px;
    transition: width 0.4s ease;
}

.project-hero-fill--not-started {
    background-color: #dfe3ea;
}

.project-hero-fill--running {
    background-image: linear-gradient(90deg, var(--color-brand-navy), var(--color-brand-blue));
}

.project-hero-fill--complete {
    background-image: linear-gradient(90deg, #15803d, #34a853);
}

/*
 * Where today falls between the start and end dates. Progress on its
 * own cannot say whether a job is ahead or behind; this line is what
 * makes the bar answer that.
 */
.project-hero-today {
    position: absolute;
    top: -6px;
    bottom: -6px;
    width: 2px;
    background-color: var(--color-text);
    border-radius: 1px;
}

.project-hero-today-label {
    position: absolute;
    bottom: calc(100% + 4px);
    left: 50%;
    transform: translateX(-50%);
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* What was agreed, where a job has been given longer. Under the
   scale so it reads as a footnote to the dates above it rather than
   as another figure competing with them. */
.project-hero-extension {
    margin-top: var(--space-2);
    color: var(--color-warning);
    font-size: 12px;
    font-weight: 600;
}

.project-hero-scale {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    margin-top: var(--space-3);
    color: var(--color-text-muted);
    font-size: 12.5px;
}

.project-hero-scale .is-overdue {
    color: var(--color-danger);
    font-weight: 600;
}

.project-hero-status {
    flex: none;
}

.project-hero-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-6);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

.project-hero-fact-label {
    color: var(--color-text-muted);
    font-size: 12px;
    font-weight: 500;
}

/* What the figure above comes to once VAT is taken into account. Quiet
   beside it: the award is the number somebody came to this page for,
   and this is the reading of it. */
.project-hero-fact-note {
    margin: 2px 0 0;
    color: var(--color-text-muted);
    font-size: 12px;
}

.project-hero-fact-value {
    margin-top: 2px;
    font-size: 16px;
    font-weight: 700;
}

/* Four fields on one line: name, designation, email, contact. */
.project-contact-fields {
    flex: 1;
    min-width: 0;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(0, 1.3fr);
    gap: var(--space-2);
}

@media (max-width: 900px) {
    .project-contact-fields {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 560px) {
    .project-contact-fields {
        grid-template-columns: 1fr;
    }

    .project-hero-value {
        font-size: 38px;
    }
}

/* Percentage input with its unit shown inside the field. */
.progress-input {
    position: relative;
    max-width: 140px;
}

.progress-input-suffix {
    position: absolute;
    top: 50%;
    right: var(--space-3);
    transform: translateY(-50%);
    color: var(--color-text-muted);
    font-size: 13px;
    font-weight: 600;
    /* Clicks pass through to the input behind it. */
    pointer-events: none;
}

.progress-input .field-input {
    padding-right: 30px;
}

/* A form footer with no card around it. */
.form-footer--standalone {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

/* --- File uploads ---
   Markup from <x-file-field />, behaviour from the uploader module
   in public/js/app.js.

   The real <input type="file"> is still the control: it holds the
   file and submits it. It is only taken out of sight once `has-js`
   is set, because everything that replaces it - the drop zone, the
   chosen-file chip - needs the script. Without it the plain input
   is shown, which is not pretty but is never broken.

   It is moved out of sight by clipping, never by display:none, so
   it stays in the tab order and can still be operated from the
   keyboard. The label below it is what is seen. */

.uploader {
    position: relative;
}

.has-js .uploader-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    white-space: nowrap;
    border: 0;
    clip-path: inset(50%);
}

/* Without the script the plain input is the field, so it is given
   the ordinary input styling rather than being left bare. */
.uploader-input {
    display: block;
    width: 100%;
    padding: 10px var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

/* --- The drop zone ---
   Dashed, so it reads as somewhere to put something rather than as
   a filled control that already holds a value. */

.uploader-drop {
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-6) var(--space-5);
    background-color: #fbfcfe;
    border: 2px dashed var(--color-border);
    border-radius: var(--radius-large);
    text-align: center;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

.has-js .uploader-drop {
    display: flex;
}

.uploader-drop:hover {
    background-color: #f4f7fd;
    border-color: var(--color-brand-blue);
}

/* The input is out of sight, so its focus ring has to be drawn on
   the label - otherwise tabbing to this field shows nothing at all. */
.uploader-input:focus-visible + .uploader-drop {
    border-color: var(--color-brand-blue);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

/* While a file is over the zone. */
.uploader-drop.is-dragging {
    background-color: #eef2fb;
    border-color: var(--color-brand-blue);
    border-style: solid;
}

.uploader-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    background-color: #eef2fb;
    border-radius: 50%;
    color: var(--color-brand-blue);
}

.uploader-icon svg {
    width: 21px;
    height: 21px;
}

.uploader-drop.is-dragging .uploader-icon {
    background-color: var(--color-brand-blue);
    color: #ffffff;
}

.uploader-title {
    color: var(--color-text);
    font-size: 14px;
    font-weight: 600;
}

/* Looks like the link it behaves as - the whole zone opens the
   picker, but this is where the eye goes. */
.uploader-browse {
    color: var(--color-brand-blue);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.uploader-hint {
    color: var(--color-text-muted);
    font-size: 12.5px;
}

/* --- The chosen file ---
   Replaces the drop zone once there is a file, so the field shows
   its state rather than both at once. */

.uploader-chosen {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background-color: #f4fbf6;
    border: 1px solid #cbe9d4;
    border-radius: var(--radius-large);
}

.uploader-chosen[hidden] {
    display: none;
}

.uploader-chosen-icon {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    background-color: #e7f6ec;
    border-radius: 50%;
    color: #15803d;
}

.uploader-chosen-icon svg {
    width: 18px;
    height: 18px;
}

.uploader-chosen-body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    /* Pushes the remove button to the far edge. */
    margin-right: auto;
}

/* A long file name wraps rather than being cut off: which file was
   chosen is the whole point of this chip. */
.uploader-chosen-name {
    font-size: 14px;
    font-weight: 600;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

.uploader-chosen-size {
    color: var(--color-text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

/* --- What is already held ---
   Shown above the zone when editing, so it is clear a new file
   replaces it rather than adding to it. */

.uploader-current {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background-color: #f8f9fc;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.uploader-current-icon {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background-color: #eef2fb;
    border-radius: var(--radius);
    color: var(--color-brand-blue);
}

.uploader-current-icon svg {
    width: 17px;
    height: 17px;
}

.uploader-current-body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.uploader-current-label {
    color: var(--color-text-muted);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.uploader-current-name {
    font-size: 14px;
    font-weight: 600;
    overflow-wrap: anywhere;
}

/* --- Document rows ---
   Shared by the client and vendor forms. A name beside a file, so a
   record is not left holding "scan_0042.pdf" with nothing to say
   what it is. */

.document-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
    padding: var(--space-3);
    background-color: #f8f9fc;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.document-row-fields {
    flex: 1;
    min-width: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    gap: var(--space-2);
}

/*
 * File inputs are drawn by the browser and cannot be restyled
 * wholesale. Only the button part is themed, which every current
 * engine supports; the rest is left alone rather than faked with a
 * hidden input, which breaks keyboard use.
 */
.document-file {
    padding: 7px var(--space-2);
    font-size: 13px;
}

.document-file::file-selector-button {
    margin-right: var(--space-3);
    padding: 5px var(--space-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    color: var(--color-text);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

.document-file::file-selector-button:hover {
    border-color: var(--color-brand-blue);
    color: var(--color-brand-navy);
}

.documents-note {
    margin-top: var(--space-3);
}

@media (max-width: 720px) {
    .document-row-fields {
        grid-template-columns: 1fr;
    }
}

/* --- Record detail pages ---
   Shared by the client and vendor detail screens. */

/* An address reads as a block, not as label/value pairs, so it is
   set out the way it would be written on an envelope. */
.address-block {
    font-style: normal;
    line-height: 1.7;
}

.person-list {
    display: grid;
    gap: var(--space-4);
}

.person {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
}

.person-body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.person-name {
    font-weight: 600;
}

.person-role,
.person-phone {
    color: var(--color-text-muted);
    font-size: 13px;
}

/* Attachments listed on a detail page. */
.document-list {
    display: grid;
    gap: var(--space-2);
}

.document-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background-color: #f8f9fc;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
}

.document-icon {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background-color: #eef2fb;
    border-radius: var(--radius);
    color: var(--color-brand-navy);
}

.document-icon svg {
    width: 16px;
    height: 16px;
    display: block;
}

.document-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.document-name {
    font-size: 14px;
    font-weight: 600;
}

/* The filename confirms what would download; the name above is what
   people actually scan for, so this stays secondary. */
.document-meta {
    color: var(--color-text-muted);
    font-size: 12.5px;
    overflow-wrap: anywhere;
}

/* A list of short labels, such as the services a vendor provides. */
.tag-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    justify-content: flex-end;
}

/* -------------------------------------------------------------
   7. Accessibility helpers
   ------------------------------------------------------------- */

/* Visually hidden, but still announced by screen readers. Use for
   labels and status text that sighted users get from context. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Respect the operating system's "reduce motion" setting. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* -------------------------------------------------------------
   8. Shared responsive rules

   Only for components defined in this file. The shell's own
   breakpoints live in css/layout.css, beside the shell.
   ------------------------------------------------------------- */

@media (max-width: 1023px) {
    .content-grid {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 720px) {
    /* Four columns of contact fields will not fit a phone: two, then
       the wider controls span both. */
    .contact-row-fields {
        grid-template-columns: 84px minmax(0, 1fr);
    }

    .contact-email,
    .contact-row-fields .phone-group {
        grid-column: span 2;
    }
}

@media (max-width: 640px) {
    .card-body,
    .card-header {
        padding-left: var(--space-4);
        padding-right: var(--space-4);
    }

    .table th,
    .table td {
        padding-left: var(--space-4);
        padding-right: var(--space-4);
    }
}

/* --- Date range filter ---
   A from/to pair sitting opposite a section heading. Used by the
   dashboard money section; written here rather than in that page's
   stylesheet because the component is shared. */

.daterange {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.daterange-field {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.daterange-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-muted);
}

/* Sized to hold a date and its picker button and no more, so two of
   them and the buttons still sit on one line beside a heading. */
.daterange-input {
    width: 9.5rem;
    padding: 6px var(--space-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
    color: var(--color-text);
    font: inherit;
    font-size: 13px;
}

.daterange-input:focus {
    outline: none;
    border-color: var(--color-brand-blue);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

@media (max-width: 640px) {
    /* Under a heading rather than beside it, once the row runs out
       of width. */
    .daterange {
        width: 100%;
    }

    .daterange-field {
        flex: 1 1 auto;
    }

    .daterange-input {
        width: 100%;
    }
}

/* ===============================================================
   Overtime, on an employee's page

   Three cards side by side: what an hour is worth, what the month
   comes to, and the form that adds to it. Wrapping to one column
   on a narrow screen rather than shrinking, because a date field
   and an hours field squeezed into a third of a phone are two
   fields nobody can hit.
   =============================================================== */

.overtime-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--space-4);
    align-items: start;
}

/* The sum spelled out under the rate. Quieter than the figures it
   explains, and given room to breathe from the list above it. */
.overtime-working {
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px dashed var(--color-border);
    line-height: 1.6;
}

/* "12 hours" beside the Overtime label: part of the label, not a
   figure of its own, so it reads at the weight of one. */
.detail-note {
    margin-left: var(--space-2);
    color: var(--color-text-muted);
    font-size: 12px;
    font-weight: 400;
}

/* A pay change, up or down.

   Colour carries the direction, but never alone: the figure is
   already signed, so somebody who cannot tell the two apart reads
   the same answer from the + or the -. */

.pay-change {
    font-weight: 600;
}

.pay-change--up {
    color: #0f7b4f;
}

.pay-change--down {
    color: #b42318;
}

/* ===============================================================
   Record a pay change

   A disclosure on the edit form, shut until it is opened.
   Changing somebody's pay is not part of correcting a phone
   number, so it does not sit open inviting to be filled in.
   =============================================================== */

.pay-disclosure {
    margin-top: var(--space-5);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-large);
}

.pay-disclosure-summary {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    cursor: pointer;
    list-style: none;
}

/* The browser's own marker, replaced by the chevron below. */
.pay-disclosure-summary::-webkit-details-marker {
    display: none;
}

.pay-disclosure-title {
    font-size: 15px;
    font-weight: 700;
}

.pay-disclosure-note {
    flex: 1;
    color: var(--color-text-muted);
    font-size: 13px;
}

.pay-disclosure-chevron {
    flex: none;
    width: 18px;
    height: 18px;
    color: var(--color-text-muted);
    transition: transform 0.15s ease;
}

.pay-disclosure[open] .pay-disclosure-chevron {
    transform: rotate(180deg);
}

.pay-disclosure[open] .pay-disclosure-summary {
    border-bottom: 1px solid var(--color-border);
}

.pay-disclosure-body {
    display: grid;
    gap: var(--space-4);
    padding: var(--space-5);
}

/* The note wraps under the title on a narrow screen rather than
   squeezing both onto one line. */
@media (max-width: 640px) {
    .pay-disclosure-summary {
        flex-wrap: wrap;
    }

    .pay-disclosure-note {
        flex-basis: 100%;
    }
}

/* ===============================================================
   A temporary password, just issued

   Loud on purpose. It is shown once, has to be copied exactly, and
   is gone on the next page load - so it is not something to be
   skimmed past on the way to the table.
   =============================================================== */

.temp-password {
    border-color: #f0c36d;
    background-color: #fffaf0;
}

.temp-password-label {
    color: #8a5a00;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* The password itself: monospace, spaced out, big enough to read
   off a screen from arm's length while somebody types it. */
.temp-password-value {
    margin-top: var(--space-2);
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 26px;
    font-weight: 700;
    letter-spacing: 0.08em;
    user-select: all;
}

.temp-password-note {
    margin-top: var(--space-3);
    color: var(--color-text-muted);
    font-size: 13px;
    line-height: 1.6;
}

/* "Sign out instead", under the create-password form. */
.login-signout {
    margin-top: var(--space-4);
    text-align: center;
}

/* Icon actions in a table row.

   Right-aligned and close together, so the column stays narrow and
   the figures beside it keep the reader's attention. The form that
   wraps one of them would otherwise sit on a line of its own. */

.row-icons {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-1);
}

.row-icons form {
    display: inline-flex;
}

/* The expense register's filter bar.

   Five controls that narrow one list, laid out as a grid so they
   wrap onto as many rows as the screen needs rather than shrinking
   to nothing. */

.expense-filter-grid {
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

.expense-filter-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

/* The downloads sit at the far end, away from Apply: they are a
   different kind of action and should not be hit by mistake. */
.expense-filter-actions .export-buttons {
    margin-left: auto;
}

/* A cancelled row still shows its figures - the money did move, and
   moved back - so it is dimmed rather than struck through, which
   would make the amount hard to read. */
.table tbody tr.is-cancelled td {
    opacity: 0.55;
}

/* ===============================================================
   The expense register's filter bar

   One row rather than a card: five controls that narrow a list
   should not fill a third of the screen before a single row of it
   is shown. Each control carries its own label - a date box reads
   "From", a project list reads "Every project" - so nothing has to
   be printed twice above them.
   =============================================================== */

/*
   The ready-made periods a report offers beside its two date
   boxes: this quarter, last quarter, the year.

   Chips rather than a dropdown, because they are shortcuts to the
   boxes above rather than a separate answer - and the one in
   force is shown as chosen, so the page always says which period
   is on screen.
*/
.filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: calc(var(--space-4) * -1 + var(--space-1)) 0 var(--space-4);
}

.filter-chip {
    padding: 4px 10px;
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 999px;
    color: var(--color-text-muted);
    font-size: 12.5px;
    text-decoration: none;
}

.filter-chip:hover {
    border-color: var(--color-brand-navy);
    color: var(--color-brand-navy);
}

.filter-chip--on {
    background-color: var(--color-brand-navy);
    border-color: var(--color-brand-navy);
    color: #fff;
}

.filter-bar-fields {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    flex: 1;
    min-width: 0;
}

/*
   A field in a form is width: 100% - one to a line is the whole idea.
   In a row of filters that makes every control ask for the entire
   line, which is what left one dropdown squeezed to nothing and the
   next one alone across the page. Here each takes what it needs.
*/
.filter-bar .field-input {
    width: auto;
}

.filter-bar select.field-input {
    flex: 0 1 200px;
    min-width: 168px;
}


/*
   Two modifiers for when the default width is the wrong one.

   A filter dropdown is 200px by default, which suits a list of
   people's names. It is the wrong width in both directions: a
   project called "Supply of scaffolding and access equipment" is cut
   off in it, and "Paid" leaves two thirds of it empty - and every
   dropdown asking for the same width is what pushes the row onto a
   second line before it needs to be there.

   Widths rather than content-fitting, because the control has to be
   the same width whatever happens to be selected in it. A dropdown
   that grows when a long project is chosen moves every control after
   it along the row.
*/

/* For a long list: project names, budget lines. */
.filter-bar select.filter-select--wide {
    flex: 1 1 280px;
    min-width: 240px;
}

/* For two or three short words: paid or unpaid, and who settles it. */
.filter-bar select.filter-select--narrow {
    flex: 0 1 164px;
    min-width: 148px;
}

/* Once the search box is in front of a dropdown, that wrapper is the
   control standing in the row - so it takes the widths above, and
   the same default as a plain one where it was given no modifier. */
.filter-bar .selectsearch {
    flex: 0 1 200px;
    min-width: 168px;
}

.filter-bar .selectsearch.filter-select--wide {
    flex: 1 1 280px;
    min-width: 240px;
}

.filter-bar .selectsearch.filter-select--narrow {
    flex: 0 1 164px;
    min-width: 148px;
}

/* The dates stay together and keep their own width - a date box that
   grows is a date box with an inch of space after the year. */
.filter-dates {
    flex: 0 0 auto;
}

/* Apply, Clear, and the downloads: their own size, never stretched. */
.filter-bar .button {
    flex: 0 0 auto;
}

/* The search takes what is left, so it is the field that grows rather
   than the dropdowns. */
.filter-search {
    flex: 1 1 240px;
    min-width: 200px;
}

/* Two dates read as one range, so they sit together with the dash
   between them rather than as two unrelated boxes. */
.filter-dates {
    display: flex;
    align-items: center;
    gap: var(--space-1);
}

.filter-dates .field-input {
    width: auto;
}

.filter-dash {
    color: var(--color-text-muted);
}

/* How many rows the narrowing left, beside the controls that
   did the narrowing - the answer to "did that filter work?" is
   the number, and it belongs where the filters are. */
.filter-count {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-small);
    white-space: nowrap;
}

/* The downloads at the far end, away from Apply: a different kind
   of action, and not one to hit by mistake. */
.filter-bar .export-buttons {
    margin-left: auto;
}

/* On a phone every control takes the full width - a row of five
   squeezed into 360px is five controls nobody can hit. */
@media (max-width: 720px) {
    .filter-bar-fields > * {
        flex: 1 1 100%;
    }

    /* Including the ones given a size of their own above: a row of
       five controls squeezed into 360px is five controls nobody can
       hit. */
    .filter-bar .field-input,
    .filter-bar select.field-input,
    .filter-dates {
        width: 100%;
    }

    .filter-dates > .field-input {
        flex: 1;
        width: auto;
    }

    /* The downloads under the fields rather than beside them. */
    .filter-bar .export-buttons {
        width: 100%;
        margin-left: 0;
    }
}

/* -------------------------------------------------------------
   SORTABLE COLUMN HEADING
   -------------------------------------------------------------
   A heading that is also a link, with a small arrow saying which
   way the column is being read. It has to look like the headings
   beside it rather than like a link, so it takes its colour and
   its weight from the cell and shows what it does on hover.
   ------------------------------------------------------------- */

.table-sort {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: inherit;
    font: inherit;
    text-decoration: none;
    white-space: nowrap;
}

.table-sort:hover .table-sort-arrow,
.table-sort:focus-visible .table-sort-arrow {
    color: var(--color-brand-blue);
}

/* Small, and a shade lighter than the word: it is a state, not a
   second heading. */
.table-sort-arrow {
    color: var(--color-text-muted);
    font-size: 0.65em;
    line-height: 1;
}

/* -------------------------------------------------------------
   A SETTLED FIELD
   -------------------------------------------------------------
   Where a value was decided before the form opened and is shown
   rather than asked for - the job an expense is being recorded
   against, chosen on the way in.

   Set like a value and not like a control: no border and no box,
   because a box invites a click that does nothing.
   ------------------------------------------------------------- */

.field-static {
    margin: 0;
    padding: var(--space-2) 0;
    line-height: 1.4;
}