/* ===========================================================================
   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;
}

/* 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; }
.btn-brand-accent { background-color: var(--brand-accent-text); color: white; border: none; }
.btn-brand-accent:hover { background-color: var(--brand-accent-dark); 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);
}
.custom-modal--wide { width: min(520px, 100%); }
.custom-modal--danger { border-top-color: var(--danger); }

.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) {
    .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; }

/* 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);
}
