/* Travelon admin theme — token layer over Tabler.
   Dark mode is deliberately deferred: add a [data-bs-theme=dark] block
   redefining ONLY these tokens. Do not hardcode colour below :root. */

:root {
    /* Canvas is WHITE, not a tinted panel. A grey page with white cards on it
       is the shape of a 2015 admin; the reference this follows puts content
       directly on white and uses hairlines, not containers, to separate it.
       --tw-surface-2 is now the navigation/inset tone rather than a card fill. */
    /* Tabler's page rhythm, retuned rather than overridden. Stock is
       --tblr-page-padding-y: var(--tblr-spacer-4) (~24px), which it applies as
       .page-body's top AND bottom margin and .page-header's top margin — 72px
       of vertical space before a table starts. Setting the variable moves all
       three at once and leaves the variable telling the truth; overriding the
       rules one by one would not. */
    --tblr-page-padding: 8px;
    --tblr-page-padding-y: 8px;

    --tw-canvas: #ffffff;
    --tw-surface: #ffffff;
    --tw-surface-2: #f7fafc;
    --tw-border: #e3e8ee;
    --tw-border-strong: #d5dbe3;
    --tw-border-soft: #f0f3f7;

    /* Near-black rather than a blue-black, and a grey that is genuinely
       readable. --tw-ink-faint is DECORATION ONLY (4.03:1 on white — chart
       ticks, placeholder glyphs). Anything a person has to read uses
       --tw-ink-muted at 4.78:1. */
    --tw-ink: #1a1f36;
    --tw-ink-2: #3c4257;
    --tw-ink-muted: #697386;
    --tw-ink-faint: #767f91;

    /* Brand green, DARKENED. The logo's own greens cannot do this job:
       #7EC242 is 2.17:1 on white and #28B34B is 2.75:1 — and because a filled
       button is white-on-green, the deep one fails 2.75:1 in that direction
       too. Both are unreadable as buttons, links or focus rings. So the raw
       brand greens stay decorative (the logo, and the sidebar's active-nav
       text where #7EC242 gets 8.14:1 against the dark), and everything
       interactive uses these, which pass in both directions:
         --tw-accent      5.08:1 on white AND under white
         --tw-accent-ink  6.79:1 on white, 6.11:1 on --tw-accent-soft */
    --tw-accent: #1B7F35;
    --tw-accent-soft: #EAF6EC;
    --tw-accent-ink: #17692C;

    /* Status: deep ink on a pale tint, rather than a saturated fill. This is
       most of why the reference reads calm while still carrying colour on
       every row. All five land 6.07-6.46:1 — a tighter and higher band than
       the 4.84-5.30 these replace. */
    /* Success moved from green to TEAL when the accent became green. Left as
       it was, "primary action" and "succeeded" would have been the same colour
       family — a green Save button beside a green Paid badge, on exactly the
       screens (bookings, payments) where the distinction carries money. Teal
       keeps the calm ink-on-tint form and lands at 6.15:1, inside the
       6.07-6.21 band the other four statuses hold. */
    --tw-success: #0C6060;
    --tw-success-soft: #D6F0F0;
    --tw-success-ink: #0C6060;
    --tw-warning: #ca8a04;
    --tw-warning-soft: #fcedb9;
    --tw-warning-ink: #983705;
    --tw-danger: #dc2626;
    --tw-danger-soft: #fde2e2;
    --tw-danger-ink: #a41c1c;
    --tw-neutral-soft: #f0f3f7;
    --tw-neutral-ink: #4f5b6d;

    /* Info: a fourth status colour so part-payment stops sharing green with
       paid-in-full, in the same ink-on-tint form as the rest (6.46:1). */
    --tw-info: #0284c7;
    --tw-info-soft: #d6ecff;
    --tw-info-ink: #0b5394;

    /* Sidebar: DARK, and its own token set rather than reused content tokens.
       The logo's wordmark is white — ~4,200 opaque white pixels across the
       lower third of logo.png — so on the old #f7fafc sidebar the brand read
       as a floating bird with half its name missing. A dark rail is what the
       asset was drawn for.

       Separate tokens because every one of these is measured against
       --tw-sidebar, not against white; feeding --tw-ink-muted into a dark
       surface would be the same class of mistake as the greens above.
         ink     14.90:1   muted    7.06:1
         active  10.45:1 (7.97:1 on its own fill)   brand #7EC242  8.14:1 */
    --tw-sidebar: #141A16;
    --tw-sidebar-ink: #E8EDE9;
    --tw-sidebar-muted: #B3C0B7;
    --tw-sidebar-hover: #1E2A22;
    --tw-sidebar-active: #1F2E1D;
    --tw-sidebar-active-ink: #A9E07A;
    --tw-sidebar-border: #23302A;
    /* The thumb is the only sidebar surface with no line token that suits it:
       --tw-sidebar-border is a 1.35:1 hairline, too faint to grab hold of.
       Named here so it can never be reached for from the content palette,
       which is how --tw-border-strong ended up painting it white. */
    --tw-sidebar-scroll: #3A4A41;
    /* The unprocessed-tickets count. Deliberately NOT --tw-danger: a queue
       length is a workload, not an error, and danger is needed for things
       that genuinely are. */
    --tw-sidebar-badge: #2A3A2E;
    --tw-sidebar-badge-ink: #C7D4CB;
    /* The logo's own light green, used ONLY here and only against the dark
       rail, where it measures 8.14:1. It must never appear on white. */
    --tw-brand-green: #7EC242;

    /* Login brand panel. A DARK-CENTRED field, not a flat gradient: the logo
       and tagline sit in the middle, so the middle must stay dark enough for
       white text (18:1 here) while the brand green blooms in the corners. A
       flat dark-to-green gradient failed this — a tagline landed at 3.80:1 on
       the bright end. */
    --tw-auth-panel-base: #0E1712;
    --tw-auth-panel-glow-1: rgba(126, 194, 66, .26);
    --tw-auth-panel-glow-2: rgba(27, 127, 53, .40);

    /* Smaller corners. 14px radii on every surface reads as a consumer app;
       the reference stays at 6-8px, which is what makes dense data look
       engineered rather than decorated. */
    --tw-radius-card: 8px;
    --tw-radius-control: 6px;
    --tw-radius-nav: 6px;
    --tw-control-h: 36px;

    /* Type scale. Every size is a token so density stays adjustable from one
       place rather than being sprinkled through component rules. */
    --tw-text-base: 14px;
    --tw-text-sm: 13px;
    /* 12px is the floor AdminThemeTest holds this at. Reducing further is a
       deliberate decision about legibility, not a styling tweak, and the test
       exists to make that an explicit conversation rather than one more
       reasonable-looking step. */
    --tw-text-cell: 12px;
    --tw-text-label: 11px;
    --tw-text-micro: 10.5px;
    --tw-text-h1: 30px;

    /* The label column of the booking form's horizontal rows. A token rather
       than a literal because the value is shared by three rules that must
       agree — the label's own width, and the indent of the hint and the
       validation message that sit UNDER the control, not under the label. */
    --tw-field-label-w: 210px;

    /* Depth is now reserved for things that genuinely float — popovers, the
       pinned save bar. Content surfaces get a hairline instead. Shadows on
       every card were a large part of what read as "busy": twenty soft
       rectangles competing on one screen. */
    --tw-shadow-raised: 0 1px 1px rgb(26 31 54 / .04);
    --tw-shadow-card: none;
    --tw-shadow-lift: 0 1px 1px rgb(26 31 54 / .04);
    --tw-shadow-pop: 0 4px 16px rgb(26 31 54 / .16), 0 1px 3px rgb(26 31 54 / .08);

    /* Motion. Durations are short enough to feel like feedback rather than
       animation; the easing overshoots slightly on entry, which is what makes
       a hover feel responsive instead of mechanical. Every use is wrapped by
       the prefers-reduced-motion block at the foot of this file. */
    --tw-motion-fast: 120ms;
    --tw-motion: 180ms;
    --tw-motion-slow: 320ms;
    --tw-ease: cubic-bezier(.32, .72, .28, 1);

    /* Brand green at the same alpha the indigo ring used. Hardcoded rgb()
       rather than a token because color-mix() on a hex custom property is not
       safe across the browsers this panel supports. If --tw-accent changes,
       THIS LINE MUST CHANGE WITH IT — it is the one place the accent is not
       read from its token. */
    --tw-focus-ring: 0 0 0 3px rgb(27 127 53 / .28);

    /* Placeholder text is TEXT, so AA applies: 4.5:1. Browser defaults sit
       around 3:1, which is why this is set rather than inherited. #6b7587
       measures 4.65:1 on white and is still far lighter than an entered value
       (--tw-ink at 16.24:1), so a placeholder never reads as a filled field. */
    --tw-placeholder: #6b7587;

    /* Login cover panel: gradient placeholder built from existing tokens, standing
       in for a licensed travel photograph. Replace this single declaration with
       `url('/img/admin-login-cover.jpg')` (or similar) once a real, rights-cleared
       image is available — no other file needs to change. */
    --tw-auth-cover: linear-gradient(135deg, var(--tw-ink) 0%, var(--tw-accent-ink) 55%, var(--tw-accent) 100%);

    /* Button foregrounds vary by background to ensure WCAG AA contrast on 13px
       text. Warning (#ca8a04) is light enough that white fails at 2.94:1, so it
       keeps dark ink.

       Success FLIPPED to white when it became teal. On the old #059669 fill,
       white was 3.77:1 and dark ink was the fix. On #0C6060 that reverses hard:
       dark ink is 2.43:1 — worse than the problem it was introduced to solve —
       while white is 7.35:1. Re-derive this pairing whenever a fill changes;
       it is not a preference, it is a measurement. */
    --tw-btn-fg-on-accent: #ffffff;
    --tw-btn-fg-on-success: #ffffff;
    --tw-btn-fg-on-warning: #0f172a;
    --tw-btn-fg-on-danger: #ffffff;

    /* Bridge into Tabler so untouched CRUD screens inherit. */
    --tblr-body-bg: var(--tw-canvas);
    --tblr-body-color: var(--tw-ink);
    --tblr-primary: var(--tw-accent);
    --tblr-border-color: var(--tw-border);
    --tblr-border-radius: var(--tw-radius-control);
    --tblr-secondary: var(--tw-ink-muted);
    --tblr-success: var(--tw-success);
    --tblr-warning: var(--tw-warning);
    --tblr-danger: var(--tw-danger);
    --tblr-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;

    /* Button foregrounds, via Tabler's own token rather than a `color`
       declaration. tabler.min.css wires .btn-VARIANT's --tblr-btn-color,
       --tblr-btn-hover-color, --tblr-btn-active-color AND
       --tblr-btn-disabled-color all to var(--tblr-VARIANT-fg) — one token
       drives every state. Setting `color` inside .btn-success instead would
       fix only the base state: `.btn:hover { color: var(--tblr-btn-hover-color) }`
       has specificity (0,2,0) and beats .btn-success's (0,1,0) in every
       browser regardless of source order. Redefining the token means that
       hover rule still wins the property but now resolves to OUR value.
       Tabler's default for all four is var(--tblr-light) — near-white, which
       fails AA on success (3.77:1) and warning (2.94:1). */
    --tblr-primary-fg: var(--tw-btn-fg-on-accent);
    --tblr-success-fg: var(--tw-btn-fg-on-success);
    --tblr-warning-fg: var(--tw-btn-fg-on-warning);
    --tblr-danger-fg: var(--tw-btn-fg-on-danger);

    /* Hover/active background. Tabler's stock formula is
       `color-mix(in oklab, var(--tblr-VARIANT), transparent 20%)` — it
       LIGHTENS toward whatever is behind the button (our near-white canvas).
       For the two variants carrying a white foreground that pushes contrast
       below AA: primary 6.29 -> 4.03:1, danger 4.83 -> 3.35:1. Mixing 12%
       toward black instead restores it (primary 8.08:1, danger 6.43:1) and
       still reads as a darker, pressed state.

       success/warning are deliberately absent: they pair a DARK foreground
       (--tw-ink), so Tabler's lightening only improves them (6.41:1 and
       7.76:1 respectively). Overriding those would make hover darker than
       the base state for no contrast gain.

       The literal hex on the first line of each pair is a fallback for
       engines without color-mix() (Safari < 16.2): without it the invalid
       declaration is dropped and the button falls back to Tabler's OWN
       darken value, which is computed from Tabler's stock blue — a blue
       hover on an indigo button. Tabler ships the same two-line pattern for
       the same reason. Both values are inside :root, where literals are
       allowed; they are the 12%-black mixes of --tw-accent and --tw-danger. */
    --tblr-primary-darken: #413ac1;
    --tblr-primary-darken: color-mix(in oklab, var(--tblr-primary), black 12%);
    --tblr-danger-darken: #b91e1e;
    --tblr-danger-darken: color-mix(in oklab, var(--tblr-danger), black 12%);
}

[data-bs-theme="light"],
[data-menu-theme="light"] {
    --tblr-bg-surface: var(--tw-surface);
    --tblr-bg-surface-tertiary: var(--tw-surface-2);
    --tblr-bg-surface-backdrop: none;
    --tblr-bg-surface-border: 1px solid var(--tw-border);
    --tblr-bg-surface-shadow: none;
}

body {
    background: var(--tw-canvas) !important;
    background-image: none !important;
    font-size: var(--tw-text-base);
    -webkit-font-smoothing: antialiased;
}

/* ---- The content area ----
   Full width by choice: these are operational screens whose tables carry ten
   or more columns, and capping the measure pushed columns out of the layout
   on exactly the screens that need them. The gutters still keep content off
   the window edge, which is what the cap was really there to fix.

   `container-xxl`, not `container-xl`: layouts/vertical.blade.php emits
   `container-fluid` when options.useFluidContainers is true and
   `container-xxl` when it is false. `.container-xl` was in this list and is
   neither, so it matched nothing in either state — and would have kept
   matching nothing on the day someone flipped the flag.

   Gutters cut from 32px to 14px on 20 August 2026: at eleven columns the
   32px each side was 64px that the table wanted more than the margin did. */
.page-body > .container-fluid,
.page-body > .container-xxl,
.page-header > .container-fluid,
.page-header > .container-xxl,
.header-operation.container-fluid {
    max-width: none;
    padding-left: 8px;
    padding-right: 8px;
}

/* ---- Cards: a hairline, not a container ----
   Boxes inside boxes inside boxes is what made the panel read as busy: a card
   with a shadow, holding a bordered table, holding pills. A card now marks a
   boundary and nothing more — no shadow, no fill, one hairline. */
.card {
    border: 1px solid var(--tw-border);
    border-radius: var(--tw-radius-card);
    box-shadow: none;
}
.card-header {
    border-bottom: 1px solid var(--tw-border);
    font-size: 16px;
    font-weight: 600;
    color: var(--tw-ink);
    background: transparent;
    letter-spacing: -.01em;
    padding: 15px 18px;
}

/* ---- Page headings ---- */
/* The jump from this to the metadata beneath it is where the page gets its
   hierarchy — and its absence is why the old design read as flat: everything
   sat within a few points of everything else.

   `.page-body` is in the selector to beat theme-tabler's own
   `.page-body .container-fluid h2 { font-size: 27px; font-weight: 400 }`
   (style.css), which is (0,2,1) against the (0,1,1) this rule used to carry.
   That vendor rule had been winning on EVERY page title in the panel, so the
   heading size declared here has never once applied. Matching its specificity
   is enough, since admin-theme.css is the last stylesheet in the document. */
.page-body .header-operation h1,
.page-body .header-operation h2,
.page-body .container-fluid h2.tw-title,
.header-operation h2,
h1.page-title {
    font-size: var(--tw-text-h1);
    font-weight: 700;
    letter-spacing: -.03em;
    line-height: 1.15;
    color: var(--tw-ink);
}

/* Focus is visible on every interactive element. Tabler's default relies on
   Bootstrap's box-shadow, which our card/control rules overwrite — leaving
   keyboard users with no indicator at all on the very controls we restyled. */
a:focus-visible,
button:focus-visible,
.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.nav-link:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid transparent;
    box-shadow: var(--tw-focus-ring);
    border-radius: var(--tw-radius-nav);
}
.form-control:focus,
.form-select:focus {
    border-color: var(--tw-accent);
    box-shadow: var(--tw-focus-ring);
}

/* ---- Tables ---- */
/* Vertical padding is generous, horizontal is not, and that asymmetry is
   deliberate. Roomier rows are most of what makes a table feel current, but
   horizontal padding is what decides whether the columns fit: at 16px each
   side this table outgrew a 1500px viewport, DataTables Responsive collapsed
   Date of Excursion, Date Booked and Language behind a ⋮ expander, and a
   bookings list silently lost its dates. Breathing room is worth less than the
   data. */
.table td,
.table th {
    /* Tightened again on 20 August 2026 from 12px/11px and a 1.5 line: the
       reference these lists are being matched to runs ~39px rows, and at
       13.5px on 12px of padding this table was at ~44px. Now ~34px. */
    /* Tightened again 1 Sep 2026, 6px/9px -> 4px/8px. Rows go 39px -> ~35px,
       which over a 25-row page is a hundred pixels of scrolling saved. Cells
       wrap now, so vertical padding is no longer the only thing keeping rows
       apart — the line box does some of that work. */
    padding: 4px 8px !important;
    font-size: var(--tw-text-cell) !important;
    line-height: 1.45 !important;
    vertical-align: middle !important;
}
/* The first and last cells keep the edge inset the rest give up. */
.table td:first-child,
.table th:first-child { padding-left: 10px !important; }
.table td:last-child,
.table th:last-child { padding-right: 10px !important; }

/* ---- Tables: one line per cell ---- */
/* CELLS DO NOT WRAP. Decided 1 Sep 2026 after seeing both measured side by side
   on the bookings list:

       wrapping   46px rows, 464px per page, table narrows to fit
       nowrap     35px rows, 350px per page, table demands 1410px at any width

   Shorter rows won. The cost is real and worth stating: a cell that cannot wrap
   cannot narrow, so a wide table overflows and DataTables Responsive hides the
   overflowing columns behind the ⋮ expander. The padding note above records that
   happening before — a bookings list "silently lost its dates". Anyone widening
   a list, or adding a long column to one, should check what falls off the end.

   The rule is still declared rather than simply deleted: it also cancels the
   `white-space: normal` that Bootstrap's .table sets, so removing it would not
   give the same result.

   !important and the doubled selector are both needed: DataTables ships
   `table.dataTable.nowrap th/td`, one element selector more specific than
   `.table.nowrap td`, and silently wins otherwise. */
table.dataTable.nowrap th,
table.dataTable.nowrap td,
.table.nowrap th,
.table.nowrap td { white-space: nowrap !important; }

/* Controls are the exception — an action button split across two lines reads as
   two broken buttons. Applies wherever they appear, not just the last column. */
.table td .btn,
.table td .badge,
.table th .btn { white-space: nowrap; }

/* Header labels wrap like cells do. DataTables sizes each column to the wider
   of its heading and its content, so a long heading sets the floor: "Product
   ID" plus the select-all checkbox plus two sort arrows held that column at
   195px to display "5,155". A heading that can take two lines stops deciding
   the width of the data underneath it. */
.crud-table thead .dt-column-title,
.crud-table thead th { white-space: normal !important; }

/* Identifier columns hug their content. `width: 1%` under auto layout means
   "as narrow as the content allows" — the browser gives the surplus to the
   columns that want it instead of spreading it evenly.

   EXACT attribute matches only. The rule set this replaces used substrings
   (`*="product"`), which on the Products list matched product_name,
   product_location, product_supplier_id AND product_status_id — handing
   "Destination", holding "Lanzarote", the same 302px as a 48-character product
   name. A substring match across thirty independently-named lists will always
   hit something it was not aimed at. */
.crud-table th[data-column-name="id"],
.crud-table th[data-column-name="product_id"],
.crud-table th[data-column-name="booking_id"],
.crud-table th[data-column-name="transaction_id"] {
    width: 1%;
    /* Exempt from the wrapping heading rule above. `width: 1%` alone shrank
       this to 64px — narrower than its own heading, so "Product ID" broke onto
       two lines to fit a column holding "5,155". The column now hugs the wider
       of the value and the heading kept on ONE line, which for an id is the
       heading. */
    white-space: nowrap !important;
}
.crud-table th[data-column-name="id"] .dt-column-title,
.crud-table th[data-column-name="product_id"] .dt-column-title,
.crud-table th[data-column-name="booking_id"] .dt-column-title,
.crud-table th[data-column-name="transaction_id"] .dt-column-title { white-space: nowrap !important; }

/* Whatever still will not fit scrolls the TABLE. The page itself must never
   scroll sideways: a horizontal scrollbar on <body> hides the sidebar and the
   topbar controls along with it. */
/* 2.x class name only. `.dataTables_wrapper` is the retired 1.x prefix, and
   AdminThemeTest fails the whole file if one appears — copy-paste from an old
   answer is exactly how it would get back in. */
.dt-container { overflow-x: auto; }

@media (max-width: 575.98px) {
    /* The search box shares its row with the filter navbar on a desktop; on a
       phone that row has no slack, so it takes a line of its own. */
    .datatable_search_stack { width: 100% !important; }
    .table td, .table th { font-size: calc(var(--tw-text-cell) - 0.5px) !important; }
}

/* EVEN COLUMNS. Asked for on 20 August 2026 against a v1 screenshot: every
   column the same width, filling the table, rather than each sized to whatever
   it happens to contain.

   `table-layout: fixed` is what does it — the browser stops measuring content
   and divides the width by the column count. It only survives because Backpack
   initialises DataTables with `autoWidth: false`
   (datatable_logic.blade.php:308); with autoWidth on, DataTables measures the
   content and writes an inline width onto every <th>, and inline styles beat
   this file on every render.

   This replaces a 210px cap that existed for the opposite reason — to stop one
   long value starving the others. An even split solves that case by
   construction: Quote Id, the 32-character string that once claimed 297px and
   pushed the date columns out of the bookings list, now gets exactly the same
   share as everything else. */
/* SUPERSEDED 1 September 2026 — columns size to their content again.

   The even split above was the right answer while cells could not wrap: a long
   value then forced a wide column, so dividing the width evenly was the only
   way to stop one field starving the rest. Cells wrap now (see the nowrap
   override earlier in this file), which removes that constraint — a long value
   takes two lines instead of taking another column's width.

   What the even split cost, and why it was raised: on Products, "Destination"
   holds "Lanzarote" and was given 376px, the identical share as "Product Name"
   at 33 characters. Every column the same width means every column is wrong
   except by accident.

   `auto` measures content again. The starvation case the 210px cap once
   guarded against is handled by wrapping plus the max-width below, rather than
   by refusing to measure anything. */
.crud-table {
    table-layout: auto;
    width: 100%;
}

/* No single column may take more than a third of the table. Wrapping means a
   long value costs height rather than another column's width, and this caps
   how much width it can claim before it starts wrapping. */
.crud-table td,
.crud-table th { max-width: 33%; }
/* NO first-child width, deliberately — and this is the second time that
   conclusion has been reached.

   Bulk select does not add a checkbox column. Backpack renders the select-all
   checkbox INSIDE the existing first column: measured in the browser on the
   payments list, column 0 is data-column-name="transaction_id" and contains
   the checkbox. A 3% pin here was therefore squeezing a real data column to
   49px on every list that took bulk select — "Transaction Id" had no room to
   render at all.

   If anything the first column now needs MORE room, not less, since it carries
   a control as well as its value. */

/* PER-COLUMN WIDTH HINTS — REMOVED 1 September 2026.

   They existed to compensate for `table-layout: fixed`: with the browser
   refusing to measure content, each column had to be told its share by hand.
   Auto layout measures again, so the hints are not just redundant, they are
   wrong — and one was actively harmful.

   `th[data-column-name*="product"] { width: 23% }` was written for the bookings
   list, where exactly one column matches. On Products FOUR do — product_name,
   product_location, product_supplier_id, product_status_id — so "Destination"
   (holding "Lanzarote") was handed the same 302px as a 33-character product
   name. That is the report this removal answers.

   A substring match across 30 lists whose columns are named independently was
   always going to hit something it was not aimed at. Nothing replaces these:
   content decides, and the max-width above caps the outlier. */

/* Only the actions column is pinned. Three buttons in a 1/11th share would
   be clipped, and a clipped button is worse than a narrow column.

   The FIRST column is deliberately NOT pinned, though the v1 reference shows a
   narrow checkbox cell there. v2 renders no bulk-select column at all — bulk
   delete and bulk clone are backpack/pro operations and pro is not installed,
   so no controller registers them. Verified in the rendered markup: the first
   cell of the bookings list is "Booking Ref", not a checkbox. Pinning
   :first-child to a checkbox width would have crushed the first DATA column of
   all 26 lists. */
/* 11%, re-measured after the archive icon was removed and the gap cut to 1px.
   Counted off the datatable response rather than assumed: an ordinary row
   renders Preview, Edit, Delete and "Email the ticket" — four — with Restore
   as a fifth on an archived row. Five at 26px with 1px between and 18px of
   cell padding needs 152px; 11% is ~154px on a 1400px table.

   Was 14% when the row still carried six buttons. The three points released
   went to Product, which is the column that wanted them. */
.crud-table th:last-child,
.crud-table td:last-child { width: 9%; }
/* And never clipped. Every other cell hides its overflow and ellipsises; a
   button cannot be ellipsised, and half a button is not a control. If the
   viewport ever narrows past what the 14% holds, the buttons spill rather
   than being sliced. */
.crud-table td:last-child {
    overflow: visible;
}
/* With a fixed layout a cell can no longer widen to fit, so anything too long
   is cut with an ellipsis rather than wrapped — wrapping would give each row a
   different height and lose the even rhythm the reference has. */
.crud-table td {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* The header and footer have to agree with the body. They carry nowrap and,
   before this, no overflow rule at all — so a label wider than its column
   painted straight over its neighbour rather than being cut. "Booking Ref" is
   ~62px at 11.5px and its column was ~56px, so it crossed into Customer. */
.crud-table > thead > tr > th,
.crud-table > tfoot > tr > th {
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Sticky header: on a 10-row table it barely matters, but "entries per page"
   goes to 100, and scrolling a long list with the column names gone is the
   single most common way a dense table becomes unreadable. */
/* Sentence case at body size, not shouted small-caps on a grey band. The
   header is a label for the column, not a section of its own. */
.table thead th {
    font-size: 11.5px !important;
    text-transform: none;
    letter-spacing: 0;
    font-weight: 600;
    color: var(--tw-ink-muted);
    white-space: nowrap;
    background: var(--tw-surface);
    border-bottom: 1px solid var(--tw-border);
    position: sticky;
    top: 0;
    z-index: 2;
}

/* The same, at a specificity that beats DataTables.
   DataTables 2.x ships `table.dataTable > thead > tr > th { position: relative }`
   so it can absolutely position the sort arrow. That selector is (0,1,4); the
   rule above is (0,1,2), so it lost — but ONLY on sortable columns, because the
   rule only targets those. Measured on the Permissions list: "Permission" and
   "Guard" computed `relative` while "Held by", "Used in code" and "Actions"
   computed `sticky`. Two of five cells pinning is worse than none pinning.
   Sticky is still a positioned ancestor, so the sort arrow is unaffected. */
table.dataTable.crud-table > thead > tr > th {
    position: sticky;
    top: 0;
    z-index: 2;
}
/* A hairline between columns. The reference draws one, and across ten columns
   of dates and identifiers it is what lets the eye stay on a row — the
   argument for leaving them out (a list reads lighter than a grid) holds for
   four columns and stops holding at ten. Soft rather than the full border
   token, so the vertical lines stay quieter than the header rule. */
.crud-table > thead > tr > th,
.crud-table > tbody > tr > td,
.crud-table > tfoot > tr > th {
    border-right: 1px solid var(--tw-border-soft);
}
/* Not on the outer edge, where it reads as a stray border rather than a
   divider between two things. */
.crud-table > thead > tr > th:last-child,
.crud-table > tbody > tr > td:last-child,
.crud-table > tfoot > tr > th:last-child {
    border-right: 0;
}

.table > :not(caption) > * > * {
    box-shadow: none;
    /* Row rules are lighter than structural borders, so a long table reads as
       a list rather than a grid. */
    border-bottom: 1px solid var(--tw-border-soft);
    color: var(--tw-ink-2);
}
.table-striped > tbody > tr:nth-of-type(odd) > * {
    background: transparent;
}
/* Hover marks the row with an accent edge as well as a tint, so the row under
   the cursor is identifiable without relying on a colour difference this
   subtle being perceivable. */
.table tbody tr {
    transition: background var(--tw-motion-fast) var(--tw-ease);
}
.table tbody tr:hover > * {
    background: #fafbfc;
}
/* No accent bar on hover: a whole-row tint is enough, and the bar was a
   second signal for the same thing. */
/* Digits line up in a column. Applied to EVERY cell rather than to named
   ones: the three `td[data-column-name=...]` selectors this replaces matched
   nothing at all. The attribute is on the <th> only — <tbody> arrives from
   DataTables over AJAX, so body cells carry no column identity — and these
   rules named `td`. Tabular figures are harmless on text, so the blunt version
   is the one that actually works. */
.table td,
.table td.text-end,
.table th.text-end {
    font-variant-numeric: tabular-nums;
}

/* DataTables types each column by sampling its data and ships
   `td.dt-type-numeric, td.dt-type-date { text-align: right }`. That singled
   out Booking Ref — a bare integer, so typed numeric — and right-aligned it
   alone, while Amount rendered "€99.00", sampled as a string and stayed left.
   The one column worth right-aligning was not, and an identifier nobody sums
   was.

   Right alignment earns its place on quantities you compare down a column. An
   id is a label. Rather than chase DataTables' guess per column, the automatic
   alignment is switched off and everything reads from the same left edge.

   Amount is deliberately NOT excepted here: body cells have no column identity
   to target, so making it right-aligned needs a class added at the controller,
   not a selector in this file. */
table.dataTable th.dt-type-numeric,
table.dataTable th.dt-type-date,
table.dataTable td.dt-type-numeric,
table.dataTable td.dt-type-date {
    text-align: left;
}

/* ---- Row actions ----
   Five unlabelled 11px icons in a row is the least understandable thing on the
   list screen: nothing says which is which until you hover, and the targets
   are below the 24px minimum that makes them comfortably clickable. Give them
   real hit areas, a hover state that names the affordance, and a tooltip
   driven by the title attribute Backpack already emits. */
.table td .btn,
.table td a.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 26px, down from 30px. The box, not the text, is what set the row
       height on every list — a 30px button under 8px of cell padding made a
       46px row regardless of how small the type got. Kept at 26 rather than
       taken lower: 24px is the floor at which these become uncomfortable to
       hit, and AdminThemeTest holds it there. */
    width: 26px;
    height: 26px;
    min-height: 26px;
    padding: 0;
    /* The glyph grows as the box shrinks. What made these hard to read was
       never the button size, it was a 13px icon floating in a 30px box.
       Raised to 17px on request, then settled back to 15px: 17 filled the
       26px box to within ~4.5px a side, which read as heavy once five and six
       buttons sat together in one cell. 15px keeps the glyph clearly bigger
       than the 13px it started at without crowding its neighbours. */
    font-size: 15px;
    line-height: 1;
    border-radius: var(--tw-radius-nav);
    color: var(--tw-ink-muted);
    transition: background var(--tw-motion-fast) var(--tw-ease),
                color var(--tw-motion-fast) var(--tw-ease),
                transform var(--tw-motion-fast) var(--tw-ease);
}
.table td .btn:hover,
.table td a.btn:hover {
    background: var(--tw-surface-2);
    color: var(--tw-accent);
    transform: translateY(-1px);
}
.table td a.btn.text-danger:hover {
    background: var(--tw-danger-soft);
    color: var(--tw-danger-ink);
}
/* 1px, down from 2px. With four icons in a cell the gap is doing less work
   than the width it costs, and each button already carries 26px of its own
   box to separate the glyphs. */
.table td .btn + .btn { margin-left: 1px; }

/* The actions column is the one place a row should not read as clickable. */
.table td:last-child { white-space: nowrap; }

/* ---- List page header ----
   Backpack renders the title and the record count as siblings on one baseline
   (`<h1>Bookings</h1><p>Showing 1 to 10 of 2,295 entries.</p>`), so the count
   sat at nearly the title's weight and the two competed. Stacking them puts
   the count where metadata belongs — underneath, quieter — which is the same
   jump the dashboard uses. */
.header-operation.datatable-header {
    flex-direction: column;
    align-items: flex-start !important;
    gap: 3px;
    /* 10px -> 6px. Measured 1 Sep 2026: 186px sat above the first data row —
       a 58px title band, an 82px toolbar row and their margins. The table is
       what the screen is for; the furniture above it was taking a fifth of a
       1000px viewport before anything was shown. */
    margin-bottom: 6px !important;
}
.header-operation.datatable-header > p {
    margin-left: 0 !important;
    font-size: 13px;
    color: var(--tw-ink-muted);
}
.header-operation h1 { text-transform: none !important; }

/* The record count, once admin-ui.js has moved it below the pager. Backpack
   renders it into the page header beside the <h1> and re-does that on every
   draw; the script moves it down, and this is where it lands.

   A footnote to the table, not a heading for whatever comes next — so it takes
   the muted ink and the small size the rest of the panel uses for metadata,
   and keeps the Reset button Backpack appends to it on the same baseline. */
.tw-dt-info {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
    font-size: 12.5px;
    color: var(--tw-ink-muted);
}
.tw-dt-info .dt-info {
    margin: 0;
    padding: 0;
}

/* ---- Row actions ----
   Recessive, never hidden. Fifty icons on a ten-row page do compete with the
   data, but hiding them until hover was the wrong answer: an empty Actions
   column reads as broken, the controls are the ones staff use on every row,
   and nothing discoverable should depend on finding it by accident.

   Holding them at reduced opacity keeps the column quiet while leaving every
   action visible and countable at rest; the row under the cursor brings its
   own actions to full strength.

   .8 is a floor, not a taste call. Opacity over a white ground lightens the
   icon, and these are UI components, so WCAG 1.4.11 wants 3:1. Measured
   against white: .6 gives 2.31:1 and fails; .8 gives 3.25:1 for the muted
   icons and 3.77:1 for the red Archive. Do not lower it to quiet the column
   further — change the colour instead. */
.crud-table tbody td:last-child .btn {
    opacity: .8;
    transition: opacity var(--tw-motion-fast) var(--tw-ease);
}
.crud-table tbody tr:hover td:last-child .btn,
.crud-table tbody tr:focus-within td:last-child .btn {
    opacity: 1;
}

/* ---- Controls ---- */
.form-control,
.form-select {
    min-height: var(--tw-control-h);
    border-radius: var(--tw-radius-control);
    border-color: var(--tw-border-strong);
    font-size: 13px;
}
.form-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--tw-ink-muted);
    margin-bottom: 4px;
}
.btn {
    min-height: var(--tw-control-h);
    border-radius: var(--tw-radius-control);
    font-size: 13px;
    font-weight: 600;
}
/* Button variants: background and foreground declared together because
   our contrast calculations are only valid if we control both ends. */
.btn-primary {
    color: var(--tw-btn-fg-on-accent);
    background: var(--tw-accent);
    border-color: var(--tw-accent);
}
.btn-success {
    color: var(--tw-btn-fg-on-success);
    background: var(--tw-success);
    border-color: var(--tw-success);
}
.btn-warning {
    color: var(--tw-btn-fg-on-warning);
    background: var(--tw-warning);
    border-color: var(--tw-warning);
}
.btn-danger {
    color: var(--tw-btn-fg-on-danger);
    background: var(--tw-danger);
    border-color: var(--tw-danger);
}

/* Backpack's own form_save_buttons.blade.php renders every CRUD save button
   as `class="btn btn-success text-white"`, and Tabler's utility is
   `.text-white { color: ... !important }` — which beats the plain, non-important
   `color` declarations above in EVERY state, base included. That is why white
   text on the old #059669 success fill (3.77:1) shipped despite the pairing
   above. Since success became teal (#0C6060) white is now the CORRECT
   foreground at 7.35:1, so this override and Backpack's markup finally agree
   — but the mechanism still matters for the other variants.

   Scoped to `.btn-VARIANT.text-white` (both classes, one element) so it wins
   on specificity (0,2,0 vs 0,1,0) while `!important` matches the utility's
   own weight. A bare `.text-white` override would win the same fight but
   would also repaint every OTHER text-white element on the page — badges,
   avatars, ribbons — which is not ours to change.

   Each variant reads --tblr-VARIANT-fg (overridden at :root above) rather
   than --tw-btn-fg-on-* directly, so any future change to the pairing has
   exactly one edit site and cannot drift between the two mechanisms. */
.btn-primary.text-white {
    color: var(--tblr-primary-fg) !important;
}
.btn-success.text-white {
    color: var(--tblr-success-fg) !important;
}
.btn-warning.text-white {
    color: var(--tblr-warning-fg) !important;
}
.btn-danger.text-white {
    color: var(--tblr-danger-fg) !important;
}

/* ---- Validation: fix Bootstrap's sibling-selector miss inside input groups.
   .invalid-feedback rendered as a sibling of .input-group (not of the
   .is-invalid input) never matches `.is-invalid ~ .invalid-feedback`,
   so password errors render but stay display:none. ---- */
.input-group:has(.is-invalid) ~ .invalid-feedback,
.input-group .is-invalid ~ .invalid-feedback {
    display: block;
}

/* ---- Status pills: colour ALWAYS paired with text ----
   All four variants are tokenised (--tw-*-soft background / --tw-*-ink or
   --tw-ink-muted text). No exception remains here: a dark-mode block only
   has to redefine the :root tokens for every pill to follow. ---- */
.tw-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 9px;
    border-radius: 20px;
    font-size: 12px;
    font-weight: 600;
}
.tw-pill-success { background: var(--tw-success-soft); color: var(--tw-success-ink); }
.tw-pill-warning { background: var(--tw-warning-soft); color: var(--tw-warning-ink); }
.tw-pill-danger  { background: var(--tw-danger-soft); color: var(--tw-danger-ink); }
.tw-pill-info    { background: var(--tw-info-soft); color: var(--tw-info-ink); }
.tw-pill-neutral { background: var(--tw-neutral-soft); color: var(--tw-neutral-ink); }

/* ---- Forms: one field per line, label in a column on the left ----
   The shape of the v1 reference the booking form was built to, made the
   panel's standard on 21 August 2026. It replaces a two-column grid of
   label-above-control, which is what put two fields on a line.

   TARGETED AT THE WRAPPER BACKPACK ALREADY EMITS rather than at a class each
   controller adds. Every field on all 26 CRUD forms renders as
   `form .row > .form-group` — including the screens no controller in this repo
   builds by hand — so this reaches them all, and there is no second mechanism
   to keep in step. The opt-in `.tw-field-row` class this replaces had to be
   applied by a loop in two controllers that could drift apart.

   GRID RATHER THAN FLEX, which is the load-bearing choice. Not every wrapper
   is a tidy label/control pair: the upload field emits `.existing-file` AND
   `.backstrap-file`, several fields append a `.help-block`, and any field can
   gain an `.invalid-feedback`. Grid drops every non-label child into the value
   column, stacked. Flex would lay them across the row, and correcting that
   means naming every control class there will ever be — which is how the
   earlier version of this rule missed `.tw-select`, the wrapper the searchable
   dropdown JS builds at runtime.

   :not(.saveActions) because the save bar carries `form-group` too — read off
   a rendered product form, not assumed. Without it the Save button indents by
   the width of the label column and sits under a label that is not there. */
form .row > .form-group:not(.saveActions) {
    display: grid;
    grid-template-columns: var(--tw-field-label-w) 1fr;
    column-gap: 16px;
    align-items: center;
    /* Overrides the Bootstrap column classes Backpack puts on the wrapper
       (col-sm-12, col-md-6): one field per line is the point. */
    flex: 0 0 100%;
    max-width: 100%;
}
/* The value column. Declared before the label rule below, which is more
   specific and wins for the label itself. */
form .row > .form-group:not(.saveActions) > * {
    grid-column: 2;
    min-width: 0;
}
form .row > .form-group:not(.saveActions) > label {
    grid-column: 1;
    text-align: right;
    margin-bottom: 0;
    /* Overrides the 12px muted .form-label: at that size, right-aligned, a
       column of labels reads as a caption track rather than as the names of
       the things beside it. */
    font-size: 13px;
    color: var(--tw-ink);
    line-height: 1.35;
    /* Long labels wrap inside their column instead of widening it — every row
       on the form starts at the same x, which is what makes a column of
       fifteen read as a list. */
    overflow-wrap: anywhere;
}
/* A hint or a validation message belongs under the control, not beside it, and
   not stretched to the control's height by `align-items: center`. */
form .row > .form-group:not(.saveActions) > .help-block,
form .row > .form-group:not(.saveActions) > .invalid-feedback {
    align-self: start;
    margin-bottom: 0;
}

/* One column on a narrow viewport. A 210px label column beside a control
   leaves the control less room than its own label, and a <select> narrower
   than its options is unusable. */
@media (max-width: 991.98px) {
    form .row > .form-group:not(.saveActions) {
        display: block;
    }
    form .row > .form-group:not(.saveActions) > label {
        text-align: left;
        margin-bottom: 4px;
    }
}

/* ---- Form screens ---- */
form .form-group {
    margin-bottom: 20px;
}
form textarea.form-control {
    min-height: 104px;
    line-height: 1.6;
}
/* The theme already appends the asterisk (style.css:688 targets
   `form .form-group.required > label`); this only ties its colour to our
   token so it cannot drift from the rest of the danger palette. */
form .form-group.required > label:not(:empty):not(.form-check-label)::after {
    color: var(--tw-danger);
}
/* `.form-control::placeholder` rather than a bare `::placeholder`: Tabler
   declares the former at (0,1,1) and a bare pseudo-element is (0,0,1), so the
   unqualified rule lost and placeholders rendered at Tabler's #9ca3af — about
   2.6:1, well under the 4.5:1 this token exists to guarantee. Measured on the
   rendered form, not assumed. */
::placeholder,
.form-control::placeholder,
.form-select::placeholder,
textarea.form-control::placeholder {
    color: var(--tw-placeholder);
    opacity: 1; /* Firefox dims placeholders by default, undoing the contrast. */
}

/* A read-only field still shows its placeholder, and that placeholder is
   usually the explanation of what fills it — so it must not be dimmed into
   illegibility along with the control. */
.form-control[readonly]::placeholder {
    color: var(--tw-placeholder);
}

.form-control:disabled,
.form-control[readonly] {
    background: var(--tw-surface-2);
    color: var(--tw-ink-muted);
    cursor: not-allowed;
}

/* A disabled <select> keeps its caret.
   Tabler removes the background-image on `.form-select:disabled` and keeps the
   48px of right padding the caret sat in, so a locked dropdown renders as a
   grey box with an unexplained gap at its right edge — indistinguishable from a
   text input. Measured on the Unprocessed Ticket form, where most fields are
   read-only selects: computed background-image came back `none` while
   padding-right stayed 48px.

   The caret is the only thing on a locked field that says "this value came from
   a list", which is most of what someone reading a record they cannot edit
   wants to know.

   Same glyph as the enabled state, stroked in --tw-ink-muted (#697386) rather
   than Tabler's #9ca3af, so it reads as present-but-inactive rather than as an
   invitation to click. The colour is inlined because a data URI cannot resolve
   a CSS custom property; it is the same value as the token and must be changed
   with it. */
.form-select:disabled {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23697386' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: calc(100% - 16px) 50%;
    background-size: 16px 12px;
}
.invalid-feedback {
    font-size: var(--tw-text-micro);
    color: var(--tw-danger-ink);
    font-weight: 600;
}
.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--tw-danger);
}

/* ---- Form sections ----
   Real <fieldset>/<legend> pairs, so a legend is announced when focus enters
   any control in its group rather than only when someone browses headings.

   Both elements need their user-agent styling undone: a fieldset carries a
   border and inline padding, and a legend is inline-ish with its own padding
   and does not fill its container. */
.tw-fieldset {
    border: 0;
    padding: 0;
    margin: 0 0 6px;
    min-width: 0; /* a fieldset defaults to min-content, which breaks flex children */
    width: 100%;
}
.tw-legend {
    display: block;
    float: none;
    width: 100%;
    font-size: 16px;
    font-weight: 600;
    letter-spacing: -.01em;
    color: var(--tw-ink);
    margin: 16px 0 14px;
    padding: 0 0 10px;
    border-bottom: 1px solid var(--tw-border);
}
.tw-fieldset:first-of-type .tw-legend { margin-top: 0; }

/* Opt-in from the controller for fields the :has() rules below cannot
   recognise — a table, or anything else that needs the whole row. */
form .row > .tw-field-wide {
    flex: 0 0 100%;
    max-width: 100%;
}

/* ---- Full-width fields ----
   Opt-in from the controller for a field that needs the whole line, label and
   all: the Booking Items builder is a pair of cards with a table inside, and a
   210px label column beside it takes the width its columns need.

   `.tw-field-row` used to live here — the class the horizontal shape was
   opt-in through before it became the default for every form above. It is gone
   rather than kept as an alias: a class that no longer decides anything is a
   class the next reader has to prove is dead. */
form .row > .form-group.tw-field-wide {
    display: block;
    flex: 0 0 100%;
    max-width: 100%;
}
form .row > .form-group.tw-field-wide > label {
    display: block;
    text-align: left;
    margin-bottom: 8px;
}

/* ---- The items builder's own inner rows ----
   Same shape as a form field, one level down, and declared separately because
   these are NOT `form .row > .form-group` — they are the builder's own markup
   inside a single field's wrapper. */
.tw-items-row {
    display: grid;
    grid-template-columns: var(--tw-field-label-w) 1fr;
    column-gap: 16px;
    align-items: center;
    margin-bottom: 14px;
}
.tw-items-row:last-of-type {
    margin-bottom: 0;
}
.tw-items-row > label {
    grid-column: 1;
    text-align: right;
    margin-bottom: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--tw-ink);
    line-height: 1.35;
}
.tw-items-row > .form-control,
.tw-items-row > .form-select,
.tw-items-row > .tw-items-radios {
    grid-column: 2;
    min-width: 0;
}

@media (max-width: 991.98px) {
    .tw-items-row {
        display: block;
    }
    .tw-items-row > label {
        text-align: left;
        margin-bottom: 4px;
    }
}

/* ---- Booking Items builder ----
   Two cards inside one field: a Form that composes a line, a Table Detail that
   lists the lines. Both are plain Tabler cards, so they inherit the panel's
   own surface, border and header treatment rather than reproducing the v1
   screenshot's grey bars. --- */
.tw-items-card {
    margin-bottom: 16px;
}
.tw-items-card:last-child {
    margin-bottom: 0;
}
.tw-items-card .card-title {
    font-size: 13px;
    font-weight: 600;
    margin: 0;
}
.tw-items-card .card-title .la {
    color: var(--tw-ink-muted);
    margin-right: 6px;
}
.tw-items-row {
    margin-bottom: 14px;
}
.tw-items-row:last-of-type {
    margin-bottom: 0;
}
/* A fieldset carries a border, inline padding and a min-width of min-content,
   all of which have to go for the group to sit on the row like every other
   label/control pair.

   IT IS NOT A FLEX ROW, unlike every other .tw-items-row. A <legend> that is
   the first child of a <fieldset> is the element's RENDERED LEGEND and is
   excluded from the fieldset's formatting context — `display: flex` on the
   parent does not make it a flex item, and measured on the page it sat at the
   far right of the card while the radios started at the card's left edge.

   `position: absolute` is what takes a legend out of that special case, and it
   is used here in preference to `float: left`, which was tried first and does
   not work: the radios are a flex container, a flex container establishes an
   independent formatting context, and such a box drops BELOW a float it cannot
   fit beside rather than narrowing to sit next to it. Measured again — the
   radios landed exactly one line under the legend. Out of flow entirely, there
   is no float for anything to clear.

   The label column is then reserved with padding on the fieldset, so the
   radios line up with the controls above and below them without knowing that
   the legend is there at all.

   Kept as a real fieldset/legend throughout: the two radios are answers to one
   question, and a <label> beside them names neither. */
.tw-items-fieldset {
    display: block;
    position: relative;
    border: 0;
    padding: 0 0 0 calc(var(--tw-field-label-w) + 16px);
    margin: 0 0 14px;
    min-width: 0;
}
.tw-items-fieldset > legend {
    position: absolute;
    left: 0;
    top: 0;
    width: var(--tw-field-label-w);
    margin: 0;
    padding: 0;
    text-align: right;
    font-size: 13px;
    font-weight: 600;
    color: var(--tw-ink);
    /* Matches the control height above and below it, so the question lines up
       with its own answers rather than with the top of them. */
    line-height: var(--tw-control-h);
}
.tw-items-fieldset > .tw-items-radios {
    min-height: var(--tw-control-h);
}
.tw-items-radios {
    display: flex;
    align-items: center;
    gap: 18px;
}
.tw-items-note:empty {
    display: none;
}
.tw-items-note {
    margin: 4px 0 0;
    padding-left: calc(var(--tw-field-label-w) + 16px);
    font-size: var(--tw-text-micro);
    color: var(--tw-ink-muted);
}
.tw-items-table th {
    font-size: var(--tw-text-label);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--tw-ink-muted);
}
.tw-items-table td {
    font-size: var(--tw-text-sm);
}
/* The row being edited: a tint AND the left rule, because a tint alone is the
   only cue and roughly 1 in 12 men cannot rely on one. */
.tw-items-table tr.tw-items-editing td {
    background: var(--tw-accent-soft, var(--tw-neutral-soft));
    box-shadow: inset 3px 0 0 var(--tw-accent);
}
.tw-items-empty {
    color: var(--tw-ink-muted);
    font-size: var(--tw-text-sm);
    text-align: center;
    padding: 18px 12px;
}
.tw-items-actions .btn {
    padding: 3px 8px;
    min-height: 0;
}
.tw-items-actions .btn + .btn {
    margin-left: 4px;
}

/* ---- Sticky save bar on long forms ----
   Pinned to .saveActions, which is what crud/inc/form_save_buttons.blade.php
   actually renders. The previous rule targeted `form .card-footer` and
   `.form-actions`; crud/edit.blade.php renders neither, so on a form with
   thirty fields the Save button scrolled off the bottom exactly as it would
   have with no rule at all.

   The selector carries three classes because .saveActions also has Bootstrap's
   .my-3 — a lighter selector sets `position` but loses the margins, leaving
   the bar pinned with a gap under it. */
form .saveActions.form-group {
    position: sticky;
    bottom: 0;
    z-index: 20;
    margin: 8px 0 0;
    padding: 14px 16px;
    background: var(--tw-surface);
    border-top: 1px solid var(--tw-border);
    border-radius: var(--tw-radius-card);
    /* Shadow points upward: the bar floats above the content scrolling beneath
       it, so the depth cue has to come from the same direction. */
    box-shadow: 0 -4px 16px rgb(15 23 42 / .06);
}

/* ---- List screens: DataTables 2.x ----
   Every selector below was read off a rendered list page. The rules this
   block replaces were written for DataTables 1.x (.dataTables_wrapper,
   .dataTables_filter, .dataTables_length, .dataTables_empty) while the app
   loads cdn.datatables.net/2.1.8 — those four names match zero elements, so
   the entire block was dead code and the 26 list screens wore stock chrome.
   If a DataTables upgrade changes these, re-derive them from the DOM. */

/* The container wrapping the length select, the table and the pagination.
   Backpack's search box (.datatable-search-input) is a SIBLING above it, not
   a descendant, so it stays a toolbar control rather than joining the card. */
/* The table is the content; it does not need a container drawn around it.
   Wrapping it in a bordered, rounded card put a box around a box — the table
   already has its own header rule and row hairlines. */
.dt-container {
    background: transparent;
    border: 0;
    border-radius: 0;
    padding: 0;
}
.dt-container .dt-layout-table { margin-top: 0; }

.datatable-search-input,
.dt-length select {
    min-height: var(--tw-control-h);
    border-radius: var(--tw-radius-control);
    border: 1px solid var(--tw-border-strong);
    font-size: 12.5px;
}
.dt-length,
.dt-paging {
    padding: 10px 16px;
    font-size: var(--tw-text-sm);
    color: var(--tw-ink-muted);
}

/* ---- Index page toolbar ----
   The page title, the record count, the action buttons and the search box are
   four separate blocks that had no relationship to each other. Give the search
   field the same height and weight as the buttons it sits beside, and put the
   magnifier inside it so it reads as one control. */
.datatable-search-input {
    padding-left: 36px;
    background: var(--tw-surface);
}
.datatable_search_stack .input-icon-addon,
.datatable_search_stack .input-icon > .input-icon-addon {
    color: var(--tw-ink-faint);
    left: 12px;
}
.datatable-search-input:focus {
    background: var(--tw-surface);
}

/* Sort affordances: Tabler ships arrows that sit at the same weight whether a
   column is sorted or not, so the table never shows what it is sorted by. */
.crud-table thead th.sorting,
.crud-table thead th.sorting_asc,
.crud-table thead th.sorting_desc {
    cursor: pointer;
    transition: color var(--tw-motion-fast) var(--tw-ease);
}
.crud-table thead th.sorting:hover { color: var(--tw-accent); }
.crud-table thead th.sorting_asc,
.crud-table thead th.sorting_desc {
    color: var(--tw-accent);
}

/* Pagination reads as one control rather than a row of separate boxes. */
.dt-paging .page-item .page-link {
    border: 1px solid transparent;
    background: transparent;
    min-width: 32px;
    text-align: center;
    transition: background var(--tw-motion-fast) var(--tw-ease),
                color var(--tw-motion-fast) var(--tw-ease);
}
.dt-paging .page-item .page-link:hover {
    background: var(--tw-surface-2);
    color: var(--tw-accent);
}
.dt-paging .page-item.active .page-link {
    background: var(--tw-accent);
    color: var(--tw-btn-fg-on-accent);
    border-color: var(--tw-accent);
    font-weight: 650;
}
.dt-paging .page-item.disabled .page-link {
    color: var(--tw-ink-faint);
    opacity: .6;
}

/* No `overflow: hidden` on the table wrapper, however tempting it is for
   clipping the corners: any overflow other than `visible` makes an element a
   scroll container, and `position: sticky` resolves against the nearest one.
   The sticky <thead> would then pin to the top of that box — where it already
   sits — and stop following the page scroll, silently undoing the thing it was
   added for. The container's own padding keeps the header clear of the rounded
   edge, so there is nothing to clip. */
.crud-table thead th:first-child { border-top-left-radius: var(--tw-radius-nav); }
.crud-table thead th:last-child { border-top-right-radius: var(--tw-radius-nav); }

/* DataTables paints the zebra stripe as an inset box-shadow — not a
   background — via
     table.table.dataTable.table-striped>tbody>tr:nth-of-type(2n+1)>*
   at specificity (0,4,3). The old rule set `background: transparent` on
   `.table-striped > tbody > tr:nth-of-type(odd) > *` (0,2,2): wrong property,
   wrong rows, and out-specified regardless — so stripes shipped on top of the
   hairline borders this design uses instead.

   Mirroring their selector exactly matches their specificity, and
   admin-theme.css is the last stylesheet in the document, so the tie falls to
   us. Deliberately not `!important`: the row-selection rule that follows
   theirs must still be able to paint a selected row. */
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
    box-shadow: none;
}

/* Backpack repeats the column headers in a <tfoot>. This file used to hide it,
   on the reasoning that it carries nothing the <thead> does not and reads as a
   rendering fault. Overruled on 20 August 2026: the v1 screenshot supplied as
   the reference for these lists shows it, and on a 100-row page it saves a
   scroll back to the top to remember what a column is.
   Styled as a closing rule rather than a second header — same weight as the
   thead labels, but the emphasis is the border above it, not a fill. */
.crud-table tfoot th {
    font-size: 11.5px;
    font-weight: 600;
    color: var(--tw-ink-muted);
    text-transform: none;
    letter-spacing: 0;
    white-space: nowrap;
    background: var(--tw-surface);
    border-top: 1px solid var(--tw-border);
    border-bottom: 0;
}
.page-link {
    border-radius: var(--tw-radius-nav);
    border-color: var(--tw-border);
    color: var(--tw-ink-muted);
    font-size: 12.5px;
}
.page-item.active .page-link {
    background: var(--tw-accent);
    border-color: var(--tw-accent);
}
.alert {
    border-radius: var(--tw-radius-card);
    border: 1px solid var(--tw-border);
    font-size: 12.5px;
}
.dt-empty,
.tw-empty {
    color: var(--tw-ink-muted);
    font-size: 12.5px;
    text-align: center;
    padding: 28px 12px !important;
}

/* ---- Sidebar ---- */
/* `.navbar.navbar-vertical` rather than `.navbar-vertical`: the sidebar also
   carries Tabler's `.navbar-transparent`, which resets the background to
   nothing at the same (0,1,0) weight this rule had. Two rules of equal
   specificity in different sheets is a coin toss decided by load order, and
   relying on that is how the earlier dead selectors happened. */
.navbar.navbar-vertical {
    background: var(--tw-sidebar);
    border-right: 1px solid var(--tw-sidebar-border);
    /* Set here so `inherit` resolves to it. The account dropdown at the top of
       the sidebar carries Bootstrap's `.text-reset`, which is
       `color: inherit !important` — it cannot be targeted by a normal rule at
       any specificity, so the only way to colour it is to fix what it
       inherits FROM. Measured at 3.70:1 before this; #E8EDE9 is 14.90:1. */
    color: var(--tw-sidebar-ink);
}
/* Backpack's own style.css carries `aside .nav-item { color: var(--tblr-navbar-color) }`
   — a DIRECT rule on the item, which inheritance from the sidebar cannot beat
   no matter how the parent is coloured. It repainted the account dropdown at
   the top of the rail to #697386 (3.70:1 on the dark ground). At (0,3,0) this
   outranks that rule's (0,1,1) without !important, and it also fixes the
   dropdown's `.text-reset` child, which is `color: inherit !important` and so
   is only ever reachable through its parent. */
.navbar.navbar-vertical .nav-item {
    color: var(--tw-sidebar-ink);
}

/* The brand is now an image, so the type properties that styled the old
   "<b>Travelon</b> World" markup are gone rather than left to apply to
   nothing. Centred because the asset is square-ish (300x300, mark stacked
   over wordmark) — left-aligning it would hang the wordmark off-centre
   against the nav labels below. */
/* CENTRED WITH text-align, NOT flex. Backpack renders the brand as
   `<h1 class="navbar-brand d-none d-lg-block ...">`, and Tabler's
   `.d-lg-block` is `display: block !important` — so a `display: flex` here
   loses outright and `justify-content` silently does nothing. Verified
   against the rendered DOM, not the stylesheet. The inner `<a>` is made a
   block so the image's auto margins have a width to centre within. */
.navbar-vertical .navbar-brand {
    text-align: center;
    padding: 18px 14px 14px;
}
.navbar-vertical .navbar-brand > a {
    display: block;
}
.tw-brand-logo {
    width: 100%;
    max-width: 132px;
    height: auto;
    margin: 0 auto;
}

/* The heading is a real <button> so it is reachable by keyboard and announces
   its expanded state; the button chrome is reset because it must still read as
   a label, not a control. */
.tw-nav-group {
    display: flex;
    align-items: center;
    width: 100%;
    appearance: none;
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: .08em;
    font-weight: 700;
    color: var(--tw-sidebar-muted);
    padding: 0 12px;
    margin: 22px 0 6px;
    transition: color var(--tw-motion-fast) var(--tw-ease);
}
.tw-nav-group:hover { color: var(--tw-sidebar-ink); }
.tw-nav-section:first-child .tw-nav-group { margin-top: 6px; }

/* A chevron drawn in CSS rather than an icon font, so it cannot arrive late or
   fail to load; it rotates to point at the state the section is IN. */
.tw-nav-caret {
    margin-left: auto;
    width: 7px;
    height: 7px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    transition: transform var(--tw-motion) var(--tw-ease);
    opacity: .8;
}
.tw-nav-group[aria-expanded="false"] .tw-nav-caret {
    transform: rotate(-45deg) translate(-1px, 1px);
}

/* Collapsed items are removed from the layout AND the accessibility tree —
   `display: none` rather than a height animation, because the items are links
   and a keyboard user must not be able to tab into a section they closed. */
.tw-nav-collapsed { display: none !important; }

.navbar-vertical .nav-item { padding: 0 8px; }

/* SPECIFICITY, measured in the browser rather than assumed. Tabler ships
   `.navbar-expand-lg .nav-item.active .nav-link` (0,4,0) inside a min-width
   992px block, pointing colour at --tblr-navbar-active-color (#1a1f36). A
   plain `.navbar-vertical .nav-link.active` is only (0,3,0), so it loses
   outright — the dark navy ink landed on the dark green fill at roughly
   1.3:1 and the active item read as a smudge.
   `.navbar.navbar-vertical` (the sidebar carries both classes) plus Tabler's
   own `.nav-item.active .nav-link` shape takes this to (0,5,0), which wins
   without reaching for !important. Same reasoning as the background rule at
   the top of this section. */
.navbar.navbar-vertical .nav-link {
    border-radius: var(--tw-radius-nav);
    padding: 7px 10px;
    font-size: var(--tw-text-sm);
    color: var(--tw-sidebar-ink);
    font-weight: 500;
    position: relative;
    transition: background var(--tw-motion-fast) var(--tw-ease),
                color var(--tw-motion-fast) var(--tw-ease),
                padding-left var(--tw-motion-fast) var(--tw-ease);
}
.navbar.navbar-vertical .nav-link:hover {
    background: var(--tw-sidebar-hover);
    color: #fff;
}
/* The active item is marked twice — fill and ink — and no longer a third time
   with an inset accent bar. Across 29 rows the bar was the signal that turned
   the column stripey, and it was doing work the lifted ink already does. The
   fill and ink were both retuned when it went: #1F2E1D under #A9E07A holds
   9.29:1, so the row reads on its own. */
.navbar.navbar-vertical .nav-item.active .nav-link,
.navbar.navbar-vertical .nav-link.active {
    background: var(--tw-sidebar-active);
    color: var(--tw-sidebar-active-ink);
    font-weight: 600;
}

/* Icons sit back so the label leads. A column of 26 competing glyphs is
   noise; the words are what people scan. */
.navbar.navbar-vertical .nav-link .nav-icon,
.navbar.navbar-vertical .nav-link i {
    opacity: .5;
    margin-right: 10px;
    font-size: 15px;
}
.navbar.navbar-vertical .nav-item.active .nav-link i,
.navbar.navbar-vertical .nav-link.active i { opacity: 1; }

.tw-nav-item-wrap { position: relative; }
.tw-nav-badge {
    position: absolute;
    right: 18px;
    top: 50%;
    transform: translateY(-50%);
    pointer-events: none;
    background: var(--tw-sidebar-badge);
    color: var(--tw-sidebar-badge-ink);
    font-size: var(--tw-text-micro);
    font-weight: 700;
    padding: 1px 7px;
    border-radius: 10px;
}
.tw-nav-badge[data-nav-badge="0"] { display: none; }

/* The sidebar is the tallest scrolling region in the panel and the default
   scrollbar is a heavy grey slab down the edge of it — one of the loudest
   things on screen, attached to the least important. Slim it, and only tint
   the thumb on hover. */
.navbar-vertical .navbar-collapse,
.navbar-vertical .navbar-nav {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}
.navbar-vertical .navbar-collapse:hover,
.navbar-vertical .navbar-nav:hover {
    scrollbar-color: var(--tw-sidebar-scroll) transparent;
}
.navbar-vertical ::-webkit-scrollbar { width: 8px; }
.navbar-vertical ::-webkit-scrollbar-track { background: transparent; }
.navbar-vertical ::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 4px;
    border: 2px solid transparent;
    background-clip: content-box;
    transition: background var(--tw-motion) var(--tw-ease);
}
.navbar-vertical:hover ::-webkit-scrollbar-thumb {
    background: var(--tw-sidebar-scroll);
    background-clip: content-box;
}

/* Space separates the groups, not a rule. The hairline this replaces was
   `1px solid var(--tw-border)` — #e3e8ee, a content-surface value from the
   light palette that was never redefined for the dark rail, so it shipped as
   a near-white line across #141A16. Rather than recolour it, it goes: the
   heading already marks where a section begins, and a rule as well as a label
   is two devices doing one job. The extra margin is what carries it now. */
.tw-nav-section:not(:first-child) .tw-nav-group {
    margin-top: 26px;
}
.tw-nav-section { list-style: none; }

/* The .tw-search* block lived here — topbar search box, ⌘K hint, results
   dropdown. Removed with the markup on 17 August 2026 rather than left behind:
   eleven rules styling selectors that render nowhere is exactly the dead weight
   that makes this file hard to reason about. */

/* ---- Login: single-screen brand backdrop ----
   The login page (only) sits on a dark-centred green field — dark where the
   card and logo sit, brand green blooming in the corners. Scoped to
   `.tw-auth-page` (a class the published login override adds to its wrapper)
   so it cannot leak onto the other six auth screens that share `.page-center`
   — password reset, verify, register — which keep their light look.

   A dark-CENTRED field, not a flat gradient: content sits in the middle, so
   the middle must stay dark enough for the card's shadow to read and for the
   logo's white wordmark to show. A flat dark-to-green wash put the bright end
   under the content and failed it. */
.tw-auth-page.page-center {
    background:
        radial-gradient(circle at 82% 14%, var(--tw-auth-panel-glow-1), transparent 42%),
        radial-gradient(circle at 12% 90%, var(--tw-auth-panel-glow-2), transparent 48%),
        var(--tw-auth-panel-base);
}

/* No plate here. The global `.auth-logo-container` plate (further down) exists
   because the OTHER auth pages show the white-wordmark logo on a light column;
   on this dark backdrop the logo reads directly, so the plate is removed and a
   drop-shadow lifts it off the field instead. */
.tw-auth-page .auth-logo-container .tw-brand-logo {
    max-width: 168px;
    padding: 0;
    background: none;
    border-radius: 0;
    filter: drop-shadow(0 8px 26px rgb(0 0 0 / .4));
}

/* The card returns to an OPAQUE white surface here — the form's dark ink must
   never sit on the dark backdrop. No border (as asked), a soft lift instead so
   it floats off the green rather than being boxed in. */
.tw-auth-page .card {
    border: 0;
    background: var(--tw-surface);
    box-shadow: 0 20px 60px -12px rgb(0 0 0 / .45), 0 4px 12px rgb(0 0 0 / .2);
    border-radius: var(--tw-radius-card);
}

/* ---- Auth screens: the logo needs a dark plate ----
   The remaining auth pages (password reset, verify, register) show the
   project_logo in a LIGHT column, and the asset's wordmark is white — so
   without a dark ground behind it those pages would show a green bird above
   nothing. The login page overrides this above; here it stands for the rest.
   Sized and rounded like an app icon so it reads as deliberate. ---- */
.auth-logo-container {
    display: flex;
    justify-content: center;
}
.auth-logo-container .tw-brand-logo {
    box-sizing: content-box;
    width: 100%;
    max-width: 148px;
    height: auto;
    padding: 18px 20px;
    background: var(--tw-sidebar);
    border-radius: var(--tw-radius-card);
}

/* ---- Bars ---- */
.tw-bars { padding: 8px 0 12px; }
.tw-bar-row {
    display: flex; align-items: center; gap: 12px; padding: 8px 16px;
    font-size: var(--tw-text-sm);
    border-radius: var(--tw-radius-nav);
    transition: background var(--tw-motion-fast) var(--tw-ease);
}
.tw-bar-row:hover { background: var(--tw-surface-2); }
.tw-bar-label { width: 130px; color: var(--tw-ink-muted); flex: none;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tw-bar-track { flex: 1; height: 10px; background: var(--tw-surface-2);
    border-radius: 5px; overflow: hidden; }
/* Bars grow from zero on first paint, which makes their relative lengths
   register as a comparison rather than as static decoration. */
.tw-bar-fill {
    display: block; height: 10px; border-radius: 5px;
    transform-origin: left center;
    animation: tw-bar-grow var(--tw-motion-slow) var(--tw-ease) both;
}
@keyframes tw-bar-grow {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
}
.tw-bar-value { width: 62px; text-align: right; font-weight: 600;
    font-variant-numeric: tabular-nums; flex: none; }
.tw-bar-accent  { background: var(--tw-accent); }
.tw-bar-success { background: var(--tw-success); }
.tw-bar-warning { background: var(--tw-warning); }
.tw-bar-danger  { background: var(--tw-danger); }
.tw-bar-info    { background: var(--tw-info); }
.tw-bar-neutral { background: var(--tw-ink-faint); }

/* ---- Dashboard range picker ---- */
/* Tabs rather than a segmented pill. The range is a view of the page, and
   tabs are how a page says "same thing, different slice" — a pill control
   reads as a setting sitting on top of the content. */
.tw-range {
    display: flex;
    gap: 22px;
    border-bottom: 1px solid var(--tw-border);
    width: 100%;
    margin-top: 18px;
}
.tw-range-option {
    padding: 0 0 11px;
    font-size: var(--tw-text-base);
    font-weight: 500;
    color: var(--tw-ink-muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.tw-range-option:hover { color: var(--tw-ink); }
/* The active option carries the accent as a filled chip. Weight and background
   both change, so the selection is not signalled by colour alone. */
.tw-range-active,
.tw-range-active:hover {
    color: var(--tw-ink);
    font-weight: 600;
    border-bottom-color: var(--tw-accent);
}

/* ---- Metrics ----
   No card, no chip, no tinted wash. Four bordered, shadowed tiles carrying a
   coloured icon each were four containers competing with the number they
   existed to show. What is left is a label, a figure and a delta, separated
   from the page by one hairline underneath the row. The size jump between
   label and figure does the work the boxes were doing. */
.tw-kpi {
    padding: 0 28px 0 0;
    background: transparent;
    border: 0;
    box-shadow: none;
    border-radius: 0;
}
.tw-kpi:hover { box-shadow: none; transform: none; }
.tw-kpi::after { display: none; }

.tw-kpi-head { display: flex; align-items: center; gap: 6px; }
/* The icon chip is gone; the label leads. */
.tw-kpi-icon { display: none; }
.tw-kpi-k {
    font-size: var(--tw-text-base);
    text-transform: none;
    letter-spacing: 0;
    color: var(--tw-ink-muted);
    font-weight: 500;
}
.tw-kpi-v {
    font-size: 30px;
    font-weight: 600;
    letter-spacing: -.025em;
    margin-top: 9px;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
    color: var(--tw-ink);
}
.tw-kpi-d { font-size: 12.5px; margin-top: 7px; color: var(--tw-ink-muted); }

/* The delta is a coloured word, not a chip. A pill here competed with the
   status pills in the table below, which carry actual meaning. */
.tw-up, .tw-down {
    display: inline;
    padding: 0;
    border-radius: 0;
    background: none;
    font-weight: 600;
}
.tw-up { color: var(--tw-success-ink); }
.tw-down { color: var(--tw-danger-ink); }

/* One rule under the whole row separates metrics from what follows. */
.tw-metrics {
    border-bottom: 1px solid var(--tw-border);
    padding-bottom: 26px;
    margin-bottom: 30px;
}

/* Section heading that sits on the page, card-free. */
.tw-sec {
    display: flex;
    align-items: baseline;
    margin-bottom: 16px;
}
.tw-sec h2 { font-size: 16px; font-weight: 600; letter-spacing: -.01em; }
.tw-sec .tw-sec-meta { margin-left: auto; font-size: 13px; color: var(--tw-ink-muted); }

/* ---- Explanatory hint ----
   Plain-language help attached to a metric whose name cannot carry it.
   Rendered from a title attribute so it degrades to the native tooltip if the
   script never runs. */
.tw-hint {
    display: inline-flex; align-items: center; justify-content: center;
    width: 15px; height: 15px; margin-left: 5px;
    border-radius: 50%;
    border: 1px solid var(--tw-border-strong);
    color: var(--tw-ink-faint);
    font-size: 9.5px; font-weight: 700;
    cursor: help; vertical-align: middle;
    transition: color var(--tw-motion-fast) var(--tw-ease),
                border-color var(--tw-motion-fast) var(--tw-ease);
}
.tw-hint:hover { color: var(--tw-accent); border-color: var(--tw-accent); }

/* ---- Empty states ----
   A sentence in grey text reads as "something failed". An icon, a plain
   explanation and a way forward reads as "there is nothing here yet", which is
   usually the truth. */
.tw-empty-state {
    display: flex; flex-direction: column; align-items: center;
    gap: 8px; padding: 40px 20px; text-align: center;
}
.tw-empty-icon {
    width: 44px; height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: var(--tw-surface-2);
    color: var(--tw-ink-faint);
    font-size: 20px;
}
.tw-empty-title { font-size: var(--tw-text-sm); font-weight: 650; color: var(--tw-ink); }
.tw-empty-help { font-size: var(--tw-text-micro); color: var(--tw-ink-muted); max-width: 320px; }

/* ---- Chart hover readout ---- */
/* The SVG has a 620x132 viewBox and width:100%, so on a wide screen it scales
   to roughly 280px tall and the line ends up a hairline adrift in empty space.
   Cap the height and thicken the stroke so the shape stays the subject. */
.tw-chart {
    position: relative;
    cursor: crosshair;
}
.tw-chart svg {
    overflow: visible;
}
.tw-chart svg path {
    stroke-width: 2.5;
}
.tw-chart-tip {
    position: absolute;
    transform: translate(-50%, -100%);
    background: var(--tw-ink);
    color: #fff;
    font-size: var(--tw-text-micro);
    font-weight: 600;
    padding: 5px 9px;
    border-radius: var(--tw-radius-nav);
    box-shadow: var(--tw-shadow-pop);
    pointer-events: none;
    white-space: nowrap;
    opacity: 0;
    transition: opacity var(--tw-motion-fast) var(--tw-ease);
    z-index: 5;
}
.tw-chart-tip[data-visible="true"] { opacity: 1; }
.tw-chart-tip-date { display: block; font-weight: 500; opacity: .75; }

.tw-role-chip {
    font-size: var(--tw-text-micro);
    font-weight: 650;
    padding: 3px 10px;
    border-radius: 20px;
    background: var(--tw-accent-soft);
    color: var(--tw-accent-ink);
    margin-right: 12px;
    white-space: nowrap;
}

/* ---- Topbar ----
   The `vertical` layout ships no header at all, so this styles the one the
   published layouts/vertical.blade.php reinstates. Without it the global
   search existed only in the sidebar's d-lg-none strip and measured zero
   pixels tall on every desktop. */
.tw-topbar {
    background: var(--tw-surface);
    border-bottom: 1px solid var(--tw-border);
    min-height: 56px;
    padding-top: 0;
    padding-bottom: 0;
}
/* The user dropdown sits at the far right; give it room from the role chip
   without the vendor's asymmetric padding. */
.tw-topbar .dropdown.pe-3 { padding-right: 0 !important; }

/* ---- Entry motion ----
   Cards fade up in sequence on load. The delay is capped so a long page never
   leaves the last card visibly late. */
.tw-reveal {
    animation: tw-reveal var(--tw-motion-slow) var(--tw-ease) both;
}
@keyframes tw-reveal {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
}

/* ---- Reduced motion ----
   One block, at the end, so every animation and transition above is covered by
   it. Motion here is decoration; nothing in this design depends on it to be
   understood. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
    .tw-kpi:hover { transform: none; }
}

/* ---- Searchable dropdowns ----
   Paired with searchableSelects() in public/js/admin-ui.js. The native <select>
   stays inside .tw-select and stays what the form submits; it is made
   transparent rather than display:none so that a REQUIRED select still counts
   as focusable and native validation keeps working. Without the JS none of
   these classes are ever applied and the select renders as usual. */
.tw-select {
    position: relative;
}
.tw-select > select[data-tw-searchable] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    pointer-events: none;
}
.tw-select__button {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    width: 100%;
    text-align: left;
    cursor: pointer;
}
.tw-select__button::after {
    content: "";
    flex: none;
    width: .5rem;
    height: .5rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translate(-2px, -2px);
    opacity: .5;
}
.tw-select__button--empty {
    color: var(--tw-ink-muted, #6b7280);
}
.tw-select__panel {
    position: absolute;
    /* Above the sticky save bar, which is z-index 20. It was 20 here too, and
       on a tie the later element in the DOM wins — the save bar is rendered
       after the fields, so an open dropdown was painted under the Save buttons.
       Deliberately NOT a Bootstrap-scale value: staying below 1050 means a
       modal still covers this panel rather than the other way round. */
    z-index: 30;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    padding: .5rem;
    background: var(--tw-surface, #fff);
    border: 1px solid var(--tw-border, #e5e7eb);
    border-radius: var(--tw-radius-card);
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
}
.tw-select__list {
    max-height: 15rem;
    margin: .5rem 0 0;
    padding: 0;
    overflow-y: auto;
    list-style: none;
}
.tw-select__option {
    padding: .375rem .5rem;
    border-radius: var(--tw-radius-nav);
    cursor: pointer;
}
.tw-select__option--active,
.tw-select__option:hover {
    background: var(--tw-accent-soft, #eef2ff);
}
.tw-select__option[hidden] {
    display: none;
}

/* ---------------------------------------------------------------------------
   Record read views (bookings, payments)
   ---------------------------------------------------------------------------
   Opening a record used to mean opening its edit form. These are the read
   screens, and the summary strip is the part that earns them: identity, state
   and the money question answered above the fold, so the commonest job — being
   asked about a booking on the phone — needs no scrolling.

   Only the classes the two show views actually render are styled here. */

.tw-record-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 22px;
    padding: 16px 18px;
    margin: 0 0 4px;
    background: var(--tw-surface-2);
    border: 1px solid var(--tw-border);
    border-radius: var(--tw-radius-nav);
}

.tw-record-who {
    font-size: 18px;
    font-weight: 600;
    letter-spacing: -.01em;
    color: var(--tw-ink);
}

/* Figures sit to the right of identity and state, and wrap beneath them
   rather than shrinking, so an amount is never truncated mid-digit. */
.tw-record-figure {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-left: auto;
}
.tw-record-figure + .tw-record-figure { margin-left: 0; }

.tw-record-figure-k {
    font-size: var(--tw-text-micro);
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--tw-ink-faint);
}

.tw-record-figure-v {
    font-size: 17px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--tw-ink);
}

/* Label/value pairs. Two columns on a wide screen, stacked on a narrow one —
   a definition list rather than a table because these are pairs, not rows. */
.tw-record-list {
    display: grid;
    grid-template-columns: minmax(140px, max-content) 1fr;
    gap: 8px 20px;
    margin: 0;
}

.tw-record-list dt {
    font-size: var(--tw-text-sm);
    font-weight: 500;
    color: var(--tw-ink-muted);
}

.tw-record-list dd {
    margin: 0;
    font-size: var(--tw-text-sm);
    color: var(--tw-ink);
}

@media (min-width: 992px) {
    .tw-record-list {
        grid-template-columns: minmax(140px, max-content) 1fr minmax(140px, max-content) 1fr;
    }
}

/* The action bar above a read view. Wraps rather than scrolls: four buttons at
   a narrow width is two rows, not a hidden fourth button. */
.tw-record-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 14px;
}

/* ---- Booking detail panel ----
   Built to a v1 screenshot: a flat label/value list rather than the grouped
   fieldsets the booking show view used before. Kept scoped to .tw-detail* so
   the payments show view, which still uses .tw-record-*, is untouched.

   The values deliberately do NOT reproduce the reference's brown/maroon text.
   That was v1's palette; this panel sits in a green-branded panel and a third
   accent colour on a read-only list buys nothing. Labels carry the emphasis,
   values take the ordinary body ink. */
.tw-detail {
    border: 1px solid var(--tw-border);
    border-radius: 6px;
    overflow: hidden;
    margin-bottom: 18px;
}
.tw-detail-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    background: var(--tw-surface-2);
    border-bottom: 1px solid var(--tw-border);
    font-weight: 600;
    font-size: 13px;
    color: var(--tw-ink);
}
.tw-detail-head i { opacity: .65; }
.tw-detail-list { margin: 0; }

/* A fixed label column, not a flex pair: with `auto` the label width tracked
   the longest label in each row and every row started at a different x, which
   is what stops a list of fifteen reading as a list. */
.tw-detail-row {
    display: grid;
    grid-template-columns: 260px 1fr;
    gap: 12px;
    padding: 9px 14px;
    font-size: 12.5px;
}
.tw-detail-row:nth-child(odd) {
    background: var(--tw-surface-2);
}
.tw-detail-row dt {
    font-weight: 600;
    color: var(--tw-ink);
    margin: 0;
}
.tw-detail-row dd {
    margin: 0;
    color: var(--tw-ink-2);
    /* Values here are free text a supplier typed — an address, a note — so
       they wrap rather than ellipsis. This is a record, not a dense table. */
    overflow-wrap: anywhere;
}

/* Booking Items sits INSIDE the panel, between Additional Info and the two
   cost rows, which is where the reference puts it. Inset so it reads as part
   of the record rather than as the next section down. */
.tw-detail-sub {
    margin: 10px 14px 14px;
    border: 1px solid var(--tw-border);
    border-radius: 6px;
    overflow: hidden;
}
.tw-detail-subhead {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 12px;
    background: var(--tw-surface-2);
    border-bottom: 1px solid var(--tw-border);
    font-weight: 600;
    font-size: 12.5px;
    color: var(--tw-ink);
}
.tw-detail-subhead i { opacity: .65; }
.tw-detail-table { margin: 0; }
.tw-detail-table th {
    font-weight: 600;
    white-space: nowrap;
}
.tw-detail-empty {
    color: var(--tw-ink-muted);
    font-size: 12.5px;
}
/* An explanation standing in for a value — "no booking matches this payment".
   Muted so it does not read as the value itself, which is the failure a plain
   <dd> of prose invites. */
.tw-detail-muted {
    color: var(--tw-ink-muted);
}

/* The Actions row of the shared record panel (crud/inc/show_table.blade.php).
   Flex rather than the browser's inter-element whitespace, so the gap between
   Edit and Delete is a decision rather than a side effect of how the blade
   happens to be indented. The row runs taller than the others because a button
   is taller than a line of text — that is the button's height, not padding
   added here. */
.tw-detail-actions {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* One column on a narrow viewport: a 260px label column beside a value leaves
   the value unreadably narrow on a phone, and this screen is opened on one. */
@media (max-width: 768px) {
    .tw-detail-row {
        grid-template-columns: 1fr;
        gap: 2px;
    }
}

/* ---- Filter bar ----
   Right-aligned. The search box already sits top-right, and filters are the
   other half of the same job — narrowing the list. Split across opposite
   corners they read as unrelated controls and answering one question means
   scanning both ends of the screen.

   justify-content on the nav itself rather than on .navbar-collapse: the
   filter icon is a sibling OUTSIDE the collapse, so aligning only the collapse
   would leave that icon stranded at the left edge. */
.navbar-filters {
    justify-content: flex-end;
}
.navbar-filters .navbar-collapse {
    flex-grow: 0;
}

/* The toolbar row once admin-ui.js has moved the filter bar into it: top
   buttons on the left, filters pushed right so they sit against the search
   box in the next column. Marked with a class by the script rather than styled
   through .row, so no other Bootstrap row in the panel is affected. */
/* Only the first column is touched. The .row itself is left as Bootstrap's
   grid — overriding it to a flex row while its children kept their col-*
   widths and gutter padding is what broke this layout once already.

   space-between, NOT `margin-left: auto` on the filter nav. Measured in the
   browser: an auto margin has no effect on that element — set inline, the nav
   did not move a pixel — while space-between puts its right edge 8px from the
   search box in the next column, which is the result wanted. The auto-margin
   version shipped twice and looked correct in the stylesheet both times. */
.tw-toolbar-row > [class^="col"]:first-child {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
}

/* The toolbar row measured 82px tall on 1 Sep 2026 — taller than two data rows,
   to hold one button and a search box. Its own margin is trimmed with it; the
   row below is a table, which brings its own top edge. */
.tw-toolbar-row { margin-bottom: 4px !important; }
/* The filter navbar was 56px inside an 82px row — Bootstrap's navbar carries
   its own vertical padding, which a single dropdown does not need. */
.tw-toolbar-row .navbar-filters,
.tw-toolbar-row .navbar-collapse,
.tw-toolbar-row .navbar-nav { min-height: 0 !important; padding-top: 0 !important; padding-bottom: 0 !important; }
.tw-toolbar-row .navbar-nav > li { margin-top: 0 !important; margin-bottom: 0 !important; }
.tw-toolbar-row .btn { padding-top: .3rem; padding-bottom: .3rem; }
.tw-toolbar-row .datatable_search_stack .form-control { padding-top: .3rem; padding-bottom: .3rem; }
.tw-toolbar-row .navbar-filters {
    /* Positioning comes from the column's justify-content, not from here: an
       auto margin on this element is inert (verified in the browser). */
    padding: 0;
    /* The nav ships with its own bottom margin for the standalone position it
       no longer occupies. */
    margin-bottom: 0;

    /* Tabler paints .navbar as a surface: a background, a 3.5rem min-height,
       and a dividing rule drawn as an INSET BOX-SHADOW rather than a border.
       On a toolbar row all three read as a stray panel sitting behind the
       filters, and `border: 0` removes none of them. */
    background: transparent;
    box-shadow: none;
    border: 0;
    min-height: 0;
}

/* Backpack renders a "remove filters" link alongside the filters and marks it
   with Bootstrap's .invisible while no filter is active. .invisible is
   `visibility: hidden`, which still reserves the box — measured in the browser
   at 122px of empty space between the last filter and the search input, which
   is what made a 30px gap read as 152px.

   display:none collapses it, and it returns the instant a filter is applied
   and the class comes off. Scoped to the toolbar row so the standalone filter
   bar on any other screen is untouched. */
.tw-toolbar-row .navbar-filters .remove_filters_button.invisible {
    display: none;
}
