/*
 * UnitOps overrides on top of the Xintra template.
 *
 * Keep this file small and additive - styles.css is vendor code and gets
 * replaced wholesale when the template is updated.
 */

/* ---------------------------------------------------------------------------
 * Poppins, served from here rather than from Google
 *
 * styles.css line 2 used to be:
 *
 *     @import url("https://fonts.googleapis.com/css2?family=Poppins:...");
 *
 * which made every page of UnitOps - the sales page, and every screen behind
 * the login - fetch a stylesheet and then five font files from Google, on every
 * visit by every user. That is a request to a third party in the United States
 * carrying the visitor IP address and the address of the page they were on,
 * made before anybody has been asked anything, on pages whose URLs name
 * clients, staff and invoices.
 *
 * It was found by reading the network log while checking that the analytics tag
 * does not load before consent. The tag did not. This did, and had been doing so
 * since the first commit.
 *
 * Nothing was gained by it: the font is 68KB of woff2 that now ships with the
 * application like every other asset, and self-hosting removes a third-party
 * dependency from the critical path of every page load as well as from the
 * privacy notice. It is also what CLAUDE.md asks for - "no CDN link" - which
 * the vendor stylesheet was quietly breaking.
 *
 * The `@import` is deleted from styles.css and styles.min.css. **Those are
 * vendor files and a template update will put it back**, so if Poppins ever
 * starts arriving from fonts.googleapis.com again, that is what happened.
 *
 * Latin and latin-extended only. The Devanagari subset Google also serves is
 * dropped because nothing in this application is written in it; Poppins has no
 * Cyrillic at all, so Russian already falls back to a system font and did before
 * this change too.
 *
 * Regenerating these: fetch the css2 URL above with a browser user-agent, keep
 * the latin and latin-ext faces, and download each woff2 into
 * public/assets/fonts/poppins/.
 * ------------------------------------------------------------------------- */

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 300;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-300-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 300;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-300-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-400-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-400-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-500-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-500-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-600-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-600-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-700-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Poppins";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/assets/fonts/poppins/poppins-700-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ---------------------------------------------------------------------------
 * Brand logo
 *
 * The supplied UnitOps-Logo.png is a 1024x1024 canvas that is ~90% transparent
 * padding around a 662x159 landscape wordmark, so it renders tiny and
 * off-centre if used directly. Two trimmed derivatives are used instead:
 *
 *   unitops-logo-wide.png   full wordmark, for expanded//horizontal layouts
 *   unitops-logo-mark.png   icon only, for the collapsed sidebar
 *
 * Both are sized by height with width following, so the aspect ratio holds.
 * ------------------------------------------------------------------------- */

/*
 * Size only. Never set `display` here: the template's CSS uses display to pick
 * exactly one of the six variants for the current theme, and overriding it
 * would show several logos at once.
 */
.header-logo img {
    width: auto;
    max-width: 100%;
    object-fit: contain;
}

/* Expanded sidebar and the mobile/horizontal header bar. */
.header-logo img.desktop-logo,
.header-logo img.desktop-dark,
.header-logo img.desktop-white {
    height: 32px;
}

/* Collapsed sidebar - only the mark shows. */
.header-logo img.toggle-logo,
.header-logo img.toggle-dark,
.header-logo img.toggle-white {
    height: 30px;
}

.main-sidebar-header {
    display: flex;
    align-items: center;
}

/* The installer runs outside the template, so it carries its own styles. */
.install-logo {
    display: block;
    height: 52px;
    width: auto;
    margin: 0 auto 0.85rem;
}

/* ---------------------------------------------------------------------------
 * Contextual help
 *
 * The "?" beside a page title. It has to be findable without competing with
 * the page's own controls, so it sits at the muted end of the palette until
 * hovered and only then takes the brand colour.
 *
 * Behaviour lives in public/assets/js/help.js; the icon is a plain link, so
 * everything here is decoration and none of it is load-bearing.
 * ------------------------------------------------------------------------- */

.help-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.4rem;
    height: 1.4rem;
    margin-inline-start: 0.35rem;
    border-radius: 50%;
    vertical-align: middle;
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1;
    text-decoration: none;
    transition: color 0.2s ease, background-color 0.2s ease;
}

.help-link:hover,
.help-link:focus-visible {
    color: var(--primary-color);
    background-color: rgba(var(--primary-rgb), 0.1);
}

.help-link:focus-visible {
    outline: 2px solid rgba(var(--primary-rgb), 0.5);
    outline-offset: 1px;
}

/* The page title is often the flex item; keep the icon on its baseline. */
.page-title .help-link {
    font-size: 0.85em;
}

/* --- The help modal ---------------------------------------------------- */

.help-modal .modal-body {
    max-height: 70vh;
}

/* --- Article prose ------------------------------------------------------
 *
 * Help bodies are hand-written HTML fragments, so the spacing is set once here
 * rather than with utility classes repeated across thirty files.
 * ---------------------------------------------------------------------- */

.help-article {
    font-size: 0.85rem;
    line-height: 1.7;
}

.help-article h3 {
    font-size: 0.95rem;
    font-weight: 600;
    margin-block: 1.5rem 0.6rem;
}

.help-article h3:first-child {
    margin-block-start: 0;
}

.help-article h4 {
    font-size: 0.85rem;
    font-weight: 600;
    margin-block: 1.1rem 0.4rem;
}

.help-article p,
.help-article ul,
.help-article ol,
.help-article dl {
    margin-block-end: 0.85rem;
}

.help-article ul,
.help-article ol {
    padding-inline-start: 1.2rem;
}

.help-article li {
    margin-block-end: 0.3rem;
}

.help-article dt {
    font-weight: 600;
}

.help-article dd {
    margin-inline-start: 0;
    margin-block-end: 0.5rem;
    color: var(--text-muted);
}

.help-article code,
.help-article kbd {
    padding: 0.1rem 0.35rem;
    border-radius: 0.25rem;
    background-color: rgba(var(--primary-rgb), 0.08);
    color: var(--primary-color);
    font-size: 0.9em;
}

.help-article table {
    width: 100%;
    margin-block-end: 0.85rem;
    border-collapse: collapse;
}

.help-article th,
.help-article td {
    padding: 0.4rem 0.6rem;
    border: 1px solid var(--default-border);
    text-align: start;
    vertical-align: top;
}

.help-article th {
    font-weight: 600;
    background-color: var(--default-background);
}

/*
 * Callouts. `note` explains, `tip` shortcuts, `warn` is for the handful of
 * actions that cannot be undone - keep that one rare or it stops registering.
 */
.help-callout {
    padding: 0.65rem 0.85rem;
    margin-block-end: 0.85rem;
    border-inline-start: 3px solid var(--primary-color);
    border-radius: 0.3rem;
    background-color: rgba(var(--primary-rgb), 0.06);
}

.help-callout > :last-child {
    margin-block-end: 0;
}

.help-callout-tip {
    border-inline-start-color: rgb(var(--success-rgb));
    background-color: rgba(var(--success-rgb), 0.08);
}

.help-callout-warn {
    border-inline-start-color: rgb(var(--warning-rgb));
    background-color: rgba(var(--warning-rgb), 0.08);
}

/* --- /help index ------------------------------------------------------- */

.help-hero-icon {
    font-size: 5rem;
    color: rgba(var(--primary-rgb), 0.15);
}

.help-topic-card {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    padding: 0.75rem;
    margin-block-end: 0.5rem;
    border: 1px solid var(--default-border);
    border-radius: 0.4rem;
    color: inherit;
    text-decoration: none;
    height: calc(100% - 0.5rem);
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.help-topic-card:hover {
    border-color: var(--primary-color);
    box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.06);
}

.help-topic-card-text {
    min-width: 0;
}

/* --- /help/{topic} contents rail --------------------------------------- */

.help-contents {
    position: sticky;
    top: 5rem;
    max-height: calc(100vh - 7rem);
    overflow-y: auto;
}

.help-contents-section {
    padding: 0.5rem 0.75rem;
}

.help-contents-heading {
    display: block;
    margin-block-end: 0.25rem;
    color: var(--text-muted);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.help-contents-link {
    display: block;
    padding: 0.25rem 0.5rem;
    border-radius: 0.3rem;
    color: var(--default-text-color);
    font-size: 0.8rem;
    text-decoration: none;
}

.help-contents-link:hover {
    background-color: var(--default-background);
    color: var(--primary-color);
}

.help-contents-link.active {
    background-color: rgba(var(--primary-rgb), 0.1);
    color: var(--primary-color);
    font-weight: 600;
}

/* The contents rail is a sidebar on a desktop and a nuisance on a phone: it
 * sticks a second scrolling column above the prose you came to read. Below the
 * md tier it goes back to being an ordinary block at the top of the page. */
@media (max-width: 991.98px) {
    .help-contents {
        position: static;
        max-height: none;
        overflow-y: visible;
    }
}

/* ---------------------------------------------------------------------------
 * Mobile
 *
 * The rules in CLAUDE.md ("Mobile") describe what these do and why. Everything
 * here is a correction to the Xintra theme, which is built desktop-first and
 * makes three assumptions that do not hold on a phone:
 *
 *   - that a 13px form control is readable          (iOS zooms; see below)
 *   - that a 28px icon button is tappable           (it is not)
 *   - that a table that overflows will be scrolled  (only if you know it can be)
 *
 * Scoped to `.main-content` wherever a rule could otherwise reach the header or
 * sidebar, which the theme already handles and which break if you resize their
 * controls.
 * ------------------------------------------------------------------------- */

/* --- Form controls: 16px on small screens --------------------------------
 *
 * iOS Safari zooms the viewport when a focused input has a font-size under
 * 16px, and does not zoom back out when the field is blurred. The user is left
 * at ~1.3x with the layout overflowing and no obvious way back, which reads as
 * the page being broken rather than as a browser behaviour.
 *
 * The theme sets 0.8125rem (13px) on every control, so this affects every form
 * in the application. 1rem is the whole of the fix - Safari checks the computed
 * size against 16px exactly.
 * ---------------------------------------------------------------------- */

@media (max-width: 767.98px) {

    .form-control,
    .form-select,
    .form-control-sm,
    .form-select-sm,
    .input-group-text,
    .choices__input,
    .choices__list--single .choices__item {
        font-size: 1rem;
    }

    /* Labels stay at their smaller size - they are not focusable, so they do
     * not trigger the zoom, and lifting them too would cost a lot of height. */
    .form-label {
        font-size: 0.8rem;
        margin-block-end: 0.3rem;
    }
}

/* --- Tap targets ---------------------------------------------------------
 *
 * 44px is the figure Apple publishes and roughly what WCAG 2.5.5 asks for.
 * The theme's `.btn-icon.btn-sm` - the view/edit/delete controls on every list
 * row - is 28px with an 8px gap, so the three of them together occupy less than
 * two thumbs' width and one of them deletes the record.
 *
 * Keyed on `pointer: coarse` rather than a width, because a touchscreen laptop
 * at 1280px has exactly the same problem and a mouse at 380px does not.
 * ---------------------------------------------------------------------- */

@media (pointer: coarse) {

    /*
     * `.authentication` is the signed-out pages - sign in, reset a password,
     * accept an invitation, choose a company. They are not inside
     * `.main-content` because they carry no app chrome, and leaving them out
     * would mean the first form anybody touches is the one form with 28px
     * targets on it.
     *
     * `.landing` is the sales page, added for the same reason and found the
     * same way: it is a standalone template using neither of the other two, so
     * its signup form had 34px buttons while every form behind the login had
     * 44px ones. It is now literally the first thing anybody touches, which is
     * the case the paragraph above was written about.
     *
     * `.legal` is /terms, /privacy and /cookies, added for the same reason
     * again. Those three have to render for somebody with no account, so they
     * cannot use the app layout - and the "Got it" on the cookie strip and the
     * document switcher along the top are the controls a phone user meets
     * first.
     *
     * Any future standalone page needs adding here too. The four names are a
     * list of "places that are not `.main-content`" rather than a pattern, and
     * nothing fails loudly when one is missed - it just quietly has small
     * targets. app/tests/mobile_test.php checks the list here against the one
     * it knows about, which is the nearest thing to a loud failure available.
     */
    :is(.main-content, .authentication, .landing, .legal) .btn {
        min-height: 2.75rem;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    :is(.main-content, .authentication, .landing, .legal) .btn-icon,
    :is(.main-content, .authentication, .landing, .legal) .btn-icon.btn-sm {
        width: 2.75rem;
        height: 2.75rem;
        min-height: 2.75rem;
        font-size: 1rem;
        padding: 0;
    }

    /* Row actions sit closer together than anything else in the app and
     * include the destructive one. Widen the gap so edit and delete are not a
     * mis-tap apart - see CLAUDE.md, which asks for separation rather than
     * relying on this alone. */
    .main-content td .hstack {
        gap: 0.75rem !important;
    }

    :is(.main-content, .authentication, .landing, .legal) .form-control,
    :is(.main-content, .authentication, .landing, .legal) .form-select {
        min-height: 2.75rem;
    }

    /* Checkboxes and radios are ~13px of target inside a much larger label.
     * The label is already clickable; this makes the box itself findable. */
    :is(.main-content, .authentication, .landing, .legal) .form-check-input {
        width: 1.25rem;
        height: 1.25rem;
        margin-top: 0.125rem;
    }

    :is(.main-content, .authentication, .landing, .legal) .form-check {
        min-height: 1.75rem;
        padding-inline-start: 1.9rem;
    }

    :is(.main-content, .authentication, .landing, .legal) .form-check .form-check-input {
        margin-inline-start: -1.9rem;
    }

    /* Pagination is a row of ~30px squares by default. */
    .main-content .page-link {
        min-width: 2.75rem;
        min-height: 2.75rem;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* --- Wide tables ---------------------------------------------------------
 *
 * Columns are dropped below the md tier with `d-none d-md-table-cell` in the
 * templates; this handles what is left over.
 *
 * The scroll shadows are the load-bearing part. `.table-responsive` scrolls
 * horizontally with no indication that it can - the table simply looks cut off,
 * and on a touch screen there is no scrollbar to notice. The gradients are
 * pinned to the scroll container with `background-attachment: local`, so the
 * two "cover" gradients travel with the content and uncover the shadow beneath
 * only at the edge where there is more to see.
 * ---------------------------------------------------------------------- */

.table-responsive {
    background:
        linear-gradient(to right, var(--custom-white) 30%, transparent) left center,
        linear-gradient(to left, var(--custom-white) 30%, transparent) right center,
        radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0)) left center,
        radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0)) right center;
    background-repeat: no-repeat;
    background-size: 2.5rem 100%, 2.5rem 100%, 0.6rem 100%, 0.6rem 100%;
    background-attachment: local, local, scroll, scroll;
}

@media (max-width: 767.98px) {

    /* Buys back roughly a column's worth of width across a six-column table. */
    .main-content .table > :not(caption) > * > * {
        padding: 0.5rem 0.5rem;
    }

    /* An email address or a long reference is one unbreakable word, and it
     * pushes its card past the viewport rather than wrapping. This is the most
     * common cause of a page that scrolls sideways. */
    .main-content .table td,
    .main-content .card-body,
    .main-content .breadcrumb-item {
        overflow-wrap: anywhere;
    }

    /*
     * `text-nowrap` on a table is a desktop decision and it defeats the column
     * priority above.
     *
     * With the low-value columns hidden a list is down to three or four, which
     * fits - but only if the cells may wrap. Held to one line instead, the name
     * column grows to its longest asset number, the table stays wider than the
     * screen, and the actions column, being last, sits permanently off the
     * right-hand edge. Those buttons are the most-used thing on the row, so
     * that is the worst possible column to lose.
     *
     * Wrapping a date across two lines is untidy. Putting Edit and Delete
     * behind a horizontal scroll nobody can see is a defect.
     *
     * `!important` because `.text-nowrap` is a Bootstrap utility and carries
     * its own. Without it this rule silently loses, and the symptom is not
     * "the text did not wrap" - it is "the Delete button is off the screen".
     */
    .main-content .table.text-nowrap,
    .main-content .table.text-nowrap th,
    .main-content .table.text-nowrap td {
        white-space: normal !important;
    }

    /* Row actions are the exception: they wrap to two rows of buttons and
     * double the row height for no gain. */
    .main-content .table td .btn-list,
    .main-content .table td .hstack {
        flex-wrap: nowrap;
        white-space: nowrap !important;
    }

    /*
     * A document's line table: give the description a floor.
     *
     * This one table keeps all five of its columns on a phone, because dropping
     * quantity or unit price leaves a total nobody can check - it is one of the
     * two entries on the column-priority exemption list in mobile_test.php.
     *
     * The failure mode that produces is not the usual one. The table does not
     * overflow: it *fits*, by handing the four numeric columns the width they ask
     * for and squeezing the description to whatever is left - 27px at 390px, which
     * renders "Moving light package" as one character per line, twenty lines tall.
     * A scroll container it never triggers is no help, and nothing in the markup
     * test can see it, because the markup is correct.
     *
     * A minimum width on the first column is what turns it back into the ordinary
     * case: the table becomes wider than the screen, and `.table-responsive` and
     * the scroll shadows above - which are already wrapped round it - do their job.
     *
     * Found in the browser at 390px while adding the client-facing document link.
     * It predates that work; the table has read this way since the module shipped.
     */
    .main-content .invoice-lines th:first-child,
    .main-content .invoice-lines td:first-child {
        min-width: 11rem;
    }

    /*
     * The same defect in the other shape of table, found the same way.
     *
     * A key/value table - `<th scope="row">` down the left, no `<thead>` - has
     * no column priority to apply and nothing in mobile_test.php can look at
     * it, because that test compares heading classes against cell classes and
     * there are no headings to compare. So at 390px the label column collapses
     * to whatever the value column leaves it, and a single-word label with no
     * break opportunity is broken *inside the word*: "Refer / ence", "Owne / r",
     * "Char / ge".
     *
     * Same fix as the invoice lines above and for the same reason: give the
     * label column a floor, let the table become wider than the screen, and let
     * `.table-responsive` do the job it is already wrapped round.
     *
     * Found in the browser at 390px on a loan record (issue #20 phase 3). It is
     * the fifth defect this repo has found by looking rather than by testing,
     * and the second of that batch to be a table that reads as correct markup.
     */
    .main-content .detail-table th[scope="row"] {
        min-width: 8rem;
    }
}

/* --- Layout ---------------------------------------------------------------
 *
 * Flex rows that were only ever seen at desktop width. Without `flex-wrap`
 * these squash their children into slivers instead of stacking them, and the
 * page title ends up two characters wide beside a row of buttons.
 * ---------------------------------------------------------------------- */

.page-header-breadcrumb,
.card-header,
.pagination-row {
    flex-wrap: wrap;
    gap: 0.5rem;
}

@media (max-width: 575.98px) {

    /* A four-button toolbar is a wall on a 390px screen. Full-width buttons
     * stacked are taller but they are readable and they are tappable, which
     * three-abreast at 90px each is not. */
    .page-header-breadcrumb .btn-list {
        display: flex;
        flex-wrap: wrap;
        gap: 0.5rem;
        width: 100%;
        margin-block-end: 0;
    }

    .page-header-breadcrumb .btn-list > .btn {
        flex: 1 1 calc(50% - 0.25rem);
        margin: 0;
    }

    /* The primary action gets its own row at the top rather than sharing. */
    .page-header-breadcrumb .btn-list > .btn-primary {
        flex-basis: 100%;
        order: -1;
    }

    .main-content .card-body {
        padding: 1rem 0.85rem;
    }
}

/* --- The scroll-to-top button --------------------------------------------
 *
 * `.scrollToTop` is `position: fixed` at 20px from the bottom-right corner. On
 * a desktop the footer text is centred in a wide bar with a lot of empty space
 * beside it and the button lands harmlessly in that space. At 390px the text
 * spans almost the full width, so the button sits directly on top of it and
 * eats the last few words - which is why the copyright line reads as truncated
 * on a phone and not on a laptop.
 *
 * Found in the browser pass; there is no way the markup test could have caught
 * it, because nothing in the markup is wrong.
 * ---------------------------------------------------------------------- */

@media (max-width: 767.98px) {
    /*
     * The padding goes on the inner container, not on `.footer` itself: the
     * footer carries Bootstrap's `py-3`, which is a utility and therefore
     * `!important`, so anything set here on the element loses silently. Same
     * trap as `.text-nowrap` above.
     */
    .footer .container {
        padding-block-end: 3.25rem;
    }
}

@media (pointer: coarse) {
    .scrollToTop {
        width: 2.75rem;
        height: 2.75rem;
    }
}

/* ----------------------------------------------------------------------
 * The banners: subscription, and the support view
 *
 * Both sit between the sidebar and the page's own `.main-content`, and both
 * wear `.main-content` themselves so that they pick up the top padding the
 * theme uses below 992px to clear the fixed header.
 *
 * Without the rule below that clearance would then be applied twice - once
 * to the banner and again to the page under it - leaving a visible gap on
 * exactly the screens where vertical space is scarcest. `padding-block-start`
 * here is not a Bootstrap utility, so no `!important` is needed: this
 * selector is simply more specific than the theme's plain `.main-content`.
 *
 * Three selectors rather than two, because a platform admin inside a lapsed
 * company sees both banners at once and the middle one needs its clearance
 * suppressed as much as the page does.
 * ---------------------------------------------------------------------- */

@media (max-width: 991.98px) {
    .subscription-banner + .main-content,
    .impersonation-banner + .main-content,
    .impersonation-banner + .subscription-banner {
        padding-block-start: 0;
    }
}

/*
    ...and the other half of wearing those classes, which is not optional.

    `.app-content` is what carries the fixed-header clearance and the sidebar
    offset, so a banner has to have it. It also carries
    `min-height: calc(100vh - 8rem)` from the theme, which on a strip of one
    line of text is a full empty screen between the banner and the page under
    it. The page is still there - it has simply been pushed below the fold, so
    the symptom is "the dashboard is blank", which sends you looking anywhere
    but here.

    `!important` because the theme states this four times over, once plainly
    and three more under `[data-toggled=...]` selectors for the collapsed
    sidebar states - which are ordinary things a user clicks, so a plain
    two-class selector would fix this in one sidebar state and not the others.

    **This was already wrong for the subscription banner**, which has worn the
    same classes since it was written and which nobody had seen stacked above a
    page, because seeing it means being on a trial that is running out. It is
    fixed here rather than left for later: the two are one mechanism and a
    comment saying "the other one is still broken" would not survive.
*/
.main-content.subscription-banner,
.main-content.impersonation-banner {
    min-height: 0 !important;
}

/* ----------------------------------------------------------------------
 * Support-view banner
 *
 * The gap between the two, on the one screen that shows both: a platform
 * admin viewing an account inside a company whose trial has lapsed. Applied
 * as a margin on the upper banner rather than as padding on the lower one, so
 * it survives the clearance-suppression rule above at every width instead of
 * fighting it below 992px.
 *
 * The alert's own colours are Bootstrap's `alert-danger` and are deliberately
 * left alone. The strip should look like everything else in this application
 * that means "stop and read me", rather than like a decoration somebody stops
 * seeing after twenty minutes.
 * ---------------------------------------------------------------------- */

.impersonation-banner:has(+ .subscription-banner) {
    margin-block-end: 0.5rem;
}

/* ----------------------------------------------------------------------
 * Platform companies list
 *
 * The trial-extension box is a fixed width from md up, where the row is
 * horizontal and an input that fills the cell would look like a mistake.
 * Below that the form stacks and each control takes the full width of the
 * cell, which is what stops the three of them demanding half the screen and
 * squeezing the company name into two wrapped lines.
 * ---------------------------------------------------------------------- */

@media (min-width: 768px) {
    .platform-extend {
        width: 5.5rem;
    }
}

/* ----------------------------------------------------------------------
 * The header's light/dark toggle is a <button>, not an <a>
 *
 * Every other control in the header bar is an anchor, and `.header-link`
 * carries the padding that gives them all their 42x48 hit area. This one has
 * to be a button because it submits a form: it changes stored state, and a GET
 * that changes state gets fired by link prefetchers and mail scanners - the
 * same argument the unsubscribe page makes at greater length.
 *
 * A bare <button> brings its own user-agent chrome, so it needs the three
 * resets below. The first attempt reached for Bootstrap's `.btn.btn-link.p-0`
 * instead, which does reset the chrome and also replaces `.header-link`'s
 * padding with none - the toggle rendered 32x32 beside its 42x48 neighbours,
 * a smaller tap target than everything around it and visibly out of line.
 * That is what these three lines exist to avoid, so do not swap them back for
 * a utility class.
 *
 * No `!important` and no size of its own: the point is to let `.header-link`
 * do the sizing, including the 44px it gets under `pointer: coarse` above.
 * ---------------------------------------------------------------------- */

button.header-link {
    background: none;
    border: 0;
    font: inherit;
}

/* ----------------------------------------------------------------------
 * Icon-font glyphs in the header bar sit low
 *
 * Six of the header's icons are inline SVGs and three are icon-font `<i>`
 * elements - the "?", the company switcher's building, and the search glyph.
 * They share `.header-link-icon`, which the theme draws as a 2rem bordered
 * square with 0.4rem of padding, leaving a content box 17.2px tall.
 *
 * An SVG fills that box and is done. A glyph does not: it is text, so it is
 * placed on a baseline inside a line box, and the inherited line-height of
 * 1.5 makes that line box 24px - taller than the 17.2px it has to sit in.
 * The overflow is distributed by the baseline rather than by the centre, so
 * the glyph is pushed down about 2.5px and the "?" rides visibly lower than
 * the moon and the fullscreen arrows on either side of it.
 *
 * `line-height: 1` is the part that fixes it; the flex centring is what keeps
 * it fixed if the padding or the box size is ever changed, because it centres
 * on the box rather than trusting an em box to happen to be the right height.
 *
 * Scoped to `i` so the SVG icons, which are already correct, are left alone -
 * that is the only job the element selector does. No `!important` is needed:
 * the theme's rule is `.main-header-container .header-link-icon`, and this
 * file loads after `styles.css`, so it would win on order even at equal
 * specificity. The matching `.main-header-container` prefix is there to clear
 * that rule's two classes, not to fight a utility.
 * ---------------------------------------------------------------------- */

.main-header-container i.header-link-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

/* ----------------------------------------------------------------------
 * Flags in the language menu
 *
 * A hairline ring round each one, because several of these flags have white
 * at the edge and the dropdown they sit in is white. Poland is the plain
 * case - its top half is white - and without a ring it renders as a red
 * semicircle floating in the menu, which reads as a broken image rather than
 * as a flag. France, Italy and the Union Jack all have white at one edge or
 * another and gain from it too.
 *
 * `inset`, so the ring eats a pixel of the flag rather than growing the
 * avatar and knocking the row heights out. Translucent black rather than a
 * colour from either palette, so it stays quiet in dark mode - where it is
 * not needed anyway, the white edges having plenty of contrast there.
 *
 * 28% and not less: the first attempt used 18% and Poland still read as a red
 * semicircle at 20px. The ring only does its job if it is visible at the size
 * it is actually drawn, which is smaller than it looks while you are working
 * on it.
 *
 * `object-fit: cover` because the source files are 64x42 going into a circle.
 * Without it the theme letterboxes them and the flag sits in a squashed band
 * across the middle of the avatar.
 * ---------------------------------------------------------------------- */

.language-selector .flag-avatar {
    box-shadow: inset 0 0 0 1px rgb(0 0 0 / 28%);
}

.language-selector .flag-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* --- Notifications: the bell, and the page behind it -------------------
 *
 * Migration 027. Every row in both lists is a `<button>` inside a form rather
 * than a link, because opening a notification marks it read and a GET that
 * changes stored state gets prefetched - the argument set out in
 * app/Routes/alerts.php and the same one that turned the header's light/dark
 * toggle and language menu into forms.
 *
 * A button is not a row, though, and the theme has no class that makes one look
 * like one: `.btn-link` renders it as blue underlined text and the bare element
 * carries a border and a grey background. These four rules are what make the
 * control read as the list item it is.
 *
 * Not `!important` anywhere here, and it does not need to be: nothing in the
 * markup carries a Bootstrap utility for colour, background or alignment on
 * these elements, so there is no `!important` from the utility layer to fight.
 * See CLAUDE.md's mobile rule 7 for what happens when there is.
 * ---------------------------------------------------------------------- */

.notification-open {
    width: 100%;
    text-align: start;
    color: var(--default-text-color);
    background-color: transparent;
    border: 0;
    border-radius: 0.35rem;
}

.notification-open:hover,
.notification-open:focus-visible {
    background-color: rgba(var(--light-rgb), 0.5);
    color: var(--default-text-color);
}

/*
 * The flex child that holds the wording. Without `min-width: 0` a flex item
 * refuses to shrink below its content's intrinsic width, so `text-truncate`
 * inside it does nothing at all and a long booking name pushes the dismiss
 * button off the right-hand edge - which is exactly the symptom the mobile pass
 * chased for an afternoon when `.text-nowrap` beat a wrapping rule. Same fix as
 * `.help-topic-card-text` above.
 */
.notification-text {
    min-width: 0;
}

/*
 * The bell, on a phone.
 *
 * styles.css hides it outright below the sm tier:
 *
 *     @media (max-width: 575.98px) {
 *         .cart-dropdown, .notifications-dropdown, .header-fullscreen {
 *             display: none !important;
 *         }
 *     }
 *
 * which is defensible for the other two - there is no cart, and a full-screen
 * toggle on a phone toggles nothing anybody can see - and is exactly backwards
 * for this one. The people CLAUDE.md's mobile rules are written for are standing
 * in a warehouse aisle, and they were the only ones who could not be told that a
 * booking had moved.
 *
 * `!important` because the rule being fought carries it, and `flex` rather than
 * `block` because that is what `.header-element` computes to everywhere else in
 * the bar - `block` puts the icon a few pixels out of line with its neighbours.
 *
 * The theme's 21rem width goes with it: 336px inside a 390px screen leaves the
 * menu touching both edges, and less than that on the phones narrower still.
 * `100vw` minus a thumb's worth either side, capped so it never grows past the
 * width the theme intends.
 */
@media (max-width: 575.98px) {
    .notifications-dropdown {
        display: flex !important;
    }

    .main-header-container .notifications-dropdown .main-header-dropdown {
        width: min(21rem, calc(100vw - 1.5rem));
    }
}

/* --- Legal documents ----------------------------------------------------
 *
 * /terms, /privacy and /cookies reuse `.help-article` for their prose, because
 * the two are the same job - hand-written HTML fragments whose spacing is set
 * once rather than with utility classes repeated through every file. What is
 * different is the reading: a help topic is skimmed mid-task, and a contract is
 * read straight through by somebody deciding whether to sign it. Hence a larger
 * body size and more air between clauses.
 * ---------------------------------------------------------------------- */

.legal-article {
    font-size: 0.9rem;
}

.legal-article h3 {
    font-size: 1rem;
    margin-block: 1.9rem 0.7rem;
}

/*
 * Each clause is linkable - "see clause 5.2" is only useful if you can send
 * somebody to it. Without this the browser puts the heading hard against the
 * top edge of the window.
 */
.legal-article h3[id] {
    scroll-margin-block-start: 1.5rem;
}

/* The paragraph under the title of each document, before the clauses begin. */
.legal-article .lead-in {
    font-size: 0.95rem;
    color: var(--default-text-color);
}

/* --- Cookie notice ------------------------------------------------------
 *
 * Not here. See app/Views/partials/cookie-notice.html, which carries its own
 * styles inline along with its own script.
 *
 * The reason is public/.htaccess: every .css and .js file is served with
 * `max-age=31536000` and nothing in this application appends a version to an
 * asset URL, so a browser that has been here once keeps this stylesheet for a
 * year. For most rules that is a cosmetic delay. For the cookie notice it is
 * not: without its positioning the strip is not a strip, it is a stray
 * paragraph and a button below the footer of every page, which is a worse look
 * on a legal notice than having no notice at all - and it would appear that way
 * to exactly the people who have visited before, which is everybody who
 * matters.
 *
 * Inline styles in a partial are the exception in this codebase, not the
 * pattern. Reach for it only when a component must render correctly on a
 * browser holding a stylesheet from before the component existed.
 * ---------------------------------------------------------------------- */

/* --- Swagger UI, on a phone ---------------------------------------------
 *
 * /settings/api/docs renders the API reference with the vendored Swagger UI
 * bundle (see public/assets/libs/swagger-ui/README.md). Its stylesheet is
 * scoped to `.swagger-ui`, so it and the Xintra theme leave each other alone -
 * these are the two places its own responsive behaviour is not good enough,
 * and they are fixed here rather than in the vendored file so that upgrading
 * it does not silently drop them.
 *
 * 1. Parameter names break mid-word. Swagger UI sets `overflow-wrap: anywhere`
 *    on `.parameter__name` and gives the column 78px at 390px, so `per_page`
 *    renders as "per_pag / e" - the same defect CLAUDE.md records as "Revo /
 *    ke" on the keys page, and it reads as a typo rather than as a wrap. These
 *    are identifiers a reader is about to type into their own code, so they
 *    have to be legible as written; the horizontal scroll goes on the table
 *    that needs it instead, which is what the Mobile section asks for.
 *
 * 2. The scheme/response tables can be wider than a phone. Given the same
 *    treatment: the table scrolls inside its own box rather than pushing the
 *    page sideways.
 *
 * Both are `@media (max-width: 575.98px)` so nothing changes on a desktop,
 * where Swagger UI's own layout is fine.
 *
 * These arrive late for anybody who has been here before: .htaccess serves this
 * file with `max-age=31536000` and nothing versions the URL, so a returning
 * browser keeps its copy - see the note at the foot of this file. That is
 * acceptable here and would not be for anything load-bearing: the worst case is
 * a parameter name reading "per_pag / e" on a phone until the cache turns over.
 * ---------------------------------------------------------------------- */
@media (max-width: 575.98px) {
    .swagger-ui .parameter__name,
    .swagger-ui .parameter__type,
    .swagger-ui .parameter__in {
        overflow-wrap: normal;
        word-break: keep-all;
    }

    /* The name column takes what the identifier needs. */
    .swagger-ui .parameters-col_name {
        width: auto;
        min-width: 6.5rem;
    }

    .swagger-ui .table-container,
    .swagger-ui .responses-inner,
    .swagger-ui .opblock-section-header {
        overflow-x: auto;
    }
}
