/* ============================================================================
   theme_v2 - COMPONENT LIBRARY
   ----------------------------------------------------------------------------
   Every reusable piece of UI in theme_v2 is styled here and nowhere else.
   A page never invents a control: it includes a template from boxs/ui/ and
   that template uses the classes below. Fix a control here once and every
   page that uses it is fixed.

   Depends only on the design tokens in global.css. Never references theme_v1,
   the framework assets, or bootstrap's stylesheet.

   Bootstrap's JS bundle IS loaded (modals, dropdowns, tabs) but its CSS is
   NOT, so anything bootstrap's JS drives has to be drawn here - that is why
   the modal, tab and dropdown sections below use bootstrap's class names.

   Contents
     1  form field
     2  text controls
     3  select
     4  combo + picker
     5  choice: radio, checkbox, switch, option cards
     6  file
     7  layout: page head, section, grid, action bar
     8  modal
     9  drawer
    10  tabs
    11  accordion
    12  alert / empty / toolbar
    13  toast
    14  small helpers
   ========================================================================= */


/* ==========================================================================
   1  FORM FIELD
   ========================================================================== */

.f-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.f-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-width: 0;
}

.f-field > .label,
.f-label {
    font-size: 11.5px;
    font-weight: 700;
    color: var(--text-2);
    display: flex;
    align-items: center;
    gap: 5px;
}

.f-req {
    color: var(--bad);
    font-weight: 800;
}

.f-hint,
.f-note {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: var(--text-3);
}

.f-note .material-icons-outlined,
.f-hint .material-icons-outlined { font-size: 13px; }


/* ==========================================================================
   2  TEXT CONTROLS
   .input and .textarea are declared in global.css; only the states are here.
   ========================================================================== */

.input:disabled,
.textarea:disabled,
.input[readonly]:not(.f-pick > input),
select.input:disabled {
    background: var(--surface-3);
    color: var(--text-3);
    cursor: not-allowed;
}

.input::placeholder,
.textarea::placeholder { color: var(--text-4); }

/* number spinners are noise on money fields */
.input[type="number"]::-webkit-outer-spin-button,
.input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.input[type="number"] { -moz-appearance: textfield; }

/* password with a reveal button */
.f-reveal {
    position: relative;
    display: block;
}

.f-reveal > .input { padding-inline-end: 38px; }

.f-reveal > button {
    position: absolute;
    inset-inline-end: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-3);
    cursor: pointer;
    display: grid;
    place-items: center;
}

.f-reveal > button:hover { background: var(--surface-3); color: var(--text); }
.f-reveal > button .material-icons-outlined { font-size: 17px; }

/* read-only value with copy, and a reveal when it is a secret
   - boxs/ui/secret.html. The buttons sit beside the field rather than on top
   of it, because there can be two of them. */
.f-secret {
    display: flex;
    align-items: center;
    gap: 4px;
}

.f-secret > .input {
    flex: 1;
    min-width: 0;
    background: var(--surface-2);
    font-family: var(--mono, ui-monospace, monospace);
    font-size: 12px;
}

.f-secret > .input:focus { box-shadow: none; }

.f-secret-btn {
    flex: none;
    width: 32px;
    height: 32px;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--text-3);
    cursor: pointer;
    display: grid;
    place-items: center;
}

.f-secret-btn:hover { background: var(--surface-3); color: var(--text); }
.f-secret-btn .material-icons-outlined { font-size: 16px; }

.f-secret.is-empty {
    padding: 8px 10px;
    border: 1px dashed var(--border);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.f-secret-empty { font-size: 12px; font-weight: 600; color: var(--text-4); }

/* read-only marks - boxs/ui/marks.html */
.f-marks {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 5px;
}

.f-marks-tile {
    display: grid;
    place-items: center;
    width: 54px;
    height: 34px;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: #fff;
    overflow: hidden;
}

.f-marks-tile > img { max-width: 86%; max-height: 80%; object-fit: contain; }
.f-marks-empty { color: var(--text-4); }

/* the credential panel the gateway create form reveals for the chosen
   processor type */
.f-settings-panel[hidden] { display: none; }

/* input with a leading icon */
.f-with-icon { position: relative; display: block; }
.f-with-icon > .material-icons-outlined {
    position: absolute;
    inset-inline-start: 10px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 16px;
    color: var(--text-3);
    pointer-events: none;
}
.f-with-icon > .input { padding-inline-start: 33px; }


/* ==========================================================================
   3  SELECT
   The chevron is a real element so it takes the scheme's colour and flips
   with `dir` without a second rule.
   ========================================================================== */

.f-select {
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
    padding-inline-end: 30px;
    text-overflow: ellipsis;
}

.f-select::-ms-expand { display: none; }

.f-select-wrap {
    position: relative;
    display: block;
    min-width: 0;
}

.f-select-wrap > .f-caret {
    position: absolute;
    inset-inline-end: 9px;
    top: 50%;
    transform: translateY(-50%);
    pointer-events: none;
    font-size: 17px;
    color: var(--text-3);
}

.f-select-wrap > select:disabled + .f-caret { opacity: .5; }


/* ==========================================================================
   3b  SEARCHABLE SELECT
   A <select data-ui-search> is enhanced by components.js into a button and a
   filterable panel with flags. The real <select> stays in the DOM and stays
   the value the form posts - the panel only writes back into it - so the
   field works unchanged if the script fails and FormData never sees any of
   this. That is the difference from the widget it replaces, which removed the
   control and kept the value in markup it had invented.
   ========================================================================== */

.f-select-wrap.is-enhanced > select {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.f-select-wrap.is-enhanced > .f-caret { display: none; }

.f-ss-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    height: 36px;
    padding: 0 9px 0 11px;
    border: 1px solid var(--border);
    background: var(--surface);
    border-radius: var(--r-md);
    font-size: 12.5px;
    color: var(--text);
    cursor: pointer;
    text-align: start;
}

.f-ss-btn:focus-visible,
.f-select-wrap.is-open .f-ss-btn {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.f-ss-flag {
    width: 20px;
    height: 14px;
    object-fit: cover;
    border-radius: 3px;
    flex: none;
    background: var(--surface-3);
}

.f-ss-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.f-ss-btn > .f-caret {
    flex: none;
    font-size: 17px;
    color: var(--text-3);
}

.f-ss-panel {
    position: absolute;
    z-index: 1250;
    top: calc(100% + 4px);
    inset-inline-start: 0;
    width: 100%;
    min-width: 240px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-lg);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    max-height: 300px;
}

.f-ss-panel[hidden] { display: none; }

.f-ss-search {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border-bottom: 1px solid var(--border-2);
    background: var(--surface-2);
    flex: none;
}

.f-ss-search .material-icons-outlined { font-size: 16px; color: var(--text-3); }

.f-ss-search input {
    flex: 1 1 auto;
    min-width: 0;
    border: 0;
    background: transparent;
    outline: none;
    font-size: 12.5px;
    color: var(--text);
}

.f-ss-list {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 5px;
}

.f-ss-opt {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 7px 9px;
    border: 0;
    background: transparent;
    border-radius: var(--r-sm);
    font-size: 12.5px;
    color: var(--text-2);
    cursor: pointer;
    text-align: start;
}

.f-ss-opt:hover,
.f-ss-opt.is-active { background: var(--surface-3); color: var(--text); }

.f-ss-opt.is-selected {
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 700;
}

/* <optgroup> heading inside the filterable panel. Indenting the options under
   it rather than boxing the group keeps the list scannable at forty rows. */
.f-ss-group {
    padding: 9px 9px 4px;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .4px;
    text-transform: uppercase;
    color: var(--text-4);
}

/* every option after the first heading is inside some group, so one general
   sibling rule indents them all - a leading ungrouped blank option sits before
   the first heading and is correctly left alone */
.f-ss-group ~ .f-ss-opt { padding-inline-start: 20px; }

.f-ss-group[hidden] { display: none; }

/* the native control, for anyone who never triggers the enhancement */
.f-select optgroup {
    font-weight: 800;
    color: var(--text-3);
}

.f-select optgroup option { font-weight: 400; color: var(--text); }

.f-ss-opt > span {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.f-ss-none {
    padding: 18px 10px;
    text-align: center;
    font-size: 12px;
    color: var(--text-3);
}

/* inside a composite field the button loses its own border - the .f-combo
   draws it - and the panel is allowed to be wider than the code column */
.f-combo .f-ss-btn {
    height: 100%;
    border: 0;
    border-inline-end: 1px solid var(--border);
    border-radius: 0;
    background: var(--surface-3);
    font-weight: 700;
    color: var(--text-2);
}

.f-combo .f-ss-btn:focus-visible { box-shadow: none; }
.f-combo .f-ss-panel { min-width: 290px; }

/* .f-combo clips its children to get the rounded box, which would also clip
   the dropdown - let it out while the panel is open and round the button
   itself so the corner still looks right */
.f-combo:has(.f-select-wrap.is-open) { overflow: visible; }

.f-combo > .f-select-wrap:first-child .f-ss-btn {
    border-start-start-radius: var(--r-md);
    border-end-start-radius: var(--r-md);
}


/* ==========================================================================
   4  COMBO + PICKER
   Two controls that must read as one - a dialling code beside a number, a
   readonly name beside its search button.
   ========================================================================== */

.f-combo,
.f-pick {
    position: relative;
    display: flex;
    align-items: stretch;
    height: 36px;
    border: 1px solid var(--border);
    background: var(--surface);
    border-radius: var(--r-md);
    overflow: hidden;
}

.f-combo:focus-within,
.f-pick:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* -- combo -- */

.f-combo > .f-select-wrap {
    flex: 0 0 auto;
    width: auto;
    min-width: 118px;
    max-width: 172px;
    height: 100%;
}

.f-combo > .f-select-wrap > select {
    height: 100%;
    border: 0;
    border-inline-end: 1px solid var(--border);
    border-radius: 0;
    background-color: var(--surface-3);
    box-shadow: none;
    font-weight: 700;
    color: var(--text-2);
}

.f-combo > .f-select-wrap > select:focus { box-shadow: none; }

.f-combo > input {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
}

.f-combo > input:focus { box-shadow: none; }

/* -- picker -- */

.f-pick > input {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    border: 0;
    background: transparent;
    outline: none;
    padding: 0 11px;
    font-size: 12.5px;
    color: var(--text);
    cursor: pointer;
    text-overflow: ellipsis;
}

.f-pick > input::placeholder { color: var(--text-4); }

.f-pick > button {
    flex: none;
    width: 38px;
    border: 0;
    border-inline-start: 1px solid var(--border);
    background: var(--surface-3);
    color: var(--text-2);
    cursor: pointer;
    display: grid;
    place-items: center;
}

.f-pick > button:hover { background: var(--accent); color: #fff; }
.f-pick > button .material-icons-outlined { font-size: 17px; }

.f-pick.is-locked { background: var(--surface-3); cursor: not-allowed; }
.f-pick.is-locked > input { color: var(--text-2); cursor: not-allowed; }


/* ==========================================================================
   5  CHOICE: radio, checkbox, switch, option cards
   ========================================================================== */

.f-opts {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.f-opts-row {
    flex-direction: row;
    flex-wrap: wrap;
}

.f-opt {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 11px;
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    background: var(--surface);
    cursor: pointer;
}

.f-opt:hover { border-color: var(--accent); }

.f-opt > input[type="radio"],
.f-opt > input[type="checkbox"] {
    width: 15px;
    height: 15px;
    flex: none;
    margin: 0;
    accent-color: var(--accent);
    cursor: pointer;
}

.f-opt-text {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-2);
}

.f-opt-sub {
    font-size: 11px;
    color: var(--text-3);
    margin-top: 2px;
}

.f-opt:has(input:checked) {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.f-opt:has(input:checked) .f-opt-text { color: var(--text); }

.f-opt:has(input:disabled) {
    opacity: .55;
    cursor: not-allowed;
}

/* a plain inline checkbox, no card around it */
.f-check {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2);
}

.f-check > input {
    width: 15px;
    height: 15px;
    margin: 0;
    accent-color: var(--accent);
    cursor: pointer;
}

/* switch */
.f-switch {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2);
}

.f-switch > input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.f-switch-track {
    width: 38px;
    height: 21px;
    border-radius: 999px;
    background: var(--surface-3);
    border: 1px solid var(--border);
    position: relative;
    flex: none;
    transition: background .15s ease, border-color .15s ease;
}

.f-switch-track::after {
    content: "";
    position: absolute;
    top: 2px;
    inset-inline-start: 2px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: var(--surface);
    box-shadow: var(--sh-sm);
    transition: transform .15s ease;
}

.f-switch > input:checked + .f-switch-track {
    background: var(--accent);
    border-color: var(--accent);
}

.f-switch > input:checked + .f-switch-track::after { transform: translateX(17px); }
[dir="rtl"] .f-switch > input:checked + .f-switch-track::after { transform: translateX(-17px); }

.f-switch > input:focus-visible + .f-switch-track { box-shadow: 0 0 0 3px var(--accent-soft); }


/* image choice - boxs/ui/image_choice.html
   Tiles wrap and the strip scrolls rather than growing the page, because a
   gateway can offer three marks or thirty. The logos are supplied on white by
   the file host, so the tile face stays white in dark mode too - inverting it
   would recolour the brands. */
.f-imgpick {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    max-height: 216px;
    overflow-y: auto;
    padding: 4px;
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.f-imgpick-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    width: 78px;
    padding: 6px 4px;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    cursor: pointer;
}

.f-imgpick-tile:hover { background: var(--surface-3); }
.f-imgpick-tile > input { position: absolute; opacity: 0; width: 0; height: 0; }

.f-imgpick-box {
    display: grid;
    place-items: center;
    width: 100%;
    height: 40px;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: #fff;
    overflow: hidden;
}

.f-imgpick-box > img {
    max-width: 86%;
    max-height: 80%;
    object-fit: contain;
}

.f-imgpick-text {
    font-size: 10.5px;
    font-weight: 600;
    color: var(--text-3);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.f-imgpick-tile.is-on {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.f-imgpick-tile.is-on .f-imgpick-box { border-color: var(--accent); }
.f-imgpick-tile.is-on .f-imgpick-text { color: var(--text); }
.f-imgpick-tile > input:focus-visible + .f-imgpick-box { box-shadow: 0 0 0 3px var(--accent-soft); }


/* ==========================================================================
   6  FILE
   ========================================================================== */

.f-file {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px dashed var(--border);
    border-radius: var(--r-md);
    background: var(--surface-2);
    cursor: pointer;
}

.f-file:hover { border-color: var(--accent); background: var(--accent-soft); }
.f-file > input[type="file"] { display: none; }
.f-file .material-icons-outlined { font-size: 19px; color: var(--text-3); }

.f-file-text {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-2);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


/* ==========================================================================
   7  LAYOUT: page head, section, grid, action bar
   ========================================================================== */

/* No overflow:hidden. It would clip anything a field inside the section opens
   downward - a searchable select's panel got cut off two rows in. The header
   rounds its own top corners instead, which is the only thing the clipping
   was buying. */
.f-sec {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-sm);
}

.f-sec-head {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 13px 16px;
    border-bottom: 1px solid var(--border-2);
    background: var(--surface-2);
    border-start-start-radius: var(--r-lg);
    border-start-end-radius: var(--r-lg);
}

.f-sec-ico {
    width: 32px;
    height: 32px;
    border-radius: 10px;
    flex: none;
    display: grid;
    place-items: center;
    background: var(--accent-soft);
    color: var(--accent);
}

.f-sec-ico .material-icons-outlined { font-size: 18px; }

.f-sec-text { min-width: 0; }

.f-sec-title {
    font-size: 13px;
    font-weight: 800;
    letter-spacing: -.01em;
    color: var(--text);
}

.f-sec-sub {
    font-size: 11.5px;
    color: var(--text-3);
    margin-top: 2px;
}

.f-sec-actions { margin-inline-start: auto; display: flex; gap: 7px; }

.f-sec-body { padding: 16px; }

/* 12 column grid */

.f-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 14px;
}

.f-grid > * { grid-column: span 12; }

.f-2  { grid-column: span 2; }
.f-3  { grid-column: span 3; }
.f-4  { grid-column: span 4; }
.f-5  { grid-column: span 5; }
.f-6  { grid-column: span 6; }
.f-7  { grid-column: span 7; }
.f-8  { grid-column: span 8; }
.f-9  { grid-column: span 9; }
.f-12 { grid-column: span 12; }

@media (max-width: 1180px) { .f-2 { grid-column: span 3; } }
@media (max-width: 980px)  { .f-2, .f-3, .f-4, .f-5 { grid-column: span 6; } }
@media (max-width: 620px)  { .f-2, .f-3, .f-4, .f-5, .f-6, .f-7, .f-8, .f-9 { grid-column: span 12; } }

/* THERE IS NO HALF-WIDTH UTILITY, deliberately.

   .f-half lived here for a day, capping the quick pay list at half the
   content area on the reasoning that a short row does not need a wide table.
   On screen it read as the one page in the portal that had been shrunk. Every
   list is the width of the content area; a page that is narrower than the one
   before it looks like a mistake, whatever its row length says. If a table is
   too sparse, the answer is a column worth reading, not a smaller table. */

/* sub-form card inside a section */

/* same reason as .f-sec - the currency select inside a fee card opens
   downward and must not be clipped by the card */
.f-svc {
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    background: var(--surface-2);
    height: 100%;
    display: flex;
    flex-direction: column;
}

.f-svc-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 13px;
    border-bottom: 1px solid var(--border-2);
    background: var(--surface);
    border-start-start-radius: var(--r-lg);
    border-start-end-radius: var(--r-lg);
}

.f-svc-head .material-icons-outlined { font-size: 17px; }

.f-svc-title {
    font-size: 12px;
    font-weight: 800;
    color: var(--text);
}

.f-svc-body {
    padding: 13px;
    display: flex;
    flex-direction: column;
    gap: 11px;
    flex: 1 1 auto;
}

/* sticky action bar */

.f-foot {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 16px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-lg);
}

.f-foot .spacer { margin-inline-end: auto; }


/* ---- busy state ---------------------------------------------------------
   A control that silently stops responding reads as a broken page, which is
   exactly what makes people click it a second time. While a form is in flight
   its submit button says so. */

.btn:disabled,
.btn[disabled] {
    opacity: .62;
    cursor: not-allowed;
    pointer-events: none;
}

.f-spin {
    width: 13px;
    height: 13px;
    flex: none;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: f-spin .6s linear infinite;
}

@keyframes f-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
    .f-spin { animation-duration: 1.8s; }
}


/* ==========================================================================
   8  MODAL
   Bootstrap's JS toggles .show / .fade and adds .modal-backdrop, but its CSS
   is not loaded, so all of the geometry and chrome is drawn here. Without
   this a modal renders as unpositioned static markup at the top of the page.
   Applies to EVERY modal in theme_v2, not just the ones from boxs/ui/.
   ========================================================================== */

/* Centred by making the open modal a flex container, not by margin:auto on
   the dialog. shell.css sets `.modal-dialog{margin:1.75rem auto !important}`,
   and an !important there beats anything written here, so centring that
   depends on the margin is not reliable. This does not care what the margin is. */
/* NAME COLLISION - read before touching.
   global.css:183 defines `.modal` as the supplied template's own card panel:
       .modal{width:min(560px,100%);background:var(--surface);border-radius:...;
              box-shadow:...;overflow:hidden}
   That is a different component that happens to share bootstrap's class name.
   Bootstrap's `.modal` is the full-screen overlay, so every one of those
   declarations has to be undone here - a fixed element given `inset:0` AND an
   explicit width anchors to the start edge, which pinned every modal as a
   560px white box against the left of the viewport. */
.modal {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: none;
    overflow-x: hidden;
    overflow-y: auto;
    outline: 0;
    padding: 24px 16px;

    /* undo the template's panel styling */
    width: auto;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
}

/* !important is load-bearing here, not laziness: bootstrap's JS sets
   `style.display = "block"` on the element when it opens a modal, and an
   inline style beats any stylesheet rule without it. This is the only way a
   stylesheet can make an open modal a flex container. */
.modal.show {
    display: flex !important;
    align-items: center;
    justify-content: center;
}

/* the dialog is centred by the flex container above, so its own margin must
   not fight it - bootstrap ships `margin: 1.75rem auto` */
.modal.show > .modal-dialog { margin: 0 !important; }

.modal.fade .modal-dialog {
    transform: translateY(-12px);
    opacity: 0;
    transition: transform .18s ease, opacity .18s ease;
}

.modal.fade.show .modal-dialog {
    transform: none;
    opacity: 1;
}

.modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1190;
    background: rgba(11, 22, 28, .66);
    opacity: 0;
    transition: opacity .18s ease;
}

.modal-backdrop.show { opacity: 1; }

body.modal-open { overflow: hidden; }

.modal-dialog {
    position: relative;
    width: 100%;
    max-width: 520px;
    margin: 0 auto;
    pointer-events: none;
    flex: 0 1 auto;
}

.modal-dialog-centered {
    display: flex;
    align-items: center;
    min-height: 0;
}

.modal-sm { max-width: 400px; }
.modal-lg { max-width: min(820px,  calc(100vw - 40px)); }
.modal-xl { max-width: min(1120px, calc(100vw - 40px)); }

.modal-content {
    position: relative;
    width: 100%;
    pointer-events: auto;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-xl);
    box-shadow: var(--sh-lg);
    overflow: hidden;
    /* the dialog never grows past the viewport - the body scrolls instead, so
       a 3000-row picker list cannot push the header and footer off-screen */
    max-height: calc(100vh - 56px);
}

.modal-header {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 0 0 auto;
    padding: 13px 18px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
}

.modal-title {
    margin: 0;
    font-size: 14px;
    font-weight: 800;
    letter-spacing: -.01em;
    color: var(--text);
}

.modal-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 16px 18px;
}

.modal-body.p-0 { padding: 0; }

.modal-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    flex: 0 0 auto;
    padding: 12px 18px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
}

/* bootstrap draws its close button with a background image from its own
   stylesheet - not loaded here, so draw the X from the element itself */
.btn-close {
    margin-inline-start: auto;
    width: 30px;
    height: 30px;
    padding: 0;
    flex: none;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--text-3);
    cursor: pointer;
    position: relative;
    opacity: 1;
    appearance: none;
}

.btn-close:hover {
    border-color: var(--bad);
    color: var(--bad);
    background: var(--bad-soft);
}

.btn-close::before,
.btn-close::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 12px;
    height: 1.5px;
    background: currentColor;
    border-radius: 2px;
}

.btn-close::before { transform: translate(-50%, -50%) rotate(45deg); }
.btn-close::after  { transform: translate(-50%, -50%) rotate(-45deg); }

/* a table inside a modal must scroll on its own, never widen the dialog */
.modal-body .tbl-wrap,
.modal-body .table-wrapper,
.modal-body .table-container { overflow-x: auto; max-width: 100%; }

/* A picker modal's body is a list table, and that table renders itself as a
   full card - border, shadow, and its own "Owner List" heading. Inside a
   modal that reads as a card in a card with the title said twice. Flatten it:
   the modal header already says which list this is. */
.modal-body > .card,
.modal-body > .table-container {
    border: 0;
    box-shadow: none;
    background: transparent;
    border-radius: 0;
}

.modal-body > .card > .card-header,
.modal-body > .table-container > .card-header {
    padding-top: 0;
    padding-inline: 0;
    border-bottom: 0;
}

.modal-body > .card > .card-header > h5,
.modal-body > .card > .card-header > p,
.modal-body > .table-container > .card-header > h5,
.modal-body > .table-container > .card-header > p { display: none; }

.modal-body > .card > .card-body,
.modal-body > .table-container > .card-body { padding-inline: 0; }


/* ==========================================================================
   9  DRAWER
   ========================================================================== */

.f-drawer {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: none;
}

.f-drawer.show { display: block; }

.f-drawer-mask {
    position: absolute;
    inset: 0;
    background: rgba(16, 31, 38, .5);
}

.f-drawer-panel {
    position: absolute;
    inset-block: 0;
    inset-inline-end: 0;
    width: min(520px, 100%);
    background: var(--surface);
    box-shadow: var(--sh-lg);
    display: flex;
    flex-direction: column;
}

.f-drawer-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 13px 18px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
    flex: 0 0 auto;
}

.f-drawer-body { flex: 1 1 auto; overflow-y: auto; padding: 16px 18px; }

.f-drawer-foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 18px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    flex: 0 0 auto;
}


/* ==========================================================================
   10  TABS
   ========================================================================== */

/* ONE row by default, with two ways past the edge.
 *
 * A record can carry eleven sections - the entity page does - and eleven tabs
 * do not fit a row at most widths. Wrapping them all the time was tried and
 * looks unfinished; hiding the overflow with no way to reach it was worse.
 *
 * So: the strip scrolls sideways as it always did, AND a small chevron at the
 * end opens it out to however many rows the tabs need. Scroll for anyone who
 * has a trackpad or a touchscreen and just wants to flick past; the button for
 * everyone else, and for anyone who would rather see the whole set at once.
 *
 * The chevron shows itself only when there is something out of sight - it is
 * hidden by script on a page whose tabs already fit, so a four-tab record
 * carries no control it does not need.
 *
 * The border moves to the wrapper: it then runs under the button as well, and
 * under the LAST row when the strip is open, so the strip keeps one clean edge
 * against the panel at any height.
 */
.f-tabs-wrap {
    display: flex;
    align-items: flex-end;
    gap: 4px;
    border-bottom: 1px solid var(--border);
}

.f-tabs {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: nowrap;
    gap: 2px;
    row-gap: 0;
    overflow-x: auto;
    scrollbar-width: none;
}

.f-tabs::-webkit-scrollbar { display: none; }

/* Opened out. overflow visible rather than auto, or the wrapped rows would be
   clipped to the height of one. */
.f-tabs-wrap.expanded .f-tabs {
    flex-wrap: wrap;
    overflow-x: visible;
}

.f-tabs-more {
    flex: 0 0 auto;
    border: 0;
    background: transparent;
    padding: 9px 8px;
    line-height: 0;
    color: var(--text-3);
    cursor: pointer;
    /* the same 2px the tabs reserve for their underline, so the chevron sits on
       the same baseline as the row it belongs to rather than 2px below it */
    border-bottom: 2px solid transparent;
}

.f-tabs-more:hover { color: var(--text); }

.f-tabs-more .material-icons-outlined {
    font-size: 18px;
    transition: transform .15s ease;
}

/* One icon, turned over, rather than swapping the glyph: the control keeps its
   width and nothing beside it shifts when it is pressed. */
.f-tabs-wrap.expanded .f-tabs-more .material-icons-outlined { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
    .f-tabs-more .material-icons-outlined { transition: none; }
}

.f-tab {
    border: 0;
    background: transparent;
    padding: 9px 13px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-3);
    border-bottom: 2px solid transparent;
    cursor: pointer;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    text-decoration: none;
}

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

.f-tab.active {
    color: var(--accent);
    font-weight: 800;
    border-bottom-color: var(--accent);
}

.f-tab .material-icons-outlined { font-size: 16px; }

.f-tab-count {
    display: inline-grid;
    place-items: center;
    min-width: 19px;
    height: 19px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--surface-3);
    color: var(--text-3);
    font-size: 10.5px;
    font-weight: 700;
}

.f-tab.active .f-tab-count { background: var(--accent-soft); color: var(--accent); }

.f-tab-panel { padding-top: 14px; }
.f-tab-panel[hidden] { display: none; }

/* ---- entity view identity strip ---- */

.f-ident {
    display: flex;
    align-items: flex-start;
    gap: 15px;
    flex-wrap: wrap;
}

/* The tile beside a record's name. It holds EITHER a logo image or, when the
   record has none, a material icon standing in for one.

   The icon case has to be centred, and it was being centred by every page that
   used it - the same style="display:grid;place-items:center;" repeated in
   fifteen templates. Four of them forgot, so the icon sat in the top-left
   corner of the tile on the category, quick-pay, received-invoice and
   notification pages. That is what a shared component's own stylesheet is for.

   overflow:hidden keeps a cover image inside the rounded corners; the pages
   that wanted it were carrying that inline too. */
.f-ident-logo {
    width: 56px;
    height: 56px;
    border-radius: 14px;
    flex: none;
    object-fit: cover;
    background: var(--surface-3);
    border: 1px solid var(--border);
    display: grid;
    place-items: center;
    overflow: hidden;
    color: var(--text-4);
}

/* An <img> has no children to centre, and display:grid on a replaced element
   changes how the box is sized. It keeps the behaviour it always had. */
img.f-ident-logo {
    display: block;
}

.f-ident-text { min-width: 0; flex: 1 1 240px; }

.f-ident-name {
    font-size: 17px;
    font-weight: 800;
    letter-spacing: -.02em;
    color: var(--text);
    word-break: break-word;
}

.f-ident-sub { font-size: 12.5px; color: var(--text-3); margin-top: 3px; }

.f-ident-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 7px;
    margin-top: 9px;
}

/* key/value grid used across the overview */
.f-kv-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 14px;
}


/* ==========================================================================
   11  ACCORDION
   ========================================================================== */

.f-acc { border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.f-acc + .f-acc { margin-top: 8px; }

.f-acc-head {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 15px;
    border: 0;
    background: var(--surface-2);
    cursor: pointer;
    text-align: start;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--text);
}

.f-acc-head:hover { background: var(--surface-3); }
.f-acc-head .f-acc-caret { margin-inline-start: auto; font-size: 18px; color: var(--text-3); transition: transform .15s ease; }
.f-acc.open .f-acc-caret { transform: rotate(180deg); }
.f-acc-body { padding: 14px 15px; border-top: 1px solid var(--border-2); background: var(--surface); }
.f-acc:not(.open) .f-acc-body { display: none; }


/* ==========================================================================
   12  ALERT / EMPTY / TOOLBAR
   ========================================================================== */

.f-alert {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    padding: 12px 15px;
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    font-size: 12.5px;
    color: var(--text-2);
}

.f-alert .material-icons-outlined { font-size: 18px; flex: none; }
.f-alert-title { font-weight: 800; color: var(--text); margin-bottom: 2px; }

.f-alert-info    { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.f-alert-good    { background: var(--good-soft);   box-shadow: inset 3px 0 0 var(--good); }
.f-alert-warn    { background: var(--warn-soft);   box-shadow: inset 3px 0 0 var(--warn); }
.f-alert-bad     { background: var(--bad-soft);    box-shadow: inset 3px 0 0 var(--bad); }

[dir="rtl"] .f-alert-info { box-shadow: inset -3px 0 0 var(--accent); }
[dir="rtl"] .f-alert-good { box-shadow: inset -3px 0 0 var(--good); }
[dir="rtl"] .f-alert-warn { box-shadow: inset -3px 0 0 var(--warn); }
[dir="rtl"] .f-alert-bad  { box-shadow: inset -3px 0 0 var(--bad); }

.f-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 9px;
    padding: 46px 20px;
    text-align: center;
}

.f-empty .material-icons-outlined { font-size: 34px; color: var(--text-4); }
.f-empty-title { font-size: 13px; font-weight: 800; color: var(--text-2); }
.f-empty-sub { font-size: 12px; color: var(--text-3); }

.f-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 11px 14px;
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    background: var(--surface);
}

.f-toolbar .spacer { margin-inline-start: auto; }


/* ==========================================================================
   11b  BREADCRUMB
   v1 page bodies open with
       <div class="page-breadcrumb d-none d-sm-flex ..."> ... <ol class="breadcrumb">
   theme_v2 shows the trail in the header and the title through page_head, so
   that block is duplicate chrome and shell.css hid it. The utility layer then
   un-hid it: `.d-sm-flex{display:flex !important}` beats a plain display:none.
   Hidden again, with the !important it now needs. And because a bare <ol> was
   showing as a numbered list wherever it did appear, .breadcrumb is given real
   styling for any page that legitimately wants one.
   ========================================================================== */

.page-breadcrumb { display: none !important; }

.breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 7px;
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 12px;
    color: var(--text-3);
}

.breadcrumb-item {
    display: flex;
    align-items: center;
    gap: 7px;
}

.breadcrumb-item + .breadcrumb-item::before {
    content: "/";
    color: var(--text-4);
}

.breadcrumb-item a { color: var(--text-3); text-decoration: none; }
.breadcrumb-item a:hover { color: var(--accent); }
.breadcrumb-item.active { color: var(--text); font-weight: 700; }


/* ==========================================================================
   12a  BADGE
   The table engine renders status cells as <span class="badge bg-grd-success">.
   Bootstrap supplies .badge's display, padding, font-size and line-height, and
   its stylesheet is not loaded here - so the pill had none of them and hugged
   its text with no breathing room at all.

   The bg-grd-* variants also painted a solid saturated fill with white text,
   which is far heavier than anything else in the theme. They are mapped onto
   the same soft treatment as .pill-*: tinted background, coloured text.

   !important is needed because utilities.css defines .bg-grd-* with
   !important and loads after this file; these selectors are more specific, so
   important-vs-important is decided on specificity and these win.
   ========================================================================== */

.badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    line-height: 1.55;
    white-space: nowrap;
    vertical-align: middle;
    text-transform: capitalize;
    background-image: none;
}

.badge.bg-success,
.badge.bg-grd-success  { background: var(--good-soft)   !important; color: var(--good)   !important; }

.badge.bg-danger,
.badge.bg-grd-danger   { background: var(--bad-soft)    !important; color: var(--bad)    !important; }

.badge.bg-warning,
.badge.bg-grd-warning  { background: var(--warn-soft)   !important; color: var(--warn)   !important; }

.badge.bg-info,
.badge.bg-grd-info     { background: var(--info-soft)   !important; color: var(--info)   !important; }

.badge.bg-primary,
.badge.bg-grd-primary,
.badge.bg-grd-royal    { background: var(--accent-soft) !important; color: var(--accent) !important; }

.badge.bg-secondary,
.badge.bg-light        { background: var(--surface-3)   !important; color: var(--text-3) !important; }


/* ==========================================================================
   12b  PAGINATION
   The table engine renders bootstrap's <ul class="pagination"><li class=
   "page-item">. With no bootstrap stylesheet the <ul> keeps its default list
   styling, so the pager came out as a vertical bulleted list.
   ========================================================================== */

.pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.pagination .page-item { display: block; }

.pagination .page-link {
    display: grid;
    place-items: center;
    min-width: 30px;
    height: 30px;
    padding: 0 8px;
    border: 1px solid var(--border);
    background: var(--surface);
    border-radius: var(--r-sm);
    font-size: 12px;
    font-weight: 600;
    color: var(--text-2);
    cursor: pointer;
    user-select: none;
}

.pagination .page-link:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.pagination .page-item.active .page-link {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

.pagination .page-item.disabled .page-link {
    opacity: .45;
    cursor: not-allowed;
    background: var(--surface-2);
}


/* ==========================================================================
   13  TOAST
   ========================================================================== */

.f-toast-host {
    position: fixed;
    bottom: 20px;
    inset-inline-end: 20px;
    z-index: 1400;
    display: flex;
    flex-direction: column;
    gap: 9px;
}

.f-toast {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    box-shadow: var(--sh-lg);
    padding: 11px 14px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text);
    min-width: 250px;
    max-width: 380px;
}

.f-toast .material-icons-outlined { font-size: 18px; flex: none; }
.f-toast-good .material-icons-outlined { color: var(--good); }
.f-toast-bad  .material-icons-outlined { color: var(--bad); }
.f-toast-warn .material-icons-outlined { color: var(--warn); }
.f-toast-info .material-icons-outlined { color: var(--accent); }


/* ==========================================================================
   14  SMALL HELPERS
   ========================================================================== */

.f-copy {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--border);
    background: var(--surface);
    border-radius: var(--r-sm);
    padding: 3px 8px;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--text-2);
    cursor: pointer;
}

.f-copy:hover { border-color: var(--accent); color: var(--accent); }
.f-copy .material-icons-outlined { font-size: 14px; }

.f-kv { display: flex; flex-direction: column; gap: 3px; }
.f-kv-key { font-size: 11px; font-weight: 700; color: var(--text-3); }
.f-kv-val { font-size: 12.5px; font-weight: 600; color: var(--text); word-break: break-word; }


/* ==========================================================================
   15  STANDALONE OVERLAY  (.f-overlay)

   The POS sale panel, its QR result and the dashboard export are not
   bootstrap modals - their own JS opens them by writing style.display, and
   pos.html opens the QR panel by writing style.cssText, which REPLACES the
   whole inline style. So every property those overlays depend on has to live
   in a stylesheet: anything left inline is wiped the first time a sale
   completes. That is how they ended up hard-coding a #fff panel on a black
   scrim - unreadable in dark mode, and off-brand in light.

   Named .f-overlay and NOT .modal on purpose: `.modal` is already bootstrap's
   overlay in section 8 AND the supplied template's card panel in global.css.
   A third meaning for that name is the last thing this stylesheet needs.
   ========================================================================== */

.f-overlay {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: none;
    /* flex-start, not center. With margin:auto on the panel this still centres
       it while it fits and starts scrolling once it does not; align-items:
       center overflows in BOTH directions, which is what put the top of the
       POS form above the screen with nothing to scroll back to. */
    align-items: flex-start;
    justify-content: center;
    overflow-x: hidden;
    overflow-y: auto;
    padding: 24px 16px;
    background: rgba(11, 22, 28, .66);
}

/* the QR panel is opened BY the POS panel, so it has to outrank it */
.f-overlay-top { z-index: 1210; }

.f-overlay-panel {
    width: 100%;
    max-width: 560px;
    margin: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-xl);
    box-shadow: var(--sh-lg);
    overflow: hidden;
}

.f-overlay-panel-lg { max-width: 700px; }


/* --------------------------------------------------------------------------
   POS sale form

   Every control in this form is 40px tall. They were 40 (currency), 38
   (amount) and 34 (the ... menu button) - three heights across a two-field
   row. The menu button carried an explicit height from .btn, so the row's
   own stretch could not correct it.
   -------------------------------------------------------------------------- */

/* NOT .modal-body: that carries overflow-y:auto, which per spec forces the
   other axis to auto as well and turns the panel body into a scroll container
   - the ... menu is absolutely positioned inside it and would be clipped. */
.pos-body { padding: 16px 18px; }

.pos-head-ico {
    display: grid;
    place-items: center;
    flex: none;
    width: 28px;
    height: 28px;
    border-radius: var(--r-sm);
    background: var(--accent-soft);
    color: var(--accent);
}

.pos-row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }
.pos-row > .pos-currency   { flex: 0 0 152px; min-width: 130px; }
.pos-row > .pos-amount-col { flex: 1 1 210px; min-width: 0; }

.pos-label {
    display: block;
    margin: 0 0 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--text-3);
}

.pos-amount { display: flex; align-items: center; gap: 8px; }

.pos-amount > .form-control,
.pos-currency .custom-image-select-container .dropdown-toggle {
    height: 40px !important;
    min-height: 40px !important;
}

.pos-amount > .form-control { flex: 1 1 auto; font-weight: 600; }

.pos-menu-anchor { position: relative; flex: none; }

/* the border is restated because shell.css gives every .btn a TRANSPARENT one,
   which next to a bordered input reads as a floating white square */
.pos-menu-btn {
    width: 40px;
    height: 40px;
    padding: 0;
    flex: none;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text-3);
}

.pos-menu-btn:hover { border-color: var(--accent); color: var(--accent); }

.pos-menu {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    inset-inline-end: 0;
    z-index: 100;
    min-width: 152px;
    padding: 5px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    box-shadow: var(--sh-lg);
}

.pos-menu-item {
    display: block;
    padding: 7px 10px;
    border-radius: var(--r-sm);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2);
    cursor: pointer;
}

/* :hover, not the pair of inline onmouseover/onmouseout handlers this used to
   carry - those hard-coded #f3f4f6, which is invisible on a dark surface. */
.pos-menu-item:hover { background: var(--surface-3); color: var(--text); }

/* the discount / VAT cards selectAction() builds at runtime */
.pos-field-card {
    padding: 12px;
    margin-bottom: 8px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
}

.pos-field-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 8px;
    font-size: 12px;
    font-weight: 700;
    color: var(--text);
}

.pos-field-card .form-control,
.pos-field-card .form-select { height: 38px !important; min-height: 38px !important; }

.pos-summary {
    padding: 10px 13px;
    margin-bottom: 14px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
}

.pos-summary-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    font-size: 12px;
    color: var(--text-3);
}

.pos-summary-row + .pos-summary-row { margin-top: 5px; }

/* scoped to .pos-summary so it ties with the sibling selector above on
   specificity and wins on order - as a bare class it would lose, and the
   total would sit 5px from the VAT row with no separation */
.pos-summary .pos-summary-total {
    margin-top: 9px;
    padding-top: 9px;
    border-top: 1px solid var(--border);
    font-size: 13.5px;
    font-weight: 800;
    color: var(--text);
}

.pos-submit { width: 100%; height: 44px; font-size: 13.5px; }


/* --------------------------------------------------------------------------
   QR result panel - what the customer is shown, so it is the one that most
   needs to not be a white card floating in a dark room.
   -------------------------------------------------------------------------- */

.pos-qr-body { padding: 24px 26px 20px; text-align: center; }

.pos-qr-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 18px;
}

.pos-qr-img {
    width: 320px;
    max-width: 100%;
    border-radius: var(--r-lg);
    margin-bottom: 16px;
    background: #fff;   /* a QR has to stay black-on-white to be scannable */
    padding: 10px;
}

.pos-qr-link {
    display: block;
    padding: 13px;
    border-radius: var(--r-md);
    background: var(--accent);
    color: var(--accent-fg);
    text-decoration: none;
    font-weight: 700;
    font-size: 13.5px;
}

.pos-qr-link:hover { background: var(--accent-2); color: var(--accent-fg); }


/* ==========================================================================
   VALIDATION - shared by every control above
   ========================================================================== */

.f-field .input.is-invalid,
.f-field .textarea.is-invalid,
.f-field select.is-invalid { border-color: var(--bad); }

.f-field:has(.is-invalid) > .f-pick,
.f-field:has(.is-invalid) > .f-combo { border-color: var(--bad); }

/* An image_choice has no .input for ui.field.error() to mark, so the flagged
   state is drawn on the field itself. */
.f-field.has-err > .f-imgpick { border-color: var(--bad); }

.api-field-error,
.f-field .invalid-feedback {
    display: block;
    font-size: 11px;
    font-weight: 600;
    color: var(--bad);
}

/* handleApiErrors() appends its message to the control's parentNode, which
   inside a .f-pick / .f-combo is the flex row itself - float it underneath
   instead of letting it stretch the control */
.f-pick > .api-field-error,
.f-combo > .api-field-error {
    position: absolute;
    top: calc(100% + 5px);
    inset-inline-start: 0;
    white-space: nowrap;
}

.f-field:has(> .f-pick > .api-field-error),
.f-field:has(> .f-combo > .api-field-error) { padding-bottom: 17px; }


/* ==========================================================================
   MOBILE
   Everything above reflows on its own; these are the places that need a real
   decision rather than just less width.
   ========================================================================== */

/* Nothing may push the page sideways. One overflowing table or dropdown makes
   the whole document pannable, and every fixed element then drifts with it. */
html, body { max-width: 100%; overflow-x: hidden; }

/* a table wide enough to need it scrolls inside its own box, never the page */
.table-wrapper,
.table-container,
.tbl-wrap { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* the searchable-select panel must never be wider than the screen it opens on */
.f-ss-panel,
.f-combo .f-ss-panel { max-width: min(340px, calc(100vw - 24px)); }

@media (max-width: 900px) {
    /* a picker modal is nearly full screen here, so the padding that framed it
       on a desktop is just lost space */
    .modal { padding: 12px 10px; }
    .modal-content { max-height: calc(100vh - 24px); border-radius: var(--r-lg); }
    .modal-header  { padding: 11px 13px; }
    .modal-body    { padding: 13px; }
    .modal-footer  { padding: 11px 13px; }
    .modal-xl, .modal-lg, .modal-sm { max-width: 100%; }

    .f-sec-body { padding: 13px; }
    .f-sec-head { padding: 11px 13px; }
}

@media (max-width: 620px) {
    /* the action bar wraps rather than squeezing the labels off the buttons,
       and the primary action takes the full width so it is the obvious target */
    .f-foot { flex-wrap: wrap; padding: 10px 12px; gap: 7px; }
    .f-foot > .btn,
    .f-foot > a.btn { flex: 1 1 auto; justify-content: center; }
    .f-foot > .btn-primary { flex: 1 1 100%; order: -1; }

    .page-head { gap: 11px; }
    .page-title { font-size: 18px; }
    .page-head-actions { width: 100%; }
    .page-head-actions > .btn { flex: 1 1 auto; justify-content: center; }

    /* a dialling code and a number cannot both be comfortable on a narrow
       screen - give the code the room it needs and no more */
    .f-combo > .f-select-wrap { min-width: 96px; max-width: 118px; }

    .f-svc-body { padding: 11px; }

    .f-toast-host { inset-inline: 12px; bottom: 12px; }
    .f-toast { min-width: 0; max-width: 100%; }

    /* card view: the engine asks for 280px columns, which overflows a 320px
       screen once padding is counted */
    .card-view-container {
        grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr)) !important;
    }

    .pagination .page-link { min-width: 28px; height: 28px; font-size: 11.5px; }
}

/* a finger needs a bigger hit area than a mouse */
@media (pointer: coarse) {
    .f-ss-opt { padding: 10px 9px; }
    .f-opt    { padding: 10px 11px; }
    .btn-close { width: 34px; height: 34px; }
}

/* entity view: status actions sit at the trailing edge of the identity strip */
.f-ident-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 7px;
    margin-inline-start: auto;
    align-self: center;
}

@media (max-width: 620px) {
    .f-ident-actions { margin-inline-start: 0; width: 100%; }
    .f-ident-actions > .btn { flex: 1 1 auto; justify-content: center; }
}


/* ==========================================================================
   BOOTSTRAP NAV / TAB PANES
   Bootstrap's JS drives `data-bs-toggle="tab"` by toggling .active and .show,
   but its stylesheet - which is what gives those classes any meaning - is not
   loaded here. So a <ul class="nav nav-pills"> rendered as a bulleted list and
   every .tab-pane stacked on top of the others, or showed nothing useful.

   Used by the E-Invoice block on the entity view and by the sub-system pages
   that have not been ported yet.
   ========================================================================== */

.nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.nav-item { display: block; }

.nav-link {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 12px;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2);
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
}

.nav-link:hover { background: var(--surface-3); color: var(--text); }

.nav-pills .nav-link.active,
.nav-link.active {
    background: var(--accent-soft);
    border-color: transparent;
    color: var(--accent);
    font-weight: 800;
}

.nav-link .material-icons-outlined { font-size: 17px; }

/* only the active pane is shown - this is the rule whose absence made the
   panes render on top of each other */
.tab-content > .tab-pane { display: none; }
.tab-content > .tab-pane.active { display: block; }

.fade { transition: opacity .15s linear; }
.fade:not(.show) { opacity: 0; }
.tab-pane.active.show,
.tab-pane.active { opacity: 1; }


/* ==========================================================================
   SPLIT LAYOUT - a form with a standing aside

   Used where a form is deciding something the user needs to keep an eye on
   while they fill it in (user create/update: what the person will be able to
   reach). The aside sticks, so the answer stays visible while the form
   scrolls. Below 1100px it stops being an aside and simply leads the form -
   on a phone the summary is context you read once, not a companion.
   ========================================================================== */

.f-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 18px;
    align-items: start;
}

.f-split-aside { position: sticky; top: 84px; }

@media (max-width: 1100px) {
    .f-split { grid-template-columns: minmax(0, 1fr); }
    .f-split-aside { position: static; order: -1; }
}


/* ==========================================================================
   SCOPE SUMMARY - boxs/ui/scope_summary.html
   ========================================================================== */

.f-scope {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-sm);
    overflow: hidden;
}

.f-scope-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
}

.f-scope-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex: 0 0 32px;
    border-radius: var(--r-md);
    background: var(--accent-soft);
    color: var(--accent);
}

.f-scope-ico .material-icons-outlined { font-size: 18px; }
.f-scope-text { min-width: 0; }
.f-scope-title { font-size: 13px; font-weight: 800; color: var(--text); }
.f-scope-sub { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }

.f-scope-chain {
    list-style: none;
    margin: 0;
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 0;
}

/* the connector runs on the leading edge, so it mirrors in Arabic without a
   second rule - hence padding-inline-start and inset-inline-start */
.f-scope-step {
    position: relative;
    padding-inline-start: 20px;
    padding-bottom: 14px;
}

.f-scope-step:last-child { padding-bottom: 0; }

.f-scope-step::before {
    content: '';
    position: absolute;
    inset-inline-start: 0;
    top: 4px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--accent);
}

.f-scope-step::after {
    content: '';
    position: absolute;
    inset-inline-start: 4px;
    top: 13px;
    bottom: 0;
    width: 1px;
    background: var(--border-2);
}

.f-scope-step:last-child::after { display: none; }
.f-scope-step.is-fixed::before { background: var(--border-2); }

.f-scope-step-key {
    display: block;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .3px;
    text-transform: uppercase;
    color: var(--text-4);
}

.f-scope-step-val {
    display: block;
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    word-break: break-word;
    margin-top: 2px;
}

.f-scope-step.is-fixed .f-scope-step-val { color: var(--text-2); font-weight: 600; }

.f-scope-step-tag {
    display: inline-block;
    margin-top: 3px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--surface-3);
    font-size: 10px;
    font-weight: 700;
    color: var(--text-3);
}

.f-scope-note {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 0;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    font-size: 11.5px;
    line-height: 1.55;
    color: var(--text-3);
}

.f-scope-note .material-icons-outlined { font-size: 15px; flex: 0 0 auto; color: var(--info); }


/* ==========================================================================
   REVEALABLE BLOCK - a group of fields that only appears when asked for

   The password pair on user/update. Blank means "keep the current password",
   so the fields must start empty AND look deliberate - a pair of empty
   password boxes on an edit form reads as "the password was lost".
   ========================================================================== */

.f-reveal-block[hidden] { display: none; }
.f-reveal-block { margin-top: 12px; }


/* ==========================================================================
   FEE ROW - boxs/fee_group/create + update

   A fee group is three fees, and three full section cards for three rows of
   four numbers is more chrome than content. One card, three labelled bands.
   ========================================================================== */

.f-fee-row {
    padding: 14px 0;
    border-top: 1px solid var(--border);
}

.f-fee-row:first-of-type { border-top: 0; padding-top: 4px; }

.f-fee-role {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
}

.f-fee-role .material-icons-outlined { font-size: 18px; color: var(--accent); }
.f-fee-role-text { min-width: 0; }
.f-fee-role-title { font-size: 13px; font-weight: 800; color: var(--text); }
.f-fee-role-sub { font-size: 11.5px; color: var(--text-3); margin-top: 1px; }

/* The routing pair, hidden until asked for. */
.f-fee-routing[hidden] { display: none; }


/* ==========================================================================
   DIFF LIST - boxs/ui/diff_list.html

   One row per column that moved: what it was, what it is now. Three columns
   on a wide screen; on a narrow one the arrow turns and they stack, because
   before-over-after still reads as a change while before-beside-after in a
   40px box does not.

   Values are pre-wrapped and breakable: an audit row can hold a URL, a JSON
   blob or a 400-character description, and a cell that clips one of those is
   a cell that lies about what changed.
   ========================================================================== */

.f-diff { display: flex; flex-direction: column; }

.f-diff-row {
    display: grid;
    grid-template-columns: minmax(140px, 1fr) minmax(0, 1.4fr) 20px minmax(0, 1.4fr);
    gap: 10px;
    align-items: start;
    padding: 10px 0;
    border-top: 1px solid var(--border-2);
}

.f-diff-row:first-child { border-top: 0; padding-top: 2px; }

.f-diff-field {
    font-size: 12px;
    font-weight: 700;
    color: var(--text-2);
    word-break: break-word;
}

.f-diff-table { display: block; font-size: 10.5px; font-weight: 600; color: var(--text-4); margin-top: 2px; }

.f-diff-val {
    font-size: 12px;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
    min-width: 0;
}

.f-diff-before { color: var(--text-3); text-decoration: line-through; text-decoration-color: var(--border); }
.f-diff-after  { font-weight: 700; }

.f-diff-empty { color: var(--text-4); font-weight: 600; text-decoration: none; }

.f-diff-arrow {
    font-size: 16px;
    color: var(--text-4);
    justify-self: center;
    margin-top: -1px;
}

@media (max-width: 720px) {
    .f-diff-row { grid-template-columns: 1fr; gap: 4px; }
    .f-diff-arrow { justify-self: start; transform: rotate(90deg); }
}

/* Logo cell in a data table - gateway marks, card brands, entity logos.
   contain, never cover: these are logos, and a wide mark cropped to fill a
   square is no longer the brand. Height is fixed so every row lines up and
   width follows the artwork, which is why the tile is a flex box rather than
   a sized <img>. */
.tbl-logo {
    display: block;
    height: 26px;
    width: auto;
    max-width: 72px;
    object-fit: contain;
    object-position: center;
}

/* What the same cell draws when there is no logo, or when the one on file no
   longer resolves. Same 26px height as .tbl-logo so a page of merchants where
   most have none does not shift row to row, and a muted tile rather than an
   image - "nothing here" should not draw the eye the way a brand mark does. */
.tbl-logo-empty {
    display: grid;
    place-items: center;
    height: 26px;
    width: 26px;
    border-radius: 7px;
    background: var(--surface-3);
    border: 1px solid var(--border);
    color: var(--text-4);
}

.tbl-logo-empty .material-icons-outlined {
    font-size: 15px;
}

/* display:grid beats the attribute on its own, so say it. */
.tbl-logo-empty[hidden] {
    display: none;
}

/* ==========================================================================
   cdash - dashboard primitives: stat tiles, meters, split bars, ranked rows.
   Named apart from f-* because these encode DATA, and the rules that keep
   them readable are not the form rules. Three of them:
     - marks are thin, with 4px rounded ends, and sit on the surface
     - a 2px surface gap separates two fills, so adjacent halves stay countable
     - text never wears a mark colour; the dot beside it carries identity
   Everything draws from the theme tokens, so dark mode follows for free.
   ========================================================================== */

.cdash {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.cdash-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.cdash-title {
    margin: 0;
    font-size: 20px;
    font-weight: 700;
    color: var(--ink);
}

.cdash-sub {
    margin: 2px 0 0;
    font-size: 13px;
    color: var(--text-3);
}

.cdash-head-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.cdash-asof {
    font-size: 12px;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ---- stat tiles --------------------------------------------------------- */

.cdash-kpis {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
}

.cdash-kpi {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 16px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
}

.cdash-kpi-label {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    color: var(--text-3);
}

/* Tabular figures so the four tiles line up digit-for-digit instead of
   jittering as the numbers change width. */
.cdash-kpi-value {
    font-size: 26px;
    font-weight: 700;
    line-height: 1.15;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

.cdash-kpi-value small {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-3);
}

.cdash-kpi-foot {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
}

/* ---- status dot: identity beside a label, never instead of one ---------- */

.cdash-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: 0 0 auto;
    background: var(--text-4);
}

.cdash-dot-good  { background: var(--good); }
.cdash-dot-warn  { background: var(--warn); }

/* ---- meter: one value, one hue ----------------------------------------- */

.cdash-meter {
    margin-top: 6px;
    height: 6px;
    border-radius: 999px;
    background: var(--surface-3);
    overflow: hidden;
}

.cdash-meter-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--accent);
}

/* ---- split bar: two fills, 2px of surface between them ------------------ */

.cdash-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 20px;
    margin-bottom: 10px;
}

.cdash-legend-item {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 13px;
    color: var(--text-2);
    font-variant-numeric: tabular-nums;
}

.cdash-split {
    display: flex;
    gap: 2px;
    height: 10px;
}

.cdash-split-paid,
.cdash-split-unpaid {
    height: 100%;
    min-width: 2px;
}

.cdash-split-paid {
    background: var(--good);
    border-radius: 4px 0 0 4px;
}

.cdash-split-unpaid {
    background: var(--warn);
    border-radius: 0 4px 4px 0;
}

.cdash-total {
    font-size: 14px;
    font-weight: 700;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

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

.cdash-cols {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
}

.cdash-rank,
.cdash-clients {
    list-style: none;
    margin: 0;
    padding: 0;
}

.cdash-rank-row {
    display: grid;
    grid-template-columns: minmax(90px, 130px) 1fr auto;
    align-items: center;
    gap: 12px;
    padding: 7px 0;
}

.cdash-rank-name {
    font-size: 13px;
    color: var(--text-2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cdash-rank-track {
    height: 8px;
    border-radius: 999px;
    background: var(--surface-3);
    overflow: hidden;
}

.cdash-rank-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--accent);
}

/* The value is the label; the count under it is the qualifier. Both tabular
   so the column reads as a column. */
.cdash-rank-value {
    text-align: end;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.cdash-rank-value small {
    display: block;
    font-size: 11px;
    font-weight: 500;
    color: var(--text-4);
}

/* ---- top clients -------------------------------------------------------- */

.cdash-client {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
}

.cdash-client:last-child {
    border-bottom: 0;
}

.cdash-client-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.cdash-client-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cdash-client-sub {
    font-size: 12px;
    color: var(--text-3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cdash-client-figures {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex: 0 0 auto;
}

.cdash-client-amount {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.cdash-client-count {
    font-size: 11px;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.cdash-debug {
    margin: 0;
    font-size: 11px;
    color: var(--text-4);
    font-variant-numeric: tabular-nums;
}

@media (max-width: 1100px) {
    .cdash-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cdash-cols { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
    .cdash-kpis { grid-template-columns: 1fr; }
    .cdash-rank-row { grid-template-columns: minmax(70px, 100px) 1fr auto; gap: 8px; }
}


/* ==========================================================================
   25  LINE ITEMS  -  boxs/ui/line_items.html

   An editable table of things being charged for. It is a table because the
   rows are the same four questions repeated, and reading down a column is how
   anyone checks their own work - a stack of cards makes "did I price these
   consistently" a scrolling exercise.

   The inputs are borderless until focused so the grid, not the boxes, is what
   the eye follows. They are still real .input controls underneath, so the
   focus ring, the error state and the dark theme all come from one place.
   ========================================================================== */

.f-lines { border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; background: var(--surface); }

.f-lines .tbl { width: 100%; min-width: 640px; border-collapse: collapse; }
.f-lines .tbl th { padding: 9px 12px; }
.f-lines .tbl td { padding: 5px 8px; vertical-align: middle; }
.f-lines .tbl tbody tr:hover { background: transparent; }

.f-lines-input {
    width: 100%;
    padding: 7px 9px;
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    background: transparent;
    font: inherit;
    font-size: 12.5px;
    color: var(--text);
}
.f-lines-input::placeholder { color: var(--text-4); }
.f-lines-input:hover { border-color: var(--border); }
.f-lines-input:focus { outline: 0; border-color: var(--accent); background: var(--surface); box-shadow: 0 0 0 3px var(--accent-soft); }
.f-lines-input.is-num { text-align: end; font-variant-numeric: tabular-nums; }

/* The browser's spinner on a money field is a trap: a click changes the
   amount by one minor unit and nothing says it happened. */
.f-lines-input[type=number]::-webkit-outer-spin-button,
.f-lines-input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.f-lines-input[type=number] { -moz-appearance: textfield; }

.f-lines-total { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); white-space: nowrap; }

.f-lines-drop {
    display: inline-grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-4);
    cursor: pointer;
}
.f-lines-drop:hover { background: var(--bad-soft); color: var(--bad); }
.f-lines-drop:disabled { opacity: .35; cursor: not-allowed; }
.f-lines-drop:disabled:hover { background: transparent; color: var(--text-4); }
.f-lines-drop .material-icons-outlined { font-size: 18px; }

.f-lines-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 10px 12px;
    border-top: 1px solid var(--border-2);
    background: var(--surface-2);
}
.f-lines-foot .spacer { margin-inline-start: auto; }
.f-lines-count { font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* The row is flagged, not the individual box: an item is wrong as an item. */
.f-lines .tbl tr.has-err td { background: var(--bad-soft); }
.f-lines .tbl tr.has-err .f-lines-input { border-color: var(--bad); }


/* ==========================================================================
   26  INVOICE SUMMARY  -  the money panel beside an invoice form

   Four numbers that have to agree with the lines above them. Given its own
   block rather than a .f-kv-grid because the last line is the answer and has
   to look like it.
   ========================================================================== */

.f-sum { display: flex; flex-direction: column; gap: 2px; }

.f-sum-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 6px 0;
    font-size: 12.5px;
    color: var(--text-2);
}
.f-sum-row + .f-sum-row { border-top: 1px solid var(--border-2); }
.f-sum-val { font-variant-numeric: tabular-nums; color: var(--text); white-space: nowrap; }
.f-sum-val.is-minus { color: var(--bad); }

.f-sum-total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-top: 8px;
    padding-top: 11px;
    border-top: 2px solid var(--border);
}
.f-sum-total-key { font-size: 13px; font-weight: 700; color: var(--text); }
.f-sum-total-val { font-size: 19px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; }
.f-sum-total-val small { font-size: 12px; font-weight: 600; color: var(--text-3); margin-inline-start: 4px; }


/* ---- accordion header extras  -  boxs/ui/accordion.html -------------------
   The component says what is inside without being opened: a sub line under the
   title, and a summary of the current values on the trailing edge. A
   disclosure the operator has to open to find out whether they need to open it
   has not saved anybody anything. */

.f-acc-head > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.f-acc-title { display: flex; align-items: center; gap: 8px; min-width: 0; }

/* ---- the facts row  -  c_chips ------------------------------------------
   READ-ONLY, and deliberately not .f-chip: that one is a dashed button that
   opens a panel, and something that looks like a button and does nothing is
   worse than plain text. These are measurements - HTTP 502, 378 ms, 4.1 KB -
   sitting under the title so the disclosure can be READ rather than opened.

   The tone is only ever spent on something wrong. A row where every fact is
   coloured has no emphasis left for the one fact that matters. */

/* The step number. Fixed width so twenty-four of them stack into a column and
   the titles beside them line up - a ragged left edge on a numbered list reads
   as damage. */
.f-acc-head-tall { align-items: flex-start; }
.f-acc-head-tall > .f-acc-note,
.f-acc-head-tall > .f-acc-caret { margin-top: 2px; }

/* Selected through .f-acc-head so it beats `.f-acc-head > span`, which turns
   every direct child of the header into a flex column and would leave the
   number sitting on the top edge of its own badge. */
.f-acc-head > .f-acc-step {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    min-width: 22px;
    height: 22px;
    padding: 0 5px;
    border-radius: 7px;
    background: var(--surface-3);
    font-size: 11px;
    font-weight: 700;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
}
.f-acc.open > .f-acc-head > .f-acc-step { background: var(--accent-soft); color: var(--accent); }

.f-acc-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 5px 6px;
    margin-top: 4px;
}

.f-fact {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 7px;
    border-radius: 6px;
    background: var(--surface-3);
    font-size: 10.5px;
    font-weight: 600;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.f-fact .material-icons-outlined { font-size: 12px; color: var(--text-4); }

.f-fact-bad  { background: var(--bad-soft);  color: var(--bad); }
.f-fact-bad .material-icons-outlined  { color: var(--bad); }
.f-fact-warn { background: var(--warn-soft); color: var(--warn); }
.f-fact-warn .material-icons-outlined { color: var(--warn); }

.f-acc-sub {
    font-size: 11.5px;
    font-weight: 400;
    color: var(--text-3);
    white-space: normal;
}

.f-acc-note {
    margin-inline-start: auto;
    font-size: 11.5px;
    font-weight: 500;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.f-acc-head .f-acc-note + .f-acc-caret { margin-inline-start: 8px; }

/* The nested sections inside a disclosure are already framed by it. */
.f-acc-body > .f-sec { border: 0; box-shadow: none; background: transparent; }
.f-acc-body > .f-sec > .f-sec-head { padding-inline: 0; }
.f-acc-body > .f-sec > .f-sec-body { padding-inline: 0; }
.f-acc-body > .f-sec + .f-sec > .f-sec-head { border-top: 1px solid var(--border-2); }


/* ==========================================================================
   27  INVOICE SHEET  -  the create / edit invoice layout

   One column, read top to bottom, at the width of a document rather than the
   width of the screen. The two-column split it replaces put the money in a
   sticky aside beside the lines, which meant the operator's eye had to hold
   two places at once on the one page where the numbers are the point.

   Long measure is the enemy of a form. 900px is about as wide as a row of
   three fields wants to be before the label and its box stop reading as one
   thing.
   ========================================================================== */

.f-sheet { max-width: 900px; }
.f-sheet > * + * { margin-top: 14px; }

/* ---- the optional half of the form, as chips ------------------------------
   A discount, a note, a different due date: things a minority of invoices
   need. As labelled sections they are permanent furniture that everybody
   reads past. As chips they are one line of quiet buttons that say what they
   would add, and the panel appears under them when asked for.

   This is the difference between a form with twenty fields and a form with
   six and a row of offers. */

.f-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: flex-start;
}

/* A row of offers sitting under the fields it belongs to needs air above it,
   or it reads as a fourth row of the form rather than as the quiet line of
   things the form is NOT asking for. The gap is the .f-grid's own row gap, so
   the chips sit exactly where a fifth field would have. */
.f-grid + .f-chips { margin-top: 14px; }

/* An open panel takes the whole width of the row rather than the width of the
   chip that opened it. .f-more is display:contents, so the panel is a flex
   item of this row even though the DOM has it one level down - which is why
   this is a descendant selector and not a child one. */
.f-chips .f-more-body { flex: 1 0 100%; }

.f-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 13px;
    border: 1px dashed var(--border);
    border-radius: 999px;
    background: var(--surface);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2);
    cursor: pointer;
    transition: border-color .12s ease, color .12s ease, background .12s ease;
}
.f-chip:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.f-chip .material-icons-outlined { font-size: 16px; }

/* Once its panel is open the chip stops being an offer and becomes the panel's
   own header - solid, and the icon turns into a way to put it away again. */
.f-more.open > .f-chip {
    border-style: solid;
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
}
.f-more.open > .f-chip .f-chip-plus { transform: rotate(45deg); }
.f-chip-plus { transition: transform .15s ease; }

/* The current value, so the chip answers its own question without being
   opened: "Due 4 Sep 2026" rather than "Due date". */
.f-chip-value {
    font-weight: 500;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
}
.f-more.open > .f-chip .f-chip-value { color: var(--accent); }

.f-more { display: contents; }
.f-more-body {
    flex: 1 0 100%;
    margin-top: 2px;
    padding: 15px;
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    background: var(--surface);
}
.f-more:not(.open) > .f-more-body { display: none; }

/* ---- the totals, under the lines, where an invoice puts them ------------- */

.f-sheet-total {
    display: flex;
    justify-content: flex-end;
    padding-top: 14px;
    margin-top: 14px;
    border-top: 1px solid var(--border-2);
}
.f-sheet-total .f-sum { width: 100%; max-width: 320px; }

/* ---- the total, repeated in the action bar ------------------------------
   The bar is sticky, so this is the one number that is on screen no matter
   how far down the lines the operator has scrolled. */

.f-foot-total {
    display: flex;
    align-items: baseline;
    gap: 9px;
}
.f-foot-total-key {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-3);
}
.f-foot-total-val {
    font-size: 17px;
    font-weight: 700;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.f-foot-total-val small { font-size: 11.5px; font-weight: 600; color: var(--text-3); margin-inline-start: 3px; }

@media (max-width: 620px) {
    .f-foot-total-key { display: none; }
    .f-sheet-total .f-sum { max-width: none; }
}


/* ==========================================================================
   28  INVOICE SHEET  -  systems/code/boxs/code/{create,update,view}

   The invoice IS the form. Not a form beside a preview: there is one surface,
   and every value the customer will read is typed where it will print. That
   removes a whole class of screen-disagrees-with-record problems by removing
   the second copy.

   Two faces of the same sheet, switched by .inv-seg:
     Invoice   what the customer receives
     Settings  what they never see - notifications, references, gateways

   Neither is a drawer, a chip row, or a panel bolted under the page. Both are
   the same card, the same paper, the same type. Anything that floats beside a
   document reads as an afterthought, and gets treated as one.
   ========================================================================== */

/* THE SHEET FILLS THE PAGE. It was capped at 940px, which left the invoice in
   a narrow column with the rest of the window empty beside it - and the page
   head above it running the full width, so the card looked like it had failed
   to load rather than like a document with a margin.

   940 was chosen as a reading measure, and a measure is the right idea for a
   paragraph. This is not one: it is a masthead, a bill-to block, a line table
   with six columns and a totals stack, and every one of them was being
   squeezed - the description column truncating while a third of the screen sat
   unused. Wider gives the lines room; the type inside is sized in its own
   units and does not stretch with it. */
.inv-shell { display: flex; flex-direction: column; gap: 11px; }

/* ---- the two faces ------------------------------------------------------- */
.inv-seg {
    display: inline-flex; align-self: flex-start;
    padding: 3px; gap: 3px;
    border: 1px solid var(--border); border-radius: 999px; background: var(--surface-2);
}
.inv-seg button {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 15px; border: 0; border-radius: 999px; background: transparent;
    color: var(--text-3); font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer;
    transition: background .14s, color .14s;
}
.inv-seg button:hover { color: var(--text); }
.inv-seg button[aria-selected="true"] { background: var(--surface); color: var(--text); box-shadow: var(--sh-sm); }
.inv-seg .inv-tick { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: none; opacity: 0; transition: opacity .14s; }
.inv-seg .inv-tick.on { opacity: 1; }

/* ---- the paper ----------------------------------------------------------- */
/* The paper was set for a printed page: 34/36px margins and 22px between every
   block. On screen, inside a card, inside a padded page, that is three nested
   frames of empty space before anything is read - the header alone pushed the
   dates a third of the way down. Tighter margins and tighter blocks; the sheet
   still reads as a document, with less scrolling to see one. */
.inv-sheet {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--r-lg); box-shadow: var(--sh-md);
    padding: 22px 26px 20px;
    display: flex; flex-direction: column; gap: 15px;
}
.inv-sheet[hidden] { display: none; }
.inv-rule { height: 1px; background: var(--border); border: 0; margin: 0; }
.inv-eyebrow {
    font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
    color: var(--text-3); margin-bottom: 7px;
}

/* A value typed onto the paper. Document text until you go near it: a hairline
   on hover, the accent on focus. It must never look like a form control, and
   must never look inert either. */
.inv-w {
    font: inherit; color: inherit; background: transparent;
    border: 0; border-bottom: 1px solid transparent; border-radius: 0;
    padding: 2px 1px; margin: 0; width: 100%;
    transition: border-color .12s, background .12s;
}
.inv-w::placeholder { color: var(--text-4); }
.inv-w:hover { border-bottom-color: var(--border); }
.inv-w:focus { outline: 0; border-bottom-color: var(--accent); background: var(--accent-soft); }
.inv-w.num { text-align: end; font-variant-numeric: tabular-nums; }
.inv-w[type=number]::-webkit-inner-spin-button,
.inv-w[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.inv-w[type=number] { -moz-appearance: textfield; }

/* A CHOICE on the paper - reads as the word it replaces, but dashed and
   chevroned so it is plainly pickable. */
.inv-pick { position: relative; display: inline-flex; align-items: center; }
.inv-pick select {
    font: inherit; color: inherit; background: transparent;
    border: 0; border-bottom: 1px dashed var(--text-4); border-radius: 0;
    padding: 1px 17px 1px 2px; margin: 0; cursor: pointer;
    -webkit-appearance: none; appearance: none;
    transition: border-color .12s, color .12s, background .12s;
}
/* Whatever a pickable word looks like on the paper, its dropdown reads as a
   dropdown. The .inv-doc override below says the same thing again because it
   has to outscore its own title styling. */
.inv-pick select option {
    font-size: 13px; font-weight: 500; letter-spacing: normal; text-transform: none;
    color: var(--text); background: var(--surface);
}
.inv-pick select:hover { border-bottom-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.inv-pick select:focus { outline: 0; border-bottom-style: solid; border-bottom-color: var(--accent); background: var(--accent-soft); }
.inv-pick::after {
    content: ""; position: absolute; inset-inline-end: 5px; top: 50%;
    width: 5px; height: 5px;
    border-right: 1.5px solid var(--text-4); border-bottom: 1.5px solid var(--text-4);
    transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
.inv-pick:hover::after { border-color: var(--accent); }

/* ---- masthead ------------------------------------------------------------ */
.inv-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; flex-wrap: wrap; }
.inv-issuer { display: flex; gap: 12px; align-items: center; }
.inv-mark {
    width: 42px; height: 42px; border-radius: var(--r-md); flex: none;
    background: var(--accent); color: var(--accent-fg);
    display: grid; place-items: center; font-weight: 800; font-size: 15px; letter-spacing: -.02em;
    overflow: hidden;
}
.inv-issuer-name { font-weight: 700; font-size: 15px; }
.inv-issuer-line { font-size: 12px; color: var(--text-3); }
.inv-doc { text-align: end; display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
/* THE DOCUMENT WORD, at the size of the issuer's name opposite it.
   It was 22px at weight 800 in the accent colour - louder than the customer,
   louder than the total, louder than anything the invoice is actually about,
   and it is the one word on the page nobody needs to be told twice. At 15px
   and 700 it sits on the same line as the issuer name across the header and
   the two balance, which is what the rest of the sheet does: the paper is
   quiet and the values are what you read. */
.inv-doc .inv-pick select {
    font-size: 15px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
    color: var(--text-2); padding-inline-end: 16px;
}
.inv-doc .inv-pick select:hover,
.inv-doc .inv-pick select:focus { color: var(--accent); }

/* THE WORD IS THE TITLE. THE LIST IS A LIST.
   A native <option> inherits the select's font, so the styling that makes the
   closed control read as the document's title - 22px, 800 weight, .13em
   tracking, uppercase, accent - was being applied to every row of the open
   dropdown too. Four lines of giant spaced-out blue capitals is not a menu you
   read, it is a menu you squint at. The options are plain text at the size
   everything else on the sheet is. */
.inv-doc .inv-pick select option {
    font-size: 13px;
    font-weight: 500;
    letter-spacing: normal;
    text-transform: none;
    color: var(--text);
    background: var(--surface);
}
.inv-doc .inv-pick::after { width: 6px; height: 6px; border-width: 2px; inset-inline-end: 3px; }

/* The same word where it is stated rather than picked - the view page and the
   paid sheet. Same size, so an invoice does not change shape between reading
   it and editing it. */
.inv-kind { font-size: 15px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--text-2); }
.inv-no { display: flex; align-items: center; gap: 5px; font-family: var(--mono); font-size: 12.5px; color: var(--text-3); }
.inv-no .inv-w { text-align: end; font-family: var(--mono); font-size: 12.5px; max-width: 158px; }

/* ---- who / when ---------------------------------------------------------- */
.inv-parties { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 22px; }
.inv-to-name .inv-w { font-size: 16px; font-weight: 700; }
.inv-to-rows { display: flex; flex-direction: column; gap: 2px; margin-top: 5px; font-size: 13px; color: var(--text-2); }
.inv-to-line { display: flex; align-items: center; gap: 6px; }
.inv-to-line .pre { color: var(--text-4); font-size: 12px; flex: none; }
.inv-dates { display: flex; flex-direction: column; gap: 5px; }
.inv-date { display: grid; grid-template-columns: auto minmax(0, 142px); align-items: center; gap: 10px; justify-content: end; }
.inv-date .k { font-size: 12px; color: var(--text-3); text-align: end; }
.inv-date .inv-w { font-size: 12.5px; text-align: end; font-variant-numeric: tabular-nums; }

/* ---- lines --------------------------------------------------------------- */
.inv-lines-wrap { overflow-x: auto; }
table.inv-lines { width: 100%; min-width: 580px; border-collapse: collapse; }
table.inv-lines th {
    font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
    color: var(--text-3); text-align: start; padding: 0 8px 8px;
    border-bottom: 1px solid var(--border); white-space: nowrap;
}
table.inv-lines th.end, table.inv-lines td.end { text-align: end; }
table.inv-lines td { padding: 6px 8px; border-bottom: 1px solid var(--border-2); vertical-align: middle; }
table.inv-lines tr[data-line-deleted] { display: none; }
.inv-amt { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.inv-kill {
    width: 24px; height: 24px; border: 0; border-radius: var(--r-sm);
    background: transparent; color: var(--text-4); cursor: pointer;
    display: grid; place-items: center; font-size: 17px; line-height: 1;
}
.inv-kill:hover { background: var(--bad-soft); color: var(--bad); }
.inv-kill:disabled { opacity: .3; cursor: not-allowed; }
.inv-kill:disabled:hover { background: transparent; color: var(--text-4); }
.inv-link {
    background: transparent; border: 0; padding: 5px 2px; text-align: start;
    color: var(--accent); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.inv-link:hover { text-decoration: underline; }

/* ---- the money, under the lines where an invoice puts it ----------------- */
.inv-totals { display: flex; justify-content: flex-end; }
.inv-totals-in { width: 100%; max-width: 340px; display: flex; flex-direction: column; }
.inv-trow { display: flex; justify-content: space-between; align-items: center; gap: 14px; padding: 5px 0; font-size: 13px; color: var(--text-2); }
.inv-trow .v { font-variant-numeric: tabular-nums; color: var(--text); white-space: nowrap; }
.inv-trow .v.minus { color: var(--bad); }
.inv-trow .lbl { display: flex; align-items: center; gap: 6px; min-width: 0; }
.inv-trow .lbl .inv-w { width: 72px; }
.inv-grand {
    display: flex; justify-content: space-between; align-items: baseline; gap: 14px;
    margin-top: 8px; padding-top: 11px; border-top: 2px solid var(--border);
}
.inv-grand .k { font-size: 13px; font-weight: 700; }
.inv-grand .amtwrap { display: flex; align-items: baseline; gap: 7px; }
.inv-grand .v { font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.inv-grand .inv-pick select { font-size: 12.5px; font-weight: 700; color: var(--text-3); letter-spacing: .04em; }
.inv-grand .cur { font-size: 12.5px; font-weight: 700; color: var(--text-3); letter-spacing: .04em; }

.inv-note .inv-w { font-size: 13px; color: var(--text-2); resize: vertical; min-height: 50px; border-bottom-color: var(--border-2); }

/* ---- the other face ------------------------------------------------------ */
.inv-back { display: flex; align-items: center; gap: 9px; font-size: 12.5px; color: var(--text-3); }
.inv-settings { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 22px 32px; }
.inv-grp { display: flex; flex-direction: column; gap: 11px; min-width: 0; }
.inv-grp > header { display: flex; flex-direction: column; gap: 2px; }
.inv-grp h3 { margin: 0; font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); }
.inv-grp .sub { font-size: 12px; color: var(--text-3); line-height: 1.45; }

/* The merchant's own live gateways. All on = no restriction, which is exactly
   what settings_multi_paymentMethod_id means when it is empty. */
/* The merchant's live gateways, as chips. They were bordered tiles two across
   with a code under each name - a block the height of the notifications beside
   them, for a control most invoices never touch. Every chip on means NO
   restriction, which is what an empty multi_paymentMethod_id says to the API,
   so leaving them alone sends nothing at all. */
.inv-meths { display: flex; flex-wrap: wrap; gap: 6px; }
.inv-meth { position: relative; display: inline-flex; }
.inv-meth input { position: absolute; opacity: 0; width: 0; height: 0; }
.inv-meth span {
    display: inline-block; padding: 4px 12px; border-radius: 999px;
    border: 1px solid var(--border); background: var(--surface);
    font-size: 12.5px; font-weight: 600; color: var(--text-3); cursor: pointer;
    transition: border-color .12s, color .12s, background .12s;
}
.inv-meth span:hover { border-color: var(--accent); color: var(--accent); }
.inv-meth input:checked + span { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.inv-meth input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.inv-pairs { display: flex; flex-direction: column; gap: 7px; }
.inv-pair { display: grid; grid-template-columns: 1fr 1fr 26px; gap: 7px; align-items: center; }
.inv-pair input {
    font: inherit; font-size: 13px; padding: 7px 9px; min-width: 0;
    border: 1px solid var(--border); border-radius: var(--r-sm);
    background: var(--surface); color: var(--text);
}
.inv-pair input:focus { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* the total, repeated in the sticky action bar */
.inv-foot-total { display: flex; align-items: baseline; gap: 9px; }
.inv-foot-total .k { font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); }
.inv-foot-total .v { font-size: 18px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
.inv-foot-total .v small { font-size: 11.5px; font-weight: 600; color: var(--text-3); margin-inline-start: 3px; }

/* ---- read-only face, for the detail page -------------------------------- */
.inv-sheet.is-read .inv-w,
.inv-sheet.is-read .inv-val { border-bottom: 0; color: var(--text); }
.inv-kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; font-size: 13px; }
.inv-kv dt { color: var(--text-3); }
.inv-kv dd { margin: 0; color: var(--text); }

@media (max-width: 760px) {
    .inv-sheet { padding: 22px 18px; }
    .inv-parties { grid-template-columns: minmax(0, 1fr); gap: 20px; }
    .inv-date { justify-content: start; grid-template-columns: auto minmax(0, 1fr); }
    .inv-date .k, .inv-date .inv-w { text-align: start; }
    .inv-doc { text-align: start; align-items: flex-start; }
    .inv-foot-total .k { display: none; }
}

/* the lines footer, on the paper: quiet links, not a toolbar */
.inv-lines-foot { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding-top: 4px; }
.inv-lines-foot .spacer { margin-inline-start: auto; }
.inv-lines-foot .f-lines-count { font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.inv-lines-foot .material-icons-outlined { display: none; }

/* ---- reordering and the per-line discount flag --------------------------- */
.inv-move {
    width: 22px; height: 22px; border: 0; border-radius: var(--r-sm);
    background: transparent; color: var(--text-4); cursor: pointer;
    display: inline-grid; place-items: center; font-size: 13px; line-height: 1;
}
.inv-move:hover { background: var(--accent-soft); color: var(--accent); }
tr[data-line]:first-child [data-line-up],
tr[data-line]:last-child  [data-line-down] { opacity: .25; pointer-events: none; }

.inv-flag { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--text-2); cursor: pointer; }
.inv-flag input { accent-color: var(--accent); margin: 0; }
.inv-lines-foot .inv-pick select { font-size: 12px; font-weight: 600; }

/* ---- a line that came from the catalogue ----------------------------------
   The chip sits inside the description cell, at the end of the box, and only
   while the row carries an item link (tr[data-line-linked], set by the picker
   in line_items.html). The x removes the link, not the line. */
[data-line-desc] { position: relative; }
.inv-item-flag {
    position: absolute; top: 50%; inset-inline-end: 12px; transform: translateY(-50%);
    display: none; align-items: center; gap: 1px;
    padding: 2px 3px 2px 6px; border-radius: var(--r-sm);
    background: var(--accent-soft); color: var(--accent);
}
.inv-item-flag .material-icons-outlined { font-size: 13px; display: inline-block; }
tr[data-line-linked] .inv-item-flag { display: inline-flex; }
tr[data-line-linked] [data-line-name] { padding-inline-end: 58px; }
.inv-item-unlink {
    width: 16px; height: 16px; border: 0; border-radius: var(--r-sm);
    background: transparent; color: inherit; cursor: pointer; padding: 0;
    display: grid; place-items: center; font-size: 14px; line-height: 1;
}
.inv-item-unlink:hover { background: var(--bad-soft); color: var(--bad); }

/* ---- who the invoice is for, compactly -----------------------------------
   Three words side by side rather than a dropdown, and the one kind that has
   to be typed gets placeholder boxes rather than a stacked label per field.
   Seven labelled rows was a block taller than the lines it was billing for.
   ========================================================================== */

/* THESE PILLS WERE CALLED .inv-kind, AND SO IS THE WORD "INVOICE" at the top
   of the sheet (line ~3004: 22px, 800 weight, .13em tracking, uppercase). Two
   different things under one name, and the older one won on everything the
   newer one did not restate - so the pills inherited the tracking and the
   uppercasing off the document title and rendered as C L I E N T. That is the
   "type not clear" and the "button too huge". Renamed, and the two properties
   that leaked are now stated outright so no future collision can do it again. */
.inv-who { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-bottom: 12px; }
.inv-who-opt { position: relative; display: inline-flex; }
.inv-who-opt input { position: absolute; opacity: 0; width: 0; height: 0; }
.inv-who-opt span {
    display: inline-block; padding: 5px 14px; border-radius: 999px;
    border: 1px solid var(--border); background: var(--surface);
    font-size: 12.5px; font-weight: 600; line-height: 1.3;
    letter-spacing: normal; text-transform: none;
    color: var(--text-3); cursor: pointer;
    transition: border-color .12s, color .12s, background .12s;
}
.inv-who-opt span:hover { border-color: var(--accent); color: var(--accent); }
.inv-who-opt input:checked + span { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.inv-who-opt input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.inv-who .inv-link { margin-inline-start: 5px; letter-spacing: normal; text-transform: none; }

/* AN ADDRESS BLOCK, which is what "Bill to" is on an invoice - a narrow
   stack of short lines, one thing per line, the width of an address rather
   than the width of the page.

   It has been a column of labelled full-width rows (too tall), then a
   four-across auto-fit grid (placeholders running into each other), then two
   columns (still a form). The shape was always wrong because it was a form
   shape. This is the shape the thing itself has. */
.inv-fields {
    display: flex;
    flex-direction: column;
    gap: 5px;
    max-width: 340px;
    margin-top: 2px;
}
.inv-fields > * { min-width: 0; }

/* Lines on an address block sit on their own baseline. At 2px of gap and 2px
   of padding the dotted rules were 4px apart and read as one hatched block. */
.inv-fields .inv-w { padding: 4px 1px; line-height: 1.35; }

/* The four fields behind "More details" are the same block continued, so they
   are laid out by the same rules rather than as a nested box. */
#client_more, #code_more { display: flex; flex-direction: column; gap: 5px; }
#client_more[hidden], #code_more[hidden] { display: none; }

/* The link that opens them. Small, quiet, and never mistaken for the button
   that creates the invoice. */
.inv-more {
    align-self: flex-start; margin-top: 3px; padding: 0;
    border: 0; background: none; cursor: pointer;
    font-size: 11.5px; font-weight: 600; letter-spacing: normal; text-transform: none;
    color: var(--text-3);
}
.inv-more::before { content: '+ '; }
.inv-more[aria-expanded="true"]::before { content: '\2212 '; }
.inv-more:hover { color: var(--accent); }

/* A picked client's details are the record's, not this invoice's. Locked, they
   stop offering to be typed in: no dotted rule, no hover, no placeholder in an
   empty box, and the cursor says so. Still black text - this is stated fact,
   not a disabled control.

   NOTHING HERE MAY TOUCH `opacity` ON THE NATIVE SELECT. An enhanced select is
   hidden by .f-select-wrap.is-enhanced > select { position:absolute; width:1px;
   height:1px; opacity:0 } with the .f-ss-btn drawn over it. A locked rule that
   set opacity:1 on select.f-select outscored that by one class and brought the
   1px native control back into view, so the dialling code rendered twice -
   "+973 Bahrain" from the select laid across "+973" from the button. The
   locked state is about the BUTTON; the select underneath it stays hidden. */
.inv-fields.is-locked .inv-w { cursor: default; }
.inv-fields.is-locked .inv-w::placeholder { color: transparent; }
.inv-fields.is-locked .inv-fill,
.inv-fields.is-locked .inv-fill:hover { border-bottom-color: transparent !important; }
.inv-fields.is-locked .f-ss-btn { border-bottom-color: transparent; pointer-events: none; cursor: default; }
.inv-fields.is-locked .f-ss-btn:hover { border-bottom-color: transparent; color: var(--text); }
.inv-fields.is-locked .f-ss-btn .f-caret,
.inv-fields.is-locked .f-select-wrap > .f-caret { display: none; }

/* An address block does not print blank lines. While locked, a row with
   nothing on it is removed rather than left as an empty rule with a
   placeholder floating in it. */
.inv-fields.is-locked [data-row-empty] { display: none; }

/* THE LINE THAT NAMES THE RECORD the block underneath was filled from, and the
   pen that reopens it. See systems/code/boxs/code/billto_picked.html.

   It is a CAPTION, not a card: the width of the address block it belongs to,
   one line of text, and quiet enough that the eye still lands on the invoice.
   A boxed panel here competed with the sheet itself and made the bill-to look
   like two separate questions.

   The name TRUNCATES rather than wrapping. A long company name plus an email
   would otherwise push the pen onto a second line, and a control that moves
   depending on whose name is above it is a control nobody learns the position
   of. min-width:0 on the flex child is what lets the ellipsis happen at all. */
.inv-picked {
    display: flex; align-items: center; gap: 8px;
    max-width: 340px; margin: 0 0 8px;
    padding: 6px 9px; border-radius: 8px;
    border: 1px solid var(--border); background: var(--surface-2);
}
.inv-picked[hidden] { display: none; }
.inv-picked-icon { flex: none; font-size: 17px; color: var(--accent); }
.inv-picked-who { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.inv-picked-kind {
    font-size: 9.5px; font-weight: 700; line-height: 1.4;
    letter-spacing: .08em; text-transform: uppercase; color: var(--text-3);
}
.inv-picked-name {
    font-size: 12.5px; font-weight: 600; line-height: 1.35; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* An icon button on a caption, so it is sized to the caption rather than to
   the form. It carries a title and an aria-label written by pen_state(), which
   is the whole of its accessible name - there is no text in it. */
.inv-picked-pen {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; padding: 0;
    border: 1px solid transparent; border-radius: 6px;
    background: none; color: var(--text-3); cursor: pointer;
    transition: color .12s, border-color .12s, background .12s;
}
.inv-picked-pen .material-icons-outlined { font-size: 15px; }
.inv-picked-pen:hover,
.inv-picked-pen[aria-pressed="true"] {
    color: var(--accent); border-color: var(--accent); background: var(--accent-soft);
}
.inv-picked-pen:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* THE NOTIFICATION CHANNELS ARE TICK BOXES, and they had to become tick boxes
   twice. First they were three four-option dropdowns that truncated their own
   options at column width ("Merchant and cli..."), then pill-shaped chips -
   which read as tabs rather than as answers, so an operator could not tell "SMS
   is on" from "SMS is the one I last clicked". A checkbox has exactly one thing
   to say and says it the way every checkbox on the page does. */
/* One select, its own width. A four-option question does not need the full
   column, and a control the width of its longest answer reads as a question
   with a small number of answers - which is what it is. */
.inv-who-one { max-width: 300px; }
.inv-who-one select { width: 100%; }

/* A form that is waiting on the server accepts nothing. `inert` (set by
   ui.form.busy) does the blocking; this says so - the page stops responding to
   the pointer, and it should look like it meant to. */
.f-form[data-ui-busy] { cursor: progress; }
.f-form[data-ui-busy] .inv-sheet { opacity: .62; transition: opacity .12s; }

/* A SEARCHABLE SELECT ON THE SHEET HAS TO CLEAR THE ACTION BAR. The bar is
   sticky with a z-index of its own, and the panel's 1250 only wins if nothing
   between them caps it. Raising the wrapper while it is open lifts the whole
   subtree in whatever stacking context it turns out to live in - which is the
   difference between a currency list you can read and one whose bottom half is
   behind a white strip saying TOTAL. */
.inv-shell .f-select-wrap.is-open { position: relative; z-index: 1450; }

/* And the bar gets out of the way while one is open. Raising the panel should
   be enough on its own - it outscores the bar by three orders of magnitude -
   and it was not, which means something between them is capping it. Rather than
   keep hunting for which ancestor, the bar stops competing: with no z-index of
   its own it has no stacking context to win from, and the panel is drawn over
   it whatever the chain looks like. :has() is already relied on a few hundred
   lines up (.f-combo:has(.f-select-wrap.is-open)), so this needs nothing new.

   The bar is not hidden - a currency list that covers the total for as long as
   it is open is fine; a total that disappears is not. */
.inv-shell .f-foot { z-index: 4; }
.inv-shell:has(.f-select-wrap.is-open) .f-foot { z-index: auto; }

/* A note about the choice just made, in the margin voice the sheet uses for
   anything that is not part of the document. */
.inv-hint { max-width: 340px; margin-top: 8px; font-size: 11.5px; line-height: 1.5; color: var(--text-3); }
.inv-hint[hidden] { display: none; }

/* the one line that carries two things */
.inv-pair-line { display: flex; align-items: center; gap: 10px; }
.inv-pair-line > * { min-width: 0; flex: 1 1 0; }
.inv-pair-line > .f-select-wrap { flex: 0 0 auto; }
.inv-combo { display: flex; align-items: center; gap: 5px; min-width: 0; }

/* An empty box on paper is invisible, so a fillable one says so with a dotted
   rule - solid and accent once it is being used. */
.inv-fill { border-bottom: 1px dotted var(--border) !important; font-size: 13px; }
.inv-fill:hover { border-bottom-color: var(--text-4) !important; }
.inv-fill:focus { border-bottom-style: solid !important; border-bottom-color: var(--accent) !important; }

/* The dialling-code and country pickers render as a bordered button with a
   flag - right on a form, far too heavy on a sheet, where it dwarfed every
   value beside it. Scoped to .inv-sheet, so every other page keeps its
   control. */
.inv-sheet .f-select-wrap { min-width: 0; }
.inv-sheet .f-ss-btn {
    border: 0; border-bottom: 1px dotted var(--border); border-radius: 0;
    background: transparent; padding: 2px 15px 2px 0; min-height: 0; gap: 5px;
    font-size: 13px; color: var(--text);
}
.inv-sheet .f-ss-btn:hover { border-bottom-color: var(--accent); color: var(--accent); background: transparent; }
.inv-sheet .f-ss-btn .f-ss-flag { width: 15px; height: 11px; }
.inv-sheet .f-ss-btn .f-caret,
.inv-sheet .f-select-wrap > .f-caret { font-size: 15px; inset-inline-end: 0; }
.inv-sheet .f-select-wrap > select.f-select {
    border: 0; border-bottom: 1px dotted var(--border); border-radius: 0;
    background: transparent; padding: 2px 15px 2px 0; height: auto; min-height: 0;
    font-size: 13px; color: var(--text);
}

/* ---- two things the sheet got wrong ------------------------------------- */

/* THE DIALLING CODE WAS INVISIBLE. .f-ss-label is flex:1 1 auto with
   min-width:0 and an ellipsis - correct in a form field that has a width to
   fill, fatal inside .inv-combo, which has none. The label shrank to nothing
   and the control rendered as a flag and a chevron with the +973 clipped out
   between them. It sizes to its text here and is never truncated. */
.inv-sheet .inv-combo > .f-select-wrap { flex: 0 0 auto; }
.inv-sheet .inv-combo .f-ss-label {
    flex: 0 0 auto;
    overflow: visible;
    text-overflow: clip;
    font-variant-numeric: tabular-nums;
}
.inv-sheet .inv-combo .f-ss-btn { padding-inline-end: 14px; }
.inv-sheet .inv-combo > .f-select-wrap > select.f-select { width: auto; min-width: 76px; }

/* THE ACTION BAR WAS FULL SIZE. A bordered, heavily shadowed strip with
   standard buttons, pinned under a document - it read as a second page rather
   than as the foot of this one. Same controls, less of them: no border, a
   lighter shadow, and buttons at the size of everything else on the paper. */
.inv-shell .f-foot {
    padding: 9px 14px;
    border: 0;
    border-top: 1px solid var(--border);
    border-radius: 0;
    box-shadow: 0 -8px 20px -18px rgba(16, 31, 38, .5);
    background: var(--surface);
}
.inv-shell .f-foot .btn { padding: 7px 14px; font-size: 12.5px; }
.inv-shell .f-foot .btn .material-icons-outlined { font-size: 15px; }
.inv-shell .f-foot .inv-foot-total .v { font-size: 16px; }


/* ==========================================================================
   28  PAYLOAD  -  a raw request or response, made readable

   For the stage log: what the portal sent a processor and what came back. The
   body is stored as one unindented line of JSON, XML or a form post, and the
   thing that makes it readable is not styling - it is the server-side
   reformatting in gatee_payload_format(). This is the frame around it.

   Three rules the block exists to enforce:

     A payload SCROLLS ITSELF. `white-space: pre` with the wrap turned off
     inside its own scroller: a 4000-character line must never widen the page,
     and it must never be soft-wrapped either, because a wrapped JSON line
     stops being indented and indentation is the entire point.

     A payload IS CAPPED. max-height plus overflow, so a stage whose response
     is an HTML page cannot push the next stage a screen and a half down. It
     grows to fit whatever is shorter than the cap.

     A payload SAYS WHAT IT IS. The header carries the format the server
     detected, the size, and the copy button - so nobody has to guess whether
     they are looking at JSON or at a form post that happens to have braces in
     it.
   ========================================================================== */

.f-payload {
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    overflow: hidden;
    background: var(--surface-2);
}
.f-payload + .f-payload { margin-top: 8px; }

.f-payload-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    border-bottom: 1px solid var(--border-2);
    background: var(--surface-3);
}

.f-payload-title {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-2);
    white-space: nowrap;
}

.f-payload-meta {
    margin-inline-start: auto;
    font-size: 11px;
    color: var(--text-4);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.f-payload-copy {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 7px;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--text-3);
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
}
.f-payload-copy:hover { color: var(--text); border-color: var(--text-4); }
.f-payload-copy .material-icons-outlined { font-size: 13px; }

.f-payload-body {
    margin: 0;
    padding: 10px 12px;
    max-height: 460px;
    overflow: auto;
    white-space: pre;
    tab-size: 2;
    font-family: var(--mono);
    font-size: 11.5px;
    line-height: 1.55;
    color: var(--text-2);
    background: var(--surface);
}

.f-payload-empty {
    padding: 10px 12px;
    font-size: 12px;
    color: var(--text-4);
    background: var(--surface);
}

.f-payload-note {
    padding: 6px 12px;
    border-top: 1px solid var(--border-2);
    font-size: 11px;
    color: var(--warn);
    background: var(--warn-soft);
}

/* ---- one stage of the pipeline ------------------------------------------ */

/* The exchange inside a stage: the URL it went to, then request, response and
   headers stacked in the order they happened. */
.f-stage-call { margin-top: 12px; }
.f-stage-call + .f-stage-call { padding-top: 12px; border-top: 1px dashed var(--border-2); }

.f-stage-detail { margin-top: 10px; }

/* The run's own facts, between the verdict and the list of stages. Same chips
   as a stage header wears, one step up in size - they describe the whole trip
   rather than one leg of it. */
.f-stage-totals { margin: 10px 0 14px; }
.f-stage-totals .f-fact { padding: 4px 9px; font-size: 11.5px; }
.f-stage-totals .f-fact .material-icons-outlined { font-size: 14px; }

/* The card-data caution. Quiet on purpose: it is true on every stage log and
   an alert box repeated on every visit stops being read by the third one. */
.f-stage-caution {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin-bottom: 12px;
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--text-4);
}
.f-stage-caution .material-icons-outlined { font-size: 14px; flex: none; margin-top: 1px; }

.f-stage-url {
    display: block;
    margin-bottom: 8px;
    font-family: var(--mono);
    font-size: 11.5px;
    color: var(--text-3);
    word-break: break-all;
}

/* =============================================================================
   GATE-E V-WALLET APP  (systems/gatee/boxs/wallet/app/app.html)
   The selected wallet drawn as a REAL card: payment-card width and
   proportions, never the full page. The switcher sits on the card, the money
   actions stand beside it as labeled round buttons, and everything below
   belongs to the wallet the card shows. Tokens only - the gradient rides
   --accent / --accent-2 / --accent-fg, so it recolors per system and scheme.
   ========================================================================== */

.w-wrap {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 16px 28px;
    margin-bottom: 10px;
}

.w-card {
    flex: 0 1 430px;
    min-width: 290px;
    min-height: 200px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 12px;
    position: relative;
    overflow: hidden;
    border-radius: 18px;
    padding: 18px 20px;
    color: var(--accent-fg);
    background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
    box-shadow: var(--sh-md);
}

/* one soft disc - decoration, never content */
.w-card::before {
    content: "";
    position: absolute;
    width: 190px;
    height: 190px;
    top: -100px;
    inset-inline-end: -55px;
    border-radius: 50%;
    background: rgba(255, 255, 255, .10);
    pointer-events: none;
}

.w-card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px;
}

/* The wallet switcher. Native select: its dropdown list repaints on surface
   tokens below, so it stays readable in every scheme. */
.w-hero-select {
    max-width: 230px;
    padding: 4px 8px;
    border: 1px solid rgba(255, 255, 255, .35);
    border-radius: 8px;
    background: rgba(255, 255, 255, .14);
    color: inherit;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
}

.w-hero-select option { color: var(--text); background: var(--surface); }

.w-hero-code {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .4px;
    opacity: .9;
}

.w-card-chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 9px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .18);
    font-size: 10.5px;
    font-weight: 700;
    white-space: nowrap;
}

.w-card-balance {
    font-size: 26px;
    font-weight: 800;
    letter-spacing: -.4px;
    line-height: 1.1;
}

.w-card-holder {
    margin-top: 3px;
    font-size: 12px;
    font-weight: 600;
    opacity: .88;
}

.w-card-bottom {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
}

.w-hero-vban {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    font-family: var(--mono);
    font-size: 11.5px;
    letter-spacing: .5px;
    opacity: .92;
}

.w-hero-vban > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.w-hero-copy {
    flex: none;
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 7px;
    background: rgba(255, 255, 255, .16);
    color: inherit;
    cursor: pointer;
}

.w-hero-copy:hover { background: rgba(255, 255, 255, .30); }

.w-hero-copy .material-icons-outlined { font-size: 14px; }

/* The QR on white whatever the scheme - a code on a tinted surface is a code
   some scanners refuse. */
.w-card-qr {
    flex: none;
    padding: 3px;
    border-radius: 8px;
    background: #fff;
    line-height: 0;
}

.w-card-qr img { width: 58px; height: 58px; display: block; }

/* ---- the money actions, beside the card --------------------------------- */

.w-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 14px 20px;
}

.w-action {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    min-width: 54px;
    font-size: 11px;
    font-weight: 700;
    color: var(--text-2);
    text-decoration: none;
}

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

.w-action-ico {
    width: 42px;
    height: 42px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: var(--surface);
    box-shadow: var(--sh-sm);
    color: var(--accent);
}

.w-action:hover .w-action-ico { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

.w-action-ico .material-icons-outlined { font-size: 19px; }
