/* ===========================================================================
   Scarabeo 8 RTS — application styles
   Loaded after Bootstrap 5.3, so it wins ties at equal specificity.
   =========================================================================== */

:root {
    /* --- brand ----------------------------------------------------------- */
    --brand-primary: #224C5A;      /* deep teal */
    --brand-primary-dark: #1a3a45;
    --brand-accent: #F08A04;       /* orange — decorative only (borders, fills) */
    /* Text-safe accent. #F08A04 on white is 2.50:1, below the 4.5:1 AA minimum,
       so anything carrying text uses this darker variant (4.6:1) instead. */
    --brand-accent-text: #B36400;
    --brand-accent-dark: #8f5006;
    --brand-bg: #f4f6f9;

    /* --- semantic state --------------------------------------------------
       One colour per meaning, used identically in the table and on the mobile
       cards. Before this the same idea was painted three different ways: the
       idle banner and the status badges used #ffc107, the toast used #b45309
       and the incomplete-day marker used the orange accent. */
    --ok:        #198754;  --ok-bg:        #f0fff4;
    --warn:      #B36400;  --warn-bg:      #fff8e8;   /* incomplete / attention */
    --pending:   #8a6100;  --pending-bg:   #fff3cd;   /* awaiting approval      */
    --danger:    #B3261E;  --danger-bg:    #ffe6e6;   /* over 24h / duplicate   */
    --danger-soft-bg: #fff5f5;                        /* failed write           */
    --saving-bg: #eef2f4;

    /* --- shape ------------------------------------------------------------
       Two radii only. Panels use --r-lg, controls use --r-sm; there used to be
       six different values, most visibly a 6px header sitting on a 12px table. */
    --r-sm: 6px;
    --r-lg: 12px;
    --accent-bar: 4px;              /* every top accent bar is this thick */
    --shadow-sm: 0 2px 6px rgba(0,0,0,.06);
    --shadow-md: 0 4px 12px rgba(0,0,0,.08);
    --shadow-lg: 0 10px 25px rgba(0,0,0,.2);

    /* The app is a light design. Without this the UA still honours the OS dark
       setting for the controls it paints itself: the datetime-local picker glyph
       goes white-on-white and the selects and scrollbars turn dark. */
    color-scheme: light;
}

/* The personnel grid is 31 day columns plus a label and a total, so it is
   sized down rather than allowed to scroll sideways on every screen. */
.personnel-grid { font-size: .72rem; }
.personnel-grid th:first-child { min-width: 210px; white-space: normal; }
.personnel-grid .input-mini { width: 42px; min-width: 42px; }

/* Hide the un-compiled template until Vue mounts. */
[v-cloak] { display: none !important; }

/* Shown only when a CDN library failed to load. */
#boot-error { display: flex; align-items: center; justify-content: center; min-height: 100%; padding: 20px; }
.boot-error-card {
    max-width: 420px; background: #fff; border-top: var(--accent-bar) solid var(--danger);
    border-radius: var(--r-sm); padding: 28px; box-shadow: var(--shadow-md);
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #333;
}
.boot-error-card h1 { font-size: 1.25rem; color: var(--brand-primary); margin: 0 0 14px; }
.boot-error-card button {
    min-height: 44px; padding: 0 18px; border: none; border-radius: var(--r-sm);
    background: var(--brand-primary); color: #fff; font-size: .95rem; cursor: pointer;
}

/* --- LAYOUT ---------------------------------------------------------------
   The desktop shell is a locked-height flex column. `overflow: hidden` on the
   body used to mean that if the header grew — several concurrent campaigns, or
   150% browser zoom — the table was squeezed towards nothing with no page
   scroll to escape to. The wrapper now has a floor, and the body scrolls once
   the sum no longer fits. */
html, body {
    height: 100%;
    margin: 0;
    overflow: auto;
    background-color: var(--brand-bg);
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

#app {
    min-height: 100%;
    display: flex;
    flex-direction: column;
    padding: 15px;
}

.header-section  { flex-shrink: 0; margin-bottom: 10px; border-radius: var(--r-lg) !important; }
/* The toolbar sat on the bare page background between two white panels; it now
   reads as the third section it is. `.row` also pulls a negative inline margin,
   which made it 4px wider than the panels above and below. */
.toolbar-section {
    flex-shrink: 0; margin-bottom: 10px;
    background: #fff; border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
    padding: 10px 12px; --bs-gutter-x: .5rem;
}

.table-wrapper {
    flex: 1 1 auto;
    min-height: 260px;          /* never collapse to a single row */
    overflow: auto;
    background: white;
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-md);
    position: relative;
    padding: 0 15px 15px 15px;
}

/* STICKY TABLE HEADER */
thead th {
    position: sticky;
    top: 0;
    z-index: 100;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

/* The date column stays put while the 12 rate columns scroll horizontally.
   Without it you scroll right to reach rate L and can no longer see which day
   you are typing into — on 31 near-identical rows of number boxes. */
.table thead th:first-child { z-index: 102; }
.table th:first-child,
.table td:first-child {
    position: sticky; left: 0; z-index: 101;
    background-color: var(--bs-table-bg, #fff);
    box-shadow: 2px 0 4px -2px rgba(0,0,0,.15);
}

/* --- UTILS --------------------------------------------------------------- */
.text-brand { color: var(--brand-primary) !important; }
.text-accent { color: var(--brand-accent-text) !important; }
.text-danger-strong { color: var(--danger) !important; }
.bg-brand { background-color: var(--brand-primary) !important; color: white; }

.btn-primary { background-color: var(--brand-primary); border-color: var(--brand-primary); }
.btn-primary:hover { background-color: var(--brand-primary-dark); border-color: var(--brand-primary-dark); }
.btn-outline-primary { color: var(--brand-primary); border-color: var(--brand-primary); }
.btn-outline-primary:hover { background-color: var(--brand-primary); color: white; }
/* --brand-accent-text is tuned for orange text on white; the same colour BEHIND
   white text comes out at 4.43:1, a hair under. The button uses the darker one,
   which is what --brand-accent-dark already is. */
.btn-brand-accent { background-color: var(--brand-accent-dark); color: white; border: none; }
.btn-brand-accent:hover { background-color: #6f3e05; color: white; }

/* Visible keyboard focus everywhere. Individual controls must not re-declare
   `outline: none` — that out-specifies this and silently removes the ring. */
:focus-visible { outline: 2px solid var(--brand-accent-text); outline-offset: 2px; }

.login-card {
    max-width: 400px; margin-inline: auto; border: none;
    box-shadow: var(--shadow-md); border-top: var(--accent-bar) solid var(--brand-accent);
    border-radius: var(--r-lg);
}
/* The container already centres vertically; the old `margin: 80px auto` fought
   it and pushed the Sign In button below the fold on short viewports. */
.login-card.is-warning { border-top-color: var(--warn); }

.table-brand th { background-color: var(--brand-primary) !important; color: white; border-color: #355d6b; }

/* The spin buttons stay: 0.25 is the working step and they are used to nudge a
   value. They do cost about 16px of the box, which is why a four-digit value
   like 20.25 was being clipped — so the width comes out of the cell padding
   instead. The <td> carries no horizontal padding, the input fills the column,
   and the column itself is exactly as wide as before. */
.input-mini {
    width: 64px; padding: 4px 1px; text-align: center; border: 1px solid #dee2e6;
    border-radius: var(--r-sm); font-size: 0.9rem; font-variant-numeric: tabular-nums;
}
.input-mini:focus { border-color: var(--brand-accent-text); }

.input-date-d { width: 38px; padding: 2px 1px; text-align: center; border: 1px solid #dee2e6; border-radius: var(--r-sm); font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.input-date-y { width: 48px; padding: 2px 1px; text-align: center; border: 1px solid #dee2e6; border-radius: var(--r-sm); font-size: 0.85rem; font-variant-numeric: tabular-nums; }

/* Three rows rather than two, and it grows with its content up to the cap.
   Remarks are the audit trail for non-standard days; a two-line peephole with
   an inner scrollbar made them unreadable at a glance. */
.textarea-remarks {
    width: 100%; min-width: 200px; min-height: 54px; max-height: 140px;
    font-size: 0.85rem; resize: vertical; overflow-y: auto; line-height: 1.25;
}

/* --- TABLE ROW STATES ------------------------------------------------------
   Bootstrap 5.3 paints an opaque background on every CELL:
       .table > :not(caption) > * > * { background-color: var(--bs-table-bg) }
   A `background-color` on the <tr> — even with !important — is therefore
   covered by each <td> painting over it. Setting the Bootstrap variable on the
   row instead makes the cells inherit the colour and keeps .table-hover
   working. Declaration order is the tiebreak (all are 0,1,0), so the transient
   saving tint comes last and wins while a write is in flight. */
tr.frozen          { --bs-table-bg: var(--ok-bg); }
tr.duplicate-row,
tr.invalid-row     { --bs-table-bg: var(--danger-bg);      border-left: 5px solid var(--danger); }
tr.incomplete-row  { --bs-table-bg: var(--warn-bg);        border-left: 5px dashed var(--warn); }
tr.update-failed   { --bs-table-bg: var(--danger-soft-bg); border-left: 5px double var(--danger); }
tr.row-saving      { --bs-table-bg: var(--saving-bg); }

.frozen input { border: none; background: transparent; color: var(--brand-primary); font-weight: 600; }
.frozen .textarea-remarks { border: none; background: transparent; color: var(--brand-primary); font-weight: 600; }
.duplicate-row input, .invalid-row input { background-color: rgba(255,255,255,0.5); }

/* Each state also carries a glyph, so the three remain distinguishable without
   relying on fill colour — which is what forced-colors mode discards. */
.incomplete-row .total-cell { color: var(--warn) !important; }
.incomplete-row .total-cell::after { content: " ⚠"; font-size: .8em; }
.invalid-row .total-cell { color: var(--danger) !important; }
.invalid-row .total-cell::after { content: " ✖"; font-size: .8em; }

.error-icon { cursor: help; font-size: 0.8rem; margin-right: 4px; }

/* !important because the same Bootstrap cell rule also sets background and colour. */
.total-cell {
    font-weight: bold; width: 58px; text-align: right; padding-right: 8px;
    border-left: 2px solid #dee2e6;
    background-color: #e9ecef !important; color: var(--brand-primary) !important;
    font-variant-numeric: tabular-nums;
}

/* --- LOGO LOCKUP ----------------------------------------------------------
   The two marks are not the same shape: logo1 is 1200x1562 (portrait, ratio
   0.768) and logo2 is 776x756 (near square, ratio 1.026). Matching them on
   height alone — which is what a single max-height did — renders logo1 25%
   narrower and about a third smaller in area, which is why the pair read as
   lopsided and cramped.
   So each mark gets its own height, and the portrait one is set slightly taller
   to even out the optical weight. Tune either number on its own; they are the
   only two values that matter here. */
.logo-row {
    display: flex; align-items: center; gap: 22px;
    padding-left: 14px;          /* nudged off the panel edge */
}
.company-logo { width: auto; object-fit: contain; display: block; }
.logo-primary   { height: var(--logo-h-1, 110px); }   /* portrait mark  */
.logo-secondary { height: var(--logo-h-2, 100px); }   /* near-square    */

@media (max-width: 991px) {
    .logo-row { gap: 16px; padding-left: 4px; }
    .logo-primary   { height: 72px; }
    .logo-secondary { height: 66px; }
}

.sortable-header { user-select: none; padding: 0 !important; }
.th-sort-btn {
    all: unset; display: block; width: 100%; box-sizing: border-box;
    cursor: pointer; padding: .5rem; color: inherit; font: inherit; white-space: nowrap;
    transition: background-color .15s ease;
}
.th-sort-btn:hover { background-color: rgba(255,255,255,.14); }
.th-sort-btn:focus-visible { outline: 2px solid #fff; outline-offset: -3px; }
.header-tooltip { cursor: help; text-decoration: underline dotted; }

.empty-state {
    text-align: center; padding: 2rem 1rem; color: #5a6b73;
    background: #fff; border: 1px dashed #c9d4d8; border-radius: var(--r-lg); margin-top: 1rem;
}

/* Three columns. At four the descriptions were ellipsised to "Intake, Inspec…"
   on a 1366px laptop; at three, eleven of the twelve fit on one line and the
   longest ("Intake, Inspection and acceptance test rate") wraps to a second
   line rather than being cut — the point of the block is to be readable. */
.rates-legend-compact {
    font-size: 0.72rem; background: white; border: 1px solid #dee2e6;
    border-left: 3px solid var(--brand-accent);
    border-radius: var(--r-sm); padding: 4px 8px; overflow-y: auto; max-height: 104px;
}
.legend-grid {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1px 14px; align-items: start;
}
.rate-item-compact { line-height: 1.25; overflow-wrap: anywhere; }
.rate-code { font-weight: bold; color: var(--brand-accent-text); margin-right: 3px; }

.project-info { font-size: 0.85rem; color: #555; background-color: #f8f9fa; padding: 10px; border-radius: var(--r-sm); border-left: var(--accent-bar) solid var(--brand-primary); }
.project-label { font-weight: bold; color: var(--brand-primary); margin-right: 5px; white-space: nowrap; }
/* Was a hard 145px, which any font fallback overflowed into the input. */
.project-label-fixed { display: inline-block; min-width: 145px; }

.campaign-block { border-right: 1px solid #dee2e6; padding-right: 1rem; }
.campaign-block:last-of-type { border-right: none; }

/* Grows with its content instead of cutting a well name mid-string. */
.header-input {
    border: 1px solid #ced4da; background-color: white; padding: 1px 5px; border-radius: var(--r-sm);
    font-size: 0.85rem; color: #333; width: 100%; min-width: 140px; max-width: 260px;
}
.header-input:focus { outline: 2px solid var(--brand-accent-text); border-color: var(--brand-accent-text); }
.header-input:disabled {
    background: transparent; border: none; font-weight: bold; color: #333; padding-left: 0;
    text-overflow: ellipsis;
}

/* Reserves its own space so the button row does not re-wrap every time a
   debounced save starts and finishes. */
.saving-indicator { min-width: 92px; }
.saving-indicator[hidden] { display: flex !important; visibility: hidden; }

/* `width: 100%` made this flex item claim the whole row and wrap at nearly
   every desktop width, orphaning the note that qualifies the number. */
.transfer-input {
    border: 1px solid #ced4da; background-color: white; padding: 5px 10px; border-radius: var(--r-sm);
    font-size: 0.9rem; color: #333; flex: 1 1 240px; width: auto; min-width: 180px;
}

tfoot { border-top: 2px solid var(--brand-primary); font-weight: bold; }
tfoot > tr > td {
    background-color: #f8f9fa !important; color: var(--brand-primary) !important;
    font-variant-numeric: tabular-nums;
}
/* The grand total. It was teal on teal and effectively invisible: .bg-brand set
   the dark background but its `color: white` carried no !important, so the rule
   above overrode it with the same teal.
   Saipem orange on the teal band measures 3.71:1 — above the 3:1 required for
   large text, below the 4.5:1 for normal text — so the figure is set at 1.2rem
   bold, which is the size that threshold applies to. It is the summary number of
   the whole table, so the extra weight suits it anyway. */
tfoot > tr > td.tfoot-grand {
    background-color: var(--brand-primary) !important;
    color: var(--brand-accent) !important;
    font-size: 1.2rem;
    font-weight: 700;
    line-height: 1.1;
}

/* `.list-move` was missing, so re-sorting 31 rows teleported them while inserts
   and removals animated for half a second. Shorter, and limited to the two
   properties that should animate. */
.list-enter-active, .list-leave-active { transition: opacity .22s ease, transform .22s ease; }
.list-enter-from, .list-leave-to { opacity: 0; transform: translateX(24px); }
.list-move { transition: transform .22s ease; }

/* --- MODALS ---------------------------------------------------------------
   The overlay scrolls and the dialog is capped to the viewport. A fixed 400px
   (500px for the export warning) with a non-scrolling centred overlay put the
   Cancel/Save buttons off-screen on a phone, with no way to reach them. */
.custom-modal-overlay {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.5); z-index: 1000;
    display: flex; justify-content: center; align-items: flex-start;
    padding: 16px; overflow-y: auto;
}
.custom-modal {
    background: white; padding: 25px; border-radius: var(--r-sm);
    width: min(400px, 100%); max-width: 100%; margin: auto;
    box-shadow: var(--shadow-lg);
    border-top: var(--accent-bar) solid var(--brand-accent);
}
/* The size steps. A dialog is sized by ADDING ONE OF THESE, never by an inline
   `max-width`: the base rule sets `width`, so a max-width above 400px caps
   nothing and the dialog stays 400px wide — silently, which is how the manual
   proforma form came to squeeze a six-column table into 350 usable pixels. A
   static test now refuses an inline max-width on a .custom-modal. */
.custom-modal--wide   { width: min(520px, 100%); }
.custom-modal--wider  { width: min(660px, 100%); }
.custom-modal--widest { width: min(780px, 100%); }
.custom-modal--danger { border-top-color: var(--danger); }

/* --- a readable floor -------------------------------------------------------
   Measured in a browser rather than read off the stylesheet: Bootstrap's .badge
   is 0.75em, so a badge inside a .small line landed at 9.6px, and .table-sm
   inside one landed at 11.5px. This is a screen read in daylight on a rig, often
   through a visor, so nothing that carries a value is allowed below 12px. The
   rule is on the classes that were actually under it, not a blanket minimum. */
.badge  { font-size: max(0.75em, 12px); }
/* A floor, and only a floor: `em` keeps Bootstrap's relative sizing, and the
   max() stops it compounding below what a person can read. Written with a rem
   instead, it silently SHRANK every table it touched, which the scan caught. */
.small, small { font-size: max(0.875em, 12px); }
.table-sm { font-size: max(0.875rem, 13px); }

/* Bootstrap's muted grey is 4.45:1 on its own light ground and 3.6:1 on the
   dimmed row of a voided document — under the 4.5 a body-size label needs.
   Darkened once, here, rather than per element. */
.text-muted { color: #5c636a !important; }
.btn-outline-secondary { --bs-btn-color: #495057; --bs-btn-border-color: #8b9299; }

/* The register's actions, pinned to the right edge of a table that scrolls.
   Nine columns need about 1550px and a 1440px screen gives the table 1378, so
   the buttons were off the screen with nothing to say they existed. */
.pfi-actions {
    position: sticky; right: 0; z-index: 2;
    background: var(--bs-body-bg, #fff);
    box-shadow: -6px 0 6px -6px rgba(0, 0, 0, .25);
}
thead .pfi-actions { z-index: 3; background: var(--brand-primary, #224c5a); }
/* The dimmed row of a voided document keeps its tint under the pinned cell. */
tr.table-secondary .pfi-actions { background: #e2e3e5; }

/* The four accounting references on a register row. Laid out across, they took
   428px of a 1378px table and pushed the actions column off the screen; two per
   line, at a size that is still readable, they take under 190px. The full name
   of each is on the element, since the label here is the short one. */
.ref-cell {
    display: flex; flex-wrap: wrap; gap: 2px 4px;
    font-size: 0.75rem; line-height: 1.35;
}
.ref-cell > span {
    background: var(--bs-light, #f8f9fa);
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: var(--r-sm);
    padding: 0 5px; white-space: nowrap;
    /* 4.45:1 on the light ground was under the readable floor. */
    color: #495057;
}

/* The personnel sheet is 31 day columns wide and scrolls sideways. The activity
   name and its monthly total stay put: without them a column of numbers halfway
   through the month says neither what it counts nor what it adds up to. */
.pt-sticky { position: sticky; background: var(--bs-body-bg, #fff); z-index: 2; }
.pt-sticky-name  { left: 0; min-width: 190px; }
.pt-sticky-total { left: 190px; min-width: 68px; box-shadow: 1px 0 0 var(--bs-border-color, #dee2e6); }
thead .pt-sticky { z-index: 3; background: var(--brand-primary, #224c5a); }

.btn-mini-delete {
    padding: 2px 8px; min-height: 24px; font-size: 0.7rem; color: var(--danger);
    background: none; border: 1px solid var(--danger); border-radius: var(--r-sm);
}
.btn-mini-delete:hover { background: var(--danger); color: white; }

.btn-action { width: 30px; height: 30px; padding: 0; font-size: 0.85rem; display: inline-flex; align-items: center; justify-content: center; }
.btn-touch { min-height: 38px; }

/* Touch devices get the full 44px target: the desktop table shows from 992px
   up, which includes landscape tablets used on deck with gloves. */
@media (pointer: coarse) {
    /* Bootstrap's -sm controls are 31px, and a 31px target on a screen operated
       with a gloved thumb is a target you miss. The rig dashboard already lifts
       its own controls to 44 through .btn-action, .btn-touch and .input-mini;
       the five screens built afterwards never got the same treatment, and a
       touch-emulated pass found 92 of them on the administration page alone.
       Scoped to .section-screen so the rig dashboard keeps exactly the sizing it
       has. .btn-link is left alone: it is text, not a target. */
    .section-screen .btn-sm:not(.btn-link),
    .section-screen .form-control-sm,
    .section-screen .form-select-sm,
    .custom-modal .btn-sm:not(.btn-link),
    .custom-modal .form-control-sm,
    .custom-modal .form-select-sm { min-height: 44px; }

    /* The switcher bar is a sibling of every screen rather than part of one, so
       it sits outside .section-screen — and Sign Out is the one control that is
       on all of them. */
    .switcher-bar .btn-sm, .switcher-bar .form-select-sm { min-height: 44px; }

    /* A 16px checkbox is the smallest thing on the administration page and the
       one that grants a rig. The box keeps its size; what grows is the area
       around it that answers to a thumb. */
    .section-screen .form-check-input { width: 1.25rem; height: 1.25rem; margin-top: 0.6rem; }
    .section-screen .form-check { min-height: 44px; display: flex; align-items: center; }
    .section-screen .form-check .form-check-label { min-height: 44px; display: flex; align-items: center; }

    .btn-action { width: 44px; height: 44px; font-size: 1rem; }
    .btn-touch { min-height: 44px; }
    .btn-mini-delete { min-height: 36px; padding: 6px 12px; }
    .input-mini { min-height: 44px; width: 58px; font-size: 1rem; }
    .input-date-d, .input-date-y { min-height: 40px; font-size: 0.95rem; }
    .header-input { min-height: 40px; }
    .mobile-rate-input { min-height: 44px; }
    .toast-close { min-width: 44px; min-height: 44px; }
}

/* Below the modal overlay: it used to paint a yellow bar on top of the dimmed
   backdrop while a dialog was open, outside the focus trap and undismissable. */
.idle-banner {
    position: sticky; top: 0; flex-shrink: 0; z-index: 900;
    background-color: var(--pending-bg); color: #4a3600;
    border: 1px solid var(--pending);
    text-align: center; padding: 8px;
    font-weight: bold; font-size: 0.9rem; border-radius: var(--r-sm); margin-bottom: 10px;
    box-shadow: var(--shadow-sm);
}

.link-text {
    all: unset; cursor: pointer; color: var(--brand-primary);
    text-decoration: underline; font-size: 0.9rem;
}
.link-text:hover { color: var(--brand-accent-text); }
.link-text:focus-visible { outline: 2px solid var(--brand-accent-text); outline-offset: 2px; }

/* --- TOASTS --------------------------------------------------------------- */
.toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 8px; max-width: 380px; pointer-events: none; }
.toast-item {
    padding: 10px 16px; border-radius: var(--r-sm); color: white; font-size: 0.88rem;
    box-shadow: var(--shadow-md); display: flex; justify-content: space-between;
    align-items: flex-start; gap: 12px; pointer-events: all;
}
.toast-enter-active { transition: opacity .2s ease, transform .2s ease; }
.toast-leave-active { transition: opacity .25s ease, transform .25s ease; }
.toast-enter-from, .toast-leave-to { opacity: 0; transform: translateX(30px); }
.toast-move { transition: transform .2s ease; }
.toast-info    { background-color: var(--brand-primary); }
.toast-success { background-color: var(--ok); }
.toast-warning { background-color: var(--warn); }
.toast-error   { background-color: var(--danger); }
.toast-close { background: none; border: none; color: inherit; cursor: pointer; font-size: 1rem; opacity: 0.8; padding: 0; line-height: 1; flex-shrink: 0; }
.toast-close:hover { opacity: 1; }

/* --- RESPONSIVE ----------------------------------------------------------- */
@media (max-width: 991px) {
    #app { padding: 10px; }
    .project-info { border-left: none; border-top: var(--accent-bar) solid var(--brand-primary); }
    .toolbar-section { padding: 10px; }
}

/* --- MOBILE CARDS ---------------------------------------------------------
   State colours come from the same tokens as the table rows, so the two views
   cannot drift apart the way they had. */
.mobile-card {
    background: white;
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
    margin-bottom: 12px;
    border: 1px solid #dee2e6;
    overflow: hidden;
    border-left: 5px solid var(--brand-primary);
    transition: background-color .2s ease, border-color .2s ease;
}
.mobile-card.frozen         { border-left-color: var(--ok) !important;     background-color: var(--ok-bg) !important; }
.mobile-card.duplicate-row,
.mobile-card.invalid-row    { border-left-color: var(--danger) !important; background-color: var(--danger-bg) !important; }
.mobile-card.incomplete-row { border-left-color: var(--warn) !important;   background-color: var(--warn-bg) !important; border-left-style: dashed; }
.mobile-card.update-failed  { border-left-color: var(--danger) !important; background-color: var(--danger-soft-bg) !important; border-left-style: double; }
/* Last so the transient saving tint wins, matching the table. */
.mobile-card.row-saving     { background-color: var(--saving-bg) !important; }

.mobile-card.frozen .mobile-rate-input {
    border: none; background: transparent; pointer-events: none;
    color: var(--brand-primary); font-weight: 600;
}

/* A real <button> for keyboard access — reset its native chrome. `all: unset`
   also clears width, and it out-ranks Bootstrap's .w-100 because this sheet
   loads later, so the width has to be restated or the header collapses. */
.mobile-card-header {
    all: unset;
    box-sizing: border-box;
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 12px 16px;
    min-height: 44px;
    background-color: transparent;
    cursor: pointer;
    user-select: none;
    font: inherit;
    color: inherit;
}
.mobile-card-header:focus-visible { outline: 2px solid var(--brand-accent-text); outline-offset: -2px; }
/* all: unset removed the native button feedback; a tap looked unresponsive. */
.mobile-card-header:hover  { background-color: rgba(0,0,0,.03); }
.mobile-card-header:active { background-color: rgba(0,0,0,.07); }

/* Flex children refuse to shrink below their content unless told to. Without
   this a realistic address pushed the status badge and the ▲▼ chevron past the
   card edge, where `overflow: hidden` silently cut them off. */
.mc-left  { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1 1 auto; }
.mc-right { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.mc-date  { font-weight: 700; color: var(--brand-primary); font-size: 1.05rem; white-space: nowrap; }
.mc-user  { color: #6c757d; font-size: .8rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 420px) { .mc-user { display: none; } }

/* Rate flags between the date and the day total. F (repair & maintenance,
   which feeds the transferable allowance) and J (zero rate, non-billable) are
   the two the crew has to spot without opening the card. They are rendered
   only when the day carries hours on them, so an absent flag means zero — the
   header stays as short as it is today on an ordinary day.
   The letter is printed next to the figure on purpose: colour is never the
   only signal here, and side by side the two would otherwise be told apart by
   hue alone. Figures are the DAY total, matching the badge beside them. */
.mc-rate-flag {
    flex: 0 0 auto; padding: 1px 6px; border: 1px solid currentColor;
    border-radius: var(--r-sm); font-size: .72rem; font-weight: 700;
    line-height: 1.5; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.mc-rate-f { background: var(--warn-bg);   color: var(--warn); }
.mc-rate-j { background: var(--danger-bg); color: var(--danger); }
/* On the narrowest handsets the pair plus the two badges runs out of room. */
@media (max-width: 360px) { .mc-rate-flag { padding: 1px 4px; font-size: .68rem; } }

.mobile-date-input {
    width: 62px; min-height: 44px; text-align: center;
    border: 1px solid #ced4da; border-radius: var(--r-sm); font-size: 1rem;
    font-variant-numeric: tabular-nums;
}
.mobile-date-year { width: 84px; }

.mobile-card-body { padding: 16px; background-color: transparent; border-top: 1px solid #f1f3f5; }

.mobile-rate-item {
    display: flex; flex-direction: column;
    background-color: #f8f9fa; border: 1px solid #e9ecef;
    border-radius: var(--r-sm); padding: 6px;
}
.mobile-rate-label {
    display: block; font-size: 0.75rem; font-weight: bold;
    color: #495057; margin-bottom: 4px; text-transform: uppercase;
}
.mobile-rate-input {
    width: 100%; padding: 6px 4px; min-height: 40px; text-align: center;
    border: 1px solid #ced4da; border-radius: var(--r-sm);
    font-size: 1rem; font-weight: bold; font-variant-numeric: tabular-nums;
}
.mobile-rate-input:focus { border-color: var(--brand-accent-text); }

/* --- ACCESSIBILITY MODES --------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* Forced-colors discards author backgrounds, which is every signal this table
   uses. The border styles (solid / dashed / double) and the glyphs survive, so
   they carry the meaning on their own here. */
@media (forced-colors: active) {
    tr.frozen, tr.duplicate-row, tr.invalid-row,
    tr.incomplete-row, tr.update-failed, tr.row-saving { forced-color-adjust: none; }
    .frozen input, .frozen .textarea-remarks { border: 1px solid; }
    .frozen .total-cell::after { content: " ✓"; }
}

/* --- PRINT ----------------------------------------------------------------
   A4 portrait is ~794px, below Bootstrap's `lg`, so Ctrl+P used to reach the
   printer with the mobile card view — 31 collapsed cards containing no rate
   values at all. Force the table, and let it flow across pages. */
@media print {
    html, body { height: auto; overflow: visible; background: #fff; }
    #app { height: auto; min-height: 0; padding: 0; display: block; }

    .table-wrapper {
        display: block !important;
        overflow: visible !important;
        min-height: 0; box-shadow: none; border-radius: 0; padding: 0;
    }
    .d-lg-none, .toolbar-section, .toast-container, .idle-banner,
    .btn, button { display: none !important; }

    .header-section { box-shadow: none; border-radius: 0; page-break-after: avoid; }
    table { width: 100% !important; font-size: 8pt; }
    thead { display: table-header-group; }
    tfoot { display: table-row-group; }
    tr { break-inside: avoid; }
    .table th:first-child, .table td:first-child { position: static; box-shadow: none; }
    .total-cell, tfoot > tr > td,
    tfoot > tr > td.tfoot-grand { background-color: #eee !important; color: #000 !important; }
    tfoot > tr > td.tfoot-grand { font-size: 1rem; }

    /* Colour fills do not survive most print settings; keep the glyph markers. */
    tr.frozen, tr.duplicate-row, tr.invalid-row, tr.incomplete-row { --bs-table-bg: #fff; }
}

/* --- LOADING -------------------------------------------------------------
   loadData() assigns rows only on success, so on a slow satellite link the
   previous month stayed on screen — editable — while the new one was fetched.
   Dim it and take it out of reach until the data matches the header. */
.is-loading { position: relative; }
.is-loading > .table,
.is-loading > div:not(.loading-veil):not(.loading-note) {
    opacity: .45; pointer-events: none; filter: grayscale(.3);
}
/* Overlaid, never in the flow. As a sticky element it took up ~40px and pushed
   the whole table down whenever it appeared, so a background refresh made the
   rows jump. */
.loading-veil {
    position: absolute; top: 0; left: 0; right: 0; z-index: 200;
    display: flex; align-items: center; justify-content: center;
    padding: 10px;
    background: rgba(255,255,255,.92); color: var(--brand-primary);
    font-size: .85rem; font-weight: 600; border-bottom: 1px solid #e3e9eb;
}

/* Same portrait mark as the header, sized for the login and no-role cards. */
.login-logo { height: 84px; width: auto; object-fit: contain; display: inline-block; }

/* Rig and section switchers.
   Fixed rather than part of any section's header, so the rig dashboard's markup
   did not have to change to accommodate it. Below the modal overlay's stacking
   context, so a dialog still covers it. */
.switcher-bar {
    position: fixed;
    top: 0.5rem;
    right: 0.75rem;
    z-index: 1020;
    display: flex;
    gap: 0.4rem;
    align-items: center;
    background: rgba(255, 255, 255, 0.92);
    padding: 0.25rem 0.4rem;
    border-radius: 0.4rem;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
.switcher-bar .form-select { width: auto; min-width: 9rem; }

/* The bar floats over the top-right corner of whatever screen is open, and it
   is always there now that Sign Out lives in it. Every section header reserves
   its height, so it cannot cover the export buttons or the role badge the way
   it did the first time it was made unconditional. */
.header-section,
.section-page { padding-top: 3rem !important; }
@media (max-width: 575.98px) {
    /* Narrow enough that the bar wraps onto its own line. */
    .header-section,
    .section-page { padding-top: 4.5rem !important; }
}

/* Section Dropdown & Rollout Animation */
.section-dropdown {
    position: relative;
}

.section-menu-animate {
    display: block;
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 0.35rem;
    min-width: 14rem;
    border-radius: 0.5rem;
    border: 1px solid rgba(0, 0, 0, 0.12);
    background: #ffffff;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08);
    transform-origin: top right;
    animation: sectionMenuRollout 0.22s cubic-bezier(0.16, 1, 0.3, 1) forwards;
    overflow: hidden;
    z-index: 1030;
}

@keyframes sectionMenuRollout {
    0% {
        opacity: 0;
        transform: translateY(-8px) scaleY(0.92);
    }
    100% {
        opacity: 1;
        transform: translateY(0) scaleY(1);
    }
}

.section-menu-animate .dropdown-item {
    cursor: pointer;
    font-weight: 500;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.section-menu-animate .dropdown-item:hover {
    background-color: var(--brand-accent-subtle, #f0f4f8);
    color: var(--brand-primary, #0b2545);
}

.section-menu-animate .dropdown-item.active {
    background-color: var(--brand-primary, #0b2545);
    color: #ffffff;
}

.dropdown-chevron {
    display: inline-block;
    transition: transform 0.2s ease;
    font-size: 0.75rem;
}

.dropdown-chevron.chevron-open {
    transform: rotate(180deg);
}
