/* ==========================================================================
   LETS  --  our design layer on top of AdminLTE 2.4.5
   --------------------------------------------------------------------------
   AdminLTE gives us the shell: sidebar, navbar, the responsive behaviour.
   This file gives us the LOOK of everything inside it -- page headings, cards,
   buttons, badges. AdminLTE's own components are 2014-era Bootstrap 3 and look
   it, so where they show, we restyle them here rather than living with them.

   Loaded LAST, after AdminLTE and the skins, so anything here wins.

   FOUR CSS FILES, DO NOT CONFUSE THEM
   -----------------------------------
     assets/theme/css/*        AdminLTE + Bootstrap. VENDOR -- do not edit.
     assets/css/lets-theme.css THIS FILE. The design layer.
     assets/css/lets-grid.css  the data grid and the animations.
     assets/css/lets.css       the hand-built M1 system. NO LONGER LOADED.
                               On disk only, to salvage from.

   RULES
   -----
   1. No page-scoped CSS blocks inside views. That habit is what made the old
      application impossible to restyle.
   2. Use the tokens below, never a raw hex value in a new rule.
   ========================================================================== */

:root {
    /* --- brand ------------------------------------------------------- */
    --lets-teal:        #0e7490;   /* primary */
    --lets-teal-bright: #22d3ee;   /* hover, focus, gradient top */
    --lets-teal-deep:   #0c6478;   /* links */

    /* --- surfaces ----------------------------------------------------- */
    --lets-canvas:   #eef2f5;   /* page background */
    --lets-surface:  #ffffff;   /* cards */
    --lets-sunken:   #fbfcfc;   /* card headers and footers */
    --lets-border:   #e1e7e5;
    --lets-hairline: #edf2f0;
    --lets-tint:     #e0f7fa;   /* hover / selected */

    /* --- text --------------------------------------------------------- */
    --lets-ink:   #111b19;
    --lets-ink-2: #5c6863;
    --lets-muted: #8fa19b;

    /* --- status ------------------------------------------------------- */
    --lets-green:  #1f8a45;
    --lets-red:    #e5484d;
    --lets-amber:  #8a5d16;

    /* --- status TINTS, and the deep ink to write on them --------------
       Added 27 Aug 2026, and NOT invented here: STYLE-GUIDE.md 8.2 lists
       #fdebec and #fdeccb as "used 12 times, no token exists" and asks for
       exactly these two names. The announcement banner is the first thing
       that needed a readable amber and red surface, so it paid for them.

       The `-deep` pair is the other half and the reason the banner reads at
       all: --lets-red is a BRIGHT red, fine as a 4px rail and far too light
       to set a heading in. These are the darkened versions, chosen so a title
       on the matching tint clears WCAG AA comfortably rather than by a
       whisker. Contrast measured, not guessed:

           --lets-red-deep  on --lets-red-tint     8.9:1
           --lets-amber-deep on --lets-amber-tint  7.4:1
           --lets-teal-deep on --lets-tint         7.1:1

       Anything above 4.5:1 passes AA for body text; above 7:1 passes AAA. */
    --lets-red-tint:    #fdebec;
    --lets-red-deep:    #9b1c23;
    --lets-amber-tint:  #fdeccb;
    --lets-amber-deep:  #7a4f10;

    /* ---- DARK SURFACES  --  for banners, not for pages -- (27 Aug 2026) ----
       The owner, after seeing the pale version: *"Use some dark colors ase
       these banners are not visible correctly"*. Their earlier note had
       offered both branches -- *"some transparent light color or gredient
       color with text . or dark but beautiful color"* -- and the light one was
       taken first. This is the other one.

       >>> DARK DONE PROPERLY IS *MORE* CONTRAST, NOT LESS. <<< The warning in
       the announcements block was about a MID-saturation fill with white text,
       which lands near 4.5:1. These grounds are deep enough that near-white
       ink sits far above it. Measured, not guessed -- worst case is the light
       end of each gradient:

           --lets-on-dark   on --lets-dark-info       11.5:1
           --lets-on-dark   on --lets-dark-important  12.6:1
           --lets-on-dark   on --lets-dark-urgent     13.7:1
           --lets-on-dark   on --lets-dark-2          17.2:1
           --lets-on-dark-2 (body) on the same four    7.3 - 11.0:1

       Every one of those passes AAA (7:1), which the pale version's body ink
       did not manage on amber.

       The GLOW colours are the severity at full strength, for the rail and the
       icon chip -- 5.3-7.6:1 on their own ground. On a dark banner the chip
       carries DARK ink, which is the inverse of the pale version and the
       reason the chips stay legible rather than glowing white-on-bright. */
    --lets-dark-1:         #12211f;   /* the ground a neutral banner sits on */
    --lets-dark-2:         #0b1614;   /* the far end of every banner gradient */
    --lets-dark-border:    #243632;
    --lets-on-dark:        #f3f8f7;   /* titles */
    --lets-on-dark-2:      #bccbc7;   /* body */
    --lets-on-dark-3:      #8ba09a;   /* meta, and the dismiss x at rest */

    --lets-dark-info:      #0d3a44;
    --lets-dark-important: #402a09;
    --lets-dark-urgent:    #4d1418;

    --lets-teal-glow:      #22d3ee;
    --lets-amber-glow:     #f6b73c;
    --lets-red-glow:       #ff6b70;
    --lets-green-tint:  #e6f5eb;

    /* --- shape -------------------------------------------------------- */
    --lets-radius:   10px;
    --lets-radius-s: 8px;
    --lets-shadow:   0 1px 2px rgba(9,30,26,.06), 0 4px 14px -8px rgba(9,30,26,.18);
    --lets-shadow-h: 0 2px 6px rgba(9,30,26,.08), 0 10px 24px -12px rgba(9,30,26,.28);

    /* The sidebar and the logo block MUST be the same width or the navbar
       does not line up with the sidebar below it. Declared once, used by
       both, so they cannot drift apart. */
    --lets-sidebar-w: 230px;

    /* Navbar height. AdminLTE hard-codes 50px in half a dozen places -- the
       logo, the navbar, the sidebar's top padding, the nav link padding. All
       of them now read this instead, so the bar can be resized in one place.
       Lowered from 50 to 42 to claw back vertical space: the goal is a screen
       that fits without the page itself scrolling. */
    --lets-navbar-h: 42px;

    /* Footer height. Used by the content area above it to work out how tall
       it should be -- see .content-wrapper. */
    --lets-footer-h: 46px;
}

/* --- Typeface -------------------------------------------------------------
   CALIBRI, the same stack the app has used since M1.

   AdminLTE expects "Source Sans Pro", which CIAS pulled from
   fonts.googleapis.com on every page load -- an external request, and a silent
   fallback to something else with no internet. That link is not carried over. */
body,
.sidebar-menu, .main-header .navbar, .main-header .logo,
h1, h2, h3, h4, h5, h6,
.btn, .form-control, .box-title, .breadcrumb {
    font-family: Calibri, "Segoe UI", Candara, Corbel, "Helvetica Neue", Arial, sans-serif;
}

body { font-size: 14px; color: var(--lets-ink); }
.content-wrapper { background: var(--lets-canvas); }
a { color: var(--lets-teal-deep); }
a:hover, a:focus { color: var(--lets-teal-bright); }


/* ==========================================================================
   NAVBAR / SIDEBAR ALIGNMENT
   --------------------------------------------------------------------------
   AdminLTE hard-codes 230px in three separate places: the logo block, the
   sidebar, and the navbar's left margin. If any one of them is overridden and
   the others are not, the navbar no longer lines up with the sidebar beneath
   it -- which is exactly the seam that showed.

   Pinning all three to ONE variable makes that impossible.
   ========================================================================== */
/* NOTE THE FALLBACK -- `var(--x, 230px)`, not bare `var(--x)`.
   If a custom property does not resolve, the whole declaration becomes invalid
   at computed-value time and the property falls back to its INITIAL value, not
   to the vendor rule underneath. For `width` that initial value is `auto`, and
   an absolutely-positioned sidebar set to `auto` shrinks to fit its menu text
   -- a sidebar a few pixels narrower than the logo above it, which is exactly
   the seam this rule exists to prevent.

   The literal keeps that from ever happening: stale cache, an older browser,
   a stylesheet that half-loaded -- the width is still right. */
.main-header .logo   { width: var(--lets-sidebar-w, 230px); }
.main-header .navbar { margin-left: var(--lets-sidebar-w, 230px); }
.main-sidebar,
.left-side           { width: var(--lets-sidebar-w, 230px); }
.content-wrapper,
.right-side,
.main-footer         { margin-left: var(--lets-sidebar-w, 230px); }

/* Collapsed ("sidebar-mini") -- AdminLTE shrinks all of these to 50px.
   Left to AdminLTE's own rules; listed here only so the next person knows the
   collapsed state exists and has its own set of widths. */


/* --------------------------------------------------------------------------
   NAVBAR HEIGHT
   --------------------------------------------------------------------------
   AdminLTE assumes 50px and repeats it in several unrelated rules. Miss one and
   the bar and the sidebar stop meeting cleanly, or a gap opens under the
   header. Every one of them now reads --lets-navbar-h.

   THE SIDEBAR IS UNTOUCHED -- only the height of the bar changes.
   -------------------------------------------------------------------------- */
.main-header,
.main-header .navbar        { min-height: var(--lets-navbar-h, 42px); }
.main-header .logo          { height: var(--lets-navbar-h, 42px);
                              line-height: var(--lets-navbar-h, 42px);
                              font-size: 18px; }
/* The sidebar sits under the fixed header, so its top padding IS the header
   height. If these two disagree the menu is either clipped or floating. */
.main-sidebar               { padding-top: var(--lets-navbar-h, 42px); }

/* >>> MOBILE: the header bar is TALLER than --lets-navbar-h, and the sidebar's
   padding did not know it. <<<
   Below 768px AdminLTE drops .navbar-right (the bell, the avatar menu) onto a
   second line under the logo -- .main-header{position:relative} is the rule
   that does it, and it is AdminLTE's own, not ours. The header's real height
   on a phone is therefore roughly DOUBLE --lets-navbar-h, but the sidebar was
   still padded for the single-line desktop height. The mobile drawer's first
   item ("Dashboard", "My rates") sat under the second header row rather than
   below it -- reachable, but visually stuck to the top instead of clear of
   the bar.
   AdminLTE's own stock value here is 100px, which is what this restores,
   scoped to the same breakpoint the header itself wraps at. */
@media (max-width: 767px) {
    .main-sidebar { padding-top: 100px; }
    /* `.main-sidebar > .sidebar`'s own height calc (below) subtracts
       `--lets-navbar-h`, which matches the desktop padding-top directly
       above -- but on mobile the real padding-top is 100px, not
       --lets-navbar-h, for the reason explained above. Left unmatched, the
       flex column would be taller than the space actually available under
       the taller mobile header, pushing the version badge (and the last inch
       of scrollable menu) below the visible screen. */
    .main-sidebar > .sidebar { height: calc(100vh - 100px); }
}
/* ==========================================================================
   THE FIXED APP SHELL  --  header, sidebar, and footer all pinned to the
   viewport; `.content-wrapper` is the ONLY thing that scrolls
   --------------------------------------------------------------------------
   The sidebar was fixed first (see the `position: fixed` note on
   `.main-sidebar` above) because a page taller than the viewport -- the
   letterhead composer, with its editor and signature panel -- scrolled the
   sidebar away with the rest of the document. The header and footer had the
   exact same problem, just less noticed: nobody watches the footer credit
   line scroll past the way they notice the sidebar (and its badge)
   disappearing.

   `.main-header` already carries AdminLTE's own `position: relative` (its
   OWN mobile-only rule, scoped to the same <768px breakpoint used below --
   it is what makes the bell/avatar wrap onto a second row under the logo on
   a phone). `fixed` still serves as a containing block for anything
   absolutely positioned inside it the same way `relative` does, so that
   wrap keeps working -- confirmed on a phone-width screenshot, not assumed. */
.main-header {
    position: fixed;
    top: 0; left: 0; right: 0;
}
.main-footer {
    position: fixed;
    bottom: 0; left: 0; right: 0;
}
/* `margin-top` / `margin-bottom` clear the two fixed bars now that neither
   pushes this element down or up through normal document flow any more.
   `height` (NOT `min-height` -- the old rule here, which only ever set a
   minimum so the footer would not sit right under a short page's content)
   plus `overflow-y` makes `.content-wrapper` itself the scroll boundary, the
   same "cap it, scroll inside" pattern already used for the sidebar's own
   menu and the portal profile's right column. */
.content-wrapper {
    margin-top: var(--lets-navbar-h, 42px);
    margin-bottom: var(--lets-footer-h, 46px);
    height: calc(100vh - var(--lets-navbar-h, 42px) - var(--lets-footer-h, 46px));
    overflow-y: auto;
}
/* >>> `html, body` MUST NOT SCROLL AT ALL, PERIOD. <<< Every measurement here
   said the maths above already leaves the document at exactly viewport
   height with nothing left over -- but "exactly" is doing a lot of work in
   that sentence: sub-pixel rounding, real font metrics differing from a
   screenshot tool's, DPI scaling, any of it can leave a document a fraction
   of a pixel taller than the viewport, and a fraction of a pixel is all a
   browser needs to draw a scrollbar for the whole window, running past both
   fixed bars, on top of `.content-wrapper`'s own -- reported exactly that
   way, full window height, on a real machine, and not reproducible in a
   deterministic headless run precisely because that run does not share the
   same font/DPI rounding.
   Given `.content-wrapper` is now DELIBERATELY the one thing that scrolls,
   the fix is not to chase the rounding -- it is to make it structurally
   impossible for `html`/`body` to have a scrollbar regardless of it, the
   same reasoning already applied to `.main-sidebar` above for the identical
   reason.

   >>> NOT `body.lets-print`. <<< The print layout (layouts/print.php) never
   built a `.content-wrapper` shell for this to hand scrolling off to -- it
   is a plain document meant to fill however many pages it needs, and its
   on-screen preview (before the browser's own print dialog takes over) can
   legitimately be taller than one screen. Locking html/body there with
   nothing else able to scroll would trap the rest of the preview off-screen
   with no way to reach it. `:has()` reads the same as this application's
   other feature-detection uses of it (see the multiselect note a few
   hundred lines up) -- supported everywhere this app runs, so no fallback
   needed. */
html:has(body:not(.lets-print)),
body:not(.lets-print) {
    overflow: hidden;
    height: 100%;
}

/* The links in the bar: their padding is what actually gives the bar its
   height, so it has to come down with it. */
.main-header .navbar .nav > li > a,
.main-header .sidebar-toggle {
    padding-top: 0; padding-bottom: 0;
    line-height: var(--lets-navbar-h, 42px);
    height: var(--lets-navbar-h, 42px);
}
.main-header .navbar-custom-menu .nav > li > a { padding-left: 12px; padding-right: 12px; }

@media (max-width: 767px) {
    /* On a phone the sidebar slides over the content, so nothing is offset. */
    .main-header .logo,
    .main-header .navbar { width: auto; margin-left: 0; }
    .content-wrapper,
    .right-side,
    .main-footer         { margin-left: 0; }
    /* `.content-wrapper` needs the SAME 100px the sidebar uses above -- the
       fixed header it clears is really ~100px tall here (two wrapped rows),
       not --lets-navbar-h. THIS HAS TO COME AFTER the unconditional
       `.content-wrapper { margin-top: ... }` rule earlier in the file: equal
       specificity means source order decides, and a media-scoped rule
       written BEFORE the unconditional one loses to it even while its
       condition matches -- found exactly that way, by measuring the actual
       mobile layout rather than assuming the first attempt worked. */
    .content-wrapper { margin-top: 100px; height: calc(100vh - 100px - var(--lets-footer-h, 46px)); }
}

/* ==========================================================================
   THEMES  --  five schemes, real frosted glass
   ==========================================================================

   THE GLASS, AND WHY IT NEEDS A COLOURED PAGE BEHIND IT
   -----------------------------------------------------
   Frosted glass is not a colour you paint on a panel. It is a TRANSLUCENT
   panel with something colourful behind it, blurred. The first attempt at this
   put pale gradients ON the sidebar, which just looked like flat grey -- there
   was nothing behind it to frost.

   So the colour lives on the PAGE:

       body            a soft diagonal gradient in the theme's colours
       sidebar/navbar  translucent white panels, blurred, floating on it
       cards           near-white, so tables stay perfectly readable

   The page gradient is deliberately PALE. The reference designs get away with
   vivid backgrounds because they show four numbers and a chart; this system
   shows tables of 139 rows, and saturated colour behind small text is tiring
   within minutes. Pale keeps the glass visible and the data legible.

   WHAT EACH THEME DECLARES
   ------------------------
       --th-accent / --th-accent-deep   buttons, active item, links, sort arrows
       --th-tint                        hover rows, selected states
       --th-bg-1/2/3                    the three stops of the page gradient

   Adding a sixth theme is one line here plus one block in Config/AppSkins.php.
   Every rule below reads the variables and nothing else.

   IF backdrop-filter IS NOT SUPPORTED the panels are simply more opaque and
   less interesting -- never broken. That is why every panel sets a real
   background colour as well as the blur.
   -------------------------------------------------------------------------- */

/* FIVE THEMES, ALL OURS.
   Vivid accents -- the buttons and the active menu pill should have some life
   in them -- over PALE page gradients, because saturated colour behind a
   139-row table is tiring within minutes. Vivid where you click, quiet where
   you read.

       Ember    neon coral-orange, warm and confident   (the default)
       Azure    clear cobalt blue
       Violet   electric indigo
       Jade     deep sea-green
       Rose     magenta-pink                                              */

/* Ember unchanged -- it was the one that read well. The other four came down
   in saturation: they were neon, which is fine on a marketing page and tiring
   on a screen you sit in front of all day.

   Lagoon is the "Bali water" one -- shallow-sea turquoise rather than cobalt. */
.theme-ember  { --th-accent:#f2683c; --th-accent-deep:#c8461f; --th-tint:#ffeee7;
                --th-bg-1:#ffe9df; --th-bg-2:#fff6f1; --th-bg-3:#ffdccb; }
.theme-lagoon { --th-accent:#3fa6b8; --th-accent-deep:#2b7f8f; --th-tint:#e4f5f7;
                --th-bg-1:#d9f0f2; --th-bg-2:#f0fafb; --th-bg-3:#c2e7ec; }
.theme-violet { --th-accent:#8f7cc4; --th-accent-deep:#6b5a9e; --th-tint:#f0ecf8;
                --th-bg-1:#e9e3f5; --th-bg-2:#f7f4fc; --th-bg-3:#ddd3ee; }
.theme-jade   { --th-accent:#4fa88d; --th-accent-deep:#357f68; --th-tint:#e7f5f0;
                --th-bg-1:#ddf0e9; --th-bg-2:#f1faf7; --th-bg-3:#c9e8dd; }
.theme-rose   { --th-accent:#d97a9e; --th-accent-deep:#b0567a; --th-tint:#fbecf2;
                --th-bg-1:#fae3ec; --th-bg-2:#fef4f8; --th-bg-3:#f5d3e0; }

/* Fallback, so nothing is unstyled if the class is ever missing. */
body { --th-accent:#f2683c; --th-accent-deep:#c8461f; --th-tint:#ffeee7;
       --th-bg-1:#ffe9df; --th-bg-2:#fff6f1; --th-bg-3:#ffdccb; }


/* ---- The page: the colour everything else is frosted against -------------
   `fixed` so the gradient stays put while the content scrolls -- otherwise the
   glass appears to slide over a moving background, which looks wrong. */
/* TWO LAYERS: the water texture on top, the theme gradient underneath.
   Listed first = painted on top, so the waves are lit by whichever colour the
   theme supplies. The SVG is drawn in neutral aqua-white for exactly that
   reason -- a fixed sea-green image would fight the Ember orange.

   Both `fixed`, so the water stays still while the content scrolls. If it
   scrolled with the page the whole thing would feel like it was sliding. */
body.lets-app {
    background-color: var(--th-bg-2);
    background-image:
        url("../images/water-bg.svg"),
        linear-gradient(135deg, var(--th-bg-1) 0%, var(--th-bg-2) 45%, var(--th-bg-3) 100%);
    background-size: cover, cover;
    background-position: center, center;
    background-repeat: no-repeat, no-repeat;
    background-attachment: fixed, fixed;
}
/* AdminLTE paints .content-wrapper solid; it has to be clear for the gradient
   to show at all. */
.content-wrapper, .right-side { background: transparent; }


/* ---- Frosted panels ------------------------------------------------------
   saturate(180%) is what stops the blur looking washed out -- blurring alone
   greys the colour behind it, and pushing saturation back up is what makes it
   read as glass rather than as fog. */
.main-header .navbar,
.main-header .logo,
.main-sidebar,
.left-side {
    -webkit-backdrop-filter: blur(18px) saturate(180%);
    backdrop-filter: blur(18px) saturate(180%);
}

.main-header .navbar {
    background: rgba(255,255,255,.55);
    border: none;
    border-bottom: 1px solid rgba(255,255,255,.6);
    box-shadow: 0 1px 12px rgba(31,56,77,.06);
}
.main-header .logo {
    background: rgba(255,255,255,.42);
    color: var(--th-accent-deep);
    border-bottom: 0;
    box-shadow: inset -1px 0 0 rgba(255,255,255,.5);
}


/* TWO LAYERS IN ONE DECLARATION: the translucent white wash is listed FIRST so
   it sits on top of the pattern, and the pattern is what you see through the
   glass. Without something behind it, "translucent" and "grey" look identical.

   Done with background layers rather than a ::before, because backdrop-filter
   creates a stacking context -- a negatively-stacked pseudo-element inside one
   does not paint where you would expect, and two background layers do the job
   outright. */
.main-sidebar,
.left-side {
    background-image:
        linear-gradient(180deg, rgba(255,255,255,.52), rgba(255,255,255,.34)),
        url("../images/sidebar-pattern.svg");
    background-size: cover, cover;
    background-position: center, center;
    background-repeat: no-repeat, no-repeat;
    background-attachment: fixed, fixed;
    box-shadow: inset -1px 0 0 rgba(255,255,255,.55), 1px 0 16px rgba(31,56,77,.05);
}

/* ---- The bar's contents, now that it is pale ----------------------------- */
.main-header .navbar .nav > li > a,
.main-header .sidebar-toggle { color: #47586b; }
.main-header .navbar .nav > li > a:hover,
.main-header .sidebar-toggle:hover,
.main-header .navbar .nav > .open > a {
    background: rgba(255,255,255,.55) !important;
    color: var(--th-accent-deep) !important;
}


/* ---- The menu: rounded pills, as in the reference designs ---------------- */
.sidebar-menu > li > a {
    color: #47586b; font-weight: 600;
    margin: 2px 8px; padding: 9px 12px;
    border-radius: 9px;
    border-left: none !important;      /* AdminLTE's 3px bar -- the pill replaces it */
    transition: background .16s ease, color .16s ease;
}
.sidebar-menu > li > a > .fa { color: #8798aa; width: 18px; }

/* The portal's "New Offers" unread count (layouts/portal.php) -- the only
   sidebar link with a badge at all, so it is also the only one with
   anything sitting at that right edge. Bootstrap's `.label.pull-right`
   floats it there with no margin of its own, flush against the pill's own
   padding -- which read as cramped, almost clipped, next to five other
   items with nothing there but empty space. A little room to breathe, and
   a rounder, pill shape instead of Bootstrap's squared-off label corners,
   matching the shape of the link it sits inside. */
.sidebar-menu > li > a > .label.pull-right {
    margin: 1px 2px 0 6px;
    padding: 3px 8px;
    border-radius: 999px;
    font-weight: 700;
}
.sidebar-menu > li.header {
    background: transparent; color: #8fa1b3;
    font-size: 10px; letter-spacing: .9px; font-weight: 700;
    padding: 12px 20px 5px;
}

.sidebar-menu > li:hover > a {
    background: rgba(255,255,255,.72) !important;
    color: var(--th-accent-deep) !important;
}
/* The current page: a solid accent pill. The one thing that is NOT translucent,
   because it is how you find your place at a glance. */
.sidebar-menu > li.active > a,
.sidebar-menu > li.menu-open > a {
    background: linear-gradient(135deg, var(--th-accent), var(--th-accent-deep)) !important;
    color: #fff !important;
    box-shadow: 0 4px 12px -4px rgba(31,56,77,.4);
}
.sidebar-menu > li.active > a > .fa,
.sidebar-menu > li.menu-open > a > .fa { color: #fff; }
.sidebar-menu > li:hover > a > .fa { color: var(--th-accent); }

.sidebar-menu .treeview-menu {
    background: rgba(255,255,255,.3) !important;
    margin: 0 8px 4px; border-radius: 9px; padding: 4px 0;
}
.sidebar-menu .treeview-menu > li > a {
    color: #5a6b7d; padding: 6px 12px 6px 30px; border-radius: 7px; margin: 0 4px;
}
.sidebar-menu .treeview-menu > li > a:hover,
.sidebar-menu .treeview-menu > li.active > a {
    background: rgba(255,255,255,.85) !important;
    color: var(--th-accent-deep) !important;
}

/* ---- Footer: frosted too, with an accent hairline ------------------------ */
.main-footer {
    background: rgba(255,255,255,.5);
    -webkit-backdrop-filter: blur(14px) saturate(180%);
    backdrop-filter: blur(14px) saturate(180%);
    border-top: 1px solid rgba(255,255,255,.6);
    box-shadow: inset 0 2px 0 var(--th-accent);
    color: #55677a;
}


/* ---- Cards: near-white, so tables stay readable on the gradient ---------- */
.box, .lets-grid, .lets-page {
    background: rgba(255,255,255,.88);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border-color: rgba(255,255,255,.9);
    box-shadow: 0 1px 2px rgba(31,56,77,.04), 0 8px 24px -12px rgba(31,56,77,.18);
}


/* ==========================================================================
   PAGE HEAD  --  ONE title per screen, and it lives here
   --------------------------------------------------------------------------
   Replaces AdminLTE's .content-header. Everything that identifies the page is
   in a single band: icon, title, one line of explanation, breadcrumb, and the
   page's buttons.

   Before this there were three: the navbar said "Domains", the content header
   said "Domains", and the card said "Domains". One screen, one title.

   Drawn by app/Views/partials/page-head.php -- no view writes this markup.
   ========================================================================== */
/* COMPACT ON PURPOSE.
   This band used to be ~100px tall: 18px padding, a 21px title, then the
   description on its own line below. Stacked under a 50px navbar it pushed the
   table down far enough that the page itself scrolled.

   Now it is one row about 46px tall -- icon, title and description on the same
   line, breadcrumb and buttons on the right. The description truncates with an
   ellipsis rather than wrapping to a second line and growing the band; the full
   text is still there on hover. */
.lets-page {
    display: flex; align-items: center; justify-content: space-between;
    gap: 14px; flex-wrap: wrap;
    padding: 8px 14px; margin: 0 0 12px;
    background: var(--lets-surface);
    border: 1px solid var(--lets-border);
    border-left: 3px solid var(--lets-teal);
    border-radius: var(--lets-radius);
    box-shadow: var(--lets-shadow);

    /* ----------------------------------------------------------------------
       >>> THIS position/z-index IS WHAT KEEPS THE "?" BALLOON IN FRONT. <<<
       Removing it puts the balloon BEHIND the table again.

       WHY IT IS NEEDED, because the reason is not obvious:

       `.lets-page`, `.lets-grid` and `.box` all carry `backdrop-filter` for the
       frosted-glass look. **backdrop-filter creates a NEW STACKING CONTEXT.**
       That means the balloon's own `z-index: 1040` is measured only against its
       siblings INSIDE this band -- it cannot lift the balloon above anything
       outside it, no matter how large the number is.

       The grid or card that follows makes its own stacking context too, and
       being later in the document it painted on top. So the balloon appeared
       underneath the table and could not be read.

       Giving this band a real z-index raises the WHOLE context above what
       follows, which is the only thing that fixes it. Chasing the number on the
       balloon itself does nothing at all -- that was tried.
       ---------------------------------------------------------------------- */
    position: relative;
    z-index: 30;
}
.lets-page__main {
    min-width: 0; flex: 1 1 320px;
    display: flex; align-items: baseline; gap: 10px;
}
.lets-page__title {
    display: flex; align-items: center; gap: 9px;
    margin: 0; font-size: 17px; font-weight: 700; color: var(--lets-ink);
    line-height: 1.2; white-space: nowrap; flex: 0 0 auto;
}
.lets-page__title .fa {
    width: 28px; height: 28px; flex: 0 0 28px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--lets-radius-s);
    background: linear-gradient(135deg, var(--lets-teal-bright), var(--lets-teal));
    color: #fff; font-size: 13px;
}
/* On the same line as the title, clipped rather than wrapping. */
.lets-page__sub {
    margin: 0; font-size: 12px; color: var(--lets-muted); line-height: 1.4;
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lets-page__side {
    display: flex; align-items: center; gap: 12px; flex: 0 0 auto;
}
.lets-page__crumbs {
    font-size: 12px; color: var(--lets-muted);
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.lets-page__crumbs a { color: var(--lets-teal-deep); }
.lets-page__crumbs .sep { color: #cbd6d2; }
.lets-page__actions { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }

/* A screen-level switch standing beside the buttons in the page-head -- the
   Users list's "Show deleted" is the first. Deliberately NOT a .btn: it does
   not perform an action, it changes what the screen is showing, and dressing
   it as a button would invite it to be read as one more thing to press. */
.lets-page__toggle {
    display: inline-flex; align-items: center; gap: 6px;
    margin: 0; font-size: 12.5px; font-weight: 600;
    color: var(--lets-muted); white-space: nowrap; cursor: pointer;
}
.lets-page__toggle input { margin: 0; cursor: pointer; }

@media (max-width: 991px) {
    /* Too narrow for one line -- let the description drop below the title
       rather than squeezing it to nothing. */
    .lets-page__main { flex-wrap: wrap; }
    .lets-page__sub  { flex: 1 0 100%; white-space: normal; }
}

@media (max-width: 767px) {
    /* PHONE. Everything stacks and nothing is allowed to push the page wider
       than the screen -- a page that scrolls sideways is the thing this whole
       mobile pass exists to remove.

       `flex: 1 1 320px` on the main block was the real culprit: a 320px basis
       inside a ~360px content area left no room for the breadcrumb and the
       buttons, so they ran off the right edge. min-width:0 lets a flex item
       shrink below its content size, which is not the default and is the usual
       reason a flex row refuses to fit. */
    .lets-page { padding: 10px 12px; }
    .lets-page__main { flex: 1 1 100%; min-width: 0; }
    .lets-page__side { width: 100%; flex-wrap: wrap; gap: 8px; }
    .lets-page__crumbs { flex: 1 1 100%; }
    .lets-page__actions { width: 100%; }
    /* Buttons share the row and shrink together rather than overflowing. */
    .lets-page__actions .btn { flex: 1 1 0; min-width: 0; white-space: nowrap; }
}

/* AdminLTE pads .content generously. Trim it -- with the page-head carrying its
   own margin, the extra only pushed the table further down.

   width: .content was running the full width of .content-wrapper, right up
   against the sidebar's edge with nothing between them -- reads as
   overlapping it. Pull it in 5px so there is a visible gap. */
.content-wrapper > .content { padding: 5px 14px; width: calc(100% - 5px); }


/* ==========================================================================
   BUTTONS  --  restyled. AdminLTE's are Bootstrap 3 and look 2014.
   --------------------------------------------------------------------------
   Softer corners, a real shadow, a gradient on the primary action, and a press
   that actually moves. Sizes are unchanged so nothing reflows.
   ========================================================================== */
.btn {
    border-radius: var(--lets-radius-s);
    font-weight: 650;
    padding: 7px 15px;
    border: 1px solid transparent;
    box-shadow: none;
    transition: background-color .16s ease, border-color .16s ease,
                box-shadow .16s ease, transform .1s ease, color .16s ease;
}
.btn:focus, .btn:active:focus {
    outline: none;
    box-shadow: 0 0 0 3px var(--lets-tint);
}
.btn:active { transform: translateY(1px); }

.btn-default {
    background: var(--lets-surface);
    border-color: var(--lets-border);
    color: var(--lets-ink-2);
}
.btn-default:hover, .btn-default:focus {
    background: var(--lets-tint);
    border-color: var(--lets-teal-bright);
    color: var(--lets-teal);
}

.btn-primary {
    background: linear-gradient(135deg, var(--lets-teal-bright), var(--lets-teal));
    border-color: transparent; color: #fff;
    box-shadow: 0 2px 8px -2px rgba(14,116,144,.5);
}
.btn-primary:hover, .btn-primary:focus {
    background: linear-gradient(135deg, #3ddcf2, #12849f);
    border-color: transparent; color: #fff;
    box-shadow: 0 4px 14px -3px rgba(14,116,144,.55);
}

.btn-danger {
    background: #fdebec; border-color: #f5c2c4; color: var(--lets-red);
}
.btn-danger:hover, .btn-danger:focus {
    background: var(--lets-red); border-color: var(--lets-red); color: #fff;
}
/* >>> WHITE-ON-WHITE, ON A COLOURED BOX HEADER. <<< AdminLTE forces every
   .btn inside a solid-red box header to white text --
   ".box.box-solid.box-danger>.box-header .btn{color:#fff}" -- built for its
   own transparent box-tool buttons, where that is the whole point. Against
   THIS theme's own soft-pink .btn-danger (background #fdebec, above) it
   read as white text on a background one shade off white -- invisible. The
   Escalation box's own action button is exactly this case: box-danger
   header, btn-danger button, active escalation. Restoring the text colour
   here (the background was never touched by AdminLTE's rule and was
   already fine); an ID selector outranks AdminLTE's four-class one without
   needing !important. */
#escalationOpen.btn-danger { color: var(--lets-red); }
/* The ID selector above outranks .btn-danger:hover's two classes, so
   without this it also won on hover/focus -- red text on the red hover
   background. Match its specificity here to restore white on hover. */
#escalationOpen.btn-danger:hover, #escalationOpen.btn-danger:focus { color: #fff; }

.btn-success { background: linear-gradient(135deg,#34d399,#15803d); border-color: transparent; color: #fff; }
.btn-warning { background: linear-gradient(135deg,#fbbf24,#d97706); border-color: transparent; color: #fff; }

.btn-sm { padding: 5px 11px; font-size: 12px; border-radius: 7px; }
.btn-xs { padding: 3px 8px;  font-size: 11px; border-radius: 6px; }
.btn-flat { border-radius: var(--lets-radius-s); }

/* An icon-only button should be square, not a wide pill with an icon adrift
   in the middle of it. */
.lets-btn-icon {
    width: 28px; height: 28px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
}


/* ==========================================================================
   CARDS  --  AdminLTE's .box, restyled
   ========================================================================== */
.box {
    border: 1px solid var(--lets-border);
    border-top: 1px solid var(--lets-border);   /* kill the 3px coloured stripe */
    border-radius: var(--lets-radius);
    box-shadow: var(--lets-shadow);
    margin-bottom: 18px;
}
.box.box-primary, .box.box-info, .box.box-success,
.box.box-warning, .box.box-danger, .box.box-default { border-top-color: var(--lets-border); }

.box-header.with-border { border-bottom: 1px solid var(--lets-hairline); padding: 13px 16px; }
.box-header .box-title  { font-size: 14px; font-weight: 700; color: var(--lets-ink); }
.box-body   { padding: 16px; }
.box-footer {
    background: var(--lets-sunken);
    border-top: 1px solid var(--lets-hairline);
    border-radius: 0 0 var(--lets-radius) var(--lets-radius);
    padding: 11px 16px; color: var(--lets-ink-2);
}
/* A card whose whole body is a grid needs no padding of its own -- the grid
   brings its own border and spacing. */
.box-body--flush { padding: 0; }

/* Boxes lift a little on hover, enough to read as interactive. */
.box { transition: box-shadow .18s ease, transform .18s ease; }


/* ==========================================================================
   BADGES / LABELS
   ========================================================================== */
.label {
    display: inline-block; padding: 3px 9px; border-radius: 99px;
    font-size: 11px; font-weight: 700; letter-spacing: .2px;
}
.label-primary { background: var(--lets-tint);   color: var(--lets-teal); }
.label-success { background: #e2f6e6;            color: var(--lets-green); }
.label-danger  { background: #fdebec;            color: var(--lets-red); }
.label-warning { background: #fdeccb;            color: var(--lets-amber); }
.label-info    { background: #e5edfb;            color: #2f5cb0; }
.label-default { background: var(--lets-hairline); color: var(--lets-ink-2); }


/* ==========================================================================
   FORMS
   ========================================================================== */
.form-control {
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    box-shadow: none;
    height: auto; padding: 8px 11px;
    color: var(--lets-ink);
    transition: border-color .15s ease, box-shadow .15s ease;
}
.form-control:focus {
    border-color: var(--lets-teal-bright);
    box-shadow: 0 0 0 3px var(--lets-tint);
}
.control-label { font-weight: 650; color: var(--lets-ink-2); }
.help-block { font-size: 12px; color: var(--lets-muted); line-height: 1.55; }
.has-error .form-control { border-color: var(--lets-red); }


/* ==========================================================================
   CALLOUTS  --  AdminLTE's are very loud; these are quieter
   ========================================================================== */
/*
 * >>> `!important` ALONE DID NOT FIX THIS. THE SELECTOR HAD TO MATCH TOO.
 * <<<                                                          (29 Aug 2026)
 * AdminLTE.min.css sets `.callout.callout-info` (and -warning, -danger,
 * -success) to a solid, saturated fill with `!important` on both
 * `background-color` and `color` -- a TWO-CLASS selector. The first attempt
 * here added `!important` to the bare `.callout` rule below and stopped,
 * reasoning that `!important` alone would win. It does not: when both sides
 * are `!important`, the cascade falls back to SPECIFICITY next, and a
 * two-class selector beats a one-class one regardless of which file loaded
 * last or which declaration is newer. AdminLTE kept winning, silently, and
 * the symptom -- a `btn-default` button as a pale grey pill on a bright cyan
 * fill, readable nowhere -- came back exactly as it was before that "fix".
 *
 * The rule below now repeats the compound selector for every severity
 * AdminLTE colours, so the specificity actually ties -- and this file loads
 * LAST (see layouts/app.php), so the tie goes to it.
 */
.callout,
.callout.callout-info,
.callout.callout-warning,
.callout.callout-danger,
.callout.callout-success {
    border-radius: var(--lets-radius); border: 1px solid var(--lets-border);
    border-left: 3px solid var(--lets-teal);
    box-shadow: none; padding: 14px 16px;
    background: var(--lets-surface) !important; color: var(--lets-ink-2) !important;
}
.callout h4 { font-size: 14px; font-weight: 700; color: var(--lets-ink); margin-top: 0; }
.callout-info    { border-left-color: var(--lets-teal); }
.callout-warning { border-left-color: #f0a92e; }
.callout-danger  { border-left-color: var(--lets-red); }
.callout-success { border-left-color: var(--lets-green); }

/* An explanatory note -- quieter still than a callout. */
.lets-note {
    padding: 12px 15px; margin-bottom: 18px;
    border: 1px dashed var(--lets-border); border-radius: var(--lets-radius);
    background: var(--lets-sunken); color: var(--lets-ink-2);
    font-size: 13px; line-height: 1.6;
}
.lets-note ul { margin: 7px 0 0; padding-left: 18px; }
.lets-note li { margin-bottom: 4px; }
.lets-note code, .lets-page__sub code {
    padding: 1px 5px; border-radius: 4px;
    background: var(--lets-hairline); color: var(--lets-teal-deep); font-size: 12px;
}


/* ==========================================================================
   MASTER DATA HUB  --  the cards on /master
   ========================================================================== */
.lets-hub-card { display: block; color: inherit; text-decoration: none; }
.lets-hub-card:hover, .lets-hub-card:focus { color: inherit; text-decoration: none; }
.lets-hub-card .box { height: 100%; margin-bottom: 18px; }
.lets-hub-card:hover .box {
    border-color: var(--lets-teal-bright);
    box-shadow: var(--lets-shadow-h);
    transform: translateY(-2px);
}
.lets-hub-card__head { display: flex; align-items: center; gap: 11px; }
.lets-hub-card__icon {
    width: 38px; height: 38px; flex: 0 0 38px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--lets-radius-s);
    background: var(--lets-tint); color: var(--lets-teal); font-size: 16px;
    transition: background .18s ease, color .18s ease;
}
.lets-hub-card:hover .lets-hub-card__icon {
    background: linear-gradient(135deg, var(--lets-teal-bright), var(--lets-teal));
    color: #fff;
}
.lets-hub-card__name  { font-size: 15px; font-weight: 700; color: var(--lets-ink); }
.lets-hub-card__count { font-size: 12px; color: var(--lets-muted); font-weight: 600; }
.lets-hub-card__help  { margin: 11px 0 0; font-size: 12px; color: var(--lets-muted); line-height: 1.55; }


/* ==========================================================================
   TABLES that are NOT the data grid (dashboard, profile, my access)
   ========================================================================== */
.table > thead > tr > th {
    background: #e4f0ee; color: var(--lets-ink);
    font-size: 10px; font-weight: 750; letter-spacing: .6px; text-transform: uppercase;
    border-bottom: 1px solid var(--lets-border); padding: 9px 12px;
}
.table > tbody > tr > td,
.table > tbody > tr > th { border-top: 1px solid var(--lets-hairline); padding: 9px 12px; }
.table-hover > tbody > tr:hover { background: var(--lets-tint); }


/* ==========================================================================
   MISC
   ========================================================================== */
.lets-filename { font-size: 11px; font-family: Consolas, monospace; color: var(--lets-muted); }
.lets-warn     { margin-top: 3px; font-size: 11px; font-weight: 600; color: var(--lets-amber); }

/* Row action buttons: each Retire/Delete sits in its own <form>, and a form is
   a block element -- without this they would stack vertically. */
.lets-rowactions { display: flex; gap: 4px; justify-content: flex-end; align-items: center; flex-wrap: wrap; }
.lets-inline-form { display: inline; margin: 0; }
@media (max-width: 767px) { .lets-rowactions { justify-content: flex-start; } }

/* Sidebar links nudge on hover. */
.sidebar-menu > li > a { transition: padding-left .15s ease; }
.sidebar-menu > li:not(.active) > a:hover { padding-left: 20px; }

.main-footer {
    border-top: 1px solid var(--lets-border);
    color: var(--lets-ink-2); font-size: 12.5px; padding: 13px 20px;
}

/* --- Colour scheme picker (profile screen) -------------------------------- */
.lets-skins {
    display: grid; gap: 8px;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
.lets-skin {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 10px; margin: 0;
    border: 1px solid var(--lets-border); border-radius: var(--lets-radius-s);
    font-weight: 400; cursor: pointer;
    transition: border-color .15s ease, background .15s ease;
}
.lets-skin:hover { border-color: var(--lets-teal-bright); background: var(--lets-tint); }
.lets-skin.is-current { border-color: var(--lets-teal); background: var(--lets-tint); font-weight: 650; }
.lets-skin input { margin: 0; accent-color: var(--lets-teal); }
.lets-skin__swatch {
    width: 22px; height: 22px; flex: 0 0 22px;
    border-radius: 5px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
}
.lets-skin__name { font-size: 13px; }


/* ==========================================================================
   THEME CHOOSER  --  the cards on Profile > Appearance
   --------------------------------------------------------------------------
   Each card draws a MINIATURE of the real screen in that theme's colours:
   sidebar, top bar, a button, a badge, three rows of table. You pick by
   looking at what you will get.

   The miniature is built from the same five variables the live theme uses,
   passed in as --pv-* on the card, so a preview cannot drift out of step with
   the real thing.
   ========================================================================== */
.lets-theme-card {
    display: block; margin: 0 0 16px; padding: 0;
    border: 2px solid var(--lets-border); border-radius: var(--lets-radius);
    background: #fff; overflow: hidden; cursor: pointer; font-weight: 400;
    transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}
.lets-theme-card:hover { border-color: var(--pv-accent); box-shadow: var(--lets-shadow-h); transform: translateY(-2px); }

/* ---- the chosen one ----
   TWO RULES FOR ONE STATE, AND THEY CANNOT DISAGREE: both follow the radio.

   This used to be `.is-current` alone, and `.is-current` was rendered by the
   SERVER from the SAVED skin. So clicking a card checked its radio -- the form
   was working the whole time -- but the outline and the tick stayed behind on
   the theme you already had, and the page read as "you cannot pick a theme".

   `:has()` is what actually moves the outline; appearance.php's script keeps
   `.is-current` in step as well, for a browser too old for it. The server
   still renders `.is-current` on the saved theme so the page is right on
   arrival, before either has had anything to react to. */
.lets-theme-card.is-current,
.lets-theme-card:has(input:checked) { border-color: var(--pv-accent); box-shadow: 0 0 0 3px var(--pv-tint); }

/* The radio is the real control -- kept in the DOM for keyboard and screen
   readers, just not drawn, because the whole card is the target. */
.lets-theme-card input { position: absolute; opacity: 0; width: 0; height: 0; }
.lets-theme-card input:focus-visible + .lets-tpv { outline: 2px solid var(--pv-accent); outline-offset: 2px; }

/* ---- the miniature ---- */
.lets-tpv {
    display: flex; height: 132px;
    /* the same three-stop gradient the real page uses */
    background: linear-gradient(135deg, var(--pv-bg1) 0%, var(--pv-bg2) 45%, var(--pv-bg3) 100%);
}
.lets-tpv__side {
    width: 58px; flex: 0 0 58px; padding: 6px 5px;
    display: flex; flex-direction: column; gap: 5px;
    /* frosted, like the real sidebar */
    background: rgba(255,255,255,.45);
    box-shadow: inset -1px 0 0 rgba(255,255,255,.6);
}
.lets-tpv__logo { height: 12px; border-radius: 3px; background: var(--pv-deep); opacity: .75; }
.lets-tpv__link { height: 8px; border-radius: 4px; background: rgba(255,255,255,.6); }
.lets-tpv__link.is-on { background: linear-gradient(135deg, var(--pv-accent), var(--pv-deep)); opacity: 1; }
.lets-tpv__main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.lets-tpv__bar {
    height: 18px;
    background: rgba(255,255,255,.55);
    box-shadow: inset 0 -1px 0 rgba(255,255,255,.7);
}
.lets-tpv__body { flex: 1; padding: 8px; display: flex; flex-wrap: wrap; gap: 5px; align-content: flex-start; }
.lets-tpv__btn {
    width: 42px; height: 13px; border-radius: 4px;
    background: linear-gradient(135deg, var(--pv-accent), var(--pv-deep));
}
.lets-tpv__pill { width: 28px; height: 13px; border-radius: 99px; background: var(--pv-tint); }
.lets-tpv__row { width: 100%; height: 10px; border-radius: 3px; background: rgba(255,255,255,.9); }
.lets-tpv__row.short { width: 62%; }

/* ---- the name under it ---- */
.lets-theme-card__foot {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; border-top: 1px solid var(--lets-border); background: #fff;
}
.lets-theme-card__dot {
    width: 18px; height: 18px; flex: 0 0 18px; border-radius: 50%;
    background: linear-gradient(135deg, var(--pv-accent), var(--pv-deep));
}
.lets-theme-card__name { display: block; font-size: 14px; color: var(--lets-ink); }
.lets-theme-card__note { display: block; font-size: 11.5px; color: var(--lets-muted); line-height: 1.4; }
/* The tick is now on EVERY card and merely hidden, rather than printed only on
   the saved one. `visibility` and not `display`, so the row does not jump a
   few pixels sideways as the tick moves from card to card.

   The input is a SIBLING of the foot, so this one needs neither :has() nor
   script -- it is the tick, not the outline, that carries the answer in the
   very oldest browser we would still open this page in. */
.lets-theme-card__tick { margin-left: auto; color: var(--pv-accent); font-size: 16px; visibility: hidden; }
.lets-theme-card input:checked ~ .lets-theme-card__foot .lets-theme-card__tick { visibility: visible; }


/* ==========================================================================
   COMPANIES  --  status pills, the password state, the test-email result
   --------------------------------------------------------------------------
   ALL OF THESE COLOURS ARE DELIBERATELY FIXED AND DO NOT FOLLOW THE THEME.

   They carry MEANING, not decoration -- the same rule the action icons and the
   Active/Retired badges already follow. "Ready" must read as ready under Ember
   as well as under Jade; a "Re-enter the password" warning tinted coral to
   match the furniture around it stops looking like a warning at all.

   That is also why this block sits ABOVE the accent layer rather than in it.
   ========================================================================== */

.lets-pill {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 99px;
    font-size: 11px;
    font-weight: 600;
    line-height: 18px;
    white-space: nowrap;
    vertical-align: middle;
}

/* The two "default" badges on the company name. */
.lets-pill--default { background: #e8f0fb; color: #2b5f9e; border: 1px solid #c9ddf5; }
.lets-pill--mail    { background: #eee9fa; color: #5b46a3; border: 1px solid #d9d0f2; }

/* Can this company actually send an email? */
.lets-pill--ok   { background: #e6f5ec; color: #1d7a45; border: 1px solid #c6e8d4; }
.lets-pill--warn { background: #fdf3e0; color: #9a6410; border: 1px solid #f6e2bd; }
.lets-pill--bad  { background: #fdeaea; color: #a52b2b; border: 1px solid #f5cfcf; }
.lets-pill--none { background: #f2f3f5; color: #6f7680; border: 1px solid #e2e5e9; }

/* "A password is stored for this mailbox." */
.lets-pw-state {
    margin: 0 0 8px;
    font-size: 12.5px;
    color: var(--lets-muted, #6f7680);
}
.lets-pw-state--ok i { color: #1d7a45; margin-right: 4px; }

/* What came back from the test-email button. */
.lets-test-result {
    margin-top: 10px;
    padding: 9px 12px;
    border-radius: 6px;
    border: 1px solid transparent;
    font-size: 13px;
    line-height: 1.55;
}
.lets-test-result--busy { background: #f2f3f5; color: #545b64; border-color: #e2e5e9; }
.lets-test-result--ok   { background: #e6f5ec; color: #166036; border-color: #c6e8d4; }
.lets-test-result--bad  { background: #fdeaea; color: #8f2525; border-color: #f5cfcf; }

/* The current logo on the edit form. max-width keeps a large upload from
   pushing the column open; the checkerboard shows up a transparent PNG that
   would otherwise be invisible against a white card. */
.lets-logo-preview {
    max-width: 100%;
    max-height: 90px;
    padding: 6px;
    border: 1px solid var(--lets-border, #e2e5e9);
    border-radius: 6px;
    background-color: #fff;
    background-image:
        linear-gradient(45deg, #f4f5f7 25%, transparent 25%, transparent 75%, #f4f5f7 75%),
        linear-gradient(45deg, #f4f5f7 25%, transparent 25%, transparent 75%, #f4f5f7 75%);
    background-size: 14px 14px;
    background-position: 0 0, 7px 7px;
}


/* ==========================================================================
   THE "?" TIP BALLOON  --  screen guidance, beside the heading
   --------------------------------------------------------------------------
   REPLACES the grey `.lets-note` block that used to sit UNDER the page.

   That block was not merely untidy. The data grid is sized to the viewport, so
   anything below it pushed the page past the bottom of the window and put a
   SECOND scrollbar beside the grid's own -- two scrollbars, side by side.

   This costs ZERO vertical space: the button sits inline in the heading and the
   balloon is absolutely positioned, so it is painted OVER the page and never
   reflows it. Guidance nobody is currently reading no longer occupies a third
   of the screen.

   Not a Bootstrap popover on purpose. Those need JavaScript initialisation per
   element and carry their content in a `data-` attribute, which mangles markup
   and is awkward to escape correctly. This is markup that is already on the
   page, simply shown and hidden.
   ========================================================================== */

/* A flex child of .lets-page__main, sitting between the heading and the
   subtitle. That row is `align-items: baseline`, which would hang a circular
   button off the text baseline and look dropped -- align-self re-centres it.
   flex: 0 0 auto so it never shrinks when the subtitle is long. */
.lets-tip {
    position: relative;
    display: inline-block;
    align-self: center;
    flex: 0 0 auto;
    margin-left: -2px;
}

/* The "?" itself. Deliberately quiet -- it is an offer of help, not a call to
   action, and must not compete with the heading it sits beside. */
.lets-tip__btn {
    width: 19px;
    height: 19px;
    padding: 0;
    border: 1px solid var(--lets-border, #d8dce1);
    border-radius: 50%;
    background: #fff;
    color: var(--lets-muted, #6f7680);
    font-size: 11px;
    font-weight: 700;
    line-height: 17px;
    text-align: center;
    cursor: pointer;
    transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.lets-tip__btn:hover,
.lets-tip__btn[aria-expanded="true"] {
    background: var(--th-accent, #0e7490);
    border-color: var(--th-accent, #0e7490);
    color: #fff;
}
.lets-tip__btn:focus-visible {
    outline: 2px solid var(--th-accent, #0e7490);
    outline-offset: 2px;
}

/* The balloon. `hidden` on the element does the hiding, so it is invisible to
   a screen reader too -- not merely moved off-screen. */
.lets-tip__balloon[hidden] { display: none; }

.lets-tip__balloon {
    position: absolute;
    top: calc(100% + 10px);
    left: -10px;

    /* Only meaningful against siblings inside .lets-page -- that band creates a
       stacking context of its own (backdrop-filter). What actually puts the
       balloon in front of the table is `z-index` on `.lets-page`; see the long
       note there before changing either. */
    z-index: 1040;
    display: block;

    /* ----------------------------------------------------------------------
       WIDTH SCALES WITH THE WINDOW.

       It was a flat 380px, which was cramped on a desktop and left long
       sentences broken over many short lines. clamp() gives one rule for every
       screen instead of a stack of breakpoints:

           300px   the floor -- never narrower than this
           46vw    the preferred size, so a wide monitor gets a wide balloon
           640px   the ceiling -- past roughly this, a line of text becomes too
                   long to track back to the start of the next one comfortably

       Measured in a real browser across five widths: 784px -> 360, 1264 -> 581,
       1424 -> 640 (capped), 1904 -> 640. Below 700px the mobile rule at the
       bottom of this block takes over entirely.

       max-width is the safety net at the narrow end, where the 300px floor
       could otherwise exceed a small viewport.
       ---------------------------------------------------------------------- */
    width: clamp(300px, 46vw, 640px);
    max-width: calc(100vw - 32px);

    /* A long tip (the access matrix lists nine points) must not run off the
       bottom of a short laptop screen with no way to reach the rest. */
    max-height: min(60vh, 520px);
    overflow-y: auto;
    overscroll-behavior: contain;   /* scrolling the balloon must not scroll the page behind it */

    padding: 13px 16px;
    border: 1px solid var(--lets-border, #e2e5e9);
    border-radius: 8px;
    background: #fff;
    box-shadow: 0 8px 28px -8px rgba(31, 56, 77, .28);
    text-align: left;
    white-space: normal;    /* the heading may be nowrap; this must not inherit it */
    font-size: 12.5px;
    font-weight: 400;
    line-height: 1.6;
    color: var(--lets-ink, #2c3238);
    animation: letsTipIn .14s ease-out;
}

/* The pointer. Two triangles stacked -- the lower one is the border, the upper
   one the fill sitting 1px in front of it, which is how you get an arrow with
   a visible edge without an SVG. */
.lets-tip__balloon::before,
.lets-tip__balloon::after {
    content: "";
    position: absolute;
    bottom: 100%;
    left: 14px;
    border: 8px solid transparent;
}
.lets-tip__balloon::before { border-bottom-color: var(--lets-border, #e2e5e9); }
.lets-tip__balloon::after  { border-bottom-color: #fff; margin-bottom: -1px; border-width: 7px; left: 15px; }

.lets-tip__head {
    display: block;
    margin-bottom: 6px;
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .4px;
    color: var(--lets-muted, #6f7680);
}

.lets-tip__list { margin: 0; padding-left: 16px; }
.lets-tip__list > li { margin-bottom: 7px; }
.lets-tip__list > li:last-child { margin-bottom: 0; }

@keyframes letsTipIn {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

/* Animation is opacity/transform only -- the two properties that do not force
   a re-layout -- and it switches off entirely for anyone who asked for that. */
@media (prefers-reduced-motion: reduce) {
    .lets-tip__balloon { animation: none; }
}

/* ---- PHONE AND SMALL TABLET ----------------------------------------------
   Below this width a balloon hanging off the "?" is the wrong shape: the button
   sits partway across a narrow heading, so a box anchored to it either runs off
   the right edge or is squeezed into a thin column of two-word lines.

   Instead it becomes a full-width panel directly under the heading band.

   >>> DO NOT "IMPROVE" THIS TO position: fixed. IT WAS, AND IT WAS BROKEN. <<<

   `.lets-page` has `backdrop-filter` for the frosted-glass look, and an element
   with a filter or backdrop-filter becomes THE CONTAINING BLOCK FOR ITS
   FIXED-POSITION DESCENDANTS. So `position: fixed; left:10px; right:10px;
   bottom:10px` did not resolve against the viewport at all -- it resolved
   against the short heading band, which put the panel up near the top of the
   screen with its text running off the right edge. Measured in a real browser
   at 390px wide, not guessed.

   (This is the same CSS feature that hides the balloon behind the table if the
   z-index on `.lets-page` is removed. backdrop-filter does two surprising
   things at once: it creates a stacking context AND a fixed-position
   containing block.)

   The fix is to stay `absolute` and let `.lets-tip` go static, so the nearest
   positioned ancestor becomes `.lets-page` itself -- which is exactly the width
   the panel should be. The arrow is hidden because it no longer points at
   anything.
   -------------------------------------------------------------------------- */
@media (max-width: 700px) {
    /* Static, so the balloon below anchors to .lets-page and not to the "?". */
    .lets-tip { position: static; }

    .lets-tip__balloon {
        top: calc(100% + 8px);      /* below the whole heading band */
        /* Anchored to `.lets-page`, so the panel is exactly as wide as the
           heading band above it and lines up with the card below. No viewport
           arithmetic: the band is already the right width, and measuring
           against it means the panel cannot drift out of alignment with the
           rest of the page at any screen size. */
        left: 0;
        right: 0;
        width: auto;
        max-width: none;
        max-height: 65vh;
    }
    .lets-tip__balloon::before,
    .lets-tip__balloon::after { display: none; }
}


/* ==========================================================================
   >>> THE FORM STANDARD  --  every form in the application uses this <<<
   --------------------------------------------------------------------------
   USE `.lets-form` AND THE CLASSES BELOW. DO NOT USE BOOTSTRAP'S
   `form-horizontal` / `control-label` / `col-sm-*` GRID FOR FORM FIELDS.

   WHAT WAS WRONG
   --------------
   Forms were built two different ways, and two screens used BOTH AT ONCE --
   the left-hand card horizontal (label beside the field) and the right-hand
   card stacked (label above it), side by side on the same page. Beyond looking
   unfinished, `form-horizontal` with `col-sm-4` spends a third of the card on
   an empty gutter, so a short label like "Mobile" sat a long way from its own
   input and the eye had to travel to connect them.

   THE STANDARD: LABELS SIT ABOVE THEIR FIELD.
   ------------------------------------------
   Chosen over the horizontal layout deliberately:

     - No gutter, so no gap to cross between a label and its input.
     - The field gets the full width of its card, which matters for an email
       address or an address line.
     - A long label cannot squeeze the input, so nothing has to be reworded to
       fit a column.
     - It behaves identically at every screen width -- there is no col-sm-*
       to get wrong and no mobile override to remember. The old layout needed
       one, and that is where these forms drifted apart in the first place.

   THE MARKUP  --  or just use view('partials/field', [...]), which writes it
   for you:

       <div class="lets-form__group">
           <label class="lets-form__label" for="email">
               Email <span class="lets-form__req">*</span>
           </label>
           <input class="form-control" id="email" name="email">
           <p class="lets-form__help">Shown under the field.</p>
       </div>

   Two or three SHORT related fields side by side:

       <div class="lets-form__row">
           <div class="lets-form__group"> ... </div>
           <div class="lets-form__group"> ... </div>
       </div>

   THE BUTTONS GO IN ONE `.lets-form__actions` BAR AT THE END OF THE <form>,
   outside the cards. They used to live in one card's footer, which on a
   two-column form left Save hanging under the right-hand column as though it
   only applied to that half.
   ========================================================================== */

.lets-form__group { margin-bottom: 15px; }
.lets-form__group:last-child { margin-bottom: 0; }

.lets-form__label {
    display: block;
    margin-bottom: 5px;
    font-size: 12.5px;
    font-weight: 650;
    color: var(--lets-ink-2, #3d4852);
    line-height: 1.35;
}

.lets-form__req { color: var(--lets-red, #c0392b); font-weight: 700; }

.lets-form__help {
    margin: 5px 0 0;
    font-size: 12px;
    line-height: 1.55;
    color: var(--lets-muted, #6f7680);
}

/* ---------------------------------------------------------------------------
   A FIELD THE SERVER REFUSED.                                  (27 Aug 2026)
   ---------------------------------------------------------------------------
   Eight save screens bounce with "Please check the highlighted fields", and
   before this block there was no highlight to check -- no `is-invalid` rule
   existed anywhere in the theme. The red border is what the toast has been
   promising, and `lets-form__error` is the sentence naming the reason.

   The border colour goes on `.is-invalid` rather than on the group so it
   reaches the control itself, which is what the eye lands on, and so the
   Bootstrap class already in the markup keeps its usual meaning.
   --------------------------------------------------------------------------- */
.lets-form .form-control.is-invalid,
.form-control.is-invalid {
    border-color: var(--lets-red, #c0392b);
    background-color: var(--lets-red-wash, rgba(192, 57, 43, .04));
}

.lets-form .form-control.is-invalid:focus,
.form-control.is-invalid:focus {
    border-color: var(--lets-red, #c0392b);
    box-shadow: 0 0 0 3px rgba(192, 57, 43, .16);
    outline: none;
}

.lets-form__error {
    margin: 5px 0 0;
    font-size: 12px;
    font-weight: 600;
    line-height: 1.55;
    color: var(--lets-red, #c0392b);
}

/* The label of a refused field reads as refused too, so the pairing is
   obvious when the control itself is off-screen on a long form. */
.lets-form__group--invalid > .lets-form__label { color: var(--lets-red, #c0392b); }

/* Short fields side by side. flex-wrap means they stack by themselves on a
   narrow screen -- no breakpoint needed. */
.lets-form__row {
    display: flex;
    flex-wrap: wrap;
    gap: 0 12px;
}
.lets-form__row > .lets-form__group { flex: 1 1 150px; min-width: 0; }

/* A field that should not stretch the whole card -- a port, a sort order. */
.lets-form__group--narrow .form-control { max-width: 180px; }
.lets-form__group--mid .form-control    { max-width: 340px; }

/* A tickbox and its wording on one line, aligned on the box rather than the
   text, so a two-line label does not drag the box down with it. */
.lets-form__check {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 0 0 4px;
    font-weight: 400;
    cursor: pointer;
}
.lets-form__check input { margin: 3px 0 0; flex: 0 0 auto; cursor: pointer; }
.lets-form__check span  { font-size: 13px; }

/* A value that is shown but not editable -- "signs in through the internal
   door". Styled to read as information, not as a disabled input. */
.lets-form__static {
    margin: 0;
    padding: 7px 0;
    font-size: 13px;
    color: var(--lets-ink, #2c3238);
}

/* A labelled divider inside a card, for a second group of fields. */
.lets-form__sep {
    margin: 18px 0 14px;
    border: 0;
    border-top: 1px solid var(--lets-border, #e2e5e9);
}

/* --------------------------------------------------------------------------
   THE ACTION BAR -- one per form, at the end, spanning the whole width.
   -------------------------------------------------------------------------- */
.lets-form__actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 18px;
    padding: 11px 14px;
    background: var(--lets-surface, #fff);
    border: 1px solid var(--lets-border, #e2e5e9);
    border-radius: var(--lets-radius, 8px);
    box-shadow: var(--lets-shadow, 0 1px 2px rgba(31,56,77,.04));
}
/* Everything after this is pushed to the right -- so Cancel sits left and the
   primary action sits right without either needing a float. */
.lets-form__actions .lets-form__spacer { margin-left: auto; }

/* A quiet line inside the action bar, saying what this button does and does
   not cover. Hidden on narrow screens, where the bar needs the room. */
.lets-form__hint {
    font-size: 12px;
    color: var(--lets-muted, #7c8791);
    margin-left: 14px;
}
@media (max-width: 991px) { .lets-form__hint { display: none; } }

/* --------------------------------------------------------------------------
   A STICKY ACTION BAR

   For a form with cards BELOW its own save button -- Clients has Documents and
   Notes under it, because each of those posts separately and a <form> cannot
   be nested in another. Without this the Save button scrolls away while there
   is still page underneath, which reads as "the form ended here" and makes
   everything below look unrelated.

   `bottom: -1px` rather than 0 hides the hairline that otherwise shows under
   the bar as it sticks.
   -------------------------------------------------------------------------- */
.lets-form__actions--sticky {
    position: sticky;
    bottom: -1px;
    z-index: 40;              /* above .lets-page (30), below a dropdown (60) */
    box-shadow: 0 -4px 16px rgba(31,56,77,.10);
}

@media (max-width: 600px) {
    /* On a phone the primary action goes full width and comes FIRST, because
       it is the one being reached for. */
    .lets-form__actions { flex-wrap: wrap-reverse; }
    .lets-form__actions .btn { flex: 1 1 100%; }
}

/* A small label above a group of fields inside ONE card -- "Address", "Tax
   Information" -- lighter than a second .box-header, for when a card has more
   than one logical group but does not need a full second box. Used first on
   the Clients form (Basic Information has four such groups). */
.lets-form__group-title {
    margin: 0 0 10px;
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--lets-muted, #7c8791);
}

/* A card-within-a-card -- one contact, or one rate-card language pair, inside
   a box that holds several of them. Its own border rather than lets-form__sep
   because these repeat and need a visible boundary between one and the next,
   not just a divider under a heading. */
.lets-subcard {
    padding: 14px 16px 4px;
    margin-bottom: 14px;
    border: 1px solid var(--lets-border, #e2e5e9);
    border-radius: var(--lets-radius, 8px);
    background: var(--lets-surface-2, rgba(255,255,255,.5));
}
.lets-subcard:last-child { margin-bottom: 0; }

/* ==========================================================================
   THE MULTISELECT  --  a dropdown you can tick several things in
   --------------------------------------------------------------------------
   Behaviour and the reasoning for building rather than vendoring it are in
   public/assets/js/lets-multiselect.js. In short: no CDN is allowed here, a
   native <select multiple> needs ctrl-click and so gets used wrongly, and the
   tick-grid this replaced cost a fixed block of screen whether in use or not.
   ========================================================================== */
.lets-ms { position: relative; }

/* The native select stays in the DOM and still posts -- it is just not shown.
   `display:none` rather than visually-hidden: nothing needs to focus it. */
.lets-ms__native { display: none !important; }

.lets-ms__button {
    display: flex;
    align-items: center;
    gap: 5px;
    flex-wrap: wrap;
    width: 100%;
    min-height: 34px;
    padding: 5px 28px 5px 10px;
    text-align: left;
    background: var(--lets-surface, #fff);
    border: 1px solid var(--lets-border, #e2e5e9);
    border-radius: var(--lets-radius, 8px);
    font-size: 13px;
    line-height: 1.4;
    position: relative;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.lets-ms__button:hover { border-color: var(--lets-accent, #14a1a1); }
.lets-ms.is-open .lets-ms__button {
    border-color: var(--lets-accent, #14a1a1);
    box-shadow: 0 0 0 3px var(--lets-accent-soft, rgba(20,161,161,.14));
}

.lets-ms__ph { color: var(--lets-muted, #7c8791); }

/* One chosen item, shown on the closed control. */
.lets-ms__chip {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 12px;
    background: var(--lets-accent-soft, rgba(20,161,161,.12));
    color: var(--lets-accent-ink, #0f6f6f);
    border: 1px solid var(--lets-accent-soft, rgba(20,161,161,.2));
}
.lets-ms__more { font-size: 12px; color: var(--lets-muted, #7c8791); }

.lets-ms__caret {
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--lets-muted, #7c8791);
    font-size: 12px;
}

/* ---------------------------------------------------------------------------
   >>> THE PANEL IS CLIPPED UNLESS ITS ANCESTORS LET IT OUT. <<<

   AdminLTE ships `.box-body { max-height: 300px; overflow: auto }` for one of
   its own widgets, and it applies to EVERY box. An absolutely-positioned
   dropdown inside one gets cut off at the card's edge -- which is exactly what
   happened to the language picker: the list appeared, then stopped mid-option.

   The fix is `overflow: visible` on the containers a form's dropdowns sit in.
   It is scoped to `.lets-form` rather than applied to every .box-body, because
   AdminLTE's scrolling boxes elsewhere are meant to scroll.
--------------------------------------------------------------------------- */
/* `.lets-form .box-body` is the one that matters and works everywhere. The
   :has() rule below is a bonus for browsers that support it, catching a
   picker dropped into a box outside a form -- it is deliberately NOT the
   only defence, because :has() is recent and this must work regardless. */
.lets-form .box-body,
.lets-form__cols,
.lets-form__group {
    overflow: visible;
    max-height: none;
}

.box-body:has(.lets-ms) { overflow: visible; max-height: none; }

/* ---------------------------------------------------------------------------
   >>> AND THE CARD ITSELF HAS TO BE RAISED. <<<

   Letting the panel escape its card was only half the problem. Each `.box` is
   its own stacking context, and the cards paint in document order -- so the
   NEXT card down (Address) paints over a panel hanging out of the one above
   it, no matter what z-index the panel carries. The first fix made the
   dropdown longer and it was still cut off, which is what the second
   screenshot showed.

   So the card holding an OPEN picker is lifted above its siblings. It is tied
   to `.is-open`, which the widget already sets, rather than raising every card
   permanently: a page of cards that all outrank each other has no order left
   to reason about.
--------------------------------------------------------------------------- */
.box:has(.lets-ms.is-open) { position: relative; z-index: 70; }

/* The same, for browsers without :has(). lets-multiselect.js puts this class
   on the nearest .box when a panel opens -- belt and braces, because a picker
   that silently truncates its list is a bug people work around rather than
   report. */
.box.lets-box--dropdown-open { position: relative; z-index: 70; }

.lets-ms__panel {
    position: absolute;
    z-index: 60;          /* above .lets-page, which sits at 30 */
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    background: var(--lets-surface, #fff);
    border: 1px solid var(--lets-border, #e2e5e9);
    border-radius: var(--lets-radius, 8px);
    box-shadow: 0 8px 24px rgba(31,56,77,.14);
    overflow: hidden;
}

.lets-ms__head {
    padding: 8px;
    border-bottom: 1px solid var(--lets-border, #e2e5e9);
    background: var(--lets-surface-2, #fafbfc);
}
.lets-ms__search { margin-bottom: 6px; }
.lets-ms__actions { display: flex; gap: 12px; }
.lets-ms__link {
    background: none;
    border: 0;
    padding: 0;
    font-size: 12px;
    color: var(--lets-accent-ink, #0f6f6f);
    text-decoration: underline;
    cursor: pointer;
}

.lets-ms__list { max-height: 240px; overflow-y: auto; padding: 4px; }

.lets-ms__opt {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin: 0;
    padding: 5px 8px;
    border-radius: 6px;
    font-weight: 400;
    font-size: 13px;
    cursor: pointer;
}
.lets-ms__opt:hover { background: var(--lets-accent-soft, rgba(20,161,161,.10)); }
.lets-ms__opt input { margin: 0; flex: 0 0 auto; cursor: pointer; }

/* >>> [hidden] LOSES TO A CLASS SELECTOR. THIS LINE IS WHY IT WORKS. <<<
   `.lets-ms__opt { display: flex }` above is a class rule; the browser's own
   `[hidden] { display: none }` is an attribute rule of the SAME specificity
   that lost on source order. So hiding a row by setting `hidden` on it did
   nothing at all. The same trap is recorded in HANDOVER section 6m from the
   client form; it bit a second time here. */
.lets-ms__opt[hidden] { display: none; }

/* An option that is ALREADY USED: greyed and unclickable, but still on
   screen. The owner was explicit that removing it entirely is worse --
   "disappear making user confused", because a missing row is
   indistinguishable from a broken list. See refreshOptions() in
   lets-multiselect.js. */
.lets-ms__opt.is-disabled {
    opacity: .45;
    cursor: not-allowed;
    text-decoration: line-through;
}
.lets-ms__opt.is-disabled input { cursor: not-allowed; }

/* "per word" / "per hour" beside an activity -- quiet, because it is a fact
   ABOUT the activity, not something being chosen. */
.lets-tick__unit {
    font-style: normal;
    font-size: 11px;
    color: var(--lets-muted, #7c8791);
    margin-left: 4px;
}

/* --------------------------------------------------------------------------
   THE RATE-CARD GENERATOR PANEL

   Deliberately tinted and separated from the rows it produces: it is a TOOL
   that builds data, not data itself. Making it look like the rest of the form
   invited people to fill it in and press Save, expecting the values to be
   stored -- they are not, they only generate rows.
   -------------------------------------------------------------------------- */
.lets-rate-gen {
    margin-bottom: 18px;
    border: 1px solid var(--lets-accent-soft, rgba(20,161,161,.28));
    border-radius: var(--lets-radius, 8px);
    background: var(--lets-accent-soft, rgba(20,161,161,.06));
    /* >>> NO `overflow:hidden` HERE. <<<
       It was here to keep the tinted header inside the rounded corner, and it
       CLIPPED THE MULTISELECT PANEL -- the dropdown opened showing only its
       search box, with the option list cut off at the panel's edge. The header
       gets its own rounded top corners instead, below. Any absolutely
       positioned child of this block has the same problem. */
}
.lets-rate-gen__head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 14px;
    background: var(--lets-accent-soft, rgba(20,161,161,.12));
    border-bottom: 1px solid var(--lets-accent-soft, rgba(20,161,161,.22));
    /* Rounds itself rather than relying on the parent clipping it -- see the
       note on .lets-rate-gen about why the parent must not have overflow. */
    border-radius: calc(var(--lets-radius, 8px) - 1px) calc(var(--lets-radius, 8px) - 1px) 0 0;
}
.lets-rate-gen__head > i {
    color: var(--lets-accent-ink, #0f6f6f);
    margin-top: 2px;
}
.lets-rate-gen__head strong {
    display: block;
    font-size: 13px;
    color: var(--lets-accent-ink, #0f6f6f);
}
.lets-rate-gen__head span {
    font-size: 12px;
    color: var(--lets-muted, #7c8791);
}
.lets-rate-gen__body { padding: 14px; }

/* The action bar under the pickers.
   `margin-top` and a rule above it, because the button was sitting flush
   against the fields with no separation at all -- it is a separate step, not
   part of the last field. Centred, because it is the one thing on the row and
   the panel is full width. */
.lets-rate-gen__foot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--lets-accent-soft, rgba(20,161,161,.22));
    text-align: center;
}
.lets-rate-gen__foot .lets-form__help { margin: 0; max-width: 640px; }

/* --------------------------------------------------------------------------
   THE GENERATED RATE TABLE
   --------------------------------------------------------------------------
   The vendor rate table used to be the only one in the application with a look
   of its own -- .lets-rate-table, a flat list one row per priced activity. It
   is a matrix now and shares .lets-ratetable with the client and sub-project
   cards, so those rules are gone rather than left behind to rot. The generator
   panel above it (.lets-rate-gen) is still its own thing and stays.
   -------------------------------------------------------------------------- */
/* The currency symbol against a price box. Fixed width so a column of rates
   lines up whether the symbol is a narrow one or a wide one. */
.lets-rate-cur {
    min-width: 34px;
    text-align: center;
    font-weight: 600;
}

/* --------------------------------------------------------------------------
   A ROW OF FIELDS THAT ARE NOT ALL THE SAME HEIGHT

   Bootstrap 3's .row is float-based, so two columns of DIFFERENT height (one
   with help text under it, one without) do not clear each other -- the taller
   one's help text ends up sitting on top of the NEXT row's label. That is
   exactly what happened here: "Cannot be changed once saved" landed on top of
   "Client Email".

   Flex makes every column in a row as tall as the tallest, which is what a
   grid of form fields wants anyway. Scoped to `.lets-form` so no other
   float-based layout in the application is affected.
   -------------------------------------------------------------------------- */
.lets-form .row {
    display: flex;
    flex-wrap: wrap;
}
/* >>> `display:flex` above BEATS the browser's own [hidden] rule, because a
   class selector outranks it -- so a row marked `hidden` would still be
   shown. That is not hypothetical: it made the tax fields visible with
   Taxable unticked, which is the exact thing they were hidden for.
   Anything using `hidden` inside a form needs this line to exist. <<< */
.lets-form .row[hidden] { display: none; }
.lets-form .row > [class^="col-"],
.lets-form .row > [class*=" col-"] {
    display: flex;
    flex-direction: column;
}
/* The help line is pushed to the BOTTOM of its cell, so a long help text under
   one field cannot shove its own input out of line with the input beside it. */
.lets-form .row > [class^="col-"] > .lets-form__group,
.lets-form .row > [class*=" col-"] > .lets-form__group {
    display: flex;
    flex-direction: column;
    height: 100%;
}
.lets-form .row .lets-form__group > .lets-form__help {
    margin-top: auto;
    padding-top: 5px;
}

/* ==========================================================================
   THE ON / OFF SWITCH
   --------------------------------------------------------------------------
   A styled checkbox for a setting that is genuinely a STATE rather than a
   choice in a list -- "this client is active". It reads at a glance from
   across a desk, which a small tick does not.

   It IS a real <input type="checkbox">, only drawn differently: it posts the
   same way, it is reached by Tab, and Space toggles it, all for free. The
   input is not `display:none` -- that would take it out of the tab order --
   but drawn at zero opacity over the track, so the focus ring still lands
   somewhere sensible.

   Status colours do NOT follow the accent, deliberately, for the same reason
   the grid's do not (HANDOVER 6d): green means on, grey means off, in every
   theme. An orange "Active" under Ember reads as a warning.
   ========================================================================== */
.lets-switch {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    margin: 0;
    font-weight: 400;
    cursor: pointer;
    position: relative;
}
.lets-switch input {
    position: absolute;
    opacity: 0;
    width: 44px;
    height: 24px;
    margin: 0;
    cursor: pointer;
}
.lets-switch__track {
    flex: 0 0 auto;
    width: 44px;
    height: 24px;
    border-radius: 999px;
    background: #c6ccd2;
    position: relative;
    transition: background .16s ease;
}
.lets-switch__track::after {
    content: '';
    position: absolute;
    top: 3px;
    left: 3px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(31,56,77,.3);
    transition: transform .16s ease;
}
.lets-switch input:checked + .lets-switch__track { background: var(--lets-green, #1d7a45); }
.lets-switch input:checked + .lets-switch__track::after { transform: translateX(20px); }
.lets-switch input:focus-visible + .lets-switch__track {
    box-shadow: 0 0 0 3px var(--lets-accent-soft, rgba(20,161,161,.35));
}
/* The words beside it change with the state, so the switch is readable
   without relying on colour alone. */
.lets-switch__label { font-size: 13px; font-weight: 600; color: var(--lets-muted, #7c8791); }
.lets-switch input:checked ~ .lets-switch__label { color: var(--lets-green, #1d7a45); }

/* An input with a small button beside it -- the Order box and its unlock
   button. Not Bootstrap's .input-group, which glues the two together as one
   control; these are a field and a separate action ON that field. */
.lets-inline-field { display: flex; align-items: center; gap: 8px; }
.lets-inline-field .form-control { max-width: 140px; }
.lets-inline-field .btn { white-space: nowrap; }

/* --------------------------------------------------------------------------
   A CARD HEADER WITH A SUBTITLE

   `.box-title` alone gives a card a name; this gives it a name AND a line
   saying what the card is for, which the longer forms need -- Contacts and
   Rate Card both mean nothing from their title alone.
   -------------------------------------------------------------------------- */
.lets-card-head { display: flex; align-items: flex-start; gap: 11px; }
.lets-card-head__icon {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    background: var(--lets-accent-soft, rgba(20,161,161,.14));
    color: var(--lets-accent-ink, #0f6f6f);
    font-size: 14px;
}
.lets-card-head__text { min-width: 0; }
.lets-card-head__text h3 {
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--lets-ink, #2c3238);
}
.lets-card-head__text p {
    margin: 1px 0 0;
    font-size: 12px;
    color: var(--lets-muted, #7c8791);
}

/* ==========================================================================
   THE ONE-TIME PASSWORD REVEAL                                         (D48)
   --------------------------------------------------------------------------
   Deliberately loud. Only a hash is stored, so this really is the only time
   the password can be seen -- a quiet grey line would be scrolled past, and
   the only remedy afterwards is generating a new one and re-contacting the
   client. Monospace and large, because it gets read aloud and typed.
   ========================================================================== */
.lets-password-reveal {
    margin: 0 0 16px;
    padding: 14px 16px;
    border: 2px solid var(--lets-green, #1d7a45);
    border-radius: var(--lets-radius, 8px);
    background: #e6f5ec;
}
.lets-password-reveal__head {
    margin: 0 0 8px;
    font-size: 13px;
    font-weight: 700;
    color: #166036;
}
.lets-password-reveal__value {
    display: inline-block;
    padding: 8px 14px;
    background: #fff;
    border: 1px solid #c6e8d4;
    border-radius: var(--lets-radius-s, 6px);
    font-family: Consolas, "Courier New", monospace;
    font-size: 19px;
    font-weight: 700;
    letter-spacing: .06em;
    color: #14532d;
    user-select: all;          /* one click selects the whole thing to copy */
}
.lets-password-reveal__note {
    margin: 8px 0 0;
    font-size: 12px;
    color: #166036;
}

/* The two portal buttons and their explanation, on one row. */
.lets-portal-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
.lets-portal-actions .lets-form__help { flex: 1 1 260px; }

/* ==========================================================================
   NOTES  --  the running discussion on a client                        (D47)
   ========================================================================== */
.lets-notes { margin: 0; padding: 0; list-style: none; }
.lets-note {
    display: flex;
    gap: 11px;
    padding: 12px 0;
    border-bottom: 1px solid var(--lets-border, #e2e5e9);
}
.lets-note:last-child { border-bottom: 0; }

/* The author's initials, so a long thread is scannable by who wrote what. */
.lets-note__who {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    background: var(--lets-accent-soft, rgba(20,161,161,.14));
    color: var(--lets-accent-ink, #0f6f6f);
}
.lets-note__body { flex: 1 1 auto; min-width: 0; }
.lets-note__meta {
    font-size: 12px;
    color: var(--lets-muted, #7c8791);
    margin-bottom: 3px;
}
.lets-note__meta strong { color: var(--lets-ink, #2c3238); font-weight: 600; }
/* pre-wrap so line breaks somebody typed survive, WITHOUT the text being
   treated as markup -- it is printed escaped. */
.lets-note__text {
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    white-space: pre-wrap;
    word-wrap: break-word;
}
.lets-note__x { flex: 0 0 auto; }

/* ==========================================================================
   THE CSV IMPORT STRIP  --  on the rate card
   ========================================================================== */
.lets-csv {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 10px 12px;
    margin-bottom: 14px;
    border: 1px dashed var(--lets-border, #e2e5e9);
    border-radius: var(--lets-radius, 8px);
    background: var(--lets-surface-2, #fafbfc);
}
.lets-csv__label { font-size: 12px; font-weight: 600; color: var(--lets-ink, #2c3238); }
.lets-csv__spacer { margin-left: auto; }

/* The real file input, hidden behind the button that opens it.
   VISUALLY hidden rather than `display:none` -- a display:none input is
   removed from the accessibility tree and some browsers refuse to open a
   picker for it programmatically. This keeps it a real, reachable control
   that simply cannot be seen. */
.lets-csv__file {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* Where the import reports what it did, and which lines it could not read. */
.lets-csv-result {
    margin: 0 0 14px;
    padding: 10px 12px;
    border-radius: var(--lets-radius, 8px);
    font-size: 13px;
    border: 1px solid transparent;
}
.lets-csv-result--ok  { background: #e6f5ec; color: #166036; border-color: #c6e8d4; }
.lets-csv-result--bad { background: #fdeaea; color: #8f2525; border-color: #f5cfcf; }
.lets-csv-result ul { margin: 6px 0 0; padding-left: 18px; }
.lets-csv-result li { font-size: 12px; }

/* What the file referred to that the master lists do not have, and where to
   add it. Sits ABOVE the line-by-line errors and is styled to be read first:
   it is the actionable part, whereas twenty "line N" messages caused by one
   absent language are noise. (D48) */
.lets-csv-missing {
    margin: 8px 0 0;
    padding: 9px 11px;
    background: rgba(255,255,255,.65);
    border: 1px solid currentColor;
    border-radius: var(--lets-radius-s, 6px);
}
.lets-csv-missing__intro { margin: 0 0 6px; font-size: 12px; font-weight: 700; }
.lets-csv-missing__row   { margin: 0 0 4px; font-size: 12px; }
.lets-csv-missing__row:last-child { margin-bottom: 0; }
.lets-csv-missing__link {
    margin-left: 8px;
    white-space: nowrap;
    text-decoration: underline;
    color: inherit;
    font-weight: 600;
}

/* A contact sub-card gets a quiet title bar of its own so three of them in a
   row read as three things rather than one long form. */
.lets-subcard__title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 12px;
    padding-bottom: 8px;
    border-bottom: 1px dashed var(--lets-border, #e2e5e9);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--lets-accent-ink, #0f6f6f);
}
.lets-subcard__title .fa { opacity: .75; }
/* "optional" beside a contact that does not have to be filled in. */
.lets-subcard__opt {
    margin-left: auto;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    font-size: 11px;
    color: var(--lets-muted, #7c8791);
}


/* ==========================================================================
   THE ROLE PICKER  --  extra roles on the user form
   --------------------------------------------------------------------------
   A tick list rather than a multi-select. A native multi-select needs
   ctrl-click to add a second item, which is the single least discoverable
   control in a browser -- people reliably replace their selection instead of
   extending it, and never find out why.
   ========================================================================== */

.lets-rolepick {
    border: 1px solid var(--lets-border, #e2e5e9);
    border-radius: var(--lets-radius-s, 6px);
    max-height: 260px;
    overflow-y: auto;
    background: #fff;
}

.lets-rolepick__item {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 7px 11px;
    border-bottom: 1px solid var(--lets-border, #eef0f2);
    font-weight: 400;
    cursor: pointer;
}
.lets-rolepick__item:last-child { border-bottom: none; }
.lets-rolepick__item:hover      { background: var(--lets-head-bg, #f7f8fa); }
.lets-rolepick__item input      { margin: 0; cursor: pointer; }
.lets-rolepick__item span       { flex: 1 1 auto; font-size: 13px; }
.lets-rolepick__item code {
    font-size: 10.5px;
    color: var(--lets-muted, #6f7680);
    background: transparent;
}

/* The main role: ticked and locked, so the list shows what is about to be
   saved rather than an unticked box that silently comes back ticked. */
.lets-rolepick__item.is-primary {
    background: var(--lets-tint, #eef6f8);
    font-weight: 600;
}
.lets-rolepick__item.is-primary code::after {
    content: " · main";
    font-weight: 600;
}

/* ==========================================================================
   PROFILE DETAIL ROWS  --  My Profile's "Email / Role code / Login door" list
   --------------------------------------------------------------------------
   REPLACES AdminLTE's list-group-item + pull-right. That combination floats
   the value against an inline label, and a float does not share a baseline
   with inline text -- a plain string (the email) and a value wrapped in a
   `.label` badge (taller, its own line-height) landed at two different
   heights in otherwise identical rows. It also had no floor under a long
   value: nothing wrapped or truncated it, so a long email ran to the edge of
   the box.

   Flex fixes both at once: label and value share one cross-axis, so they sit
   on the same centre line regardless of the value's own height, and the value
   can wrap onto a second line (or truncate, see below) instead of colliding
   with the border.
   ========================================================================== */
.lets-profile { margin: 4px 0 0; padding: 0; list-style: none; }

.lets-profile__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 11px 4px;
    border-top: 1px solid var(--lets-border, #eef0f2);
    text-align: left;
}
.lets-profile__row:first-child { border-top: none; }

.lets-profile__label {
    flex: 0 0 auto;
    font-weight: 700;
    font-size: 13px;
    color: var(--lets-ink, #2c3238);
}

.lets-profile__value {
    flex: 1 1 auto;
    min-width: 0;              /* lets a flex child actually shrink/truncate */
    text-align: right;
    font-size: 13px;
    color: var(--lets-ink-2, #3d4852);
    /* A long value (an email) truncates with an ellipsis rather than wrapping
       under the label or hitting the card's border. The full value is still
       on screen -- in the `title` attribute the view sets -- so nothing is
       actually lost, only what is shown at a glance. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* A badge as the value should keep its own shape, not stretch or truncate. */
.lets-profile__value .label { white-space: nowrap; }

/* ==========================================================================
   CHANGE HISTORY  --  the "from -> to" cell
   --------------------------------------------------------------------------
   The old value reads as struck-through and faded, the new one as solid. That
   is the one thing somebody scanning this screen is actually looking for, so
   it is carried by weight and colour rather than by having to read the arrow.

   NOT themed: these are the same red/green meanings used for retired/active
   everywhere else. A "removed" value tinted coral under Ember and green under
   Jade would read as a different KIND of event depending on the colour scheme.
   ========================================================================== */
.lets-audit__old {
    color: var(--lets-muted, #6f7680);
    text-decoration: line-through;
    text-decoration-color: rgba(192, 57, 43, .5);
}
.lets-audit__new   { color: #1d7a45; font-weight: 600; }
.lets-audit__arrow { color: var(--lets-border, #c3cad1); margin: 0 2px; font-size: 11px; }
.lets-audit__summary { color: var(--lets-ink-2, #3d4852); font-style: italic; }

/* A second line under a grid cell -- used for the reason an account was
   switched off, which belongs beside the status and not behind a click. */
.lets-grid__sub {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    line-height: 1.35;
    color: var(--lets-muted, #6f7680);
    white-space: normal;
}


/* ==========================================================================
   THE ACCESS MATRIX  --  roles x permissions
   --------------------------------------------------------------------------
   A wide, dense grid. Three things make it readable rather than a wall of
   identical squares:

     - the module name column is STICKY, so scrolling right does not leave you
       looking at a row of ticks with no idea which module they belong to;
     - group headings break ~40 modules into named sections;
     - a cell where the permission does not apply shows a faint dot rather than
       nothing, so a gap reads as "not applicable" instead of "not loaded".
   ========================================================================== */

.lets-matrix { margin-bottom: 0; }

.lets-matrix th,
.lets-matrix td {
    vertical-align: middle !important;
    border-top: 1px solid var(--lets-border, #e2e5e9);
}

/* The module name: sticky, so it survives a horizontal scroll. */
.lets-matrix .lets-matrix__module {
    position: sticky;
    left: 0;
    z-index: 2;
    min-width: 230px;
    background: #fff;
    font-size: 13px;
    box-shadow: 1px 0 0 var(--lets-border, #e2e5e9);
}

.lets-matrix thead th {
    background: var(--lets-head-bg, #f7f8fa);
    font-size: 11.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .3px;
    text-align: center;
    white-space: nowrap;
    border-bottom: 2px solid var(--lets-border, #e2e5e9);
}
.lets-matrix thead th.lets-matrix__module { text-align: left; z-index: 3; }

.lets-matrix__perm { text-align: center; width: 78px; }

/* A group heading row -- Setup, Delivery, Money, Sales, Reports, Admin. */
.lets-matrix__group td {
    background: var(--lets-head-bg, #f7f8fa);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .6px;
    color: var(--lets-muted, #6f7680);
    padding-top: 9px !important;
    padding-bottom: 9px !important;
}
.lets-matrix__group td { position: sticky; left: 0; }

/* A bigger hit area than a bare checkbox: the whole cell is clickable. */
.lets-matrix__box {
    display: block;
    margin: 0;
    padding: 6px 0;
    cursor: pointer;
}
.lets-matrix__box input { cursor: pointer; margin: 0; }

/* "This permission does not apply here." A faint dot, not an empty cell --
   an empty cell looks like something failed to render. */
.lets-matrix__na {
    color: var(--lets-border, #d8dce1);
    user-select: none;
}

.lets-matrix__row-all { width: 46px; text-align: center; }
.lets-matrix__rowbtn {
    border: 1px solid var(--lets-border, #e2e5e9);
    background: #fff;
    border-radius: 4px;
    font-size: 10.5px;
    line-height: 1;
    padding: 4px 6px;
    color: var(--lets-muted, #6f7680);
    cursor: pointer;
}
.lets-matrix__rowbtn:hover {
    background: var(--lets-head-bg, #f7f8fa);
    color: var(--lets-ink, #2c3238);
}

/* Keep the sticky column readable on the hover highlight. */
.lets-matrix.table-hover > tbody > tr:hover > .lets-matrix__module { background: #f5f7f9; }


/* ==========================================================================
   RECORD HEALTH  --  how complete is this record?                     (D50)
   --------------------------------------------------------------------------
   The colour is deliberately NOT the theme accent: it is a measurement, and
   it has to mean the same thing under every skin. Red below half, amber
   approaching complete, green when done -- the same traffic-light idiom the
   grid's status pills use, and for the same reason.
   ========================================================================== */
/* Screen-reader-only text. Not `display:none` -- that removes it from the
   accessibility tree, which is the opposite of the point. */
.lets-visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* --------------------------------------------------------------------------
   THE HEALTH RING  --  a circle filled to the percentage, number inside

   Sits beside the record's NAME (passed to page-head as `badge`), not in a
   band of its own -- the owner's call (D52). The colour is deliberately NOT
   the theme accent: it is a measurement and must mean the same thing under
   every skin, so it uses the same red/amber/green traffic light as the grid's
   status pills.
   -------------------------------------------------------------------------- */
.lets-ring {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Large enough that the number inside is comfortably readable next to a
       21px heading -- at 44px it read as cramped. */
    width: 54px;
    height: 54px;
    flex: 0 0 auto;
    /* Sits AFTER the heading with real separation, and pushes whatever
       follows (the subtitle/code) clear of it. */
    margin: 0 14px 0 10px;
    cursor: default;
}
.lets-ring__svg {
    width: 100%;
    height: 100%;
    /* -90deg so the ring starts filling from 12 o'clock rather than 3. */
    transform: rotate(-90deg);
}
.lets-ring__track {
    fill: none;
    stroke: var(--lets-hairline, #edf2f0);
    stroke-width: 4;
}
.lets-ring__fill {
    fill: none;
    stroke-width: 4;
    stroke-linecap: round;
    /* The dash offset is what draws the arc -- see the maths note in
       partials/health.php. Transitioning it is what makes the ring sweep
       smoothly as the form is filled in. */
    transition: stroke-dashoffset .35s ease, stroke .35s ease;
}
.lets-ring--low  .lets-ring__fill { stroke: var(--lets-red, #e5484d); }
.lets-ring--mid  .lets-ring__fill { stroke: var(--lets-amber, #8a5d16); }
.lets-ring--high .lets-ring__fill { stroke: var(--lets-green, #1f8a45); }

.lets-ring__pct {
    position: absolute;
    font-size: 14px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: var(--lets-ink, #111b19);
}
.lets-ring__pct small { font-size: 9px; font-weight: 600; opacity: .75; }

.lets-ring--low  .lets-ring__pct { color: var(--lets-red, #e5484d); }
.lets-ring--mid  .lets-ring__pct { color: var(--lets-amber, #8a5d16); }
.lets-ring--high .lets-ring__pct { color: var(--lets-green, #1f8a45); }

@media (prefers-reduced-motion: reduce) {
    .lets-ring__fill { transition: none; }
}

/* --------------------------------------------------------------------------
   FILE INPUTS  --  the browser's raw "Choose file" is the one control that
   never matches anything around it, and it cannot be styled directly.

   The trick: hide the real input's BUTTON half with ::file-selector-button
   restyled, rather than replacing the whole control with a label -- that
   keeps the filename text the browser provides for free, and keeps the
   input reachable by keyboard and screen reader with no extra wiring.
   -------------------------------------------------------------------------- */
.lets-form input[type="file"],
input[type="file"].form-control {
    display: block;
    width: 100%;
    font-size: 13px;
    color: var(--lets-ink-2, #5c6863);
    padding: 0;
    border: 1px dashed var(--lets-border, #e1e7e5);
    border-radius: var(--lets-radius-s, 8px);
    background: var(--lets-sunken, #fbfcfc);
    line-height: 1.4;
    cursor: pointer;
    height: auto;
    box-shadow: none;
    /* Room for the filename to sit beside the button without touching the
       dashed edge. */
    padding-right: 10px;
}
.lets-form input[type="file"]:hover,
input[type="file"].form-control:hover {
    border-color: var(--lets-teal, #0e7490);
    background: var(--lets-tint, #e0f7fa);
}
.lets-form input[type="file"]:focus-visible,
input[type="file"].form-control:focus-visible {
    outline: none;
    border-color: var(--lets-teal, #0e7490);
    box-shadow: 0 0 0 3px rgba(14,116,144,.18);
}

/* The button half. Both spellings are needed: ::file-selector-button is the
   standard, ::-webkit-file-upload-button is what older WebKit still answers
   to, and a browser ignores the rule it does not know. */
.lets-form input[type="file"]::file-selector-button,
input[type="file"].form-control::file-selector-button {
    margin-right: 12px;
    padding: 8px 16px;
    border: 0;
    border-radius: var(--lets-radius-s, 8px) 0 0 var(--lets-radius-s, 8px);
    background: var(--lets-teal, #0e7490);
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background-color .15s ease;
}
.lets-form input[type="file"]::-webkit-file-upload-button,
input[type="file"].form-control::-webkit-file-upload-button {
    margin-right: 12px;
    padding: 8px 16px;
    border: 0;
    border-radius: var(--lets-radius-s, 8px) 0 0 var(--lets-radius-s, 8px);
    background: var(--lets-teal, #0e7490);
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}
.lets-form input[type="file"]:hover::file-selector-button,
input[type="file"].form-control:hover::file-selector-button {
    background: var(--lets-teal-deep, #0c6478);
}

/* ==========================================================================
   THE CLIENT VIEW PAGE  --  tabs on top, Notes always on the right    (D49)
   ========================================================================== */

/* Main column (tabs) + a fixed-width Notes rail. The rail is a SIBLING of the
   tab content, not inside it, so it survives every tab switch untouched --
   see the class comment on clients/view.php for why that matters. */
.lets-view-grid {
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: 18px;
    align-items: start;
}
@media (max-width: 991px) {
    /* Below the point a 320px rail meaningfully competes with the content,
       Notes drops under the tabs instead of squeezing them. */
    .lets-view-grid { grid-template-columns: 1fr; }
}

/* >>> NO RAIL, NO RESERVED COLUMN. <<<
   The Sub-Project screen keeps Notes as a TAB rather than a rail, so the
   320px second column had nothing in it -- which is the blank strip down the
   right the owner reported, and it squeezed seven rate columns into the
   remaining width for no reason. A page without a `.lets-notes-rail` gets
   the full width. `:has()` is supported everywhere this application runs;
   the modifier class below is the explicit fallback the markup also sets. */
.lets-view-grid:not(:has(.lets-notes-rail)),
.lets-view-grid--full {
    grid-template-columns: 1fr;
}

.lets-view-main { min-width: 0; }

/* ---- Tabs -------------------------------------------------------------- */
.lets-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--lets-border, #e1e7e5);
}
.lets-tabs__btn {
    appearance: none;
    border: 1px solid transparent;
    border-bottom: none;
    background: none;
    padding: 9px 16px;
    font-size: 13px;
    font-weight: 600;
    color: var(--lets-ink-2, #5c6863);
    border-radius: var(--lets-radius-s, 8px) var(--lets-radius-s, 8px) 0 0;
    cursor: pointer;
    /* Sits ON the border-bottom below it, so the active tab visually joins
       its panel instead of floating a pixel above the line. */
    margin-bottom: -1px;
}
.lets-tabs__btn:hover { background: var(--lets-hairline, #edf2f0); color: var(--lets-ink, #111b19); }
.lets-tabs__btn.is-active {
    background: var(--lets-surface, #fff);
    border-color: var(--lets-border, #e1e7e5);
    color: var(--lets-teal-deep, #0c6478);
}

.lets-tabpanel { display: none; }
.lets-tabpanel.is-active { display: block; }

/* ---- Milestone bar -------------------------------------------------------
   A horizontal "road" of steps across the top of the Sub-Project screen --
   the owner's own description: "a progress bar which shows like road
   milestones that you need to choose this now." Each milestone IS a tab
   button (clicking one switches panels exactly like .lets-tabs__btn does),
   so the road doubles as navigation, not just a status readout -- the owner
   was explicit that the person must be free to go backward and forward
   between them, not walk a locked sequence. A milestone reads DONE (filled,
   a check), CURRENT (the one open right now) or NOT YET (empty, dim) --
   there is no percentage number, deliberately: this is "what's next", not a
   completeness score like the client health ring.
---------------------------------------------------------------------------- */
.lets-milestones {
    display: flex;
    align-items: flex-start;
    margin: 0 0 20px;
    padding: 0;
    list-style: none;
    overflow-x: auto;
}
.lets-milestone {
    display: flex;
    align-items: center;
    flex: 1 1 0;
    min-width: 108px;
}
/* >>> THE MILESTONE BUTTON MUST NOT INHERIT .lets-tabs__btn's CHROME. <<<
   Each milestone carries BOTH classes so one click handler drives the tab
   panels and the road together -- but .lets-tabs__btn.is-active paints a
   white card with a border (right for a real tab strip, wrong here: it drew
   a white box behind whichever milestone was current, which is exactly what
   was reported). These four lines strip that chrome back off; the dot and
   label carry all the state instead. */
.lets-milestone__btn,
.lets-milestone__btn.is-active,
.lets-milestone__btn:hover {
    appearance: none;
    border: none;
    background: none;
    box-shadow: none;
    border-radius: 0;
    margin-bottom: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 4px 6px;
    cursor: pointer;
    width: 100%;
    color: var(--lets-ink-2, #5c6863);
}
.lets-milestone__dot {
    /* position+z-index lift the dot ABOVE the connecting line, which is drawn
       as a pseudo-element on the <li>. Without this the line paints straight
       across the middle of every dot. */
    position: relative;
    z-index: 1;
    width: 28px;
    height: 28px;
    flex: 0 0 28px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 700;
    background: var(--lets-surface, #fff);
    border: 2px solid var(--lets-border, #d8e2df);
    color: var(--lets-ink-2, #5c6863);
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.lets-milestone__label {
    position: relative;
    z-index: 1;
    font-size: 11.5px;
    font-weight: 600;
    text-align: center;
    line-height: 1.25;
    white-space: nowrap;
}
/* The connecting line, drawn on the <li> so it can span the gap BETWEEN two
   dots. It sits at the dots' vertical centre (top of the button's padding +
   half a dot) and stops short on both sides so it meets the dot's edge
   rather than running under it. The first milestone has no incoming line --
   there is nothing to connect from. */
.lets-milestone { position: relative; }
.lets-milestone:not(:first-child)::before {
    content: '';
    position: absolute;
    top: 18px;
    right: 50%;
    left: -50%;
    margin-right: 18px;
    margin-left: 18px;
    height: 2px;
    background: var(--lets-border, #d8e2df);
    z-index: 0;
}
/* ---------------------------------------------------------------------------
   THREE SHADES: BEHIND YOU, HERE, STILL TO DO                          (D92)
   ---------------------------------------------------------------------------
   The owner asked for the bar to read as progress at a glance:

     *"the old one which milstone we came out show in dark then current in
     little light and the one we need to reach in lightest color so that we
     can identify what we have already filled or came through and what we are
     missing"*

   So the depth of colour IS the meaning, darkest to lightest:

     DONE     --th-accent-deep   solid, dark      -- behind you, finished
     CURRENT  --th-accent        solid, lighter   -- where you are now
     TO DO    --th-tint          pale outline     -- not reached yet

   Two things changed besides the shades:

   1. They follow the PAGE THEME (--th-accent) instead of the fixed teal they
      used before. The owner asked for the milestones to be "in same theme";
      a teal bar on an orange page was the only element on the screen not
      taking part in the theme picker.
   2. CURRENT now WINS over DONE. Previously `.is-current` only added a ring,
      so a step that was both done and current looked exactly like every other
      done step and "where am I?" was unanswerable. The rules below are
      ordered so the current step always paints last.
   --------------------------------------------------------------------------- */

/* ---- 1. TO DO (the default) -- palest, so it reads as "not yet" ---- */
.lets-milestone__dot {
    background: var(--th-tint, #ffeee7);
    border-color: color-mix(in srgb, var(--th-accent, #f2683c) 28%, transparent);
    color: color-mix(in srgb, var(--th-accent-deep, #c8461f) 55%, transparent);
}
.lets-milestone__label {
    color: color-mix(in srgb, var(--lets-ink-2, #5c6863) 65%, transparent);
}

/* ---- 2. DONE -- darkest. The line INTO a done step is filled too, so the
         solid run shows exactly how far you have come. ---- */
.lets-milestone.is-done:not(:first-child)::before { background: var(--th-accent-deep, #c8461f); }

.lets-milestone.is-done .lets-milestone__dot {
    background: var(--th-accent-deep, #c8461f);
    border-color: var(--th-accent-deep, #c8461f);
    color: #fff;
}
.lets-milestone.is-done .lets-milestone__label {
    color: var(--lets-ink, #111b19);
}

/* ---- 3. CURRENT -- mid shade, and LAST so it beats .is-done above.
         The ring is what makes "you are here" unmistakable even between two
         neighbouring shades of the same colour. ---- */
.lets-milestone.is-current .lets-milestone__dot {
    background: var(--th-accent, #f2683c);
    border-color: var(--th-accent, #f2683c);
    color: #fff;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--th-accent, #f2683c) 25%, transparent);
}
.lets-milestone.is-current .lets-milestone__label {
    color: var(--th-accent-deep, #c8461f);
    font-weight: 700;
}

.lets-milestone__btn:hover .lets-milestone__dot {
    border-color: var(--th-accent, #f2683c);
}

/* ---- Rate tables: a whole rate card on ONE row --------------------------
   A rate card is seven prices plus a name, and Bootstrap's .input-group with
   an .input-group-addon costs ~40px of chrome per box before a single digit
   is drawn -- so "13.0000" was being cropped to "13.0" and the row wrapped
   or scrolled. The owner asked for a custom input so all of it fits in one
   row.

   .lets-money is that input: the currency symbol is a small absolutely-
   positioned prefix INSIDE the box rather than a separate addon cell, so the
   symbol costs 14px instead of a whole bordered segment, and the field keeps
   its full width for digits.

   The spinner arrows are removed as well -- on a number input they eat
   ~16px on the right, which on a 7-column row is another whole digit's worth
   per column, and nobody increments a per-word rate by clicking.
-------------------------------------------------------------------------- */
/* The frame. A rate card is a grid of money boxes and it needs an edge, or
   the right-hand columns bleed into the page when the card is wider than the
   box and has to scroll. The radius is deliberately small -- this is a
   spreadsheet, not a panel, and a soft corner on a dense grid reads as
   sloppy rather than friendly. overflow-x already clips, so the corners
   round without any extra work. */
.lets-tablewrap {
    overflow-x: auto;
    border: 1px solid #dde5e3;
    border-radius: 6px;
}

.lets-ratetable { margin-bottom: 0; }
.lets-ratetable > thead > tr > th {
    font-size: 10.5px;
    letter-spacing: .02em;
    text-transform: uppercase;
    white-space: nowrap;
    vertical-align: bottom;
    padding: 6px 4px;
}
.lets-ratetable > tbody > tr > td {
    padding: 5px 4px;
    vertical-align: middle;
}
.lets-ratetable__act { white-space: nowrap; width: 1px; }

/* ---------------------------------------------------------------------------
   A VALUE SITS UNDER ITS OWN HEADING                                   (D91)
   ---------------------------------------------------------------------------
   The owner reported rate and fuzzy boxes not lining up with the column
   headings above them. The cause was two different alignments fighting:
   the <th> took Bootstrap's default LEFT, while the number inside each box
   was pushed RIGHT (so decimal points line up down a column, which is worth
   keeping). Across a wide box that put the label over one edge and the value
   over the other, and the eye reads them as belonging to different columns.

   Both are CENTRED now -- heading and box alike -- so a column reads as one
   vertical strip. The digits stay right-aligned INSIDE the box, so a column
   of numbers still lines up on its decimal point.

   Only the numeric columns. Source / Target / Rate card stay left-aligned:
   they hold words, and centred text is harder to scan down a list.
   --------------------------------------------------------------------------- */
.lets-ratetable > thead > tr > th.lets-num,
.lets-fuzzytable > thead > tr > th.lets-num { text-align: center; }

.lets-ratetable > tbody > tr > td.lets-num,
.lets-fuzzytable > tbody > tr > td.lets-num { text-align: center; }

/* The box itself is centred in its cell; the digits inside stay right. */
.lets-num .lets-money,
.lets-num .lets-pct { margin-left: auto; margin-right: auto; }

.lets-money { position: relative; display: block; min-width: 84px; }
.lets-money__cur {
    position: absolute;
    left: 7px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 11px;
    line-height: 1;
    color: var(--lets-muted, #8fa19b);
    pointer-events: none;
}
.lets-money__input {
    width: 100%;
    height: 30px;
    padding: 3px 5px 3px 19px;
    font-size: 12.5px;
    text-align: right;
    border-radius: 6px;
}
/* No spinners: they steal a digit's width on every column. */
.lets-money__input::-webkit-outer-spin-button,
.lets-money__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.lets-money__input { -moz-appearance: textfield; appearance: textfield; }

/* The rate-card NAME column: wide enough to read, capped so it cannot push
   the seven price columns off the row. */
.lets-nameinput { min-width: 150px; font-size: 12px; height: 30px; padding: 3px 6px; }

/* ---- The rate card's filter row ----------------------------------------
   The client form's rate card cannot be a LETS.grid -- the grid holds one
   page of twenty and fetches the rest, and a row that is not in the form is
   a rate that does not get POSTED. So it borrows the grid's LOOK instead:
   a filter row under the headings, three dropdowns, same weight and colour.

   Filtering here HIDES rows rather than paging them, so nothing can fall out
   of the form -- see the note in clients/form.php.
------------------------------------------------------------------------- */
.lets-ratefilter > th { padding: 4px 4px 8px; vertical-align: top; }
.lets-ratefilter select {
    height: 28px;
    font-size: 12px;
    padding: 2px 4px;
    min-width: 110px;
}
.lets-ratefilter__count {
    margin: 8px 2px 0;
    font-size: 12px;
    color: var(--lets-muted, #7c8791);
}

/* The fuzzy percentage boxes, same reasoning at a smaller size -- ten of
   them on one row, values are 0-100 so three digits is the worst case. */
.lets-fuzzytable { margin-bottom: 0; }
.lets-fuzzytable > thead > tr > th {
    font-size: 10.5px;
    text-transform: uppercase;
    white-space: nowrap;
    padding: 6px 4px;
}
.lets-fuzzytable > tbody > tr > td { padding: 5px 4px; vertical-align: middle; }
.lets-fuzzytable__side { white-space: nowrap; min-width: 130px; }
/* Wide enough for "100.00" plus breathing room -- the owner reported the
   value being clipped at the old 56px. Ten of these plus the scheme name
   still fit a normal screen; the wrapper scrolls if not. */
.lets-pctinput {
    width: 100%;
    min-width: 74px;
    height: 32px;
    padding: 3px 8px;
    font-size: 13px;
    text-align: right;
    border-radius: 6px;
}
.lets-pctinput::-webkit-outer-spin-button,
.lets-pctinput::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.lets-pctinput { -moz-appearance: textfield; appearance: textfield; }

/* The % suffix, inside the box rather than as a separate addon segment --
   same reasoning as .lets-money's currency prefix. The input keeps room for
   "100.00" plus the sign. */
.lets-pct { position: relative; display: block; }
.lets-pct .lets-pctinput { padding-right: 20px; }
.lets-pct__sign {
    position: absolute;
    right: 7px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 11px;
    line-height: 1;
    color: var(--lets-muted, #8fa19b);
    pointer-events: none;
}

/* ---------------------------------------------------------------------------
   LANGUAGE-PAIR CHIPS  --  a long list of pairs inside a warning       (D90)
   ---------------------------------------------------------------------------
   Used by the "no linguist yet" callout on a Sub-Project's Vendors tab. One
   <li> per pair made a fifteen-pair project into a screenful of orange that
   buried the form underneath it; wrapped chips say the same thing in three
   lines. Inherits the callout's own text colour so it works on any tint.
   --------------------------------------------------------------------------- */
.lets-pairchips {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.lets-pairchip {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.6;
    white-space: nowrap;
    color: inherit;
    background: rgba(255, 255, 255, .28);
    border: 1px solid rgba(255, 255, 255, .45);
}

/* ---------------------------------------------------------------------------
   "WHY DID THIS PRICE CHANGE?"  --  the note beside a rate Save    (D92)
   ---------------------------------------------------------------------------
   Sits in the box footer, above the Save button, so it reads as part of
   committing the change rather than as another field in the table. Capped in
   width: it is one sentence, and a box stretching the full width of a
   seven-column rate card invites an essay.
   --------------------------------------------------------------------------- */
.lets-changenote { max-width: 640px; margin-bottom: 10px; }
.lets-changenote > label {
    display: block;
    font-size: 11.5px;
    font-weight: 600;
    margin-bottom: 3px;
    color: var(--lets-ink-2, #5c6863);
}
.lets-changenote > label .lets-hint { font-weight: 400; }
.lets-changenote input { height: 32px; font-size: 12.5px; }

/* A quiet secondary line inside a table cell (currency code, scheme name). */
.lets-hint { font-size: 11px; color: var(--lets-muted, #8fa19b); font-weight: 400; }

/* A row that exists only in the browser and has NOT been saved yet -- tinted
   so "what is committed" and "what I am about to commit" are never confused
   while filling in a long rate card. */
.lets-row--staged > td { background: color-mix(in srgb, var(--lets-teal, #0f8b9e) 6%, transparent); }

/* A superseded fuzzy version: still readable, visibly not in force. */
.lets-row--old > td { opacity: .6; }

/* The "you may leave now" bar at the foot of the sub-project page. */
.lets-done-bar { margin-top: 6px; }

/* ---- A field with a FIXED prefix in front of it -------------------------
   The sub-project code: <MASTER CODE>_ is shown but not editable, only the
   part after it is typed. Deliberately not an .input-group -- the prefix is
   part of the same box here, not a separate addon segment, so it reads as
   one value rather than two fields glued together. */
.lets-prefixed {
    display: flex;
    align-items: stretch;
    border: 1px solid var(--lets-border, #d8e2df);
    border-radius: 8px;
    background: var(--lets-surface, #fff);
    overflow: hidden;
}
.lets-prefixed__fix {
    display: flex;
    align-items: center;
    padding: 0 2px 0 10px;
    font-size: 13px;
    font-weight: 600;
    color: var(--lets-muted, #8fa19b);
    white-space: nowrap;
    user-select: none;
}
.lets-prefixed__input {
    border: 0;
    box-shadow: none;
    background: none;
    flex: 1 1 auto;
    min-width: 0;
    padding-left: 2px;
}
.lets-prefixed__input:focus { box-shadow: none; outline: none; }
.lets-prefixed:focus-within { border-color: var(--lets-teal, #0f8b9e); }

/* Inline text-button that reads as a link (the custom-fuzzy actions). */
.lets-link-sm {
    font-size: 11.5px;
    color: var(--lets-teal-deep, #0c6478);
    text-decoration: underline;
    cursor: pointer;
}
.lets-link-sm--plain {
    appearance: none;
    border: 0;
    background: none;
    padding: 0;
}

/* ---- Read-only facts (label above value, same rhythm as a real form) --- */
.lets-fact-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 14px 20px;
}
.lets-fact-grid--3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 700px) { .lets-fact-grid { grid-template-columns: 1fr; } }

.lets-fact__l {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--lets-muted, #8fa19b);
    margin-bottom: 3px;
}
.lets-fact__v { font-size: 13.5px; color: var(--lets-ink, #111b19); }
.lets-fact__v--empty { color: var(--lets-muted, #8fa19b); font-style: italic; }
.lets-fact__help { margin: 3px 0 0; font-size: 11.5px; color: var(--lets-muted, #8fa19b); }

/* ---- The Notes rail ------------------------------------------------------
   Sticky, so a long tab (the rate card, say) can be scrolled while the
   conversation stays in view -- the whole point of pulling Notes out of the
   tabs in the first place. */
.lets-notes-rail {
    background: var(--lets-surface, #fff);
    border: 1px solid var(--lets-border, #e1e7e5);
    border-radius: var(--lets-radius, 10px);
    box-shadow: var(--lets-shadow, 0 1px 2px rgba(9,30,26,.06));
    position: sticky;
    top: 18px;
    max-height: calc(100vh - 36px);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
@media (max-width: 991px) {
    /* A sticky panel that is no longer beside anything just traps scroll --
       drop the stickiness once it has moved under the tabs. */
    .lets-notes-rail { position: static; max-height: none; }
}
.lets-notes-rail__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 13px 16px;
    background: var(--lets-sunken, #fbfcfc);
    border-bottom: 1px solid var(--lets-hairline, #edf2f0);
    flex: 0 0 auto;
}
.lets-notes-rail__head h3 {
    margin: 0;
    font-size: 14px;
    font-weight: 700;
    display: flex;
    align-items: center;
    gap: 7px;
}
.lets-notes-rail__body {
    padding: 6px 16px 16px;
    overflow-y: auto;
    flex: 1 1 auto;
}

/* A note's rich body -- the tags sanitize_html() allows, styled to read like
   prose rather than like an untouched WYSIWYG dump. */
.lets-note__text--rich :is(h1, h2, h3) { margin: 8px 0 4px; font-size: 14px; }
.lets-note__text--rich p { margin: 0 0 6px; }
.lets-note__text--rich p:last-child { margin-bottom: 0; }
.lets-note__text--rich ul, .lets-note__text--rich ol { margin: 0 0 6px; padding-left: 20px; }
.lets-note__text--rich blockquote {
    margin: 6px 0;
    padding: 4px 10px;
    border-left: 3px solid var(--lets-border, #e1e7e5);
    color: var(--lets-ink-2, #5c6863);
}
.lets-note__text--rich pre, .lets-note__text--rich code {
    background: var(--lets-sunken, #fbfcfc);
    border-radius: 4px;
    font-family: ui-monospace, Consolas, monospace;
    font-size: 12px;
}
.lets-note__text--rich pre { padding: 8px 10px; overflow-x: auto; }
.lets-note__text--rich code { padding: 1px 4px; }
.lets-note__text--rich table {
    border-collapse: collapse;
    width: 100%;
    margin: 6px 0;
    font-size: 12px;
}
.lets-note__text--rich table td, .lets-note__text--rich table th {
    border: 1px solid var(--lets-border, #e1e7e5);
    padding: 4px 6px;
}
.lets-note__text--rich a { color: var(--lets-teal-deep, #0c6478); }

/* ---- The note composer modal -------------------------------------------
   Auto-grows with content rather than being manually resizable (the owner's
   choice): .lets-note-editor has no fixed height, Quill's own content area
   grows as text is typed, and the DIALOG has a max-height so a very long note
   scrolls internally instead of pushing the Save button off-screen. */
/* >>> THE MODAL SCROLLS, THE BACKDROP DOES NOT. <<<
   The shell is `fixed` and scrolls its own overflow, so a tall dialog can be
   scrolled to -- but the BACKDROP must stay pinned to the viewport or it
   scrolls away with the content and leaves the page half-covered. That was a
   real reported bug ("half blur and half white page"): the backdrop was
   `absolute` inside a scrolling parent, so it only ever painted the first
   screenful. `fixed` pins it to the viewport regardless of scroll position.
   The blur is what makes it read as an overlay rather than a grey wash. */
/* >>> THE BACKDROP IS THE SHELL ITSELF, NOT A CHILD OF IT. <<<
   Two bugs were reported here and they had the same root cause. The backdrop
   used to be a `position:absolute` child, so (a) it only painted the first
   screenful when the page was scrolled and (b) `backdrop-filter` on it
   sampled only what was directly behind that child's own box -- leaving the
   page "half blurred and half white".

   Putting the tint AND the blur on `.lets-modal` -- which is `fixed` and
   covers the whole viewport whatever the scroll position -- makes both
   correct by construction: there is exactly one full-viewport layer and it
   is the thing that blurs. The `__backdrop` child stays as the click-to-
   close target only, fully transparent. */
.lets-modal {
    position: fixed;
    inset: 0;
    z-index: 1200;
    overflow-y: auto;
    background: rgba(17,27,25,.5);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}
.lets-modal[hidden] { display: none; }
.lets-modal__backdrop {
    position: fixed;
    inset: 0;
    background: none;
}
.lets-modal__dialog {
    position: relative;
    margin: 6vh auto;
    width: min(980px, 94vw);
    max-height: 88vh;
    display: flex;
    flex-direction: column;
    background: var(--lets-surface, #fff);
    border-radius: var(--lets-radius, 10px);
    box-shadow: var(--lets-shadow-h, 0 10px 40px rgba(9,30,26,.3));
    overflow: hidden;
}
.lets-modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--lets-hairline, #edf2f0);
    flex: 0 0 auto;
}
.lets-modal__head h3 { margin: 0; font-size: 15px; font-weight: 700; }
.lets-modal__x {
    appearance: none;
    border: 0;
    background: none;
    font-size: 20px;
    line-height: 1;
    color: var(--lets-muted, #8fa19b);
    cursor: pointer;
    padding: 4px;
}
.lets-modal__x:hover { color: var(--lets-ink, #111b19); }
.lets-modal__body { padding: 16px 18px; overflow-y: auto; flex: 1 1 auto; }
.lets-modal__foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 18px;
    border-top: 1px solid var(--lets-hairline, #edf2f0);
    flex: 0 0 auto;
}
.lets-modal__fallback { display: none; }

body.lets-modal-open { overflow: hidden; }

/* ---- The styled confirm dialog (replaces window.confirm) ---------------- */
.lets-confirm__dialog { width: min(460px, 92vw); }
.lets-confirm__icon { color: #d98324; margin-right: 4px; }
.lets-confirm__text {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--lets-ink, #111b19);
    white-space: pre-line;   /* messages carry \n for a second paragraph */
}

/* The editor area: a floor so an empty editor is not a sliver, no ceiling so
   it genuinely grows -- the dialog's own max-height is what caps it. */
.lets-note-editor .ql-container { min-height: 140px; font-size: 13.5px; }
.lets-note-editor .ql-editor { min-height: 140px; }


/* ==========================================================================
   FIELDS SIDE BY SIDE

   Every field used to be full width and on its own line, which turned a form
   with twenty fields into a long scroll for no reason -- a postcode does not
   need 900 pixels.

   Wrap a run of fields in .lets-form__cols and give each one
   'col' => 'half' | 'third' | 'quarter' (see partials/field.php).

   flex-basis is what does the work, and flex-wrap means narrow screens stack
   them without a single media query. The min() keeps a field from being
   squeezed to nothing on a phone: below about 260px it takes the whole row.
   ========================================================================== */
.lets-form__cols {
    display: flex;
    flex-wrap: wrap;
    gap: 0 18px;
}

.lets-form__cols > .lets-form__col { min-width: 0; }

.lets-form__col--half    { flex: 1 1 min(100%, 260px); }
.lets-form__col--third   { flex: 1 1 min(100%, 220px); }
.lets-form__col--quarter { flex: 1 1 min(100%, 170px); }

/* >>> A FIELD SHOULD BE AS WIDE AS ITS DATA, NOT AS WIDE AS THE ROW. <<<
   `flex: 1 1` lets a column absorb all the leftover width, so a 32-character
   code or a date got a box wide enough for a paragraph -- the owner's
   "fields are too long according to there data limit". Capping the GROW
   width keeps a row of short fields short and leaves the surplus as space
   rather than stretching them. Long fields (name, email, notes) are not
   capped and still take what they need. */
.lets-form__col--quarter { max-width: 220px; }
.lets-form__col--third   { max-width: 300px; }

/* THE VENDOR TAX ROW SPREADS TO FILL THE ROW, rather than clustering left
   with the cap above's usual leftover space (the owner's request). It earns
   the exception the general rule argues against: how many of these boxes are
   ON SCREEN changes with the tax type chosen (two for PAN/TAN, up to four for
   Other), so a fixed cap would leave a different, uncontrolled gap each time
   instead of using the space that is actually there. */
.lets-tax-row > .lets-form__col {
    flex: 1 1 200px;
    max-width: none;
}

/* An unwrapped field still fills its card, which is right for a textarea or
   a long name -- but a lone date or number should not. */
.lets-form__group--narrow .form-control { max-width: 200px; }
.lets-form__group--mid    .form-control { max-width: 340px; }

/* Inside a column the input fills the column -- `width` caps a box within a
   FULL-width field, and applying both would leave a narrow box in a narrow
   column with a stripe of dead space beside it. */
.lets-form__col .form-control,
.lets-form__col select.form-control {
    max-width: 100%;
}


/* ==========================================================================
   ONBOARDING  --  is this vendor usable, and who is holding it up      (D59)

   The headline answers the only question that matters ("can I give this
   vendor work?") before the detail, so nobody has to infer it from three
   ticks. Amber rather than red when not ready: an onboarding in progress is
   normal, not an error.
   ========================================================================== */
.lets-ready {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 11px 14px;
    border-radius: 4px;
    font-size: 13.5px;
    line-height: 1.6;
    margin-bottom: 14px;
}

.lets-ready i { margin-top: 2px; font-size: 15px; }

.lets-ready--yes { background: #eaf6ef; color: #1c6b3f; border-left: 3px solid #2f9e5f; }
.lets-ready--no  { background: #fdf6e9; color: #7a4f0a; border-left: 3px solid #d98c1f; }

.lets-steps { list-style: none; margin: 0 0 14px; padding: 0; }

.lets-steps__item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 9px 0;
    border-bottom: 1px solid var(--lets-hairline, #edf2f0);
    font-size: 13px;
}

.lets-steps__item:last-child { border-bottom: none; }

.lets-steps__item > i {
    margin-top: 2px;
    font-size: 14px;
    color: var(--lets-muted, #b6c2c0);
}

.lets-steps__item.is-done > i { color: #2f9e5f; }

/* Who owes an outstanding step -- the actionable half. Without it a "not
   done" line tells you nothing you can act on. */
.lets-steps__owner {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 7px;
    border-radius: 3px;
    background: #fdf6e9;
    color: #7a4f0a;
    font-size: 11px;
    font-weight: 600;
}

.lets-steps__note {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--lets-muted, #6f7680);
}

/* A step that does not gate anything (the CV) reads quieter, so the three
   that DO block stand out as the ones to chase. */
.lets-steps__item.is-optional { opacity: .72; }

.lets-steps__optional {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 7px;
    border-radius: 3px;
    background: var(--lets-hairline, #edf2f0);
    color: var(--lets-muted, #6f7680);
    font-size: 11px;
    font-weight: 600;
}

/* ==========================================================================
   OVERDUE  --  visible, not enforced                                  (D76)
   ==========================================================================
   The owner asked for automatic deactivation on a missed agreement deadline
   to be skipped: nothing in this codebase ever flips a vendor's availability
   because a date passed. So an overdue step is drawn to catch the eye -- the
   opposite treatment from .is-optional's dimming, since this is the one line
   staff most need to notice on the page -- but it changes NOTHING about
   whether the vendor is offered work. That decision stays a human one. */
.lets-steps__item.is-overdue {
    background: #fff5f4;
    border-radius: 4px;
    margin: 0 -8px 2px;
    padding-left: 8px;
    padding-right: 8px;
}

.lets-steps__item.is-overdue > i { color: #c0392b; }

.lets-steps__overdue {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 7px;
    border-radius: 3px;
    background: #f8d7d3;
    color: #8a2f24;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .3px;
}

.lets-steps__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 6px;
}


/* ==========================================================================
   THE RATE NAME PREVIEW, AND THE "ALREADY EXISTS" WARNING

   The preview shows the name rows will actually get, before any are made --
   a real value rather than a hint, so it is set in the same monospace the
   name will be shown in everywhere else.

   The warning is amber rather than red: re-agreeing a rate is ordinary
   business, not a mistake. It says what saving will DO (retire the old ones)
   so the consequence is visible in advance -- red would imply it should be
   avoided, which is wrong.
   ========================================================================== */
.lets-rate-gen__preview {
    margin: 10px 0 0;
    font-size: 12.5px;
    color: var(--lets-muted, #6f7680);
}

.lets-rate-gen__preview code {
    font-size: 12.5px;
    padding: 2px 6px;
}

.lets-rate-warn {
    margin: 12px 0 0;
    padding: 11px 14px;
    border-radius: 4px;
    border-left: 3px solid #d98c1f;
    background: #fdf6e9;
    color: #7a4f0a;
    font-size: 13px;
    line-height: 1.6;
}

.lets-rate-warn code {
    background: rgba(217, 140, 31, .16);
    color: #7a4f0a;
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 12px;
}

.lets-rate-warn em { font-style: normal; opacity: .8; }


/* ==========================================================================
   A CONFIRMATION LINE  --  read-only, and deliberately not a tickbox   (D59)

   Shown inside the card it is about (Rate Card, Bank Details) and again in
   the Onboarding summary. It reads as a STATE with a reason, not a task to
   tick: each one records somebody agreeing to something, and the action that
   creates it lives on the vendor's page because it also sends an email or
   records a financial check.
   ========================================================================== */
.lets-confirm {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 9px 0;
    font-size: 13px;
}

.lets-confirm + .lets-confirm { border-top: 1px solid var(--lets-hairline, #edf2f0); }

.lets-confirm > i {
    margin-top: 2px;
    font-size: 14px;
    color: var(--lets-muted, #b6c2c0);
}

.lets-confirm.is-done > i { color: #2f9e5f; }

.lets-confirm__note {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--lets-muted, #6f7680);
}


/* ==========================================================================
   AN ON / OFF SWITCH

   For a state that is currently one way or the other -- a vendor being active
   or retired -- rather than a task to complete. A tickbox labelled "Active"
   reads as something left undone when it is unticked, which is wrong: being
   retired is a deliberate decision, not an omission.

   It is a real checkbox underneath, so it posts exactly as one and the server
   needs no special case.
   ========================================================================== */
.lets-switch {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    margin: 0;
    cursor: pointer;
    font-weight: 600;
    font-size: 13px;
}

/* Off-screen rather than display:none -- a hidden input is skipped by
   keyboard navigation, which would make this unusable without a mouse. */
.lets-switch input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}

.lets-switch__track {
    position: relative;
    width: 42px;
    height: 23px;
    flex: 0 0 42px;
    border-radius: 999px;
    background: #c7cfd4;
    transition: background .16s ease;
}

.lets-switch__knob {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 17px;
    height: 17px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.25);
    transition: transform .16s ease;
}

.lets-switch input:checked + .lets-switch__track .lets-switch__knob {
    transform: translateX(19px);
}

/* The ring goes on the visible track, since the input itself is off-screen. */
.lets-switch input:focus-visible + .lets-switch__track {
    outline: 2px solid var(--th-accent, #17726b);
    outline-offset: 2px;
}

.lets-switch__label { color: var(--lets-muted, #6f7680); }
.lets-switch.is-on .lets-switch__label { color: var(--lets-ink, #23313a); }


/* ==========================================================================
   THE UPLOAD ROW  --  what it is, the file, the button, on one line

   This was three stacked blocks with their own action bar, which made a
   two-field form look like a whole screen. align-items:end keeps the button
   sitting on the same baseline as the inputs rather than beside their labels.
   ========================================================================== */
.lets-upload-row {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: 12px;
}

.lets-upload-row .lets-form__group { margin-bottom: 0; }

.lets-upload-row__type { flex: 1 1 200px; min-width: 0; }
.lets-upload-row__file { flex: 2 1 280px; min-width: 0; }
.lets-upload-row__go   { flex: 0 0 auto; }


/* ==========================================================================
   THE NOTES RAIL SCROLLS ITSELF

   Every note added used to make the whole PAGE taller, so a vendor with
   twenty notes meant scrolling past all of them to reach anything else. The
   rail now keeps its own height and scrolls inside.

   max-height in vh rather than px: the point is "about a screenful", and that
   is a different number of pixels on a laptop and a large monitor.
   ========================================================================== */
.lets-notes-rail__body {
    max-height: 62vh;
    overflow-y: auto;
    /* Room for the scrollbar so it does not sit on top of the text. */
    padding-right: 4px;
}

/* A quiet scrollbar -- it is a rail beside the content, not the main event. */
.lets-notes-rail__body::-webkit-scrollbar { width: 8px; }
.lets-notes-rail__body::-webkit-scrollbar-thumb {
    background: var(--lets-hairline, #dfe6e4);
    border-radius: 4px;
}
.lets-notes-rail__body::-webkit-scrollbar-thumb:hover {
    background: var(--lets-muted, #b6c2c0);
}


/* ==========================================================================
   ONBOARDING TICKS ON ONE ROW

   Four checkboxes answering one question -- has this vendor finished
   onboarding -- so they read as a row. Without this they inherit the column
   sizing meant for text inputs and end up strung out across the card.
   ========================================================================== */
.lets-form__cols--checks > .lets-form__col { flex: 1 1 auto; }
.lets-form__cols--checks .lets-form__check { margin: 0; white-space: nowrap; }


/* ==========================================================================
   A CONTACT BEHIND A TICK

   The tick reads as a heading, so the indent under it says "these belong to
   that" without a border or a box.
   ========================================================================== */
.lets-contact__toggle { font-size: 14px; }
.lets-contact__toggle .lets-tick__unit { margin-left: 6px; }

.lets-contact__body {
    padding-left: 24px;
    margin-top: 10px;
    border-left: 2px solid var(--lets-hairline, #edf2f0);
}


/* ==========================================================================
   A CHOICE OF TWO OR THREE  --  radios that look like buttons

   For a question with a small, fixed set of answers where BOTH are worth
   seeing without opening anything, and where the answer changes what the rest
   of the form asks (Individual / Agency). A dropdown hides half the question.

   Still real radios underneath, so keyboard, screen readers and the form POST
   all behave normally -- only the appearance changes.
   ========================================================================== */
.lets-choice {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 8px;
}

.lets-choice__opt {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    padding: 8px 16px;
    border: 1px solid var(--lets-hairline, #dfe6e4);
    border-radius: 4px;
    background: #fff;
    font-weight: 600;
    font-size: 13px;
    cursor: pointer;
    transition: border-color .12s, background .12s, color .12s;
}

/* The radio itself is off-screen rather than display:none -- a hidden input
   is skipped by keyboard navigation, which would make this unusable without a
   mouse. :focus-within is what puts the ring back on the visible label. */
.lets-choice__opt input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}

.lets-choice__opt:hover { border-color: var(--lets-muted, #b6c2c0); }

.lets-choice__opt:focus-within {
    outline: 2px solid var(--th-accent, #17726b);
    outline-offset: 2px;
}


/* ==========================================================================
   THE RATE CARD, PIVOTED  --  one row per language pair

   A flat list of "one row per price" is how the data is EDITED, but it reads
   badly: the same language pair repeated six times down the page, with only
   the activity changing.

   On the view page it is pivoted instead -- one row per language pair, with
   an activity in each column, which is the shape the old system used and the
   shape people already read:

       English (US) > Hindi   IT   1.00   1.00   Not added   1.00 ...

   `Not added` is deliberately not 0.00: a price nobody agreed and a price of
   zero are different things, and on a card that money is paid from, showing
   the second when you mean the first is the dangerous direction.
   ========================================================================== */
.lets-rate-matrix {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.lets-rate-matrix th {
    text-align: left;
    font-size: 11px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--lets-muted, #6f7680);
    font-weight: 600;
    padding: 8px 10px;
    border-bottom: 2px solid var(--lets-hairline, #edf2f0);
    white-space: nowrap;
}

.lets-rate-matrix td {
    padding: 8px 10px;
    border-bottom: 1px solid var(--lets-hairline, #edf2f0);
    vertical-align: middle;
}

.lets-rate-matrix tr:last-child td { border-bottom: none; }

/* Prices line up under each other -- tabular-nums stops 1.11 being narrower
   than 0.00 and making a column look ragged. */
.lets-rate-matrix .lets-rate-matrix__num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.lets-rate-matrix__pair { font-weight: 600; white-space: nowrap; }

/* The currency, once per column heading instead of once per number. */
.lets-rate-matrix__cur {
    display: block;
    font-weight: 400;
    font-size: 10px;
    letter-spacing: 0;
    text-transform: none;
    opacity: .75;
}

.lets-rate-matrix__none {
    color: var(--lets-muted, #6f7680);
    font-style: italic;
    font-size: 12px;
}

/* The rate NAME sits under its language pair rather than in a column of its
   own -- it is an identifier, not a value to compare down the page. */
.lets-rate-matrix__name {
    display: block;
    font-weight: 400;
    font-size: 11.5px;
    color: var(--lets-muted, #6f7680);
    margin-top: 2px;
}

.lets-rate-matrix__primary {
    display: inline-block;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    padding: 1px 5px;
    border-radius: 3px;
    margin-left: 6px;
    vertical-align: 1px;
}


/* ##########################################################################
   THE ACCENT LAYER MUST BE THE LAST THING IN THIS FILE.

   Everything below re-colours components that were defined earlier with a
   fixed teal -- buttons, the page-head icon, the grid header, the pager.
   CSS takes the LAST matching rule of equal specificity, so if this block
   moves back up the file those base rules win again and every theme looks
   teal. That is exactly what happened the first time.

   The same reasoning applies across files: lets-theme.css is loaded AFTER
   lets-grid.css in layouts/app.php for this reason.
   ########################################################################## */

/* ==========================================================================
   EVERYTHING ACCENT-COLOURED FOLLOWS THE THEME
   --------------------------------------------------------------------------
   Buttons, links, badges, the page-head icon, the grid's sort arrows, funnels
   and pager. Before this they were hard-coded teal, so picking Clay left half
   the screen still blue.
   ========================================================================== */

a { color: var(--th-accent-deep); }
a:hover, a:focus { color: var(--th-accent); }

.btn-primary {
    background: linear-gradient(135deg, var(--th-accent), var(--th-accent-deep));
    border-color: transparent; color: #fff;
    box-shadow: 0 3px 10px -3px rgba(31,56,77,.45);
}
.btn-primary:hover, .btn-primary:focus {
    background: linear-gradient(135deg, var(--th-accent-deep), var(--th-accent-deep));
    border-color: transparent; color: #fff;
}
.btn-default {
    background: rgba(255,255,255,.75);
    border-color: rgba(0,0,0,.08);
}
.btn-default:hover, .btn-default:focus {
    background: var(--th-tint);
    border-color: var(--th-accent);
    color: var(--th-accent-deep);
}
.btn:focus, .btn:active:focus { box-shadow: 0 0 0 3px var(--th-tint); }

.label-primary { background: var(--th-tint); color: var(--th-accent-deep); }
.form-control:focus { border-color: var(--th-accent); box-shadow: 0 0 0 3px var(--th-tint); }
.callout, .lets-page { border-left-color: var(--th-accent); }

.lets-page__title .fa { background: linear-gradient(135deg, var(--th-accent), var(--th-accent-deep)); }
.lets-page__crumbs a  { color: var(--th-accent-deep); }

.lets-hub-card__icon { background: var(--th-tint); color: var(--th-accent-deep); }
.lets-hub-card:hover .lets-hub-card__icon {
    background: linear-gradient(135deg, var(--th-accent), var(--th-accent-deep)); color: #fff;
}
.lets-hub-card:hover .box { border-color: var(--th-accent); }

/* ---- The data grid ------------------------------------------------------- */
.lets-grid__table thead th        { background: var(--th-tint); }
.lets-grid__table thead th.is-sortable:hover { background: var(--th-bg-3); }
.lets-grid__table tbody tr:hover  { background: var(--th-tint); }
.lets-grid__sort.is-on            { color: var(--th-accent); }
.lets-grid__funnel:hover,
.lets-grid__funnel.is-on          { color: var(--th-accent); }
.lets-grid__funnel.has-value      { background: var(--th-accent); color: #fff; }
.lets-grid__filters input:focus,
.lets-grid__filters select:focus  { border-color: var(--th-accent); box-shadow: 0 0 0 3px var(--th-tint); }
.lets-grid__page:hover:not(:disabled) { border-color: var(--th-accent); }
.lets-grid__page.is-current {
    background: linear-gradient(135deg, var(--th-accent), var(--th-accent-deep));
    border-color: transparent; color: #fff;
}
.lets-grid code                   { background: var(--th-tint); color: var(--th-accent-deep); }
.lets-grid__ico                   { color: var(--th-accent); }
.lets-grid__ico:hover             { background: var(--th-tint); color: var(--th-accent-deep); }
.lets-grid__cogbtn:hover          { border-color: var(--th-accent); color: var(--th-accent); }
.lets-grid__cogitem:hover,
.lets-grid__cogaction:hover       { background: var(--th-tint); color: var(--th-accent-deep); }
.lets-grid__scroll                { scrollbar-color: var(--th-accent) transparent; }
.lets-grid__scroll::-webkit-scrollbar-thumb { background: var(--th-accent); }
/* NOT themed, deliberately: green means "active" whichever scheme is picked.
 Status is meaning, not decoration -- an orange "Active" reads as a warning. */
.lets-grid__pill--active          { background: #e2f6e6; color: #1f8a45; }
/* Same reasoning for amber: "still to finish" must read as a warning in every
   colour scheme, so this one is not themed either (D90). */
.lets-grid__pill--warn            { background: #fdf0d5; color: #9a6206; }
/* --- Avatar ---------------------------------------------------------------
   Initials in a circle, instead of shipping an avatar image nobody uploaded.
   CIAS used one stock avatar.png for everybody, which told you nothing.

   THEMED BY DEFAULT -- background: var(--th-accent), so the circle on My
   Profile (a white card) matches whichever of the five colour schemes is
   picked, the same as the icon on the page-head and every button.

   THIS USED TO BE TWO SEPARATE RULES. One (further down the file, so it won,
   equal specificity) hard-coded a translucent white fill meant for sitting on
   the already-coloured navbar. That is correct THERE, wrong everywhere else --
   it is why the avatar on My Profile rendered nearly invisible until something
   else (an inline style) painted it AdminLTE's stock blue instead of the theme.
   The on-colour contexts (.main-header, the user dropdown) still override back
   to translucent white below -- that part was always right. */
.lets-avatar {
    display: inline-block;
    width: 30px; height: 30px; line-height: 30px;
    margin-top: -3px; border-radius: 50%;
    background: var(--th-accent); color: #fff;
    font-size: 12px; font-weight: 700; text-align: center; vertical-align: middle;
}
.lets-avatar--lg {
    display: block; margin: 0 auto 10px;
    width: 88px; height: 88px; line-height: 88px; font-size: 30px;
    border: 3px solid rgba(255,255,255,.25);
}


/* ---- The wordmark -------------------------------------------------------
   These rules were lost when the old skin block was replaced. Restored here,
   in the accent layer, so the logo follows the chosen theme like everything
   else. The white text-shadow lifts it off the frosted panel -- without it a
   mid-tone accent on a pale translucent block is hard to read. */
.lets-logo-text {
    font-size: 21px; font-weight: 800; letter-spacing: 3px;
    color: var(--th-accent-deep);
    text-shadow: 0 1px 0 rgba(255,255,255,.65);
}
.lets-logo-mark {
    font-size: 17px; font-weight: 800; letter-spacing: 1px;
    color: var(--th-accent-deep);
}


/* ##########################################################################
   REFINEMENTS  --  after looking at it on screen
   ##########################################################################
   Four things that only showed up once it was running. Appended at the very
   end for the same reason as the accent layer: last rule of equal specificity
   wins, and every one of these overrides something defined above.
   ########################################################################## */

/* ---- 1. THE HEADER BAR: darker, and one continuous piece ------------------
   It was near-white, which left the top of the screen looking unfinished, and
   the pale logo block made a visible step where it met the navbar.

   Now the WHOLE header -- logo block and navbar together -- is one saturated
   gradient. The sidebar below stays light glass, so the division is a clean
   horizontal one rather than an L-shaped seam. */
.main-header .navbar,
.main-header .logo {
    background: linear-gradient(120deg, var(--th-accent) 0%,
                                        var(--th-accent-deep) 60%,
                                        var(--th-accent-deep) 100%);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border-bottom: none;
    box-shadow: 0 2px 14px rgba(31,56,77,.18);
}
.main-header .logo { box-shadow: none; }
.main-header .logo:hover { filter: brightness(1.06); }

/* Dark bar -- everything on it flips to white. */
.main-header .logo,
.lets-logo-text,
.lets-logo-mark { color: #fff; text-shadow: none; }
.main-header .navbar .nav > li > a,
.main-header .sidebar-toggle { color: rgba(255,255,255,.92); }
.main-header .navbar .nav > li > a:hover,
.main-header .sidebar-toggle:hover,
.main-header .navbar .nav > .open > a {
    background: rgba(255,255,255,.16) !important;
    color: #fff !important;
}
.main-header .lets-avatar { background: rgba(255,255,255,.25); color: #fff; }


/* ---- 2. THE USER DROPDOWN: it was a blank white box -----------------------
   AdminLTE colours `.user-header` from its own skin variables, which we no
   longer set -- so it rendered as an empty white panel roughly 100px tall with
   the name floating in it. */
.main-header .user-menu .dropdown-menu,
.main-header .notifications-menu .dropdown-menu {
    /* >>> WHY THIS WAS CLIPPED AT THE SCREEN EDGE. <<<
       `.wrapper` (AdminLTE, not ours) sets `overflow-x: hidden` so the page
       itself never scrolls sideways. Both dropdowns are `position: absolute`,
       right-aligned to a button that sits close to the header's own right
       edge -- so the moment the panel's fixed width (280px for the bell,
       ~300px here) pushed its LEFT edge past x=0 relative to the wrapper, the
       overflow rule sliced it off rather than letting it show. Narrower than
       ~340px wide the corner is gone entirely, which is exactly the "profile
       dropdown truncating from the corner" the owner is describing.
       Anchoring to the viewport's right edge instead of the button's own
       right edge keeps the panel fully inside the visible page no matter how
       close to the corner the toggle sits. */
    right: 8px !important;
    left: auto !important;
    max-width: calc(100vw - 16px);
}
.main-header .user-menu .dropdown-menu {
    padding: 0; border: none; border-radius: 12px; overflow: hidden;
    box-shadow: 0 12px 34px -10px rgba(31,56,77,.4);
    background: #fff;
}
.main-header .user-menu .dropdown-menu > .user-header {
    background: linear-gradient(135deg, var(--th-accent), var(--th-accent-deep));
    height: auto; padding: 18px 14px; text-align: center;
}
.main-header .user-menu .dropdown-menu > .user-header p {
    color: #fff; margin: 8px 0 0; font-size: 15px; line-height: 1.35;
}
.main-header .user-menu .dropdown-menu > .user-header p small {
    display: block; font-size: 12px; color: rgba(255,255,255,.8); margin-top: 2px;
}
.main-header .user-menu .dropdown-menu > .user-header .lets-avatar {
    background: rgba(255,255,255,.22);
    border: 2px solid rgba(255,255,255,.35);
    color: #fff;
}
.main-header .user-menu .dropdown-menu > .user-footer {
    background: #fff; padding: 12px; border-top: 1px solid #eef1f4;
}
.main-header .user-menu .dropdown-menu > .user-footer .btn-default {
    background: #f4f6f8; border-color: #e3e8ec; color: #4a5866;
}
.main-header .user-menu .dropdown-menu > .user-footer .btn-default:hover {
    background: var(--th-tint); border-color: var(--th-accent); color: var(--th-accent-deep);
}


/* ---- 3. THE SIDEBAR: glass, with something real behind it -----------------
   The image is the point. Frosting nothing looks like nothing, which is why
   the first attempt read as flat grey.

   Layer order in the shorthand is top-to-bottom:
     1. a translucent white wash  -- the "glass"
     2. a wash of the theme colour -- so the glass belongs to the scheme
     3. the pattern image          -- what you actually see through it        */
.main-sidebar,
.left-side {
    background-color: var(--th-bg-2);
    background-image:
        /* Thinner wash than before: at .62 the pattern underneath was invisible
           and the panel read as flat colour. Glass has to show what is behind
           it or it is just paint. */
        linear-gradient(180deg, rgba(255,255,255,.34), rgba(255,255,255,.14)),
        linear-gradient(160deg, var(--th-bg-1), var(--th-bg-3)),
        url("../images/sidebar-pattern.svg");
    background-size: cover, cover, cover;
    background-position: center, center, center;
    background-repeat: no-repeat, no-repeat, no-repeat;
    background-attachment: fixed, fixed, fixed;
    -webkit-backdrop-filter: blur(20px) saturate(190%);
    backdrop-filter: blur(20px) saturate(190%);
    box-shadow: inset -1px 0 0 rgba(255,255,255,.7), 2px 0 18px rgba(31,56,77,.07);
}


/* ---- 4. THE MENU: a SELECTION, not a button ------------------------------
   The active item was a solid gradient pill with a drop shadow -- which is
   exactly what the Save button looks like. Two things that behave completely
   differently should not look identical: one you press, the other just tells
   you where you are.

   A selection now reads as: a soft tint of the theme, a solid accent bar down
   the left edge, and accent-coloured text. No shadow, no gradient, nothing
   that invites a click.

   The gradient pill is KEPT for real buttons -- see .btn-primary. */
.sidebar-menu > li.active > a,
.sidebar-menu > li.menu-open > a {
    background: rgba(255,255,255,.85) !important;
    color: var(--th-accent-deep) !important;
    font-weight: 700;
    box-shadow: inset 3px 0 0 var(--th-accent);
    border-radius: 0 9px 9px 0;
}
.sidebar-menu > li.active > a > .fa,
.sidebar-menu > li.menu-open > a > .fa { color: var(--th-accent); }

/* Hover is lighter still, so the current page stays the strongest thing. */
.sidebar-menu > li:not(.active) > a:hover {
    background: rgba(255,255,255,.55) !important;
    box-shadow: inset 3px 0 0 rgba(0,0,0,.08);
    border-radius: 0 9px 9px 0;
}

/* Submenu items: the same idea, one step quieter. */
.sidebar-menu .treeview-menu > li.active > a {
    background: rgba(255,255,255,.9) !important;
    color: var(--th-accent-deep) !important;
    font-weight: 650;
    box-shadow: inset 2px 0 0 var(--th-accent);
}

/* ==========================================================================
   COLLAPSED SIDEBAR  --  the fly-out menus must be SOLID
   --------------------------------------------------------------------------
   THE BUG THIS FIXES
   ------------------
   Collapse the sidebar and it becomes a 50px strip of icons. Hovering an icon
   flies its label -- and any submenu -- out over the page. AdminLTE styles
   that fly-out with:

       background-color: inherit;

   ...which inherits from `.main-sidebar`. That used to be a solid dark colour,
   so it worked. Ours is translucent glass, so the fly-out inherited the
   transparency and the label appeared ON TOP OF the table behind it -- two
   sets of text overlapping, unreadable.

   Glass is right for a panel with a page behind it. It is wrong for something
   floating over content. These get a solid background, a border and a shadow,
   so they read as a menu sitting above the page.

   The selectors mirror AdminLTE's own, including the :not(), because it uses
   four classes and anything shorter loses the specificity contest.
   ========================================================================== */

/* The label that flies out beside an icon. */
.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > a > span:not(.pull-right) {
    background: #fff !important;
    color: var(--th-accent-deep) !important;
    font-weight: 700;
    border-radius: 0 9px 9px 0;
    box-shadow: 6px 6px 20px -6px rgba(31,56,77,.35);
    border: 1px solid rgba(0,0,0,.06);
    border-left: none;
    z-index: 1200;
}

/* The submenu panel that flies out under it. */
.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > .treeview-menu {
    background: #fff !important;
    border-radius: 0 9px 9px 9px;
    box-shadow: 6px 8px 22px -6px rgba(31,56,77,.35);
    border: 1px solid rgba(0,0,0,.06);
    padding: 5px 0;
    margin-left: 0;
    z-index: 1200;
}
.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > .treeview-menu > li > a {
    color: #4a5866; padding: 7px 16px; margin: 0; border-radius: 0;
}
.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > .treeview-menu > li > a:hover,
.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu > li:hover > .treeview-menu > li.active > a {
    background: var(--th-tint) !important;
    color: var(--th-accent-deep) !important;
}

/* The 50px strip itself: the pattern is pointless at that width and the
   fixed-position background makes it look like a random crop. Plain wash. */
.sidebar-mini.sidebar-collapse .main-sidebar,
.sidebar-mini.sidebar-collapse .left-side {
    background-image: linear-gradient(180deg, var(--th-bg-1), var(--th-bg-3));
}

/* The logo shrinks to "LE" -- keep it centred and legible on the dark header. */
.sidebar-mini.sidebar-collapse .main-header .logo > .logo-mini { color: #fff; }


/* ---- The logo: a POPOUT on hover, not a colour change --------------------
   It was going white -- a leftover rule from the earlier light-glass version
   was still setting a translucent white background, which on the dark header
   looked like the block had broken.

   A background change is the wrong idiom here anyway: the logo is a link home,
   not a button. It now lifts very slightly and brightens, which reads as
   "this is live" without repainting a block of the header. transform and
   filter only, so nothing re-lays-out. */
.main-header .logo {
    transition: transform .18s cubic-bezier(.2,.8,.2,1), filter .18s ease;
    transform-origin: center;
}
.main-header .logo:hover {
    background: linear-gradient(120deg, var(--th-accent) 0%,
                                        var(--th-accent-deep) 60%,
                                        var(--th-accent-deep) 100%) !important;
    filter: brightness(1.1) saturate(1.1);
    transform: scale(1.04);
}
.main-header .logo:hover .lets-logo-text,
.main-header .logo:hover .lets-logo-mark {
    text-shadow: 0 0 14px rgba(255,255,255,.55);
}
.main-header .logo:active { transform: scale(1.01); }


/* ---- Action icons: FUNCTIONAL colours, not the theme accent --------------
   These were all painted with --th-accent, so under Ember all three were the
   same orange and you had to read the shape to tell Delete from Edit.

   Like the status pills, these are MEANING rather than decoration: red has to
   mean "destructive" whichever scheme is picked. So they keep fixed colours
   and deliberately ignore the theme.

       edit    blue    the ordinary, safe action
       retire  amber   reversible, but a change of state
       delete  red     the one to be careful with
   -------------------------------------------------------------------------- */
.lets-grid__ico                { color: #2f6fd0 !important; }
.lets-grid__ico--warn          { color: #d68910 !important; }
.lets-grid__ico--danger        { color: #e5484d !important; }
.lets-grid__ico:hover          { background: #e8f0fc !important; color: #1c4f9e !important; }
.lets-grid__ico--warn:hover    { background: #fdf1dc !important; color: #a8690b !important; }
.lets-grid__ico--danger:hover  { background: #fdebec !important; color: #c1272d !important; }


/* ==========================================================================
   BREADCRUMB
   --------------------------------------------------------------------------
   "Master Data  ›  Countries", on its own line above the heading card.

   It used to sit beside the "Add" button, where navigation and an action
   competed for one corner and the trail lost -- it read as a half-finished
   button. On its own line it is unmistakably navigation.

   (Four positions were compared on screen. The two that floated above the
   footer covered table rows, which is a poor trade for something you read once
   on arrival.)
   ========================================================================== */

.lets-crumbs {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: 2px 0 10px 4px;
    font-size: 12.5px; color: var(--lets-muted);
}

.lets-crumbs__home {
    font-size: 11px;
    color: var(--th-accent);
    opacity: .85;
}

/* THE FILE INPUT'S BUTTON, in the chosen theme rather than a fixed teal.
   Defined in the base layer with the fallback colour; re-coloured here for
   the same reason every other button is -- see the note at the top of this
   accent layer. Both spellings again: ::file-selector-button is the standard,
   the -webkit- one is what older WebKit answers to. */
.lets-form input[type="file"]::file-selector-button,
input[type="file"].form-control::file-selector-button {
    background: var(--th-accent) !important;
}
.lets-form input[type="file"]::-webkit-file-upload-button,
input[type="file"].form-control::-webkit-file-upload-button {
    background: var(--th-accent) !important;
}
.lets-form input[type="file"]:hover::file-selector-button,
input[type="file"].form-control:hover::file-selector-button {
    background: var(--th-accent-deep) !important;
}
.lets-form input[type="file"]:hover,
input[type="file"].form-control:hover {
    border-color: var(--th-accent);
    background: var(--th-tint);
}
.lets-form input[type="file"]:focus-visible,
input[type="file"].form-control:focus-visible {
    border-color: var(--th-accent);
    box-shadow: 0 0 0 3px var(--th-tint);
}

.lets-crumbs a {
    color: var(--th-accent-deep);
    font-weight: 650;
    padding: 2px 7px; margin: -2px -1px;
    border-radius: 6px;
    transition: background .14s ease, color .14s ease;
}
.lets-crumbs a:hover,
.lets-crumbs a:focus {
    background: rgba(255,255,255,.75);
    color: var(--th-accent);
    text-decoration: none;
}

/* The separator is deliberately faint. It is punctuation, not content -- at
   full strength a row of chevrons reads as loudly as the words between them. */
.lets-crumbs .sep {
    color: #b3c2cc;
    font-size: 15px; line-height: 1;
}

/* Where you are. Heavier and darker than the links, so the end of the trail is
   obvious without having to compare colours. */
.lets-crumbs .here {
    color: var(--lets-ink-2);
    font-weight: 700;
}

@media (max-width: 767px) {
    .lets-crumbs { margin-left: 2px; font-size: 12px; gap: 6px; }
}

/* ==========================================================================
   VERSION BADGE  --  ACTUALLY pinned to the bottom of the sidebar
   --------------------------------------------------------------------------
   Answers one question at a glance, on every screen: which of the two
   applications on this machine am I looking at? The retired CI3 system and
   this one look alike in a browser, and confusing them wastes real time.

   Deliberately quiet. It is a signpost, not information you act on -- if it
   competes with the menu above it, it is wrong.

   >>> THE COMMENT ABOVE THIS ONE HAS SAID "pinned to the bottom" SINCE IT WAS
   WRITTEN, BUT NOTHING PINNED IT. <<< The badge was just the next thing in the
   document after the menu's closing </ul>, with a margin. On a short menu (a
   Coordinator sees far fewer items than an Admin) that left it sitting right
   under the last item, with the rest of the sidebar's height empty below it --
   its position depended on how many menu items happened to be visible, i.e.
   the opposite of "does not move".

   THE FIX: `<section class="sidebar">` -- the flex column below -- already
   spans the full height of `.main-sidebar`, which is `min-height: 100%` of the
   page (AdminLTE's own rule, vendor CSS, untouched). Turning it into a flex
   column and letting the menu grow (flex: 1 0 auto) means whatever height is
   LEFT after the menu, however much or little, is empty space AFTER the
   badge -- pinning the badge to the sidebar's actual bottom edge regardless of
   menu length, without touching AdminLTE's positioning system (no
   position:fixed, nothing that could fight the mobile off-canvas slide-out).
   ========================================================================== */
.main-sidebar > .sidebar {
    display: flex;
    flex-direction: column;
    /* >>> POSITIONING CONTEXT FOR `.sidebar-menu`, EVEN WHERE `.lets-sb-scroll`
       IS NOT USED. <<< The admin sidebar (header.php) wraps `.sidebar-menu` in
       `.lets-sb-scroll` (`position: relative`), so `.sidebar-menu`'s own
       `position: absolute; inset: 0` resolves against THAT wrapper and stays
       correctly contained. The portal sidebar (layouts/portal.php) never grew
       that wrapper -- `.sidebar-menu` sits directly inside this element -- and
       with nothing here establishing a positioning context, the browser skips
       past it straight to `.main-sidebar` (`position: fixed`). An absolutely
       positioned element's `inset` is measured from its container's PADDING
       edge, not its content edge, so `inset: 0` against `.main-sidebar`
       ignored that element's 42px `padding-top` entirely -- rendering the
       whole portal menu, "MY ACCOUNT" first, starting at the very top of the
       page, under the fixed header, instead of below it.
       `position: relative` here gives `.sidebar-menu` a positioning context
       of its own regardless of which sidebar it is, so both layouts contain
       it identically -- and it changes nothing for the admin sidebar, which
       already had one two levels down. */
    position: relative;
    /* NOT `min-height: 100%`. That was tried first and silently does nothing:
       a percentage height only resolves against an ancestor whose OWN `height`
       is specified. `.main-sidebar` (AdminLTE's vendor rule) sets
       `min-height: 100%`, never `height` -- min-height does not count for this
       purpose, so `.main-sidebar`'s computed height is `auto` as far as a
       percentage child is concerned, and 100% of `auto` is nothing. This is
       the standard CSS "percentage heights don't work" trap.

       A viewport unit sidesteps it -- it needs no ancestor's help. Subtracting
       the navbar height matches `.main-sidebar`'s own `padding-top`, which is
       exactly that height, so this fills the sidebar column down to the bottom
       of the browser window on load. Same technique as `.content-wrapper`'s
       `calc(100vh - navbar - footer)` a few rules above.

       >>> `height`, NOT `min-height`, HERE TOO -- SEE THE SCROLLER BELOW. <<<
       Originally this WAS min-height, which is exactly right when the menu is
       short (it lets the column grow with a tall page) but wrong the moment a
       role's menu has enough items to be taller than the viewport on its own:
       with only a minimum, the flex column kept growing past 100vh right along
       with the menu, and since `.main-sidebar` is `position: absolute`
       (AdminLTE's own rule, not fixed), the fix for a long PAGE became the
       cause of a long SIDEBAR -- the whole body scrolled to reach the bottom
       of the menu, taking the navbar with it. Capping this to an actual
       `height` gives `.sidebar-menu` below a fixed box to scroll WITHIN
       instead. */
    height: calc(100vh - var(--lets-navbar-h, 42px));
}
/* `.main-sidebar` itself -- the OUTER box, still on AdminLTE's own
   `min-height: 100%` (vendor CSS, untouched) -- is a SEPARATE leak from the
   one fixed above. It is `position: absolute`, and a positioned element that
   overflows its containing block still counts toward the PAGE's scrollable
   area even though it takes no part in normal flow -- so if this box ends up
   even a pixel taller than the viewport (a rounding step in the calc() above,
   a DPI scale factor, anything), the whole document gains a scrollbar, on top
   of `.sidebar-menu`'s own internal one just below it. Two scrollbars for one
   overflow, and the outer one is the one nobody meant to add: the sidebar
   already has its own.

   `overflow-y: hidden` makes that leak impossible outright, regardless of the
   cause -- `.sidebar-menu` inside is what actually needs to scroll; this box
   never should. Horizontal is left alone: the collapsed icon-only sidebar
   flies submenus out to the right past this box's own width on purpose (see
   COLLAPSED SIDEBAR below), and clipping X would cut them off.

   >>> `position: fixed`, NOT AdminLTE's OWN `absolute`. <<< A page with
   enough on it to be taller than the viewport itself (the letterhead
   composer, with its editor and signature panel, is the one that surfaced
   this) scrolls the whole document -- and an absolutely-positioned sidebar
   scrolls the document's normal flow right along with it, exactly as if it
   were part of the page rather than the app's frame around it: dragging the
   version badge out from under the last menu item, sometimes off the bottom
   of the screen entirely. `.content-wrapper` is meant to be the part that
   scrolls; the sidebar (and the header) are meant to stay put around it, the
   same as they visibly do on every page short enough that this never showed.
   `fixed` pins it to the VIEWPORT instead of the document, which is what
   "stays put while the page scrolls" actually requires -- AdminLTE's own
   `top: 0; left: 0` already position it correctly for this, unchanged.
   `min-height: 100%` also starts resolving against the viewport rather than
   the (possibly much taller) document once this is fixed, which is a second,
   free fix for the same underlying problem -- the sidebar no longer inherits
   a page's height at all. */
.main-sidebar {
    position: fixed;
    overflow-y: hidden;
}
/* ==========================================================================
   THE SIDEBAR'S OWN SCROLLBAR -- CUSTOM-DRAWN, NOT THE BROWSER'S
   --------------------------------------------------------------------------
   Three things were tried here first, in order, and each was disproved on a
   real Windows/Chrome machine rather than assumed:
     1. `overflow-y: scroll` (a classic, permanently-reserved gutter) --
        still faded out on hover.
     2. `scrollbar-color` / `scrollbar-width` (the standard styling API,
        supposedly reaching the Fluent renderer where `::-webkit-scrollbar-*`
        does not) -- also still faded out on hover.
     3. The Windows setting itself, "Automatically hide scroll bars in
        Windows" -- turned off, tested in Incognito to rule out a stale
        profile, still faded out on hover.

   That rules out every lever CSS or the OS exposes for controlling the
   BROWSER'S scrollbar: Windows' newer "Fluent" renderer hides the thumb the
   instant the pointer reaches it to grab it, and nothing above can stop
   that -- it is a rendering decision made below the level any of those
   settings reach.

   So this does not try to control the native scrollbar at all -- it hides it
   (`.sidebar-menu` below) and draws a plain, ordinary `<div>` on top instead
   (`.lets-sb-track` / `.lets-sb-thumb`, positioned in header.php). A `<div>`
   has no OS opinion about when it is allowed to be visible; it shows and
   drags exactly as told, driven by lets-sidebar-scroll.js. */
.lets-sb-scroll {
    position: relative;
    /* Everything the OLD `.sidebar-menu` rule below did for layout -- grow
       to fill the space the badge does not need, but no further than the
       fixed-height column above -- now belongs to the wrapper, since
       `.sidebar-menu` is no longer the flex item itself (see below). */
    flex: 1 1 auto;
    min-height: 0;
    /* A guaranteed gap before the badge. Without it, the scrollable menu's
       box and `.lets-version`'s box touch at the exact same pixel -- fine
       for the CLIP itself (whatever is scrolled past that line is genuinely
       not painted there, not just visually crowded, confirmed with a
       hit-test), but it means the LAST visible row sits flush against the
       badge with zero room, and on some DPI/window-height combinations that
       reads as the two touching or overlapping. A margin here is a property
       of this box, not of whatever is scrolled inside it, so the gap holds
       no matter what row happens to be at the bottom. */
    margin-bottom: 8px;
}
.sidebar-menu {
    /* Fills the wrapper exactly -- NOT `height: 100%`. A percentage height
       tries to resolve against `.lets-sb-scroll`'s height, but that height
       is itself only known once flex has finished distributing space to
       `.lets-sb-scroll` -- and `.lets-version` right after it also lays
       claim to that same free space (its `margin-top: auto`, pinning it to
       the bottom, see below). That circularity is exactly the kind of thing
       percentage heights are notorious for losing, and here it meant
       `overflow-y: scroll` had nothing definite to clip AGAINST, so the
       menu's last few items rendered past its box instead of being clipped
       -- straight into the badge sitting right after it. `position: absolute`
       with `inset: 0` has no such ambiguity: once `.lets-sb-scroll` (its
       `position: relative` parent) has a final size, this fills it exactly,
       full stop -- which is also why `.lets-sb-scroll` needed `min-height: 0`
       and nothing else in flow: with its only children taken out of flow
       (this, and the track/thumb below), it has no content of its own to
       size against, so flex-grow gives it cleanly whatever space is left
       over after the badge, with nothing fighting it. */
    position: absolute;
    inset: 0;
    /* STILL the thing that actually scrolls -- wheel and keyboard keep
       working exactly as before. Only its native, OS-drawn thumb is hidden
       (both lines below); `.lets-sb-thumb` replaces it visually.

       >>> `!important` ON BOTH -- THIS IS NOT DECORATIVE. <<< AdminLTE's own
       vendor CSS (AdminLTE.min.css, untouched) carries
           `.sidebar-menu:hover { overflow: visible; }`
       for its OWN collapsed-icon sidebar, so a fly-out submenu can escape a
       50px-wide strip on hover. `:hover` gives it higher specificity than
       the plain `.sidebar-menu` rule here, and hover state applies to
       `.sidebar-menu` itself the instant the pointer is over ANY descendant
       -- so without `!important`, that vendor rule silently won on every
       single hover, switching this element to non-scrollable and dropping
       `scrollTop` back to 0 with no 'scroll' event and no trace in the DOM
       to explain it. Found only by asking the CSS engine directly
       (CDP `CSS.getMatchedStylesForNode`) which rule was actually winning --
       everything about it (no JS involved, no visible style change,
       required hovering a descendant specifically) pointed away from a
       plain specificity fight until that. We are not in the collapsed mode
       that rule exists for; this sidebar should never lose its own
       scrollability to it. */
    overflow-y: scroll !important;
    overflow-x: hidden !important;
    scrollbar-width: none;               /* Firefox */
}
.sidebar-menu::-webkit-scrollbar { display: none; }   /* Chrome / Safari / Edge */

/* The track spans the full height of the wrapper; `pointer-events: none` so
   clicks in the gap either side of the thumb fall through to the menu
   underneath rather than landing on an invisible strip that does nothing. */
.lets-sb-track {
    position: absolute;
    top: 0; bottom: 0; right: 2px;
    width: 6px;
    pointer-events: none;
}
/* The thumb itself is the one interactive part -- `top` and `height` are set
   inline, in JS, from the menu's real `scrollTop`/`scrollHeight` (the same
   "position set in JS from real state" pattern the grid's cog panel already
   uses, not a new idiom).

   >>> `rgba(255,255,255,.45)` READ AS "REMOVED". <<< It was not -- checked
   with `elementFromPoint()` at its own coordinates, which returned the thumb
   itself, on top, exactly where JS put it. The background here is a WASH
   over a pattern image (see "THE SIDEBAR: glass" above), lightest near the
   top, and a translucent-white thumb at .45 all but vanished into it right
   where a scrollbar most needs to be seen. `.85` plus a border and a shadow
   is the same treatment `.sidebar-menu > li.active > a` already uses for the
   current-page pill a few rules up -- that one is legible against this same
   background in every screenshot taken of it, so it is the proof this level
   of contrast actually holds here, not a guess. */
.lets-sb-thumb {
    position: absolute;
    left: 0; right: 0;
    border-radius: 3px;
    background: rgba(255,255,255,.85);
    border: 1px solid rgba(31,56,77,.12);
    box-shadow: 0 1px 3px rgba(31,56,77,.25);
    pointer-events: auto;
    cursor: pointer;
    touch-action: none;   /* the drag itself handles vertical movement */
}
.lets-sb-thumb:hover,
.lets-sb-thumb.is-dragging {
    background: #fff;
    box-shadow: 0 1px 4px rgba(31,56,77,.35);
}
.lets-version {
    display: flex; align-items: center; gap: 7px;
    /* auto: WHATEVER space is left above this, after the menu, goes here --
       that is what "pinned to the bottom" actually means in a flex column.
       A fixed top margin (14px, as this used to read) could only ever place
       the badge immediately under the last menu item, never lower.

       BOTTOM: 4px, not 16px -- pulls the badge closer to the sidebar's own
       bottom edge, which puts it roughly level with the footer bar outside
       the sidebar (the footer is --lets-footer-h, 46px, tall; the old 16px
       gap left the badge sitting in the upper half of that band rather than
       matching it). */
    margin: auto 10px 4px;
    padding: 7px 10px;
    border-radius: 8px;
    /* >>> SOLID, NOT GLASS. <<< `.45` opacity was the same wash the rest of
       the sidebar uses, and at the sidebar's own bottom edge that let the
       last menu row (whatever happens to be scrolled to just above the
       badge -- Vendor Agreements, Projects, anything, it depends on how
       tall the window is) show through it, which read as the badge sitting
       ON TOP of a list item rather than after it. This one badge does not
       need to look like glass; it needs a clean, unambiguous edge. */
    background: rgba(255,255,255,.94);
    border: 1px solid rgba(255,255,255,.9);
    box-shadow: 0 1px 4px rgba(31,56,77,.15);
    font-size: 11px;
    flex: 0 0 auto;
}
/* A live green dot -- the same idiom as the "Active" pill in the tables, and
   for the same reason: green means running. */
.lets-version__dot {
    width: 7px; height: 7px; flex: 0 0 7px;
    border-radius: 50%;
    background: #2eb872;
    box-shadow: 0 0 0 3px rgba(46,184,114,.18);
}
.lets-version__num  { font-weight: 800; color: var(--th-accent-deep); letter-spacing: .3px; }
.lets-version__name { margin-left: auto; font-weight: 700; color: #8fa1b3; letter-spacing: 1px; }

/* Collapsed sidebar is 50px wide -- only the dot fits. */
.sidebar-mini.sidebar-collapse .lets-version {
    /* margin-top stays `auto` -- the same bottom-pin as the expanded sidebar,
       just narrower. A flat `margin: 12px 8px` here (which this used to be)
       overwrites ALL FOUR sides including top, quietly undoing the pin the
       moment the sidebar collapses to icons-only. */
    margin: auto 8px 4px; padding: 7px 0; justify-content: center;
}
.sidebar-mini.sidebar-collapse .lets-version__num,
.sidebar-mini.sidebar-collapse .lets-version__name { display: none; }

/* The Primary badge on a pivoted rate row. In the accent layer because it
   takes the theme's accent -- it marks the tier the pricing engine uses when
   nothing else says otherwise, which is a "this one is live" signal rather
   than a warning. */
.lets-rate-matrix__primary {
    background: var(--th-accent-soft, #e8f3f1);
    color: var(--th-accent-deep, #17726b);
}

/* The chosen option in a .lets-choice pair. In the accent layer because it
   takes the theme's accent -- it is the answer the form is currently acting
   on, which is a live state rather than a decoration. */
.lets-choice__opt.is-on {
    border-color: var(--th-accent, #17726b);
    background: var(--th-accent-soft, #e8f3f1);
    color: var(--th-accent-deep, #17726b);
}

/* The switch when it is ON. In the accent layer because it takes the theme's
   accent -- a vendor being active is a live state, not a warning. */
.lets-switch input:checked + .lets-switch__track {
    background: var(--th-accent, #17726b);
}

/* ==========================================================================
   VENDOR AGREEMENTS  --  reading a legal document on screen          (D64)
   ==========================================================================
   Narrower measure and looser leading than the rest of the application. This
   is continuous prose that somebody has to READ, clause by clause, before
   agreeing to it -- not a screen of facts to scan. The same text set at the
   application's normal density is genuinely harder to read carefully, which
   is the one thing it must not be.

   The signature block is targeted by STRUCTURE (a table inside .lets-agreement)
   rather than by class: sanitize_html() strips class attributes from anything
   saved through the editor, so a class-based rule would work on the seeded
   text and silently stop working the first time somebody edits it. */
.lets-agreement {
    /* CENTRED, not left-aligned in a wide box. A capped measure keeps
       justified legal prose readable; centring it means the leftover width
       sits evenly on both sides and reads as a page rather than as a gap
       down one edge. */
    max-width: 46em;
    margin: 0 auto;
    line-height: 1.7;
}

/* An unfilled placeholder on a BLANK copy: something to write on, never an
   invisible gap. The same rule is drawn as literal underscores in the Word
   export, because Word ignores the CSS that draws it here. */
.lets-agreement__blank {
    display: inline-block;
    min-width: 160px;
    border-bottom: 1px solid #555;
}

.lets-agreement h2 {
    font-size: 20px;
    margin: 0 0 18px;
    padding-bottom: 8px;
    border-bottom: 2px solid #d2d6de;
}

.lets-agreement h3 {
    font-size: 15px;
    font-weight: 700;
    margin: 22px 0 6px;
}

.lets-agreement p {
    margin: 0 0 10px;
    text-align: justify;
}

/* The signature block: two columns, side by side, with room to write. */
.lets-agreement table {
    width: 100%;
    margin-top: 28px;
    border-collapse: collapse;
}

.lets-agreement table td {
    width: 50%;
    vertical-align: top;
    padding: 14px 14px 40px;
    border: 1px solid #d2d6de;
}

.lets-agreement table td p {
    text-align: left;
    margin: 0 0 6px;
}

/* The on-screen letterhead (D66) -- logo left, company details right, so the
   preview looks like the PDF that downloads. Flex here rather than the table
   the PDF uses: mPDF's float support is partial, a browser's is not. */
.lets-agreement__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 10px;
    margin-bottom: 20px;
    border-bottom: 2px solid #222;
}

.lets-agreement__logo img {
    max-height: 58px;
    max-width: 220px;
}

.lets-agreement__meta {
    text-align: right;
    margin-left: auto;
}

.lets-agreement__meta strong {
    font-size: 15px;
    letter-spacing: 0.5px;
}

.lets-agreement__meta span {
    font-size: 11px;
    color: #666;
}

/* ==========================================================================
   THE PORTALS  --  vendor and client                                  (D69)
   ==========================================================================
   The avatar is the SAME component the staff header uses (.lets-avatar):
   initials drawn with type on the accent colour, translucent white when it
   sits on the header bar. Shipping a second avatar treatment for the portals
   would mean the application looked like two products. */

/* In the sidebar user panel the avatar replaces AdminLTE's <img>, so it needs
   the size that markup assumed. Translucent like the header copy -- it sits on
   the same dark sidebar ground. */
.lets-avatar--panel {
    width: 40px;
    height: 40px;
    line-height: 40px;
    font-size: 15px;
    margin-top: 0;
    background: rgba(255, 255, 255, .25);
    color: #fff;
}

.user-panel > .image > a { display: inline-block; }
.user-panel > .info > p > a { color: #fff; }
.user-panel > .info > p > a:hover { color: #fff; text-decoration: underline; }

/* The few facts in the top-right dropdown. A thin list between the header and
   the buttons -- enough to confirm "am I signed in as the right account?"
   without becoming a second profile screen. */
.lets-portal-facts {
    padding: 10px 14px;
    background: #f9f9f9;
    border-bottom: 1px solid #eee;
}

.lets-portal-facts > div {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 3px 0;
    font-size: 12px;
}

.lets-portal-facts span { color: #888; }

.lets-portal-facts strong {
    font-weight: 600;
    text-align: right;
    word-break: break-all;
}

/* ==========================================================================
   THE PORTAL PROFILE'S SCROLLING RIGHT COLUMN                        (D72)
   ==========================================================================
   The owner reported a scrollbar on the whole page once documents and
   agreements were added below the account-action boxes -- the right column
   grew past the avatar card's height, so the PAGE scrolled and the left card
   (sitting beside the header) looked detached from what was being read.

   Capped to a fraction of the viewport height and scrolling INSIDE itself
   instead: the page stays put, only this column moves. The number is a
   viewport fraction rather than "100vh minus the header/footer in px"
   because portal.php's body is not `.fixed` -- the header and footer flow
   with the page rather than being pinned to fixed pixel heights, so a precise
   px subtraction would assume a layout mode this page does not use. */
.lets-portal-scroll {
    max-height: 82vh;
    overflow-y: auto;
    /* Room for the scrollbar itself so it does not sit flush against a box's
       border and does not shift box width when it appears. */
    padding-right: 6px;
}

@media (max-width: 767px) {
    /* On a phone the sidebar collapses and the column is the whole page
       already -- capping its height here would add a SECOND scrollbar
       inside the one the browser already gives a narrow screen. */
    .lets-portal-scroll {
        max-height: none;
        overflow-y: visible;
        padding-right: 0;
    }
}

/* ==========================================================================
   THE NOTIFICATION ROW  --  flex, not a floated date                 (D73)
   ==========================================================================
   Replaces an inline icon + inline title + `pull-right` date with an actual
   flex row. The float had no wrap boundary of its own: on a phone the date
   could clip against the fixed-width icon instead of dropping to its own
   line, and on desktop a long title next to a long date string had no
   reserved width on either side, so the date's box could end up narrower
   than the text it held. */
.lets-notice__row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    flex-wrap: wrap;
}

.lets-notice__icon {
    flex: 0 0 28px;
    width: 28px; height: 28px; line-height: 28px;
    border-radius: 50%;
    text-align: center;
    color: #fff;
}

.lets-notice__main {
    /* THE min-width: 0 IS WHAT LETS LONG TEXT WRAP INSTEAD OF OVERFLOWING.
       A flex child defaults to a min-width equal to its content's longest
       unbreakable run, which for a title-length string is wider than the
       row itself -- that default is what pushed the date out of the box
       rather than letting the title wrap under it. */
    flex: 1 1 200px;
    min-width: 0;
}

.lets-notice__date {
    flex: 0 0 auto;
    font-size: 12px;
    white-space: nowrap;
}

.lets-notice__body {
    margin: 4px 0 0 38px;
}

/* Narrow screens: the date drops under the title rather than fighting it for
   space on one line, and the left margin under the body matches the icon
   width dropping out of the row. */
@media (max-width: 480px) {
    .lets-notice__date { flex-basis: 100%; margin-left: 38px; white-space: normal; }
    .lets-notice__body { margin-left: 38px; }
}

/* ===========================================================================
   TASKS  (M7)
   ---------------------------------------------------------------------------
   Everything here uses the tokens in :root -- never a raw hex, per the rules
   in HANDOVER section 6. Three screens share these: the Add Task grid, the
   Tasks Drive, and the six-tab line modal.
   =========================================================================== */

/* ---- THE REASON PROMPT --------------------------------------------------
   LETS.prompt reuses the confirm dialog shell, so it needs only the two
   pieces confirm does not have: the textarea and its error line. */
.lets-prompt__input {
    margin-top: 10px;
    resize: vertical;
    min-height: 76px;
}

.lets-prompt__error {
    margin: 8px 0 0;
    font-size: 13px;
}

/* ---- LETS.choose -- the two-real-answers dialog --------------------------
   The hints are the whole point of this dialog: the two buttons are both
   decisions, and what separates them is what happens AFTERWARDS. Spelling
   that out under the question is cheaper than the PM finding out later which
   one they picked. */
.lets-choose__hints {
    margin: 12px 0 0;
    padding: 0;
    list-style: none;
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--lets-ink-2);
}

.lets-choose__hints li {
    padding: 7px 10px;
    border-left: 3px solid var(--lets-border);
    background: var(--lets-sunken);
    border-radius: 0 4px 4px 0;
}

.lets-choose__hints li + li { margin-top: 6px; }

.lets-choose .lets-modal__foot { gap: 8px; }

/* ---------------------------------------------------------------------------
   THE REVIEW STEP (Add Task, step 5)
   ---------------------------------------------------------------------------
   Deliberately NOT a form. Everything here is already typed; this page is for
   READING, and the only interactive parts are the buttons that close a gap.
   So: no boxes, no borders round every value, generous line height. The one
   thing that must catch the eye is an unassigned count, and it is the only
   thing on the page wearing a colour.
   -------------------------------------------------------------------------- */
.lets-review__facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 2px 18px;
    margin-bottom: 18px;
}

.lets-review__facts--tail {
    margin-top: 18px;
    margin-bottom: 0;
    padding-top: 14px;
    border-top: 1px solid var(--lets-hairline);
}

.lets-review__fact {
    display: flex;
    flex-direction: column;
    padding: 7px 0;
}

.lets-review__label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--lets-muted);
}

.lets-review__value {
    font-size: 14px;
    color: var(--lets-ink);
    word-break: break-word;
}

/* Not an error -- plenty of these are optional. It reads as "you have not
   said", which is what it is, and is why it is grey rather than red. */
.lets-review__value.is-missing,
.lets-review__due .is-missing {
    color: var(--lets-muted);
    font-style: italic;
}

.lets-review__lang {
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    margin-bottom: 12px;
    overflow: hidden;
}

.lets-review__langhead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 9px 14px;
    background: var(--lets-sunken);
    border-bottom: 1px solid var(--lets-hairline);
}

.lets-review__langhead strong { font-size: 14.5px; }

.lets-review__words {
    font-size: 12px;
    color: var(--lets-ink-2);
}

.lets-review__people {
    width: 100%;
    margin: 0;
}

.lets-review__people td {
    padding: 7px 14px;
    border-top: 1px solid var(--lets-hairline);
    font-size: 13px;
    vertical-align: middle;
}

.lets-review__people tr:first-child td { border-top: 0; }

.lets-review__who  { font-weight: 600; }
.lets-review__num  { text-align: right; white-space: nowrap; color: var(--lets-ink-2); }
.lets-review__due  { text-align: right; white-space: nowrap; color: var(--lets-ink-2); }

.lets-review__nobody,
.lets-review__empty {
    margin: 0;
    padding: 11px 14px;
    font-size: 13px;
    color: var(--lets-muted);
    font-style: italic;
}

.lets-review__empty { font-style: normal; }

/* ---- THE GAP. The one loud thing on the page. ---------------------------- */
.lets-review__gap {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 11px 14px;
    background: #fdf6e6;
    border-top: 1px solid #f0dfb4;
    font-size: 13px;
    color: var(--lets-amber);
}

.lets-review__gap.is-over {
    background: #fdebec;
    border-top-color: #f5c2c4;
    color: var(--lets-red);
}

/* Settled reads quiet on purpose -- it is a record, not a prompt. */
.lets-review__gap.is-settled {
    background: var(--lets-sunken);
    border-top-color: var(--lets-hairline);
    color: var(--lets-ink-2);
}

.lets-review__gaptext { flex: 1 1 260px; }
.lets-review__gaptext strong { color: inherit; }

.lets-review__acts {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* The worked sum under a warning: quieter than the headline, because it is
   there to be read once you have decided to look. */
.lets-review__working,
.lets-countissues__list {
    font-size: 12px;
    line-height: 1.6;
    color: inherit;
    opacity: .85;
}

.lets-review__working { margin-top: 5px; }

/* ---- A RADIO PAIR THAT LINES UP WITH THE INPUTS BESIDE IT ---------------
   Bootstrap's .radio-inline sits at the top of its box, so a radio pair in a
   row of selects hangs below them. Matching form-control's height and
   centring inside it puts the whole row on one baseline. */
.lets-inlineradios {
    display: flex;
    align-items: center;
    gap: 16px;
    height: 38px;
}

.lets-inlineradios label {
    margin: 0;
    font-weight: 400;
    display: flex;
    align-items: center;
    gap: 5px;
    cursor: pointer;
}

.lets-inlineradios input { margin: 0; }

/* ---------------------------------------------------------------------------
   THE COUNT EXPLANATION, under the grid
   ---------------------------------------------------------------------------
   The row badge has room for "exceeding by 790" and no more. This shows the
   working -- client total, each linguist, the sum, the difference -- because
   with five linguists on a language the badge tells you there is a problem
   and nothing about which box is wrong.
   -------------------------------------------------------------------------- */
/* A row OF the grid, sitting directly under the language it is about, so the
   explanation and the boxes it names are never more than a row apart. */
.lets-countgrid tr.lets-issuerow > td {
    padding: 8px 12px;
    font-size: 12px;
    line-height: 1.6;
    border-top: 0;
}

.lets-countgrid tr.lets-issuerow--exceeding > td {
    background: #fdebec;
    box-shadow: inset 3px 0 0 var(--lets-red);
    color: var(--lets-red);
}

.lets-countgrid tr.lets-issuerow--partial > td,
.lets-countgrid tr.lets-issuerow--unassigned > td {
    background: #fdf6e6;
    box-shadow: inset 3px 0 0 var(--lets-amber);
    color: var(--lets-amber);
}

/* Answered: a record, not a prompt. */
.lets-countgrid tr.lets-issuerow.is-settled > td {
    background: var(--lets-sunken);
    box-shadow: inset 3px 0 0 var(--lets-border);
    color: var(--lets-ink-2);
}

/* ---------------------------------------------------------------------------
   STILL TO ASSIGN  (task view)
   ---------------------------------------------------------------------------
   The same nine buckets as the Add screen, but only ONE row and two wide
   fields beside it, so it needs its own widths rather than the count grid's.
   -------------------------------------------------------------------------- */
.lets-openwork {
    padding: 12px 0;
    border-top: 1px solid var(--lets-hairline);
}

.lets-openwork:first-of-type {
    padding-top: 0;
    border-top: 0;
}

.lets-openwork__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
}

.lets-openwork__batch {
    font-size: 12px;
    color: var(--lets-ink-2);
}

/*
 * >>> ONE ROW: NINE COUNTS, THEN WHO, THEN WHEN, THEN THE BUTTON. <<<
 * The owner: "Still to assign should be everything in one row now it is in
 * two line". This used to be two separate flex groups (counts, then
 * assign-to/deadline/button) stacked on top of each other -- each internally
 * non-wrapping already, but the SPLIT into two groups was itself the second
 * line. One flex container, `nowrap`, with `overflow-x: auto` as the only
 * fallback on a window too narrow for all twelve fields -- a short
 * horizontal scroll on the row itself, never a second row.
 */
.lets-openwork__row {
    display: flex;
    flex-wrap: nowrap;
    align-items: flex-end;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 2px;
}

.lets-openwork__bucket,
.lets-openwork__field {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    flex: 0 0 auto;
    font-weight: 400;
}

.lets-openwork__bucket > span,
.lets-openwork__field > span {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--lets-muted);
    white-space: nowrap;
}

.lets-openwork__bucket input {
    width: 68px;
    text-align: center;
    padding-left: 4px;
    padding-right: 4px;
}

/* A visual gap before "Assign to", so the eye still reads two groups (how
   much, then who/when) even though they now share one row. */
.lets-openwork__row .lets-openwork__field:first-of-type {
    margin-left: 8px;
    padding-left: 16px;
    border-left: 1px solid var(--lets-hairline);
}

.lets-openwork__row select { min-width: 160px; }
.lets-openwork__row input[data-lets-datetime] { min-width: 140px; }
.lets-openwork__row .btn { white-space: nowrap; }

/* ---------------------------------------------------------------------------
   THE TASK SCREEN
   ---------------------------------------------------------------------------
   The header facts were a <dl class="dl-horizontal"> -- one fact per LINE,
   eight facts, most of a screen, to say things that are four words each. A
   grid puts the same eight across two rows and gives the page back, which is
   what the owner asked for.
   -------------------------------------------------------------------------- */
/* >>> ONE ROW, ALWAYS. <<< auto-fit with a 150px minimum wrapped the last
   fact onto a second line on a normal screen -- the owner's "currently
   deadline showing in next row". A fixed track shrinks the columns instead of
   wrapping them; the values are short enough to take it, and it only falls to
   two rows on a genuinely narrow window. */
/*
 * >>> THE BREADCRUMB IS ACTING AS THE PAGE TITLE NOW. <<<
 * The owner removed the page-head card entirely and asked for the breadcrumb
 * enlarged in its place: "increase the size of breadcrumb by 12 px so that
 * will be visible also increase the destance from top". Base `.lets-crumbs`
 * (12.5px, 2px top margin) is shared by every OTHER screen and stays
 * untouched -- this only reaches the copy wrapped in `.lets-taskcrumbs`, on
 * the task view alone.
 */
.lets-taskcrumbs .lets-crumbs {
    /* +12px on the base 12.5px, then -2px, then -2px again -- both the
       owner's own follow-up corrections. 12.5 + 12 - 2 - 2 = 20.5px. */
    font-size: 20.5px;
    font-weight: 700;
    color: var(--lets-ink);
    /* Left margin widened from the base rule's 4px to 10px -- "10px distance
       from the sidebar". */
    margin: 22px 0 18px 10px;
}

.lets-taskcrumbs .lets-crumbs a {
    font-weight: 700;
    font-size: inherit;
}

.lets-taskcrumbs .lets-crumbs .here {
    font-weight: 800;
    color: var(--lets-ink);
}

.lets-taskcrumbs .lets-crumbs__home,
.lets-taskcrumbs .lets-crumbs .sep {
    font-size: 18px;
}

/*
 * Eight facts (the ID moved into the heading itself -- "[ID] : Name" -- and
 * is not repeated here), so an even track fits them all on one line.
 */
.lets-taskfacts {
    display: grid;
    grid-template-columns: repeat(8, minmax(0, 1fr));
    gap: 2px 16px;
}

.lets-taskfact__value code { white-space: nowrap; }

@media (max-width: 1200px) {
    .lets-taskfacts { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

@media (max-width: 768px) {
    .lets-taskfacts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/*
 * >>> CENTRED. <<< The owner: "basic task details showing should be center
 * align so that they look just below the heading" -- reading as a strip of
 * facts under the title rather than a left-ragged form.
 */
.lets-taskfact {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 6px 0;
    min-width: 0;
}

.lets-taskfact__label {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--lets-muted);
}

.lets-taskfact__value {
    font-size: 13.5px;
    color: var(--lets-ink);
    word-break: break-word;
}

.lets-taskfact__value code {
    font-size: 12px;
    background: var(--lets-hairline);
    padding: 1px 6px;
    border-radius: 4px;
    color: var(--lets-ink-2);
}

/* ---- The plain-English roll-up, not the workflow state ------------------ */
.lets-workstatus {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 20px;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .04em;
    white-space: nowrap;
}

.lets-workstatus--green { background: var(--lets-green-soft, #e2f5e9); color: var(--lets-green); }
.lets-workstatus--amber { background: #fdeccb; color: var(--lets-amber); }
.lets-workstatus--teal  { background: var(--lets-tint);  color: var(--lets-teal-deep, #0c6478); }
.lets-workstatus--muted { background: var(--lets-hairline); color: var(--lets-muted); }

/* The alert pills, sitting on the Overview tab between "What the client
   sent" and "Still to assign" -- the only place they appear now that the
   page-head card is gone. Centred, per the owner: "make it center alinged". */
.lets-taskalerts {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin: -6px 0 14px;
}

/* The sub-task uid IS the vendor task name -- long, and the thing people
   quote, so it wraps rather than truncating. */
.lets-subtaskuid {
    font-size: 11px;
    background: var(--lets-sunken);
    border: 1px solid var(--lets-hairline);
    padding: 2px 6px;
    border-radius: 4px;
    word-break: break-all;
    color: var(--lets-ink-2);
}
/* Wrapped in a link on the assignment grid, where the UID opens its own vendor
   line. The chip keeps its shape and picks up the accent on hover, so it reads
   as clickable without turning into blue underlined text that would fight the
   monospace chip styling around it. */
a > .lets-subtaskuid { transition: border-color .15s ease, color .15s ease; }
a:hover > .lets-subtaskuid,
a:focus > .lets-subtaskuid {
    border-color: var(--th-accent, #0c6478);
    color: var(--th-accent-deep, #0c6478);
}

.lets-clientgrid th { font-size: 10.5px; letter-spacing: .03em; white-space: nowrap; }
.lets-assignlqa { white-space: nowrap; }

.lets-fileadd {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 10px;
}

.lets-fileadd input[type=file] { min-width: 220px; }

/* ---- Files tab: folders (D162) -------------------------------------------
   One row per fixed category (Source/Reference/CAT analysis/Deliveries/
   Other), always rendered even with zero files -- a folder that only
   appears once it has something in it cannot be told apart from one that
   will never have anything, and the owner asked for every folder to always
   be visible.

   >>> EVERY COLOR HERE IS ONE OF THIS FILE'S OWN --lets-* TOKENS. <<< The
   owner: "folder and button color and item present count color and file
   name and link color can be change depending on theme chosen." None of
   these rules hard-code a hex value -- swap a theme's --lets-teal/--lets-
   ink/--lets-muted/etc. at the :root level (see the theme blocks earlier in
   this file) and every folder, badge and download link repaints with it,
   with nothing to touch here.

   Native <details>/<summary> gives the expand/collapse for free, no JS --
   .lets-filefolder__row is the <summary>, styled to match the reference
   mockup's own .folder__row exactly (D162, "use the same files card
   code"). */
/* A fixed-height, internally-scrolling panel -- opening a folder (even a
   big one) grows the SCROLLBAR inside this box, not the page. Without this,
   expanding "Deliveries" at the bottom of a long file list used to push
   every tab below it (Comments, History) further down the page each time. */
.lets-filefolders {
    border: 1px solid var(--lets-hairline);
    border-radius: var(--lets-radius-s);
    max-height: 390px;
    overflow-y: auto;
}

.lets-filefolder { border-bottom: 1px solid var(--lets-hairline); }
.lets-filefolder:last-child { border-bottom: none; }

.lets-filefolder__row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    cursor: pointer;
    user-select: none;
    list-style: none;
}

.lets-filefolder__row::-webkit-details-marker { display: none; }
.lets-filefolder[open] > .lets-filefolder__row { background: var(--lets-tint); }

.lets-filefolder__chevron { color: var(--lets-muted); flex: 0 0 14px; display: inline-flex; transition: transform .15s ease; }
.lets-filefolder[open] > .lets-filefolder__row .lets-filefolder__chevron { transform: rotate(90deg); }

.lets-filefolder__icon { color: var(--lets-teal); flex: 0 0 18px; display: inline-flex; }
.lets-filefolder__name { font-weight: 700; font-size: 13.5px; color: var(--lets-ink); }
.lets-filefolder__sub { font-size: 11.5px; color: var(--lets-muted); margin-left: 2px; }

/* Tighter Files card (D162, "move the upload row upward so the folder
   list gets more space") -- scoped to just this card rather than every
   .box-body, so nothing else in the app shifts. */
.lets-files-body { padding-top: 6px; padding-bottom: 12px; }
.lets-files-body .lets-fileadd { margin-bottom: 2px; }
.lets-files-body .lets-form__help { margin: 0 0 10px; }

.lets-filefolder__count {
    font-size: 11px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 99px;
    background: var(--lets-hairline);
    color: var(--lets-ink);
}

/* Always shown, even at zero (D162: "so we can identify easily") -- dimmed
   rather than hidden, so an empty folder still reads as empty at a glance
   without the badge competing with folders that actually have something. */
.lets-filefolder__count--zero { background: transparent; color: var(--lets-muted); font-weight: 600; }

.lets-filefolder__size { margin-left: auto; font-size: 12px; color: var(--lets-muted); font-weight: 600; }
.lets-filefolder__body { padding: 0 16px 14px 42px; }

.lets-filetable {
    width: 100%;
    border-collapse: collapse;
    background: var(--lets-sunken);
    border: 1px solid var(--lets-hairline);
    border-radius: var(--lets-radius-s);
    overflow: hidden;
}

.lets-filetable th {
    font-size: 10px; font-weight: 750; letter-spacing: .5px; text-transform: uppercase;
    color: var(--lets-ink); opacity: .72; text-align: left; padding: 7px 12px;
    border-bottom: 1px solid var(--lets-hairline);
}

.lets-filetable td { padding: 9px 12px; border-top: 1px solid var(--lets-hairline); font-size: 12.5px; vertical-align: middle; }
.lets-filetable tr:first-child td { border-top: none; }

.lets-filename { display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--lets-ink); }
.lets-filename .fa { color: var(--lets-muted); }

.lets-filefolder__empty { padding: 12px 12px 4px; font-size: 12.5px; color: var(--lets-muted); }

.lets-dl-btn { display: inline-flex; align-items: center; gap: 5px; color: var(--lets-teal-deep); font-size: 12px; font-weight: 650; }
.lets-dl-btn:hover { color: var(--lets-teal-bright); }

/* "shared reference" badge -- the existing .label.label-info (already
   themed, see line ~740) is reused as-is rather than a new one-off class,
   matching how the reference mockup used its own equivalent .label-info. */
.lets-shared-badge { display: inline-flex; align-items: center; gap: 4px; }

/* ---- Comments ----------------------------------------------------------- */
.lets-comment {
    padding: 10px 0;
    border-top: 1px solid var(--lets-hairline);
}

.lets-comment:first-of-type { border-top: 0; padding-top: 0; }

.lets-comment__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    margin-bottom: 3px;
    font-size: 12.5px;
}

.lets-comment__when { font-size: 11px; color: var(--lets-muted); margin-left: auto; }
.lets-comment__body { font-size: 13px; line-height: 1.55; color: var(--lets-ink); }

.lets-comment--mini .lets-comment__body { font-size: 12px; color: var(--lets-ink-2); }

/* ---- History ------------------------------------------------------------ */
.lets-history td {
    border-top: 1px solid var(--lets-hairline) !important;
    vertical-align: top;
    font-size: 12.5px;
}

.lets-history tr:first-child td { border-top: 0 !important; }

.lets-history__when { white-space: nowrap; color: var(--lets-muted); font-size: 11.5px; width: 1%; }
.lets-history__who  { white-space: nowrap; color: var(--lets-muted); font-size: 11.5px; text-align: right; }

.lets-history__detail {
    display: inline-block;
    margin-left: 8px;
    color: var(--lets-ink-2);
}

.lets-history__reason {
    margin-top: 3px;
    font-style: italic;
    color: var(--lets-ink-2);
}

/* ---------------------------------------------------------------------------
   THE COMPOSER AND THE ACTIVITY FEED
   -------------------------------------------------------------------------- */
.lets-composer {
    padding-bottom: 12px;
    border-bottom: 1px solid var(--lets-hairline);
}

/* The "Send to" picker, living in the box-header next to the title (the
   owner's "small ... on the right side aligned"). box-tools already
   centres its contents vertically against the title; this only bounds
   the width so it does not stretch the header taller than the heading. */
.lets-composer__to { min-width: 260px; max-width: 60vw; }

/*
 * >>> THE QUILL CONTAINER, RESIZABLE. <<<
 * The owner asked for both "fully format" and "size can be increase and
 * decrease". Quill converts THIS element into `.ql-container` in place and
 * inserts its toolbar as a sibling immediately before it -- so resize lives
 * here, on the writing area only, and the toolbar above it never moves.
 * `overflow: auto` is what makes `resize` take effect at all, and it also
 * gives the editor its own scrollbar once the content outgrows the box
 * instead of growing the whole page.
 */
.lets-composer__box {
    resize: vertical;
    overflow: auto;
    min-height: 110px;
    font-size: 13px;
    line-height: 1.55;
}

.lets-composer__box .ql-editor { min-height: 96px; }

/* >>> ONE ROW: Post + the two chasers on the left, the filter pinned right.
   <<< The owner: "align together with the post comment box also show
   filter should be in the same row but right aligned". */
.lets-composer__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 10px;
}

.lets-composer__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.lets-feedfilter {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    font-weight: 400;
}

.lets-feedfilter select { min-width: 200px; }
.lets-feedfilter__count { font-size: 11.5px; color: var(--lets-muted); white-space: nowrap; }

/* The independently-scrolling feed / history list -- the owner: "history and
   comment should have container so that we can scroll the comment without
   scrolling page". A fixed height rather than a viewport-relative one, so it
   does not fight the page's own scroll on a short window. */
.lets-scrollbox {
    max-height: 480px;
    overflow-y: auto;
    margin-top: 10px;
    border-top: 1px solid var(--lets-hairline);
}

.lets-feeditem {
    padding: 10px 0 10px 12px;
    border-top: 1px solid var(--lets-hairline);
    border-left: 3px solid var(--lets-tint);
}

/* A system event reads quieter than a person talking: it is context for the
   comments around it, not a voice competing with them. */
.lets-feeditem.is-event {
    border-left-color: var(--lets-hairline);
    background: var(--lets-sunken);
}

/* An AUTOMATIC comment -- a Buzz, a deadline chaser -- is a machine's words
   sitting in a column of people's. The owner, 27 Aug 2026: "agar system
   generated comment hai to light grey dikhe or agar user/vendor ke bheje
   comments hai to wo fir black dikhe".

   The BODY only. The head keeps its full weight because that is where the
   "automatic" chip and the timestamp live, and a reader scanning for "who said
   this and when" needs those at the same strength on every row. It is what was
   SAID that carries less weight, not the fact that it was said.

   Italic as well as grey, for the same reason task_commented.php slants its
   own quote block: forced-colours and high-contrast modes throw the colour
   away, and greying alone would leave a chaser and a real question looking
   identical again on exactly the displays that need the difference most. */
.lets-feeditem.is-auto .lets-comment__body {
    color: var(--lets-ink-2);
    font-style: italic;
}

.lets-feeditem__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    margin-bottom: 2px;
    font-size: 12.5px;
}

.lets-feeditem__lang { font-size: 11px; color: var(--lets-ink-2); }

/* ---- Extra-effort claims on the overview -------------------------------- */
.lets-claim {
    padding: 12px 0;
    border-top: 1px solid var(--lets-hairline);
}

.lets-claim:first-of-type { border-top: 0; padding-top: 0; }

.lets-claim__head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 5px;
}

.lets-claim__amount { font-weight: 700; color: var(--lets-ink); }

.lets-claim__note {
    font-size: 13px;
    line-height: 1.55;
    color: var(--lets-ink-2);
    margin-bottom: 9px;
}

.lets-claim__decide {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 8px;
}

.lets-claim__decide input.lets-num { width: 120px; }
.lets-claim__reason { flex: 1 1 260px; }

/* ---- Additional budget, on the line page's Financials tab ---------------- */
.lets-budgetform {
    padding-bottom: 14px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--lets-hairline);
}

.lets-budgetform__time {
    display: flex;
    align-items: center;
    gap: 8px;
}

.lets-budgetform__time input { width: 110px; }

/* The live hours read-out. Quiet on purpose -- it is a mirror of what was
   typed, not a second figure to reconcile against the one that gets billed. */
.lets-budgetform__time .text-muted { font-weight: 650; }

.lets-budgetdecide {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 5px;
}

.lets-budgetapprove {
    margin-top: 8px;
    padding: 9px 10px;
    border: 1px solid var(--lets-hairline);
    border-radius: 7px;
    background: var(--lets-surface-2, #fafbfc);
}

.lets-budgetapprove__q {
    font-size: 12px;
    font-weight: 700;
    margin-bottom: 6px;
}

.lets-budgetapprove__opt {
    display: block;
    font-size: 12.5px;
    font-weight: 400;
    line-height: 1.7;
    cursor: pointer;
}

.lets-budgetapprove__opt input { margin-right: 6px; }

.lets-budgetapprove input[type="text"] { margin-top: 7px; }

/* In a table cell the dialogue needs a floor, or the three options wrap to
   two lines each and the comment box is clipped to about six characters -- a
   question nobody can read is a question nobody answers properly. In the
   right rail it must NOT have one: the rail is narrower than that, and a
   floor there would push the dialogue out past the page edge. */
td .lets-budgetapprove { min-width: 260px; }
.lets-linerail .lets-budgetapprove { width: 100%; }

/* ---- The request's own comment history ---------------------------------- */
/* One entry per thing that happened: raised, approved, rejected. The left
   edge carries the verdict as colour, so the shape of the conversation reads
   before any of it is actually read. */
.lets-budgetevent {
    padding: 10px 0 10px 12px;
    border-left: 3px solid var(--lets-hairline);
    border-top: 1px solid var(--lets-hairline);
}

.lets-budgetevent:first-of-type { border-top: 0; }
.lets-budgetevent--ok  { border-left-color: var(--lets-green, #37a169); }
.lets-budgetevent--bad { border-left-color: var(--lets-red, #d9534f); }

.lets-budgetevent__head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 4px;
}

.lets-budgetevent__who { font-size: 12px; color: var(--lets-ink-2); }

.lets-budgetevent__charge {
    font-size: 12.5px;
    margin-bottom: 4px;
}

.lets-budgetevent__note {
    font-size: 13px;
    line-height: 1.55;
    color: var(--lets-ink-2);
}

.lets-budgetapprove__act {
    display: flex;
    gap: 6px;
    margin-top: 8px;
}

/* =============================================================================
   LQA  --  the review round panel
   =============================================================================
   A round is a CONVERSATION that changes hands five times, so the panel reads
   as a transcript with the open question at the bottom. Each answered stage is
   one block with a rule above it; they accumulate downward in the order they
   happened, which is the order the next person has to read them in.
*/
.lets-lqastep {
    padding: 12px 0 12px 12px;
    border-left: 3px solid var(--lets-teal);
    border-top: 1px solid var(--lets-hairline);
}
.lets-lqastep:first-of-type { border-top: 0; }

.lets-lqastep__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 5px;
    font-size: 13px;
}

/* The verdict, the score and the rating on one line. They are read together --
   a score without its rating, or a rating without whether the report was
   actually shared, is half an answer. */
.lets-lqastep__facts {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    margin-bottom: 6px;
    font-size: 12.5px;
    color: var(--lets-ink-2);
}

.lets-lqastep p {
    font-size: 13px;
    line-height: 1.55;
    margin: 0;
}

/* ---- THE ANSWER BOXES, AS QUILL EDITORS --------------------------------
   D142. These carry the substance of a round -- the findings, the rebuttal,
   the arbitration and the decision -- and were plain textareas while the task
   instructions beside them had a full editor.

   Quill's snow theme supplies the toolbar and the border; what it does NOT
   supply is a height, because a `.ql-container` sizes to its content and
   starts one line tall. A box that grows as you type is right for a chat
   composer and wrong here: the reviewer is writing a list of findings and
   needs to see them. */
.lets-lqarich .ql-container {
    min-height: 130px;
    font-size: 13px;
    font-family: inherit;
}

.lets-lqarich .ql-editor { min-height: 130px; }

/* The compulsory ones get the same amber edge the required file picker has,
   because the asterisk is in a table header a long way left of the box. */
.lets-lqarich.is-required .ql-toolbar { border-left: 3px solid var(--lets-amber); }
.lets-lqarich.is-required .ql-container { border-left: 3px solid var(--lets-amber); }

/* >>> THE TOOLBAR MUST SIT ABOVE THE MODAL'S OWN STACKING. <<< Quill's
   link tooltip is positioned absolutely inside the container; without this it
   is clipped by the dialogue's overflow and the URL box appears half-cut. */
.lets-lqarich .ql-tooltip { z-index: 5; }

/* What those editors produce, rendered back on the card. Quill emits <p>, <ol>
   and <ul>, none of which this card styled before -- an unstyled <ol> inside a
   table cell loses its numbers to the reset. */
.lets-lqarich__out { font-size: 13px; line-height: 1.55; }
.lets-lqarich__out p { margin: 0 0 6px; }
.lets-lqarich__out p:last-child { margin-bottom: 0; }
.lets-lqarich__out ol,
.lets-lqarich__out ul { margin: 0 0 6px; padding-left: 22px; }
.lets-lqarich__out ol { list-style: decimal outside; }
.lets-lqarich__out ul { list-style: disc outside; }
.lets-lqarich__out li { margin: 2px 0; }

/* ---- THE REPORT PICKER, IN EVERY LQA MODAL ------------------------------
   Replaced four "I confirm I have uploaded..." checkboxes (D133). The chosen
   filenames are listed under the button rather than left to the browser's own
   "3 files" -- which three is the whole question, and picking the wrong folder
   is the mistake this catches.

   `.file-names` already exists but is scoped to `.lets-filegrid`, whose rules
   centre it in a 140px column. That is right for a grid cell and wrong in a
   dialogue, so this states its own rather than widening the other and changing
   Add Task's file grid by accident. */
.lets-lqafile .file-names {
    margin-top: 5px;
    font-size: 11.5px;
    line-height: 1.4;
    color: var(--lets-ink-2);
}

.lets-lqafile .file-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

/* Compulsory in this branch: a left edge in the warning colour, because the
   asterisk in the table header is a long way from the button on a wide row. */
.lets-lqafile.is-required {
    border-left: 3px solid var(--lets-amber);
    padding-left: 9px;
}

.lets-lqafile .help-block { margin-bottom: 0; }

/* The versions filed so far, each a link to Tasks::downloadFile (D134). The
   name IS the content here -- the suffix says which stage of the argument the
   file belongs to -- so it is set in code type and allowed to wrap rather than
   be truncated. */
.lets-lqareports { margin: 4px 0 0; font-size: 12px; }
.lets-lqareports > tbody > tr > td { border-top: 1px solid var(--lets-hairline); padding: 4px 8px 4px 0; }
.lets-lqareports > tbody > tr:first-child > td { border-top: 0; }
.lets-lqareports code {
    font-size: 11.5px;
    word-break: break-all;
}

/* The card's two tables. They sit side by side and carry their own heading
   row, because "Quality verdict" and "This round" are read at different
   moments -- the first is the answer somebody came for, the second is the
   context they check afterwards. Without the headings the split just looks
   like one list that wrapped. */
.lets-lqagrid thead th {
    background: var(--lets-sunken);
    font-size: 11.5px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--lets-ink-2);
}
.lets-lqagrid th { font-weight: 600; }

/* Stacked on a narrow screen, where two half-width tables of label/value
   pairs would each be too cramped to read. */
@media (max-width: 991px) {
    .lets-lqagrid { margin-bottom: 12px; }
}

/* ---- the open question --------------------------------------------------- */
.lets-lqaform {
    margin-top: 14px;
    padding: 12px;
    border: 1px solid var(--lets-hairline);
    border-radius: 7px;
    background: var(--lets-surface-2, #fafbfc);
}
.lets-lqaform__q {
    font-size: 13px;
    font-weight: 700;
    margin-bottom: 8px;
}

/* The two answers sit side by side and WRAP rather than shrink: they carry the
   owner's own sentences ("I disagree with the reviewer and provided my
   comment"), and a button truncated to "I disagree with the..." is a button
   somebody presses meaning something else. */
.lets-lqaform__acts {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 12px;
}
.lets-lqaform__acts .btn { white-space: normal; text-align: left; }

/* Inside the modal the body SCROLLS (.lets-modal__body is overflow-y:auto), so
   the answers have to be pinned or they scroll out of reach on the resolution
   form -- which has six of them under a required note. Sticky rather than a
   real __foot because the buttons carry the submit name/value that decides
   what the answer WAS, and each role contributes its own set. */
.lets-modal .lets-lqaform__acts {
    position: sticky;
    bottom: -16px;              /* cancels .lets-modal__body's padding */
    z-index: 1;
    margin: 12px 0 0;
    padding: 10px 0;
    background: var(--lets-surface, #fff);
    border-top: 1px solid var(--lets-hairline, #edf2f0);
}

/* The form fills the dialogue rather than sitting in a bordered card inside
   one -- it IS the dialogue now. */
.lets-modal .lets-lqaform {
    display: contents;
}

/* The LQA dialogue takes the page. The owner asked for it full-page, and it
   earns the room: the reviewer's form is a four-row table and the resolution
   form carries six answers under a required note, both of which are cramped
   at the shared 980px. */
.lets-modal--lqa .lets-modal__dialog {
    width: min(1240px, 96vw);
    max-height: 94vh;
    margin: 3vh auto;
}

/* --------------------------------------------------------------------------
   THE MAKE PAYMENT MODAL (vendor-invoices/document.php)          (21 Aug 2026)
   --------------------------------------------------------------------------
   >>> SAME BUG THE LQA FORM ALREADY HIT, AND THE SAME FIX. <<< The owner
   reported this one "truncating from the top and not centered" -- caused by
   the identical shape .lets-lqaform's own comment above already explains:
   <form> wraps BOTH .lets-modal__body and .lets-modal__foot, so it sits
   between .lets-modal__dialog and the two children its flex layout expects
   directly -- .lets-modal__dialog's own max-height + .lets-modal__body's
   overflow-y:auto both depend on that direct relationship. A plain
   block-level <form> in between breaks it: the form renders at its full
   natural height (calculation box + payment-info fields + footer, more
   content than the LQA form that surfaced this originally), pushes the
   dialog taller than max-height, and .lets-modal__dialog's own
   `overflow: hidden` then clips whatever does not fit -- which reads as the
   dialog being cut off and mis-positioned rather than scrolling internally
   the way it was designed to. `display: contents` removes the <form> from
   layout entirely, so __body and __foot become direct flex children again,
   exactly as if there were no <form> there at all. */
.lets-modal .lets-payform {
    display: contents;
}
.lets-modal--payment .lets-modal__dialog {
    width: min(760px, 95vw);
}

/* The five stages, on the raise-a-review form. Numbered because the order is
   the point -- it is what the PM is committing everybody to. */
.lets-lqasteps {
    margin: 0;
    padding-left: 18px;
    font-size: 12.5px;
    line-height: 1.7;
    color: var(--lets-ink-2);
}
.lets-lqasteps code { font-size: 11.5px; }

/*
 * In the Assignment tab's box-header (.box-tools is absolute-positioned,
 * top:5px -- a two-line stack risks overlapping the body below it), so label
 * and value sit on ONE line here rather than the label-above-value pairing
 * used elsewhere on this page.
 */
.lets-vendorname {
    font-size: 12px;
    font-weight: 650;
    color: var(--lets-ink);
    white-space: nowrap;
    /* .box-tools is position:absolute; top:5px -- sitting right at the top
       edge of the header. A little top padding gives it the same visual
       breathing room as the "Assignment" title beside it. */
    padding-top: 6px;
    display: inline-block;
}

.lets-vendorname .lets-taskfact__label {
    margin-right: 6px;
    font-weight: 700;
}

.lets-countissues__acts {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-left: 10px;
    vertical-align: middle;
}

/* ---- THE REVIEW'S INSTRUCTION AND FILE CARDS ---------------------------- */
.lets-review__card {
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    margin-top: 12px;
    overflow: hidden;
}

.lets-review__cardhead {
    padding: 9px 14px;
    background: var(--lets-sunken);
    border-bottom: 1px solid var(--lets-hairline);
    font-size: 13px;
    font-weight: 700;
}

.lets-review__cardhead .btn-link { padding: 0; font-weight: 600; }

/* The instructions as they will be sent -- tables, images and all. Capped and
   scrollable so a long brief cannot push the Save button off the screen. */
.lets-review__instructions {
    padding: 12px 14px;
    font-size: 13px;
    line-height: 1.6;
    max-height: 320px;
    overflow: auto;
}

.lets-review__instructions table {
    width: 100%;
    margin: 8px 0;
    border-collapse: collapse;
}

.lets-review__instructions table td,
.lets-review__instructions table th {
    border: 1px solid var(--lets-border);
    padding: 5px 8px;
}

/* A NESTED table is how Word and Outlook export a bulleted list that lives
   inside a table cell -- a bullet column and a text column. It is a layout
   device, not a grid, so the `width: 100%` above must not reach it: that
   spreads two bullets across the full width of the brief. Cell borders are
   left to the pasted markup, which carries `border:none` inline on exactly
   these cells, and an inline style already beats both rules above. */
.lets-review__instructions table table {
    width: auto;
    margin: 0;
}

.lets-review__instructions img { max-width: 100%; height: auto; }

.lets-review__file {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    margin: 2px 4px 2px 0;
    border: 1px solid var(--lets-border);
    border-radius: 20px;
    background: var(--lets-sunken);
    font-size: 12px;
}

/* ---- THE WORD-COUNT GRID (Add Task) -------------------------------------
   Ten numeric columns across, so every millimetre counts: the inputs are
   compact and the whole table scrolls sideways rather than squashing. */
.lets-countgrid {
    font-size: 13px;
}

.lets-countgrid th {
    background: var(--lets-sunken);
    border-bottom: 2px solid var(--lets-border) !important;
    font-size: 11px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--lets-ink-2);
    white-space: nowrap;
    vertical-align: middle !important;
}

.lets-countgrid td { vertical-align: middle !important; }

/* A number box wide enough for a real word count and no wider. */
.lets-countgrid input[type="number"] {
    width: 72px;
    padding: 3px 6px;
    height: 28px;
    text-align: right;         /* digits line up on the decimal point */
    font-variant-numeric: tabular-nums;
}

/* >>> THE PARENT ROW IS THE LANGUAGE, THE CHILD ROW IS A LINGUIST. <<<
   The relationship has to be readable at a glance on a grid this dense, so
   the parent gets weight and a tinted band, and each child is indented with a
   rule running down the left to tie it to its parent. */
.lets-countgrid__lang > td {
    background: var(--lets-tint);
    font-weight: 600;
}

.lets-countgrid__lang > td:first-child {
    border-left: 3px solid var(--lets-teal);
}

.lets-countgrid__assignee > td:first-child {
    padding-left: 26px !important;
    border-left: 3px solid var(--lets-hairline);
    color: var(--lets-ink-2);
    position: relative;
}

/* The little elbow that joins a child to the row above it. */
.lets-countgrid__assignee > td:first-child::before {
    content: "";
    position: absolute;
    left: 12px;
    top: 0;
    bottom: 50%;
    border-left: 1px solid var(--lets-border);
    border-bottom: 1px solid var(--lets-border);
    width: 8px;
}

/* Computed cells are the answer from the server, not an input -- they read as
   settled figures rather than something else to fill in. */
.lets-countgrid__computed {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--lets-ink);
}

/* "no rate" is a WARNING, not an error and not a zero: the task is fine, the
   money is not settled yet. Amber says that; red would overstate it. */
.lets-countgrid__norate {
    color: var(--lets-amber);
    font-weight: 600;
    font-size: 12px;
}

/* ---- THE DRIVE TILES ----------------------------------------------------
   Status counters across the top. They are BUTTONS -- clicking one filters
   the grid -- so they have to feel pressable.

   >>> THE COLOURS ARE LE2026'S, DELIBERATELY, AND ARE NOT THEMED. <<<
   The owner: "use user color theme but cards color should be same". Every
   other surface on this page follows whichever accent the user picked; these
   twelve gradients are lifted byte-for-byte from LE2026's
   `.mydashboard-new-page .status-chip` because they are how the team already
   reads this screen at a glance -- blue is new work, green is done, red is
   off. Re-tinting them per theme would make "the green one" mean something
   different for each person looking at the same board.

   The unselected state is faded (opacity .5 + saturate(.55)) rather than
   grey: twelve full-strength gradients side by side is a paint chart, and
   the one you have actually filtered by needs to be the one that stands out. */
.lets-tile {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    min-width: 118px;
    border: none;
    border-radius: 12px;
    padding: 10px 14px;
    cursor: pointer;
    color: #fff;
    text-decoration: none;
    font-size: 11.5px;
    font-weight: 650;
    letter-spacing: .1px;
    opacity: .5;
    filter: saturate(.55);
    background: linear-gradient(135deg, #94a3b8, #475569);
    transition: transform .18s cubic-bezier(.2,.8,.2,1), opacity .18s ease,
                filter .18s ease, box-shadow .18s ease;
}

.lets-tile:hover,
.lets-tile:focus {
    color: #fff;
    text-decoration: none;
    opacity: .8;
    filter: saturate(.85);
    transform: translateY(-1px);
}

/* The tile whose filter is currently applied. */
.lets-tile.is-active {
    opacity: 1;
    filter: saturate(1);
    box-shadow: 0 10px 22px -10px rgba(0,0,0,.4);
    transform: translateY(-1px) scale(1.02);
}

.lets-tile--new       { background: linear-gradient(135deg,#60a5fa,#1d4ed8); }
.lets-tile--progress  { background: linear-gradient(135deg,#fbbf24,#ea580c); }
.lets-tile--hold      { background: linear-gradient(135deg,#fb923c,#c2410c); }
.lets-tile--done      { background: linear-gradient(135deg,#4ade80,#15803d); }
.lets-tile--payhold   { background: linear-gradient(135deg,#fb7185,#9f1239); }
.lets-tile--po        { background: linear-gradient(135deg,#a78bfa,#6d28d9); }
.lets-tile--issued    { background: linear-gradient(135deg,#22d3ee,#0e7490); }
/* The three the owner asked for that LE2026 had no tile for. Hues chosen to
   sit clearly apart from the nine above rather than to match anything. */
.lets-tile--lqa       { background: linear-gradient(135deg,#818cf8,#4338ca); }
.lets-tile--financial { background: linear-gradient(135deg,#e879f9,#a21caf); }
/* Rejected (D123) -- amber-red. Deliberately NOT the same as Removed's grey
   or Cancelled's red: a rejected line is the one card on this board that is
   waiting for somebody to DO something (reassign it), so it should not read
   as an archive the way those two do. */
.lets-tile--rejected  { background: linear-gradient(135deg,#fb923c,#b91c1c); }
/* Headsup (D146) -- teal, and deliberately unlike every other tile on this
   board. The eleven around it are all stages of REAL work; this is the one
   card holding notices that carry no counts, no money and no PO, and it should
   not read as another status in the same cycle. */
.lets-tile--headsup   { background: linear-gradient(135deg,#2dd4bf,#0f766e); }
/* Bid (D147) -- amber-gold, beside Headsup and deliberately unlike it. The two
   sit together because neither carries billable work, and apart in hue because
   they ask different questions: a headsup asks "are you free?", a bid asks
   "how much?". */
.lets-tile--bid       { background: linear-gradient(135deg,#fbbf24,#b45309); }
/* Escalated -- deep red, deliberately alarming and unlike every other tile:
   this is the one card that is not a stage of ordinary work but a problem
   that needs the Vendor Manager's attention, and it should read that way at
   a glance. Also the one card visible only to VM/HOD/Admin and a project's
   own team (Tasks::scopeEscalationVisibility()) -- everyone else sees a
   zero here, never the rows. */
.lets-tile--escalated { background: linear-gradient(135deg,#f87171,#7f1d1d); }
/* Auction (D150) -- indigo-violet, and it exists only on the LINGUIST'S drive.
   The staff board has no Auction card on purpose (AUCTION_TILES says why: an
   auction is a race, and every way it can end already has a home there). On the
   vendor's own drive it is the opposite -- a won auction is the work they are
   holding, and "where did this come from" is a question they legitimately ask
   about their own list. */
.lets-tile--auction   { background: linear-gradient(135deg,#8b5cf6,#4c1d95); }
.lets-tile--removed   { background: linear-gradient(135deg,#94a3b8,#475569); }
.lets-tile--cancelled { background: linear-gradient(135deg,#fb7185,#e11d48); }
.lets-tile--all       { background: linear-gradient(135deg,#334155,#0b0f19); }

.lets-tile__count {
    font-size: 19px;
    line-height: 1.1;
    font-weight: 800;
    color: #fff;
    font-variant-numeric: tabular-nums;
}

.lets-tile__label {
    margin: 0;
    font-size: 11.5px;
    font-weight: 650;
    letter-spacing: .1px;
    text-transform: none;
    color: #fff;
    white-space: nowrap;
}

/* ---- ONE ASSIGNMENT, ON ITS OWN PAGE (tasks/line_view.php) --------------
   Main column + a right rail carrying the status and whatever actions the
   line's current state allows. The rail STICKS: the panels beside it run
   long (instructions, a comment thread, a history feed) and the whole point
   of the rail is that Accept/Complete/Hold stay reachable while you read. */
.lets-linepage {
    display: flex;
    align-items: flex-start;
    gap: 18px;
}
.lets-linepage__main { flex: 1 1 auto; min-width: 0; }

.lets-linepage__title {
    margin: 0 0 14px;
    font-size: 20px;
    font-weight: 800;
    color: var(--lets-ink);
    letter-spacing: -.2px;
}
.lets-linepage__sub {
    display: block;
    margin-top: 3px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--lets-ink-2);
}

.lets-linerail {
    flex: 0 0 268px;
    width: 268px;
    align-self: flex-start;
    position: sticky;
    top: 68px;
    background: var(--lets-surface);
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius);
    box-shadow: var(--lets-shadow);
    padding: 16px 16px 8px;
}

.lets-linerail__block { margin-bottom: 16px; }
.lets-linerail__lbl {
    font-size: 10.5px; font-weight: 750; letter-spacing: .5px;
    text-transform: uppercase; color: var(--lets-muted);
    margin-bottom: 6px;
}
.lets-linerail__row { font-size: 12.5px; color: var(--lets-ink); }
.lets-linerail__who { font-size: 13px; font-weight: 700; color: var(--lets-ink); }

.lets-linerail__actions { display: flex; flex-direction: column; gap: 7px; }
.lets-linerail__actions form { margin: 0; }
.lets-linerail__btn {
    width: 100%;
    border: 1px solid var(--lets-border);
    background: var(--lets-surface);
    color: var(--lets-ink);
    border-radius: 9px;
    padding: 9px 13px;
    font-size: 12.5px;
    font-weight: 700;
    cursor: pointer;
    transition: transform .12s ease, box-shadow .14s ease, background .14s ease, border-color .14s ease;
}
.lets-linerail__btn:hover { transform: translateY(-1px); box-shadow: var(--lets-shadow); }
.lets-linerail__btn--go {
    background: var(--th-accent, var(--lets-teal));
    border-color: transparent;
    color: #fff;
}
.lets-linerail__btn--go:hover { filter: brightness(.94); color: #fff; }
/* The moves that demand a written reason are the consequential ones. They
   are deliberately NOT the accent colour: the safe action should be the one
   the eye lands on first. */
.lets-linerail__btn--warn { border-color: var(--lets-amber); color: #8a5d16; }
.lets-linerail__btn--warn:hover { background: #fdeccb; }

/* HELD, NOT MISSING.
   A delivery in an open LQA round keeps its Inprogress / Remove / Approve
   buttons -- the owner asked to see them -- but none of them may fire until
   the review closes. They are drawn flat and faded with the reason as their
   title, so hovering answers "why can I not press this" rather than leaving
   somebody to conclude the screen is broken. `not-allowed` and the killed
   hover-lift are what stop it reading as an ordinary button that ignored the
   click. Every tone is overridden, or a held Approve would still be solid
   teal and look perfectly pressable. */
.lets-linerail__btn.is-held,
.lets-linerail__btn.is-held:hover {
    background: var(--lets-surface-2, #f4f5f7);
    border-color: var(--lets-border);
    color: var(--lets-ink-2);
    opacity: .62;
    cursor: not-allowed;
    filter: none;
    transform: none;
    box-shadow: none;
}

.lets-linerail__back {
    display: block;
    padding: 6px 0;
    font-size: 12px;
    font-weight: 650;
    color: var(--lets-ink-2);
    text-decoration: none;
}
.lets-linerail__back:hover { color: var(--th-accent, var(--lets-teal)); text-decoration: none; }

/* The rail drops under the content once there is no room for two columns --
   below the panels rather than above them, because on a narrow screen the
   thing you came to read should not be pushed off the first screenful. */
@media (max-width: 1100px) {
    .lets-linepage { flex-direction: column; }
    .lets-linerail { position: static; width: 100%; flex-basis: auto; }
    .lets-linerail__actions { flex-direction: row; flex-wrap: wrap; }
    .lets-linerail__btn { width: auto; }
}

/* ---- THE BULK ACTION BAR ------------------------------------------------
   Appears only once rows are ticked (the owner: "action button should be
   appear on select and checkbox"), between the tiles and the grid, so the
   thing that acts sits next to the thing being acted on. */
.lets-bulkbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    padding: 9px 14px;
    margin-bottom: 12px;
    border: 1px solid var(--lets-teal);
    border-radius: var(--lets-radius);
    background: var(--lets-tint);
    animation: letsBulkIn .16s ease both;
}
@keyframes letsBulkIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

.lets-bulkbar__left,
.lets-bulkbar__right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.lets-bulkbar__count { font-weight: 700; font-size: 12.5px; color: var(--lets-ink); }

/* Each action carries its own weight: the safe one reads as the default,
   the destructive one is never the easiest thing to hit by accident. */
.lets-bulkbar__btn { font-weight: 700; border-width: 1px; }
.lets-bulkbar__btn--ok   { background: var(--lets-green); border-color: var(--lets-green); color: #fff; }
.lets-bulkbar__btn--warn { background: #fff; border-color: var(--lets-amber); color: #8a5d16; }
.lets-bulkbar__btn--bad  { background: #fff; border-color: var(--lets-red);   color: var(--lets-red); }
.lets-bulkbar__btn--ok:hover   { filter: brightness(.94); color: #fff; }
.lets-bulkbar__btn--warn:hover { background: #fdeccb; color: #8a5d16; }
.lets-bulkbar__btn--bad:hover  { background: #fdebec; color: var(--lets-red); }

/* The outcome line, under the tiles. Short-lived -- it reports what just
   happened, it is not a status the page keeps. */
.lets-drivenote {
    padding: 8px 13px; margin-bottom: 12px;
    border-radius: var(--lets-radius); border: 1px solid var(--lets-border);
    font-size: 12.5px; font-weight: 650;
}
.lets-drivenote.is-ok   { background: #e2f5e9; border-color: #b7e4c7; color: #15803d; }
.lets-drivenote.is-warn { background: #fdeccb; border-color: #f0cd8f; color: #8a5d16; }
.lets-drivenote.is-bad  { background: #fdebec; border-color: #f5c2c7; color: #b02a30; }

/* ---- The reason prompt (Hold / Reject / Hold Payment) ---- */
.lets-reason {
    position: fixed; inset: 0; z-index: 1200;
    display: flex; align-items: center; justify-content: center;
    padding: 20px; background: rgba(9,30,26,.45);
}
.lets-reason__panel {
    width: 100%; max-width: 460px;
    background: var(--lets-surface); border-radius: var(--lets-radius);
    padding: 20px 22px; box-shadow: 0 24px 60px -20px rgba(9,30,26,.5);
}
.lets-reason__title { margin: 0 0 4px; font-size: 16px; font-weight: 800; color: var(--lets-ink); }
.lets-reason__sub   { margin: 0 0 12px; font-size: 12.5px; color: var(--lets-ink-2); }
.lets-reason__err   { margin: 6px 0 0; font-size: 12px; font-weight: 650; color: var(--lets-red); }
.lets-reason__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }

/* ---- DEADLINES ----------------------------------------------------------
   Vendor and client deadlines sit side by side and are NOT the same date --
   the vendor deadline is deliberately earlier. Overdue is red; the rest is
   quiet, so that red keeps meaning something. */
.lets-date { font-variant-numeric: tabular-nums; white-space: nowrap; }
.lets-date--late { color: var(--lets-red); font-weight: 600; }
.lets-date--soon { color: var(--lets-amber); }

/* ---- THE SIX-TAB LINE MODAL --------------------------------------------- */
.lets-linetabs > .nav-tabs {
    border-bottom: 1px solid var(--lets-border);
}

.lets-linetabs > .nav-tabs > li > a {
    color: var(--lets-ink-2);
    font-size: 13px;
    border-radius: var(--lets-radius-s) var(--lets-radius-s) 0 0;
}

.lets-linetabs > .nav-tabs > li.active > a,
.lets-linetabs > .nav-tabs > li.active > a:hover,
.lets-linetabs > .nav-tabs > li.active > a:focus {
    color: var(--lets-teal-deep);
    border-color: var(--lets-border) var(--lets-border) transparent;
    font-weight: 600;
}

/* Client and vendor blocks are the two SIDES of the money, so they are
   labelled by colour as well as by heading -- the same column names in both
   tables otherwise read as a duplicate. */
.lets-side {
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    overflow: hidden;
    margin-bottom: 16px;
}

.lets-side__head {
    padding: 8px 12px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    border-bottom: 1px solid var(--lets-border);
}

.lets-side--client .lets-side__head {
    background: var(--lets-tint);
    color: var(--lets-teal-deep);
}

.lets-side--vendor .lets-side__head {
    background: #eef7f0;                       /* a green wash of --lets-green */
    color: var(--lets-green);
}

.lets-side table { margin-bottom: 0; font-size: 12.5px; }
.lets-side th { font-size: 11px; text-transform: uppercase; color: var(--lets-ink-2); white-space: nowrap; }
.lets-side td, .lets-side th { vertical-align: middle !important; }
.lets-side .lets-num { font-variant-numeric: tabular-nums; }

/* The row for the linguist whose line was opened. */
.lets-side tr.is-current > td {
    background: var(--lets-tint);
    font-weight: 600;
}

/* ---- MONEY STATES -------------------------------------------------------
   Three DIFFERENT things that must never look alike:
     no cost    internal work, costless by definition
     no rate    nobody has priced it YET  (amber -- unfinished, not broken)
     an amount  the frozen figure */
.lets-money { font-variant-numeric: tabular-nums; white-space: nowrap; }
.lets-money--none  { color: var(--lets-muted); font-style: italic; }
.lets-money--unset { color: var(--lets-amber); font-weight: 600; }

/* "PO Received: No" -- a flag, not a field. */
.lets-flag--no  { color: var(--lets-red); font-weight: 700; }
.lets-flag--yes { color: var(--lets-green); font-weight: 700; }

/* ---- THE HISTORY TIMELINE ----------------------------------------------- */
.lets-timeline { list-style: none; margin: 0; padding: 0 0 0 18px; position: relative; }

.lets-timeline::before {
    content: "";
    position: absolute;
    left: 4px; top: 4px; bottom: 4px;
    width: 2px;
    background: var(--lets-hairline);
}

.lets-timeline__item { position: relative; padding: 0 0 14px 14px; }

.lets-timeline__item::before {
    content: "";
    position: absolute;
    left: -18px; top: 5px;
    width: 10px; height: 10px;
    border-radius: 50%;
    background: var(--lets-surface);
    border: 2px solid var(--lets-teal);
}

.lets-timeline__what { font-weight: 600; color: var(--lets-ink); }
.lets-timeline__meta { font-size: 12px; color: var(--lets-muted); }
.lets-timeline__why  { font-size: 12.5px; color: var(--lets-ink-2); font-style: italic; }

/* ---- SECTION BANDS ------------------------------------------------------
   PROJECT DETAILS / TASK DETAILS / CLIENT TASK DETAILS, as the working screen
   has them: a quiet full-width band rather than a heavy card header. */
.lets-band {
    background: var(--lets-sunken);
    border: 1px solid var(--lets-border);
    border-left: 3px solid var(--lets-teal);
    border-radius: var(--lets-radius-s);
    padding: 8px 12px;
    margin: 0 0 14px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--lets-ink-2);
}

/* ---- SMALL SCREENS ------------------------------------------------------
   The count grid cannot usefully shrink -- ten numeric columns is ten numeric
   columns -- so it scrolls inside its own box rather than forcing the whole
   page sideways. */
.lets-scroll-x {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

@media (max-width: 991px) {
    .lets-tile { padding: 12px; }
    .lets-tile__count { font-size: 22px; }
}

/* The tile row.
   A flex grid rather than Bootstrap columns: col-lg-3 inside a .row applies
   negative margins that fight .content's padding, and the eighth tile ran off
   the right-hand edge. This was only visible by LOOKING at the rendered page.
   The tiles size themselves, wrap when they run out of room, and never
   overflow. */
/* ONE SCROLLING STRIP, not a wrapping grid -- LE2026's own layout.
   Twelve tiles will not fit four-across on any sensible window, and wrapping
   them into three rows pushes the grid itself below the fold. A single row
   that scrolls sideways keeps every counter one gesture away and costs one
   line of vertical space. */
.lets-tiles {
    display: flex;
    flex-wrap: nowrap;
    gap: 10px;
    overflow-x: auto;
    padding: 2px 2px 10px;
    margin-bottom: 6px;
}

.lets-tiles .lets-tile { margin-bottom: 0; }

/* A thin, quiet scrollbar -- the strip is scrolled rarely and a full-height
   bar under the tiles reads as a divider. */
.lets-tiles::-webkit-scrollbar { height: 6px; }
.lets-tiles::-webkit-scrollbar-track { background: transparent; }
.lets-tiles::-webkit-scrollbar-thumb { background: var(--lets-border); border-radius: 3px; }
.lets-tiles::-webkit-scrollbar-thumb:hover { background: var(--lets-muted); }

/* Tasks Drive's PREV/NEXT buttons either side of the tile strip -- see the
   HTML comment on .lets-tiles-wrap in drive.php for why these exist instead
   of trusting the native scrollbar above to be noticeable/grabbable.
   `position: relative` on the wrap is what lets the buttons sit `absolute`,
   vertically centred, without affecting the tiles' own layout at all. */
.lets-tiles-wrap {
    position: relative;
}
.lets-tiles__nav {
    position: absolute;
    top: 50%;
    /* Centred on the TILES, not the wrap -- the wrap includes the 10px
       bottom padding/margin the scrollbar sits in, which would otherwise
       pull the buttons a few px low. Half a tile's typical height, tuned
       against a screenshot rather than guessed. */
    transform: translateY(-58%);
    z-index: 5;
    width: 28px; height: 28px;
    border-radius: 50%;
    border: 1px solid var(--lets-border);
    background: #fff;
    color: var(--lets-ink-2);
    box-shadow: 0 2px 6px rgba(31,56,77,.15);
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
}
.lets-tiles__nav:hover { background: var(--lets-tint, #f4f6f8); color: var(--lets-ink); }
/* `[hidden]` over `display:none` in JS, so re-showing it is one attribute
   removal rather than a style write -- same idiom as the bulk-action bar a
   few rules below this in drive.php. */
.lets-tiles__nav[hidden] { display: none; }
.lets-tiles__nav--prev { left: -8px; }
.lets-tiles__nav--next { right: -8px; }

/* ==========================================================================
   TASKS DRIVE -- FLEX-FILL LAYOUT   (22 Aug 2026)
   --------------------------------------------------------------------------
   This grid's height used to be `height: calc(100vh - Npx)`, N hand-measured
   against everything else stacked on the page (tiles strip, box header,
   pager, app footer) -- see the git history on this rule if it's still
   there: 292 -> 285 -> 295 in one day, and the LAST of those was still
   wrong on the owner's own screen ("your 10px didn't reflected anything
   still truncating from the bottom... if scrolled then cards get
   truncations"). A constant can only ever be right for the exact window
   size, font rendering and scrollbar style it was measured against --
   headless Chrome (used to measure it here) is provably not those things.

   Flex needs no constant. #driveBoard (its id, drive.php) is a flex column
   filling `.content-wrapper`'s own already-exact height; the tiles strip
   and the box header keep their natural size, and the grid's OWN scroll
   area is the one piece told to take "whatever is left" -- recomputed by
   the browser on every reflow (window resize, a tile strip that wraps to a
   second line, anything), not guessed once and left to drift.

   >>> flex-grow:0, NOT :1, ON THE BOX/GRID CHAIN. <<< (later the same day)
   The first version forced the card to ALWAYS claim the full remaining
   height, even for a tile with one row -- the owner, looking at Rejected
   with a single row and a card stretched almost to the footer: "compress
   the tables size dynamically according to the data they have" for a short
   tab, but keep "max length... depending on screensize" for a full
   (20-row) one.

   flex-shrink alone does both with no extra rule: flex-grow:0 lets a short
   table size to its own content (one header + one row), and flex-shrink:1
   with min-height:0 still caps a tall table (a full page of 20 rows,
   whose intrinsic height exceeds the space available) at exactly the room
   #driveBoard has -- the internal scrollbar then takes the rest, same as
   the flex-grow:1 version did for that case. Grow forces every card to the
   same height regardless of content; shrink only ever prevents a card from
   overflowing. That asymmetry is exactly the behaviour asked for. */
#driveBoard {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}
#driveBoard > .lets-tiles-wrap { flex: 0 0 auto; }
#driveBoard > .box.box-solid {
    flex: 0 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
#driveBoard > .box.box-solid > .box-header { flex: 0 0 auto; }
#driveBoard > .box.box-solid > .box-body {
    flex: 0 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
#driveGrid.lets-grid {
    flex: 0 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* The generic `.lets-grid` (lets-grid.css) carries margin-bottom: 18px
       for a grid sitting in normal block flow, spacing it from whatever
       comes after. Here the grid IS sized to exactly fill what its flex
       parent (.box-body) has left -- that margin would just be 18px of
       reserved space nothing fills, pushing the pager row up short of the
       box's own bottom edge. The box itself (.box.box-solid, Bootstrap's
       own margin-bottom) already spaces the card from the footer. */
    margin-bottom: 0;
}
/* Overrides the generic `.lets-grid__scroll` (lets-grid.css) fixed
   `height: calc(100vh - Npx)` -- this grid's own height comes from its flex
   parent chain above instead. #driveGrid's ID beats that rule's plain
   class without needing !important. */
#driveGrid .lets-grid__scroll {
    height: auto;
    flex: 0 1 auto;
    min-height: 0;
}
#driveGrid .lets-grid__foot { flex: 0 0 auto; }

/* The grid is wide enough (many columns) to need its own horizontal
   scrollbar too, beneath the last row -- 3px instead of the generic 7px
   (lets-grid.css) so it reads as a thin edge, not a bar. Same #driveGrid
   scoping as the height rule above, for the same reason. */
#driveGrid .lets-grid__scroll::-webkit-scrollbar { height: 3px; }

/* >>> [hidden] LOSES TO A CLASS SELECTOR -- FOR THE THIRD TIME. <<<
   Section 6m recorded it on the client form, D88 on the multiselect option,
   and here it is again on a .btn: Bootstrap's `.btn { display: inline-block }`
   and the browser's `[hidden] { display: none }` have the SAME specificity, so
   source order decides and the button stays visible however many times the
   JavaScript sets hidden = true.

   Caught by SCREENSHOTTING the page -- "Clear filter" was sitting there with
   no filter applied. Reading the code would never have shown it.

   If something you set `hidden` on is still visible, this is why. */
.btn[hidden] { display: none !important; }

/* >>> [hidden] ON A FILE INPUT -- the same trap, a fourth time. <<<
   AdminLTE styles `input[type=file]` with a display value, which beats the
   browser's `[hidden]` rule at equal specificity, so the raw "Choose Files"
   control sat next to the styled Attach files button. Caught by looking at
   the rendered page, not by reading the markup. */
input[type="file"][hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
   THE COUNT GRID, MADE COMPACT ENOUGH TO FIT
   ---------------------------------------------------------------------------
   The owner's LE2026 grid fits fourteen columns on one screen with no
   horizontal scrollbar, and the rebuilt one did not: 72px inputs plus default
   cell padding pushed ASSIGNED TO and VENDOR DEADLINE off the edge.

   Everything below is about reclaiming width. The numbers are chosen so that
   nine count boxes + two computed cells + the two pickers land inside a
   1366px screen, which is the narrowest the owner actually uses.
   --------------------------------------------------------------------------- */
.lets-countgrid {
    table-layout: fixed;      /* stop long language names stealing width */
    width: 100%;
    font-size: 12px;
}

.lets-countgrid th,
.lets-countgrid td {
    padding: 4px 3px !important;   /* Bootstrap's 8px each side is the biggest win */
}

.lets-countgrid th {
    font-size: 10px;
    letter-spacing: 0;
}

/* The nine count boxes. Narrow, right-aligned, no spinner -- the arrows eat
   ~16px per box and nobody clicks them on a word count. */
.lets-countgrid input[type="number"] {
    width: 100%;
    min-width: 0;
    padding: 2px 4px;
    height: 26px;
    font-size: 12px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    -moz-appearance: textfield;
}

.lets-countgrid input[type="number"]::-webkit-outer-spin-button,
.lets-countgrid input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* Column widths, in the order the header declares them. Percentages rather
   than pixels so the whole thing still breathes on a wide monitor. */
.lets-countgrid col.lets-col-lang     { width: 11%; }
.lets-countgrid col.lets-col-count    { width: 5.4%; }
.lets-countgrid col.lets-col-computed { width: 4.6%; }
.lets-countgrid col.lets-col-assignee { width: 16%; }
.lets-countgrid col.lets-col-deadline { width: 12%; }

/* The computed cells are output, not input -- no box around them, so they
   read as answers and take almost no width. */
.lets-countgrid__computed {
    display: inline-block;
    padding: 0 2px;
    font-size: 12px;
}

/* >>> A LOCKED BOX HAS TO LOOK LOCKED. <<<
   When the activity is word-based the HOURS box is disabled, and vice versa.
   Greyed and struck through says "not applicable here" rather than "broken":
   the same distinction lets-multiselect draws between disabled and hidden. */
.lets-countgrid input[type="number"]:disabled {
    background: var(--lets-hairline);
    color: var(--lets-muted);
    cursor: not-allowed;
}

/* The ASSIGNED TO widget inside a dense row. */
.lets-countgrid .lets-ms__button {
    min-height: 26px;
    padding: 2px 22px 2px 6px;
    font-size: 12px;
}

.lets-countgrid .lets-ms__chip {
    font-size: 11px;
    padding: 0 4px;
}

.lets-countgrid input[type="datetime-local"] {
    width: 100%;
    min-width: 0;
    height: 26px;
    padding: 2px 4px;
    font-size: 11px;
}

/* Only scroll when the screen genuinely cannot hold it -- on a normal monitor
   there is no bar at all now. */
@media (min-width: 1300px) {
    .lets-countgrid-wrap { overflow-x: visible; }
}

/* ---------------------------------------------------------------------------
   LETS.datetime  --  the DD-MM-YYYY picker
   ---------------------------------------------------------------------------
   Styled to match lets-multiselect, because they sit next to each other on the
   task grid and two different-looking dropdowns on one row reads as an
   accident.
   --------------------------------------------------------------------------- */
.lets-dt { position: relative; }

.lets-dt__input { font-variant-numeric: tabular-nums; }

/* A date that could not be parsed is MARKED, never silently corrected --
   rewriting somebody's half-typed date is how the wrong day gets saved. */
.lets-dt__input.is-bad {
    border-color: var(--lets-red);
    background: #fff5f5;
}

.lets-dt__panel {
    position: absolute;
    z-index: 1060;                /* above the card, like the multiselect panel */
    top: calc(100% + 4px);
    left: 0;
    /* >>> min-width BEATS max-width, SO IT HAS TO YIELD HERE ITSELF. <<<
       The JS clamps the panel to the viewport, and on a 240px screen that
       clamp was losing: `min-width: 250px` outranks any `max-width` the script
       sets, so the calendar still hung 18px off the edge with nothing to
       scroll to reach it. Capping the minimum against the viewport is the only
       version of this that a narrow screen cannot overrule. */
    min-width: min(250px, calc(100vw - 16px));
    padding: 8px;
    background: var(--lets-surface);
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    box-shadow: var(--lets-shadow);
}

.lets-dt__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}

.lets-dt__title {
    font-weight: 600;
    font-size: 13px;
    color: var(--lets-ink);
}

.lets-dt__nav {
    border: 1px solid var(--lets-border);
    background: var(--lets-sunken);
    border-radius: var(--lets-radius-s);
    width: 26px;
    height: 26px;
    line-height: 1;
    font-size: 16px;
    color: var(--lets-ink-2);
    cursor: pointer;
}

.lets-dt__nav:hover { border-color: var(--lets-teal); color: var(--lets-teal-deep); }

.lets-dt__grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}

.lets-dt__dow {
    text-align: center;
    font-size: 10px;
    text-transform: uppercase;
    color: var(--lets-muted);
    padding: 2px 0;
}

.lets-dt__day {
    border: 1px solid transparent;
    background: none;
    border-radius: var(--lets-radius-s);
    padding: 4px 0;
    font-size: 12px;
    color: var(--lets-ink);
    cursor: pointer;
    font-variant-numeric: tabular-nums;
}

.lets-dt__day:hover { background: var(--lets-tint); }
.lets-dt__day.is-today { border-color: var(--lets-teal); font-weight: 700; }

.lets-dt__day.is-selected {
    background: var(--lets-teal);
    border-color: var(--lets-teal);
    color: #fff;
    font-weight: 700;
}

.lets-dt__foot {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--lets-hairline);
    font-size: 12px;
}

.lets-dt__foot label { margin: 0; color: var(--lets-ink-2); font-weight: 600; }
.lets-dt__time { width: 92px; height: 26px; padding: 2px 4px; }

/* In the dense count grid the panel must not be squeezed by the cell. */
.lets-countgrid .lets-dt__panel { right: 0; left: auto; }

/* ---------------------------------------------------------------------------
   >>> AN OPEN DROPDOWN MUST NOT BE CLIPPED BY THE SCROLLER AROUND IT. <<<
   ---------------------------------------------------------------------------
   `.lets-scroll-x` exists so fourteen columns can scroll sideways instead of
   forcing the whole page to. But `overflow-x: auto` establishes a clipping box
   in BOTH directions -- there is no "scroll horizontally, overflow vertically"
   in CSS -- so the ASSIGNED TO panel was cut off at the bottom edge of the
   grid. That is the truncation the owner reported.

   The multiselect already marks its card `.lets-box--dropdown-open` while a
   panel is up (it raises the card for the same family of reasons). Hooking
   that flag lets the wrapper stop clipping for exactly as long as the panel
   is open, and go back to scrolling the moment it closes.

   `overflow: visible` on BOTH axes is deliberate: setting only overflow-y
   leaves overflow-x as `auto`, and a box with one axis visible and the other
   auto is treated as auto on both. That rule is why the obvious fix does not
   work.
   --------------------------------------------------------------------------- */
.lets-box--dropdown-open .lets-scroll-x,
.lets-box--dropdown-open .lets-countgrid-wrap {
    overflow: visible;
}

/* The panel itself has to sit above the rows that follow it. */
.lets-countgrid .lets-ms__panel {
    z-index: 1080;
}

/* A grid row containing an OPEN picker is lifted above its siblings -- table
   rows paint in document order, so the next language row would otherwise
   cover a panel hanging below this one. */
.lets-countgrid tr:has(.lets-ms.is-open),
.lets-countgrid tr.lets-row--dropdown-open {
    position: relative;
    z-index: 1080;
}



/* ---------------------------------------------------------------------------
   AND THE DROPDOWNS, NOW THAT THE RIGHT THING SCROLLS
   ---------------------------------------------------------------------------
   With the content pane scrolling, a panel opening near the bottom is inside
   the scroller and comes into view naturally. This stops the pane's own
   overflow clipping it in the moment before that happens -- both widgets add
   the class while a panel is up and remove it on close.
   --------------------------------------------------------------------------- */
.content-wrapper.is-dropdown-open { overflow: visible; }

/* Clear the sticky chrome around them. */
.lets-ms__panel,
.lets-dt__panel { z-index: 1090; }

/* A language sending out more than came in. Amber, not red: on a
   translate-edit-proofread chain it is correct, so this is a "look at this",
   not an error. */
/* THE FIRST CELL ONLY. Applying this to every <td> drew a vertical amber line
   down the left edge of all fourteen columns -- the "standing lines" the owner
   reported, which read as corruption rather than as a flag. */
/* Two ways a language's counts can be wrong, marked apart on purpose:
   RED = more words going out than came in (money leaving that never arrived),
   AMBER = words nobody was given (work that is not happening). Same stripe,
   different colour, so a glance down the grid tells them apart. */
.lets-countgrid tr.lets-row--overrun > td:first-child {
    box-shadow: inset 3px 0 0 var(--lets-red, #dd4b39);
}

.lets-countgrid tr.lets-row--partial > td:first-child {
    box-shadow: inset 3px 0 0 var(--lets-amber);
}

/* ---------------------------------------------------------------------------
   CENTRED COLUMNS  (D91)
   ---------------------------------------------------------------------------
   The owner: "headings should be center aligned and input box text also so
   that page looks more aligned."

   D91 already set the rule for numeric columns -- centre the heading AND the
   box -- and this grid was not following it: the headings sat left while the
   digits were pushed right, so on a fourteen-column row a label and its value
   drifted far enough apart to read as different columns. That is the exact
   fault D91 was written to fix, reappearing here.
   --------------------------------------------------------------------------- */
.lets-countgrid th,
.lets-countgrid td.lets-num {
    text-align: center;
}

.lets-countgrid input[type="number"] {
    text-align: center;
}

/* The language name stays left -- it is a label, not a figure. */
.lets-countgrid th:first-child,
.lets-countgrid td:first-child {
    text-align: left;
}

/* ---------------------------------------------------------------------------
   >>> A PANEL RENDERED ON <body> WHILE IT IS OPEN. <<<
   ---------------------------------------------------------------------------
   Both widgets move their panel to <body> on open and put it back on close.
   That is the ONLY thing that stops the task grid clipping them, and the
   reason is worth keeping:

     - `position: absolute` is clipped by EVERY ancestor with an overflow
       rule, and that grid has three (table scroller, card, content pane).
     - `position: fixed` alone does not help either, because `.box` carries
       `backdrop-filter` and an ancestor with backdrop-filter BECOMES THE
       CONTAINING BLOCK for fixed descendants -- so the panel was measured
       against the card, not the viewport, and landed in the wrong place.

   On <body> there is no overflow to clip it and no backdrop-filter to capture
   it. The geometry comes from JavaScript, because only it knows where the
   control actually is; these rules just keep the stylesheet out of the way.
   --------------------------------------------------------------------------- */
body > .lets-ms__panel,
body > .lets-dt__panel {
    z-index: 2000;              /* above the pinned header, footer and cards */
    overflow-y: auto;           /* a long linguist list scrolls inside itself */
}

/* ---------------------------------------------------------------------------
   >>> THE LIST SCROLLS, NOT THE WHOLE PANEL. <<<
   ---------------------------------------------------------------------------
   It was the panel that scrolled, with the inner list flattened to
   `overflow: visible` so there was only ever one scrollbar. One scrollbar was
   right; putting it on the panel was not, because the head goes WITH it -- the
   filter box and Select all / Clear scrolled up out of sight the moment
   anybody went looking for an activity near the bottom of the list. The filter
   is the fastest way to the item they are scrolling towards, and it left just
   as it became useful.

   A flex column pins the head and gives the list the leftover room. The
   panel's own max-height (set by the JS, from the space actually available)
   still governs the whole thing; `min-height: 0` is what lets the list shrink
   inside it, since a flex item refuses to go below its content height without
   it and would push the panel back out of the viewport.
   --------------------------------------------------------------------------- */
body > .lets-ms__panel {
    display: flex;
    flex-direction: column;
    overflow: hidden;           /* the list below owns the scrollbar */
}

/* And the guard this file has earned the hard way (see the note below): a
   `display` here outranks the browser's `[hidden] { display: none }`, so the
   moment this rule started setting one it also had to say what hidden means.
   open() appends to <body> BEFORE clearing `hidden` and close() sets `hidden`
   BEFORE moving the panel home -- both synchronous, so neither paints in that
   state today, but a panel stranded on <body> would otherwise be a panel that
   cannot be shut. */
body > .lets-ms__panel[hidden] { display: none; }

body > .lets-ms__panel .lets-ms__head { flex: 0 0 auto; }

body > .lets-ms__panel .lets-ms__list {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;           /* the panel's max-height governs now */
    overflow-y: auto;
    /* Stop a wheel at the end of the list carrying on into the page behind
       it: that scrolls the page, and a page scroll closes the panel. */
    overscroll-behavior: contain;
}

/* ---------------------------------------------------------------------------
   >>> [hidden] LOSES TO A CLASS SELECTOR -- THE SIXTH TIME IN THIS CODEBASE.
   ---------------------------------------------------------------------------
   Bootstrap's `.help-block { display: block }` and the browser's
   `[hidden] { display: none }` have the SAME specificity, so source order
   decides and the element stays visible however many times the JavaScript
   sets hidden = true.

   THE SYMPTOM THIS TIME: the duplicate-task-name warning would not go away
   after the name was corrected -- it said "a task called 'test' already
   exists" while the box read "test1".

   Previously: the client form (section 6m), the multiselect option (D88), a
   .btn badge and a file input (14 Aug). If something you set `hidden` on is
   still on screen, look here FIRST.
   --------------------------------------------------------------------------- */
.help-block[hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
   THE THREE STEPS
   --------------------------------------------------------------------------- */

/* "Which job am I pricing?" -- the strip above the word-count grid. */
.lets-context {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    align-items: baseline;
    padding: 10px 14px;
    margin-bottom: 12px;
    background: var(--lets-tint);
    border: 1px solid var(--lets-border);
    border-left: 3px solid var(--lets-teal);
    border-radius: var(--lets-radius-s);
    font-size: 13px;
    color: var(--lets-ink-2);
}

.lets-context__item strong { color: var(--lets-ink); }

/* A band that carries an action on its right. */
.lets-band--withaction {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

/* Back / Next / Save. */
.lets-stepnav {
    display: flex;
    align-items: center;
    gap: 8px;
}

.lets-stepnav .pull-right { margin-left: auto; }
.lets-stepnav .btn[disabled] { opacity: .45; cursor: not-allowed; }

/* ---------------------------------------------------------------------------
   THE INSTRUCTIONS DIALOG  --  a big writing surface that cannot overflow
   ---------------------------------------------------------------------------
   The owner asked for "a full page text box so that we can add instructions
   easily", and for it not to overflow the page. Those pull against each other,
   so the dialog is sized in VIEWPORT units and scrolls INSIDE itself: it is as
   large as the screen allows and never larger.

   The editor is the part that grows. The header, the help line and the
   buttons stay fixed, so Done is always reachable however long the
   instructions get -- an editor that pushes its own Save button off screen is
   the fault this shape avoids.
   --------------------------------------------------------------------------- */
#instructionsModal .lets-modal__dialog {
    width: 92vw;
    max-width: 1100px;
    max-height: 92vh;
    display: flex;
    flex-direction: column;
}

/* min-height: 0 is what actually lets a flex child shrink and scroll --
   without it the body keeps its content height and pushes the footer out. */
#instructionsModal .lets-modal__body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

#instructionsModal .lets-modal__head,
#instructionsModal .lets-modal__foot {
    flex: 0 0 auto;
}

/* Quill: the toolbar is fixed, the writing area takes the rest and scrolls. */
#instructionsEditor {
    flex: 1 1 auto;
    min-height: 320px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

#instructionsEditor .ql-toolbar { flex: 0 0 auto; }

#instructionsEditor .ql-container {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    font-size: 14px;
}

/* ---------------------------------------------------------------------------
   FILES & LINKS  --  one row per linguist, icon buttons under named headings
   ---------------------------------------------------------------------------
   Icon-only keeps the row narrow enough to read across; the COLUMN HEADING is
   what says which pile each button fills, so nothing depends on recognising an
   icon. Every button also carries a title for the hover.
   --------------------------------------------------------------------------- */
.lets-filegrid { font-size: 13px; }

.lets-filegrid th {
    background: var(--lets-sunken);
    font-size: 10px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--lets-ink-2);
    text-align: center;
    white-space: nowrap;
}

.lets-filegrid th:first-child,
.lets-filegrid th:nth-child(2) { text-align: left; }

.lets-filegrid td { vertical-align: middle !important; }
.lets-filegrid td.lets-filecell { text-align: center; white-space: nowrap; }

/* A file picker behind a styled label gives no feedback at all, so the count
   beside it is what tells you the pick worked. */
.lets-filegrid .file-count,
.lets-filegrid .link-count,
.lets-filegrid .cred-count {
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

/* The "must have attachments" tick. It changes whether the task can be SAVED,
   so it is given more weight than an ordinary checkbox. */
.lets-requires {
    padding: 10px 12px;
    margin: 0 0 14px;
    background: var(--lets-tint);
    border: 1px solid var(--lets-border);
    border-left: 3px solid var(--lets-amber);
    border-radius: var(--lets-radius-s);
}

.lets-requires .help-block { color: var(--lets-ink-2); }

/* --------------------------------------------------------------------------
   A STEP-1 FIELD WAITING ON THE ONE ABOVE IT              (28 Aug 2026)
   --------------------------------------------------------------------------
   The owner asked for the Add Task details step to run in order: Sub-Project
   before the task name, Activity before the languages and the dates. The
   controls are `disabled` in JS (see applyDetailGates() in tasks/form.php --
   disabled, not readonly, so an empty required box cannot trip HTML5
   validation while it is still locked).

   A browser's own disabled styling is a slightly paler control, which reads as
   "broken" rather than "not yet". This says WAITING: the label dims with it,
   so the eye moves up to the field that is actually asking for something, and
   the cursor says not-allowed on the way past. The `title` carrying the reason
   is set alongside the class.

   Deliberately no `opacity` on the group: it would fade the help text and the
   red asterisk too, and those are the parts that explain what is wanted. -- */
.form-group.is-locked > label {
    color: var(--lets-ink-2);
    opacity: 0.7;
}

.form-group.is-locked .form-control[disabled],
.form-group.is-locked .lets-ms__button[disabled] {
    background: var(--lets-sunken);
    border-style: dashed;
    cursor: not-allowed;
}

/* The instructions editor, now inline rather than in a dialog. */
#instructionsEditor { background: var(--lets-surface); }
#instructionsEditor .ql-container { min-height: 340px; font-size: 14px; }

/* ===========================================================================
   >>> [hidden] WINS. FULL STOP. <<<
   ---------------------------------------------------------------------------
   The browser's own `[hidden] { display: none }` has the SAME specificity as
   any single-class rule, so source order decides -- and Bootstrap, AdminLTE
   and this stylesheet all set `display` on classes that get hidden in
   JavaScript. Setting `hidden = true` then does nothing at all, silently.

   THIS HAS NOW BITTEN SEVEN TIMES:
     1. the client form                      (section 6m)
     2. a multiselect option                 (D88)
     3. a .btn "Clear filter"                (14 Aug)
     4. an input[type=file]                  (14 Aug)
     5. a .help-block duplicate-name warning (15 Aug)
     6. a .label "added" badge               (15 Aug)
     7. a .label overrun flag, which stayed on screen with STALE numbers
        after the counts were corrected      (15 Aug)

   Seven times is not a run of bad luck, it is a missing rule. One global
   declaration ends the entire class of bug, and anything that genuinely needs
   to show a `hidden` element can say so with its own !important -- which is
   at least visible in the code rather than being an accident of load order.
   =========================================================================== */
[hidden] { display: none !important; }

/* The shared ASSIGNED TO / DEADLINE column carries a picker on language rows
   and a date box on linguist rows, so it needs the width both would have had. */
.lets-countgrid col.lets-col-assignee { width: 20%; }

/* The chosen file names, under their button. A count says "3 files" but not
   WHICH three, and picking the wrong folder is the mistake this catches. */
.lets-filegrid .file-names {
    margin-top: 3px;
    font-size: 10.5px;
    line-height: 1.35;
    color: var(--lets-ink-2);
    max-width: 140px;
    margin-left: auto;
    margin-right: auto;
}

.lets-filegrid .file-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The common row sits above the linguists and reads as a header for them. */
.lets-filegrid__common > td {
    background: var(--lets-tint);
    border-bottom: 2px solid var(--lets-border) !important;
}

.lets-filegrid__common .help-block { font-size: 11px; }

/* A language still missing its file. Amber, and named in place, because a
   global "attachments missing" does not say WHICH language is short. */
.lets-filegrid tr.lets-row--needsfile > td:first-child {
    box-shadow: inset 3px 0 0 var(--lets-amber);
}

.lets-filegrid__error {
    font-size: 11px;
    font-weight: 600;
    margin-top: 2px;
}

/* ==========================================================================
   PURCHASE ORDERS  --  the bits the CREATE SCREEN needs           (D158)

   The DOCUMENT's styling moved out to the `.po-doc` block further down, where
   it is shared by four designs. What is left here belongs to the form.
   ========================================================================== */

/* Wide tables scroll INSIDE their own box. Fourteen bucket columns will not fit
   a laptop, and a page that scrolls sideways as a whole loses the totals off
   the right-hand edge. */
.lets-po__scroll { overflow-x: auto; }

.lets-po__totals { border-collapse: collapse; font-size: 14px; min-width: 300px; }
.lets-po__totals td { border: 1px solid var(--lets-border); padding: 6px 12px; }
.lets-po__totals td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.lets-po__totals--rule td { border-top: 2px solid var(--lets-border); }

.lets-po__totals--grand td {
    background: var(--lets-tint);
    font-weight: 700;
    font-size: 16px;
    border-top: 2px solid var(--lets-teal);
}

/* On the Create screen the same table is live, so it needs to fill its column
   rather than hug its content. */
.lets-po__totals--live { width: 100%; }

/* Each action is its OWN form -- a reason typed for Hold must never be posted
   as the reason for Cancel. */
.lets-po__act {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-right: 18px;
    margin-bottom: 8px;
}

.lets-po__act .form-control { width: 260px; }

/* ==========================================================================
   THE CREATE SCREEN
   ========================================================================== */

.lets-po__pick { font-size: 11.5px; }
.lets-po__pick th, .lets-po__pick td { padding: 4px 6px !important; white-space: nowrap; }
.lets-po__pick th { text-align: center; }
.lets-po__pick td:nth-child(n+9) { text-align: right; }
.lets-po__amt { font-weight: 600; font-variant-numeric: tabular-nums; }

.lets-po__num { text-align: right; font-variant-numeric: tabular-nums; }

/* The percent field a tax tick reveals. Indented so it reads as belonging to
   the box above it rather than as a field of its own. */
.lets-po__taxrow { margin-left: 22px; max-width: 320px; }

/* >>> NARROW THE GROUP, NEVER THE INPUT INSIDE IT. <<<
   A Bootstrap 3 `.input-group` is `display: table` and its children are table
   cells, which size themselves to fill the table. Putting `max-width` on the
   input breaks that: the cell shrinks, the table does not, and the `%` addon
   is left floating a few pixels clear of the box it belongs to -- which is
   exactly how it shipped. Constrain the GROUP and the cells still meet. */
.lets-po__taxrow .input-group { width: 160px; }
.lets-po__taxrow .input-group .form-control { max-width: none; }

/* The bare (non-grouped) fields in the same block, kept to the same width so
   the column reads as one set rather than three unrelated boxes. */
.lets-po__pct:not(.input-group .form-control) { max-width: 160px; }
#poDiscount { max-width: 220px; }

/* ==========================================================================
   PRINT  --  layouts/print, and Ctrl+P on the ordinary page
   ========================================================================== */

body.lets-print { background: #fff; }
body.lets-print .content { padding: 0; }
body.lets-print .lets-po { border: 0; box-shadow: none; padding: 0; }

@media print {
    /* Anything that is not the document itself. The print LAYOUT already
       leaves these out; this covers Ctrl+P on the ordinary staff page. */
    .main-header, .main-sidebar, .main-footer, .lets-page__actions,
    .box-footer, .lets-po__act, .alert { display: none !important; }

    .content-wrapper { margin-left: 0 !important; background: #fff !important; }
    .lets-po { border: 0; box-shadow: none; padding: 0; }
    .lets-po__scroll { overflow: visible; }
    .lets-po__lines { font-size: 9.5px; }
}

/* --------------------------------------------------------------------------
   CREATE PO  --  keeping the four columns level                     (D158b)

   The owner: *"Repair the form quality as it is not formatted all boxes going
   updown."*

   >>> BOOTSTRAP 3's GRID IS FLOAT-BASED, AND FLOATS DO NOT LEVEL. <<<
   Four `.col-md-3` cells of different heights leave ragged bottoms, and the
   NEXT row snags on the tallest one instead of starting clean. The markup fix
   was one field per cell; this is the other half -- the row itself becomes a
   flex container, so every cell in it starts at the same top and stretches to
   the same height whatever it contains.

   Scoped to this form. The float grid is load-bearing on other screens and
   this must not reach them.
   -------------------------------------------------------------------------- */

/* >>> ONLY WHERE `col-md-*` ACTUALLY HAS A WIDTH. <<<
   Bootstrap 3 gives `.col-md-3` its 25% only at >= 992px. Below that the class
   carries NO width at all and the column is a plain block that stacks. Make
   the row a flex container unconditionally and those width-less columns become
   flex items sized to their CONTENT -- so on a phone the four fields shuffle
   onto one line at 139px, 111px and whatever is left, instead of stacking.

   Measured, not assumed: at a 600px viewport PO Date and Payment Term landed
   on the same line before this media query was added. */
@media (min-width: 992px) {
    #poForm .row {
        display: flex;
        flex-wrap: wrap;
        align-items: flex-start;
    }
}

/* A flex item ignores the float but still needs the grid's own width and
   gutter, which the .col-md-* classes already carry -- so nothing to restate.
   ONE margin, on the form group, is what keeps the rows evenly spaced; adding
   a second on the column as well is how they drift apart again. */
#poForm .form-group { margin-bottom: 16px; }

/* Labels on one line, so a two-word label cannot push its box down a row
   while its neighbours stay put -- the most visible half of "going updown". */
#poForm label {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-bottom: 5px;
}

#poForm label small { font-weight: 400; }

/* ==========================================================================
   >>> ONE HEIGHT FOR EVERY CONTROL. THIS IS THE ACTUAL BUG. <<<
   ==========================================================================
   Measured on the rendered page, this form held FOUR different control
   heights side by side:

        input[type=date]   54px      <- the worst by far
        select             37px
        text input         38px
        multiselect button 34px

   Nothing in the markup asks for that. `.form-control` in this theme is
   `height: auto; padding: 8px 11px` (line 638), so every control sizes itself
   from its own content -- and Chrome's date input is a shadow-DOM widget with
   its own generous internals, so it comes out 17px taller than the select
   beside it. Two date fields in a row of four is all it takes to make the
   whole form look like it is stepping up and down.

   Pinning the box is the fix. 38px is the text input's own natural height at
   this padding, so it is the one value that changes least.

   >>> textarea IS DELIBERATELY EXCLUDED. <<< It must grow with `rows`.

   SCOPED TO THIS FORM ON PURPOSE. `height: auto` is the theme's global
   choice and every other screen is laid out against it; correcting it
   everywhere is a separate job with its own regression pass, not something to
   slip in here. Worth doing -- any screen mixing a date field with a select
   has this same stagger today.
   ========================================================================== */
#poForm .form-control:not(textarea),
#poForm .lets-ms__button {
    height: 38px;
    min-height: 38px;
    line-height: 20px;
}

/* The date input's inner spinner is what was forcing 54px. With the box
   pinned above it has to fit, and this stops it padding itself out again. */
#poForm input[type="date"].form-control { padding-top: 0; padding-bottom: 0; }

#poForm .lets-ms__button {
    padding-top: 0;
    padding-bottom: 0;
    flex-wrap: nowrap;
    overflow: hidden;
}

/* A chip has to fit inside that height rather than setting it. */
#poForm .lets-ms__chip {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ==========================================================================
   SUB-PROJECT: Status / Start Date / End Date / Term -- SAME BUG AS ABOVE.
   The Status and Term selects render ~37px tall while the two native
   input[type=date] boxes render ~54px (see "ONE HEIGHT FOR EVERY CONTROL"
   above for why: Chrome's date widget is a shadow-DOM control with its own
   generous internals). In this row that 17px gap shows up as End Date's
   help text sitting visibly lower than Term's, right beside it.

   Same fix, scoped to this form for the same reason -- .lets-form__cols is
   shared by every screen built on the new field() partial, and pinning
   every control's height there is a wider change that needs its own pass. */
#projectForm .form-control:not(textarea) {
    height: 38px;
    min-height: 38px;
    line-height: 20px;
}
#projectForm input[type="date"].form-control { padding-top: 0; padding-bottom: 0; }

/* Worked out, not typed. Readable but plainly not an input you fill in --
   `readonly` rather than `disabled`, because a disabled field is dropped from
   the form and these are only ever read. */
.lets-po__readonly {
    background: var(--lets-sunken) !important;
    font-weight: 600;
    cursor: default;
}

.lets-po__readonly:focus { box-shadow: none; border-color: var(--lets-border); }

/* The two prose boxes. Equal height, and tall enough that the terms are not
   delivered pre-scrolled with their third line cut in half. */
.lets-po__prose {
    min-height: 132px;
    resize: vertical;
    line-height: 1.5;
}

/* ==========================================================================
   THE PURCHASE ORDER DOCUMENT  --  four designs, one page          (D158c)

   The owner: *"Please make a beautiful PO format ... make 3-4 samples ...
   which have watermark as well as well professional look. and our table width
   is long so it should fit in those PO format."*

   `.po-doc` is everything the four share: the A4 page, the watermark, the
   number and wrap helpers, and the print rules. Each design then owns exactly
   one block below it -- `.po-classic`, `.po-modern`, `.po-minimal`, `.po-corp`
   -- and they never reach into each other.

   >>> THE TABLE IS NARROWED IN THE VIEW, NOT HERE. <<<
   `document.php` drops every word-count column that is zero on every line of
   the PO. Their own live document prints EIGHT columns of `0.00`, which is
   what makes it too wide for the page; no amount of font-size can fix that,
   and shrinking type to hide it is how a payment document becomes unreadable.
   ========================================================================== */

/* >>> THE SHEET IS `.po-doc`. THE BACKDROP SITS ON IT. THE CONTENT SITS ON
       TOP OF BOTH. <<<

   This was the other way round and the watermark was invisible on EVERY
   design -- `.po-doc__page` carried `background: #fff` AND `z-index: 1`, so
   the sheet painted straight over the absolutely-positioned watermark behind
   it. Measured, not guessed: the backdrop element was rendering at 945x945 and
   simply could not be seen.

   So the white, the border, the radius and the shadow belong to the OUTER
   element, and the inner one is transparent padding above the art.

   A4 at 96dpi is 794px. Capped there so the screen preview has the same
   measure as the print, and a design cannot look right in one and break in the
   other.

   `overflow: hidden` earns its place twice: it keeps the rounded corners, and
   it crops the globe to the sheet edge, which is what makes it read as part of
   the paper rather than as a picture floating behind it. */
.po-doc {
    /* >>> THE FILL AND THE TYPE ARE THE SAME COLOUR UNTIL THEY ARE NOT. <<<
       (D158y) `--ink` is what headings and kickers are SET IN; `--po-fill` is
       what the banner, the table head and the total bar are FILLED with, and
       those are lettered in white. In all ten designs so far they are one
       value, which is why this defaults to it -- nothing changes by splitting
       them. It exists because the owner wanted the company's own orange on the type
       and the earlier, browner orange behind it, and one token cannot be two
       colours. */
    --po-fill: var(--ink);
    position: relative;
    max-width: 794px;
    margin: 0 auto;
    overflow: hidden;
    color: #14201d;
    font-size: 12.5px;
    line-height: 1.5;
    background: #fff;
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius);
    box-shadow: var(--lets-shadow);
}

.po-doc__page {
    position: relative;
    z-index: 1;
    padding: 26px 30px 30px;
    background: transparent;
}

/* ---- THE WATERMARK ------------------------------------------------------
   The company name, or PREVIEW / CANCELLED when the document is one of those.
   `print-color-adjust` is what stops the browser helpfully dropping it from
   the printed copy -- which would remove the word PREVIEW from the one page
   where it matters most. */
.po-doc__wm {
    position: absolute;
    inset: 0;
    /* >>> GRID, NOT FLEX  --  AND THE DIFFERENCE IS WHY IT WRAPS. <<<
       The watermark is a bare text node, which becomes an ANONYMOUS item in
       whichever layout its parent uses. As a flex item it is sized to
       max-content and simply overflows; as a grid item it is sized to the
       track, which is the sheet, so it wraps. With flex it rendered as the
       single word "LANGU" clipped at both edges. */
    display: grid;
    /* `place-items: center` was still not enough: justify-self: center sizes
       the item to MAX-CONTENT. It has to STRETCH across the track before the
       text has any width to wrap against. Centred vertically, stretched
       horizontally, and the text centred inside it by `text-align`. */
    align-items: center;
    justify-items: stretch;
    z-index: 0;
    pointer-events: none;
    /* >>> IT HAS TO FIT THE SHEET, AND A COMPANY NAME IS NOT A FIXED LENGTH. <<<
       At 96px and `nowrap`, "LANGUEDGES IT SERVICES" measured 1327px against a
       794px page -- and now that the sheet clips (see `.po-doc`), that showed
       as the word "GUED" and nothing else. It wraps instead, and it is sized
       so a normal name lands on one line and a long one on two. */
    padding: 0 6%;
    font-size: 58px;
    line-height: 1.05;
    font-weight: 800;
    letter-spacing: 2px;
    text-align: center;
    text-transform: uppercase;
    /* >>> FAINT, BUT IT HAS TO BE READABLE. <<<
       Measured rather than eyeballed: the name renders as ONE 680px line
       inside a 699px space, so it was never clipped or wrapped -- it was
       simply too pale to perceive against white, and only showed where it
       crossed a shaded table row. That reads as a rendering fault rather than
       as branding, which is the opposite of the point. */
    color: rgba(14, 116, 144, .16);
    transform: rotate(-28deg);
    overflow-wrap: anywhere;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}

/* A preview must never be mistaken for the real thing. Red, and stronger. */
.po-doc--draft .po-doc__wm { color: rgba(229, 72, 77, .10); }

/* ==========================================================================
   >>> ON MODERN THE WATERMARK GOES IN FRONT, NOT BEHIND. <<<        (D158q)
   ==========================================================================
   The owner: *"Modern theme is not showing any watermark in its PDF preview or
   design so please add it there"*.

   Adding the element back was half the fix. Modern is built out of SOLID
   cards -- the teal banner, two facing panels, a filled table, the pay-by box
   -- so a watermark behind them shows only in the few gaps between, which is
   what "not showing any watermark" actually described. The other three designs
   are mostly unfilled, so behind is fine there and they are left alone.

   `mix-blend-mode: multiply` is what makes being in front safe: multiplying can
   only darken, never lighten, so it tints white paper and all but disappears
   over the dark banner and header instead of laying a grey film across them.
   Nothing on the page becomes harder to read, which a flat overlay at any
   useful opacity would.

   Browsers without blend support fall back to the plain 16% colour drawn on
   top -- fainter than ideal, still legible underneath, never a blocker. */
.po-modern .po-doc__wm {
    z-index: 2;
    mix-blend-mode: multiply;
}

/* ==========================================================================
   THE FOOT OF EVERY PURCHASE ORDER  --  three parts, one line     (D158q)
   ==========================================================================
   The owner: *"In all the PO computer generate on left .. center PO number and
   right aligned Team language"*.

   `1fr auto 1fr` and not `space-between`: with three cells of wildly different
   widths, space-between centres NOTHING -- it only equalises the gaps, so the
   PO number drifts left or right depending on how long the team line is. Two
   equal side tracks put the middle cell on the page's centre line and keep it
   there. */
.po-doc__sign {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: baseline;
    gap: 4px 14px;
}
.po-doc__sign > :nth-child(2) { text-align: center; }
.po-doc__sign > :last-child   { text-align: right; }
.po-doc__signno { font-weight: 700; letter-spacing: .3px; font-variant-numeric: tabular-nums; }

.po-doc__num   { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.po-doc__wrap  { white-space: normal; min-width: 150px; }
.po-doc__wrap small { display: block; font-size: 10.5px; color: #7b8a86; }
.po-doc__muted { color: #93a29e; }
.po-doc__scroll { overflow-x: auto; }
.po-doc__rule td { border-top: 2px solid currentColor; }

.po-doc table { border-collapse: collapse; width: 100%; }

/* ==========================================================================
   1. CLASSIC  --  navy bars and boxes, the shape the team already knows
   ========================================================================== */
/* >>> EVERY COLOUR THIS DESIGN OWNS, IN ONE PLACE. <<<              (D158w)
   The rules below use these and never a literal, which is what lets a palette
   variant exist without a second copy of the design -- see `.po-doc--orange`
   after the four blocks. `--ink` is the strong colour: bars, heads, headings.
   `--po-tint` the pale fill under a heading row, `--po-rule` the box borders,
   `--po-hair` the lightest divider. */
.po-classic { --ink: #1f3864; --po-tint: #eef2f9; --po-rule: #b9c4d8; --po-hair: #d7dee9; }

.po-classic__head {
    display: flex; align-items: center; justify-content: space-between; gap: 16px;
    border-bottom: 3px solid var(--ink); padding-bottom: 12px;
}
/* >>> SIZED FOR THE REAL MARK, WHICH IS ~1.8:1. <<<
   Capping only the HEIGHT is what made it small: at 62px tall this logo comes
   out 110px wide and the strapline is unreadable. The width leads now and the
   height follows it. */
.po-classic__head img { max-width: 200px; max-height: 100px; width: auto; }
.po-classic__brandtext { font-size: 17px; font-weight: 700; color: var(--ink); }
.po-classic__head h1 {
    margin: 0; font-size: 25px; font-style: italic; font-weight: 700;
    letter-spacing: .5px; color: var(--ink);
}

.po-classic__meta { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; margin: 16px 0; }
.po-classic__idbox { width: auto; }
.po-classic__idbox th, .po-classic__idbox td { border: 1px solid var(--po-rule); padding: 4px 11px; }
.po-classic__idbox th { background: var(--po-tint); text-align: left; font-weight: 700; color: var(--ink); }
/* It was wrapping "70, Kandhari Nagar, 102 Anshi / Enclave" -- a flex item
   with no basis shrinks to whatever is left. A floor stops that. */
.po-classic__from { font-style: normal; text-align: right; flex: 0 0 auto; min-width: 290px; }
.po-classic__from strong { display: block; color: var(--ink); font-size: 15px; }
.po-classic__from span   { display: block; color: #5b6b78; }

.po-classic__party th, .po-classic__party td { border: 1px solid var(--po-rule); padding: 8px 11px; vertical-align: top; }
.po-classic__party th {
    background: var(--po-fill); color: #fff; text-align: left;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}

.po-classic__lines { margin-top: 16px; font-size: 10.5px; }
.po-classic__lines th, .po-classic__lines td { border: 1px solid var(--po-rule); padding: 4px 6px; }
.po-classic__lines thead th {
    background: var(--po-tint); color: var(--ink); text-align: left; font-weight: 700;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
/* The design rule above outranks `.po-doc__num` on its own, so the money
   headings need saying again to stay over their figures. Same in minimal and
   corporate below. */
.po-classic__lines thead th.po-doc__num { text-align: right; }

.po-classic__foot { display: flex; justify-content: space-between; gap: 22px; margin-top: 18px; flex-wrap: wrap; }
.po-classic__bank { width: auto; min-width: 330px; flex: 1 1 330px; }
.po-classic__bank th, .po-classic__bank td { border: 1px solid var(--po-rule); padding: 5px 11px; }
.po-classic__bank th {
    background: var(--po-fill); color: #fff; text-align: left;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-classic__bank td:first-child { color: #5b6b78; width: 42%; }

.po-classic__totals { width: auto; min-width: 265px; align-self: flex-start; }
.po-classic__totals td { border: 1px solid var(--po-rule); padding: 6px 12px; }
.po-classic__totals td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.po-classic__grand td {
    background: var(--po-tint); font-weight: 800; font-size: 15px; color: var(--ink);
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}

.po-classic__terms { margin-top: 20px; font-size: 11.5px; color: #46555f; }
.po-classic__terms h2 {
    font-size: 12px; font-weight: 700; color: #fff; background: var(--po-fill);
    margin: 0 0 8px; padding: 4px 10px;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-classic__sign {
    margin-top: 22px; padding-top: 10px; border-top: 1px solid var(--po-hair);
    font-size: 11px; color: #6b7a85;
}

/* ==========================================================================
   2. MODERN  --  teal banner, facing cards, the total stated first
   ========================================================================== */
/* `--po-accent2` is the banner gradient's second stop; `--po-band` the
   shading on alternate jobs. See the note on `.po-classic` above. */
.po-modern { --ink: #0e7490; --po-accent2: #12a3b8; --po-band: #f5f9fa; }

/* >>> THE LOGO STRIP. WHITE, SO NOTHING HAS TO SIT BEHIND THE LOGO. <<<
   Both PNG marks are fully transparent; the white rectangle that was reported
   was a `background: #fff` on the image itself, added because the lettering is
   dark navy and vanished into a dark banner. A light strip removes the plate
   AND the contrast problem, and it is the only fix that also works for the
   third company, whose logo is a JPG and so cannot be transparent at all. */
/* Logo left, company right and right-aligned -- the owner: *"on the top
   company name deatils should be on right side, and right side aligned"*. */
.po-modern__top {
    display: flex; align-items: center; justify-content: space-between; gap: 24px;
    padding: 0 4px 14px;
}
.po-modern__top img { max-width: 200px; max-height: 76px; width: auto; flex: 0 0 auto; }
.po-modern__co { text-align: right; }
.po-modern__co strong { display: block; font-size: 15px; color: var(--ink); letter-spacing: .3px; }
.po-modern__co span   { display: block; font-size: 11.5px; color: #5c6863; }

/* ==========================================================================
   >>> THE FLAT COLOUR UNDER THE GRADIENT IS NOT DECORATION. <<<
   ==========================================================================
   The banner is the ONLY gradient anywhere in these documents, and it is the
   one block that came out BLANK in a server-generated PDF -- a white gap
   between the letterhead and the cards, with the white text invisible on it.

   `print-color-adjust: exact` was already here and is not the problem. The
   problem is that Chrome's **headless `--print-to-pdf`** drops
   `background-image` gradients while honouring flat `background-color`. Every
   other filled block on the sheet -- the table head, the Net-payable band --
   is a flat colour, which is exactly why they printed and this did not.

   A hand-pressed Ctrl+P renders the gradient perfectly, which is why the
   purchase order the owner printed by hand looked right while the invoice
   filed by `InvoiceDocument` did not. Same CSS, two different rasterisers.

   So the colour is declared TWICE, deliberately: `background-color` is the
   floor every renderer agrees on, and `background-image` is the nicety on top
   for whichever ones can. Never collapse these back into one `background:`
   shorthand -- the shorthand resets `background-color` to transparent, which
   is the bug.
   ========================================================================== */
.po-modern__banner {
    display: flex; justify-content: space-between; align-items: center; gap: 20px;
    background-color: var(--po-fill);
    background-image: linear-gradient(135deg, var(--po-fill), var(--po-accent2));
    color: #fff; border-radius: 12px; padding: 15px 22px;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-modern__banner > div:first-child strong { display: block; font-size: 19px; letter-spacing: .4px; }
.po-modern__kicker {
    display: block; font-size: 10px; text-transform: uppercase;
    letter-spacing: 1.4px; opacity: .85;
}
.po-modern__headline { text-align: right; flex: 0 0 auto; }
.po-modern__headline strong { display: block; font-size: 27px; font-weight: 800; line-height: 1.1; }

.po-modern__cards { display: flex; gap: 14px; margin: 18px 0; }
.po-modern__cards article {
    flex: 1; border: 1px solid var(--lets-border); border-radius: 10px; padding: 13px 15px;
}
/* >>> BOTH CARDS ARE THE SAME CARD NOW. <<<
   "Payment to" used to be a tinted panel and now wears the plain bordered card
   the remittance block below uses, at the owner's request -- so WHO is paid
   (here) and HOW they are paid (below the total) read as one pair rather than
   as two unrelated panels. The tint rule went with it. */
.po-modern__cards h3 {
    margin: 0 0 7px; font-size: 10px; text-transform: uppercase;
    letter-spacing: 1.3px; color: var(--ink);
}
.po-modern__cards strong { font-size: 14px; }
.po-modern__cards p { margin: 3px 0; color: #5c6863; }
.po-modern__cards dl { margin: 9px 0 0; display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; font-size: 11.5px; }
.po-modern__cards dt { color: #93a29e; }
.po-modern__cards dd { margin: 0; font-weight: 600; }

.po-modern__note {
    background: #fffaf0; border-left: 3px solid #d9a441; padding: 8px 12px;
    margin: 0 0 14px; font-size: 11.5px;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}

.po-modern__lines { font-size: 10.5px; }

/* >>> LEFT, HEADINGS AND DATA ALIKE -- MONEY EXCEPTED. <<<
   Centred was the first ask; the owner then saw `minimal` beside it and
   settled it: *"this alignment looks more clean .. instead of center aligned
   it should be left aligned"* (D158p). Every text column now starts on the
   same edge, so the eye runs straight down it instead of hunting for each
   column's middle. The money columns keep their RIGHT edge, headings included,
   because figures line up on the decimal or they cannot be compared -- that is
   what `.po-doc__num` marks, and it is the one exception in all four designs. */
.po-modern__lines th,
.po-modern__lines td { text-align: left; }
.po-modern__lines th.po-doc__num,
.po-modern__lines td.po-doc__num { text-align: right; }

.po-modern__lines th {
    background: var(--po-fill); color: #fff; padding: 8px 6px; font-weight: 600;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-modern__lines th:first-child { border-radius: 7px 0 0 0; }
.po-modern__lines th:last-child  { border-radius: 0 7px 0 0; }
.po-modern__lines td { padding: 8px 6px; vertical-align: middle; }

/* >>> ONE BAND PER JOB. <<<
   The owner: *"add some light grey row around the tasks"*. Stated on the rows
   of a job rather than by `nth-child`, because a job is two rows here -- the
   line and its breakdown -- and striping by position would shade half of one
   and half of the next. The separating rule goes on the LAST row of each job,
   so jobs are divided from each other and not from their own detail. */
.po-modern__lines tbody .po-modern__band > td {
    background: var(--po-band);
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-modern__lines tbody tr > td { border-bottom: 1px solid #e4ecee; }
.po-modern__lines tbody tr.po-modern__band + tr.po-doc__bd > td { border-bottom: 0; }
.po-modern__lines tbody tr.po-doc__bd > td { border-bottom: 1px solid #e4ecee; }

.po-modern__foot { display: flex; justify-content: flex-end; margin-top: 18px; }

/* WHO is paid, and HOW -- side by side, under the figure they refer to.
   Half the width each is what makes a long branch address wrap instead of
   stretching the page, which it did as a full-width `dl` row. */
.po-modern__remit { display: flex; gap: 16px; margin-top: 18px; }

.po-modern__remit article {
    flex: 1 1 0; min-width: 0;
    border: 1px solid var(--lets-border); border-radius: 10px; padding: 12px 15px;
}
.po-modern__remit h3 {
    margin: 0 0 7px; font-size: 10px; text-transform: uppercase;
    letter-spacing: 1.3px; color: var(--ink);
}
.po-modern__remit strong { font-size: 13.5px; }
.po-modern__remit p { margin: 3px 0; color: #5c6863; overflow-wrap: anywhere; }
.po-modern__remit dl {
    margin: 0; display: grid; grid-template-columns: auto 1fr;
    gap: 3px 12px; font-size: 11.5px;
}
.po-modern__remit dt { color: #93a29e; white-space: nowrap; }

/* The branch line is the long one. It wraps here rather than pushing the
   grid wider, which is what stretched the page before. */
.po-modern__remit dd { margin: 0; font-weight: 600; min-width: 0; overflow-wrap: anywhere; }

/* >>> ONE CARD, FULL WIDTH  --  AND THESE MUST COME AFTER THE RULES ABOVE. <<<
   Same specificity as `.po-modern__remit dl`, so source order is what decides
   which wins. Written before it, the two-pairs-per-row grid silently lost and
   the list stayed in one column.

   Two label/value pairs per row, so a single full-width card does not strand
   its values against the far edge of the page. */
.po-modern__remit--single article { flex: 1 1 100%; }
.po-modern__remit--single dl { grid-template-columns: auto 1fr auto 1fr; column-gap: 18px; }

.po-modern__totals { width: auto; min-width: 280px; align-self: flex-start; }
.po-modern__totals td { padding: 6px 12px; border-bottom: 1px solid var(--lets-hairline); }
.po-modern__totals td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.po-modern__grand td {
    background: var(--po-fill); color: #fff; font-weight: 800; font-size: 16px;
    border: 0; -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-modern__grand td:first-child { border-radius: 8px 0 0 8px; }
.po-modern__grand td:last-child  { border-radius: 0 8px 8px 0; }

.po-modern__terms { margin-top: 20px; font-size: 11.5px; color: #5c6863; }
.po-modern__terms h3 {
    margin: 0 0 6px; font-size: 10px; text-transform: uppercase;
    letter-spacing: 1.3px; color: var(--ink);
}
/* Layout comes from `.po-doc__sign` now -- no `text-align: center` here, or it
   would win on source order and put all three cells in the middle. */
.po-modern__sign {
    margin-top: 20px; padding-top: 10px; border-top: 1px solid var(--lets-hairline);
    font-size: 10.5px; color: #93a29e;
}

/* ==========================================================================
   3. MINIMAL  --  no fills, no boxes; typography does the work
   ========================================================================== */
/* Minimal has no accent by design -- its `--ink` IS near-black, and the
   hairlines it rules with are that same ink. Which is why it takes an orange
   palette as readily as the others: there is nothing to swap but the ink. */
.po-minimal { --ink: #14201d; font-size: 12px; }

.po-minimal__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; }
.po-minimal__head img { max-width: 160px; max-height: 78px; width: auto; margin-bottom: 8px; display: block; }
.po-minimal__head h1 { margin: 0; font-size: 16px; font-weight: 700; letter-spacing: .3px; }
.po-minimal__head p  { margin: 1px 0; color: #7b8a86; font-size: 11px; }
.po-minimal__title { text-align: right; }
.po-minimal__title span {
    display: block; font-size: 10px; text-transform: uppercase; letter-spacing: 2.4px; color: #7b8a86;
}
.po-minimal__title strong { font-size: 17px; letter-spacing: .4px; }

.po-minimal__facts {
    display: flex; gap: 28px; margin: 22px 0;
    border-top: 1px solid var(--ink); border-bottom: 1px solid #dfe5e3; padding: 11px 0;
}
.po-minimal__facts span {
    display: block; font-size: 9.5px; text-transform: uppercase; letter-spacing: 1.6px; color: #93a29e;
}
.po-minimal__facts strong { font-size: 12.5px; }

.po-minimal h2 {
    font-size: 9.5px; text-transform: uppercase; letter-spacing: 2px; color: #93a29e;
    margin: 0 0 6px; padding-bottom: 4px; border-bottom: 1px solid #dfe5e3;
}
.po-minimal__h { margin-top: 22px; }
.po-minimal__to p { margin: 2px 0; color: #46555f; }
.po-minimal__name { font-size: 14px; font-weight: 700; color: var(--ink); }
.po-minimal__note { margin-top: 8px !important; font-style: italic; color: #6b7a85; }

.po-minimal__lines { font-size: 10.5px; margin-bottom: 4px; }
.po-minimal__lines th {
    text-align: left; font-weight: 700; padding: 6px 6px;
    border-bottom: 1.5px solid var(--ink); font-size: 9.5px;
    text-transform: uppercase; letter-spacing: .7px;
}
.po-minimal__lines th.po-doc__num { text-align: right; }
.po-minimal__lines td { padding: 7px 6px; border-bottom: 1px solid #edf1f0; }

.po-minimal__money { margin-top: 16px; margin-left: auto; width: 300px; }
.po-minimal__money dl { display: grid; grid-template-columns: 1fr auto; gap: 4px 16px; margin: 0; }
.po-minimal__money dt { color: #7b8a86; }
.po-minimal__money dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.po-minimal__grand {
    display: flex; justify-content: space-between; align-items: baseline;
    margin: 9px 0 0; padding-top: 9px; border-top: 1.5px solid var(--ink);
}
.po-minimal__grand span { font-size: 10px; text-transform: uppercase; letter-spacing: 1.8px; color: #93a29e; }
.po-minimal__grand strong { font-size: 22px; font-weight: 800; }

.po-minimal__bank, .po-minimal__terms { margin-top: 22px; }
.po-minimal__bank p, .po-minimal__terms { color: #46555f; font-size: 11px; }
.po-minimal__sign {
    margin-top: 26px; padding-top: 9px; border-top: 1px solid #dfe5e3;
    font-size: 10px; color: #a7b3af; letter-spacing: .4px;
}

/* ==========================================================================
   4. CORPORATE  --  dark band, ruled frame, and the only signature block
   ========================================================================== */
.po-corp { --ink: #17313f; --po-tint: #eef2f4; --po-rule: #cdd6da; }

/* The same reasoning as `.po-modern__top` -- the logo gets a light strip and
   the dark band keeps only the title, which needs no plate behind it. */
.po-corp__top {
    display: flex; align-items: center; gap: 16px;
    padding: 0 2px 13px;
}
.po-corp__top img { max-width: 190px; max-height: 78px; width: auto; }
.po-corp__top strong { display: block; font-size: 15px; letter-spacing: .4px; color: var(--ink); }
.po-corp__top span   { display: block; font-size: 11px; color: #5b6b78; }

.po-corp__band {
    background: var(--po-fill); color: #fff; padding: 11px 20px;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-corp__band h1 {
    margin: 0; font-size: 19px; font-weight: 300;
    text-transform: uppercase; letter-spacing: 5px;
}
.po-corp__refs { display: flex; border: 1px solid var(--ink); border-top: 0; }
.po-corp__refs div { flex: 1; padding: 8px 13px; border-right: 1px solid var(--po-rule); }
.po-corp__refs div:last-child { border-right: 0; }
.po-corp__refs span {
    display: block; font-size: 9px; text-transform: uppercase; letter-spacing: 1.3px; color: #7b8a86;
}
.po-corp__refs strong { font-size: 12.5px; }

.po-corp__frame { border: 1px solid var(--po-rule); border-top: 0; padding: 16px; }

.po-corp__parties { display: flex; gap: 16px; margin-bottom: 16px; }
.po-corp__parties article { flex: 1; }
.po-corp h2 {
    font-size: 9.5px; text-transform: uppercase; letter-spacing: 1.6px;
    color: var(--ink); margin: 0 0 6px; padding-bottom: 3px; border-bottom: 2px solid var(--ink);
}
.po-corp__parties strong { font-size: 13.5px; }
.po-corp__parties em { display: block; color: #7b8a86; font-size: 11px; }
.po-corp__parties p { margin: 3px 0; color: #46555f; }
.po-corp__terms-inline { font-size: 11px; }

.po-corp__lines { font-size: 10.5px; }
.po-corp__lines th, .po-corp__lines td { border: 1px solid var(--po-rule); padding: 5px 6px; }
.po-corp__lines thead th {
    background: var(--po-tint); text-align: left; font-weight: 700; color: var(--ink);
    font-size: 9.5px; text-transform: uppercase; letter-spacing: .5px;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-corp__lines thead th.po-doc__num { text-align: right; }

.po-corp__foot { display: flex; justify-content: space-between; gap: 20px; margin-top: 16px; flex-wrap: wrap; }
.po-corp__bank { width: auto; min-width: 320px; flex: 1 1 320px; }
.po-corp__bank th, .po-corp__bank td { border: 1px solid var(--po-rule); padding: 5px 10px; }
.po-corp__bank th {
    background: var(--po-tint); text-align: left; color: var(--ink);
    font-size: 9.5px; text-transform: uppercase; letter-spacing: 1.1px;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.po-corp__bank td:first-child { color: #7b8a86; width: 42%; }

.po-corp__totals { width: auto; min-width: 275px; align-self: flex-start; }
.po-corp__totals td { border: 1px solid var(--po-rule); padding: 6px 12px; }
.po-corp__totals td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.po-corp__grand td {
    background: var(--po-fill); color: #fff; font-weight: 800; font-size: 15px;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}

.po-corp__terms { margin-top: 18px; font-size: 11px; color: #46555f; }

.po-corp__sign { display: flex; gap: 40px; margin-top: 30px; }
.po-corp__sign div { flex: 1; }
.po-corp__sigline { display: block; border-bottom: 1px solid #14201d; height: 34px; margin-bottom: 5px; }
.po-corp__sign strong { display: block; font-size: 11px; }
.po-corp__sign em { color: #93a29e; font-size: 10.5px; }

.po-corp__endnote {
    margin-top: 14px; padding-top: 9px; border-top: 1px solid #e4ecee;
    font-size: 10px; color: #93a29e; letter-spacing: .4px;
}

/* ==========================================================================
/* ==========================================================================
   THE ORANGE PALETTE  --  the same four designs, in orange        (D158w)
   ==========================================================================
   The owner, 21 Aug 2026: *"Create copy of all template in Orange theme"*,
   then: *"Same formats just Orange color for font table and add them as
   addtional templates"*.

   >>> A COPY OF THE PALETTE, NOT A COPY OF THE DESIGN. <<<
   Ten `.php` templates would have been the literal reading of "copy", and it
   is the one thing this file must not do: five layouts already share their
   corrections badly enough (`_modern-body.php` exists precisely because two
   designs kept drifting apart), and ten would mean every future fix applied
   twice, with the second one forgotten. The layouts are untouched. Only the
   handful of custom properties above are re-pointed, and every rule in the
   four blocks already reads them rather than a literal.

   ==========================================================================
   >>> THE ORANGE IS THE COMPANY'S OWN, SAMPLED FROM THE LOGO. <<<   (D158x)
   ==========================================================================
   The owner asked for the accent to be the orange from the company logo,
   used for table backgrounds and type.

   So it was read off the file rather than matched by eye. Every non-grey pixel
   of the mark was counted; it is one flat orange with an anti-aliased edge,
   and its core is **#ff4a18** -- hue 13 degrees.

   >>> THE COMPANY NAME WAS REMOVED FROM THIS COMMENT ON 25 AUG 2026. <<<
   A stylesheet is SERVED. Every comment in this file is readable by anyone who
   opens it in a browser, which makes it the one kind of comment that is not
   private -- and it named a customer.

   ---------------------------------------------------------------------------
   >>> AND #ff4a18 CANNOT CARRY TEXT. THAT IS WHY --ink IS NOT IT. <<<
   ---------------------------------------------------------------------------
   Measured, not assumed: against white it is **3.36:1**, whichever way round
   you use it. As a table-header fill that means white lettering at 3.36:1, and
   as a heading colour it means orange lettering on paper at 3.36:1. WCAG asks
   4.5:1 for normal text, and this is a document people read amounts off and
   then print, where the contrast only gets worse.

   `--ink` is therefore the SAME HUE darkened until white text on it clears the
   bar -- 82% of the logo value, #d13d14, which measures 4.79:1 and is hue 13
   degrees to the decimal. Side by side with the logo it reads as the same
   orange, because it is: only the lightness moved.

   >>> THE LOGO COLOUR ITSELF IS STILL ON THE PAGE. <<< It is `--po-accent2`,
   the bright end of the modern banner's gradient, where it is a large filled
   area carrying no small text and every one of its 255 counts.

   The tint, rule, hair and band are that same hue mixed toward white -- 88%,
   80%, 94% and 97% -- so a table that read as quiet stays quiet.

   The watermark follows the ink. The globe does NOT: it stays neutral grey,
   because D158i settled that a coloured line at that weight reads as a mark
   somebody made on the sheet rather than as part of the paper -- and that is
   true of orange exactly as it was of teal.

   Source order is doing real work here. `.po-doc--orange` and `.po-classic`
   are both one class, so the later one wins; this block must stay BELOW all
   four designs. */
.po-doc--orange {
    /*
     * >>> THE TYPE AND THE BARS ARE TWO DIFFERENT ORANGES. <<<         (D158z)
     * The owner, having compared them side by side: *"can we use this Modern
     * (orange, burnt bars) color to all oragne them in table .. font will use
     * the current color just change the table css color"*.
     *
     * So `--ink` is untouched -- the company's own orange, sampled from the logo and
     * darkened to 82% so white can be read on it (D158x) -- and everything the
     * bars are FILLED with goes to the browner orange the palette started on.
     *
     * It is the better colour for the job on the measurements too, which is
     * worth recording rather than treating as luck: white on #b4470f is
     * 5.46:1, against 4.79:1 on the the company fill it replaces. The bars carry
     * their lettering more comfortably than the brand colour did.
     */
    --ink: #d13d14;          /* the logo orange at 82% -- the TYPE            */
    --po-fill: #b4470f;      /* the bars, the table head, the total row       */
    --po-accent2: #ea8a2b;   /* the bright end of the modern banner gradient  */

    /*
     * The pale fills are deliberately LEFT on the the company hue. They are 13
     * degrees against the fill's 20 -- a difference nobody sees on a wash this
     * light -- and re-deriving them from the burnt orange would have taken the
     * heading type on `classic` and `corporate` from 4.11:1 to 4.03:1 against
     * its own band. Neither number is good (see the note in D158z); making one
     * worse for a hue shift that cannot be perceived is not a trade.
     */
    --po-tint: #ffe9e3;
    --po-rule: #ffdbd1;
    --po-hair: #fff4f1;
    --po-band: #fffaf8;
}

.po-doc--orange .po-doc__wm { color: rgba(209, 61, 20, .13); }
@media print {
    /* Lighter on paper, for the reason the grey one is (D158s): a tint that
       reads as faint on a screen comes off a printer as a solid shape. */
    .po-doc--orange .po-doc__wm { color: rgba(209, 61, 20, .10) !important; }
}
   PRINTING THE DOCUMENT  --  all four
   ========================================================================== */
@page { size: A4; margin: 12mm; }

@media print {
    .po-doc {
        max-width: none; margin: 0;
        border: 0; border-radius: 0; box-shadow: none;
        overflow: visible;
    }
    .po-doc__page { padding: 0; }

    /* The whole point of narrowing the table in the view is that it now fits.
       This is the safety net for a PO that genuinely uses every bucket. */
    .po-doc__scroll { overflow: visible; }
    .po-doc table  { font-size: 9px; }

    /* A row must not be split across two pages -- half a line item with its
       amount overleaf is unreadable on a payment document. */
    .po-doc tr    { page-break-inside: avoid; }
    .po-doc thead { display: table-header-group; }

    /* Keep the signature block and the totals with what they refer to. */
    .po-corp__sign, .po-classic__foot, .po-modern__foot { page-break-inside: avoid; }

    /* >>> THE TERMS MAY TAKE A SECOND PAGE. <<<                       (D158q)
       The owner: *"even we can show these condition in second page .. but
       please include those as well"*. So they are explicitly ALLOWED to break,
       rather than being squeezed or dropped -- but the heading never parts
       company with the paragraph it introduces. */
    .po-classic__terms, .po-minimal__terms, .po-modern__terms, .po-corp__terms {
        page-break-inside: auto;
    }
    .po-classic__terms h2, .po-minimal__terms h2,
    .po-modern__terms h3, .po-corp__terms h2 { page-break-after: avoid; }

    /* The footer carries the PO number, which is what makes a second sheet
       identifiable. It must not be cut in half to do it. */
    .po-doc__sign { page-break-inside: avoid; }

    /* ======================================================================
       >>> THE BACKDROP ON PAPER: LIGHTEST GREY, AND SMALLER. <<<     (D158s)
       ======================================================================
       The owner: *"it is showing Water mark in full black also glob size too
       big. Please use lightest grey for print watermark and glob"*.

       The BLACK was Bootstrap's print reset and is fixed in the shell -- see
       `layouts/print.php`. These rules are the rest of the request, and they
       are print-only for a reason: ink and a backlit screen are not the same
       medium. A 16% tint that reads as a faint watermark on a monitor comes
       off a laser printer as a solid grey shape, because the paper does not
       glow through it. Toner also spreads, so the globe's hairlines thicken.

       So on paper both drop to a tenth, the watermark goes GREY rather than
       brand teal -- a coloured tint at this weight reads as a printing fault
       rather than as branding, which is the same reasoning D158i settled the
       globe's colour on -- and the globe comes down from 43% of the sheet to
       30%. At full size on a real A4 sheet it dominates the bottom third; the
       owner is right that it is too big once it is actually on paper.

       `!important` on these two only. Everything else on the document is safe
       now that the print shell no longer loads Bootstrap, but a person can
       still hit Ctrl+P on the ordinary screen view, where it IS loaded -- and
       `.po-doc__wm` outranks its `*` selector, so at least the two things that
       printed as slabs of black cannot do it again. */
    .po-doc__wm {
        color: rgba(125, 134, 137, .13) !important;
        -webkit-print-color-adjust: exact; print-color-adjust: exact;
    }
    .po-doc__globe {
        color: rgba(140, 149, 152, .10) !important;
        /* `!important` on the geometry for a duller reason than the colour:
           this print block sits ABOVE the globe's own rules in this file, so at
           equal specificity the values below would win on source order. */
        /* >>> THE SAME SHAPE AS THE SCREEN, JUST SMALLER AND FAINTER. <<<
           (D158u) The crop is `.po-doc__globeclip`'s job now, on both media, so
           paper keeps the corner-edge reading rather than growing a full
           sphere. Only the SIZE differs, and deliberately: at 43% on a real
           sheet it dominates the bottom third. */
        width: 34% !important;
        -webkit-print-color-adjust: exact; print-color-adjust: exact;
    }

    /* PREVIEW and CANCELLED still have to shout, on paper most of all: a
       printed preview with no mark on it is a document somebody files. */
    .po-doc--draft .po-doc__wm { color: rgba(229, 72, 77, .16) !important; }
}

/* ==========================================================================
   THE PRINT PAGE'S OWN BUTTON                                     (D158t)
   ==========================================================================
   The owner: *"there is no button to to click Save then use print
   functionality and save it as PDF as direct save is not giving exact print"*.

   `?print=1` renders the document with no chrome, which is right for what
   comes out of the printer and left nothing to press: the only way on was
   Ctrl+P, and somebody who does not know that is stuck looking at a page that
   plainly wants to be printed.

   Fixed to the corner so it is reachable on a long document without scrolling
   back, and `@media print` takes it away -- it must never appear on the sheet
   it produces. It is the one piece of chrome the print shell carries. */
.lets-print__bar {
    position: fixed;
    right: 18px;
    bottom: 18px;
    z-index: 50;
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 8px 10px;
    background: #fff;
    border: 1px solid var(--lets-border, #dbe3e6);
    border-radius: var(--lets-radius, 8px);
    box-shadow: 0 6px 22px rgba(20, 32, 29, .16);
}
.lets-print__bar .btn { margin: 0; }
.lets-print__hint { font-size: 11.5px; color: var(--lets-muted, #6b7a85); max-width: 190px; line-height: 1.35; }

@media print {
    .lets-print__bar { display: none !important; }
}

/* ==========================================================================
   THE PO DESIGN PICKER  --  on the company form                   (D158r)
   ==========================================================================
   The owner: *"from there user can select the formats by previewing a sample
   data"*. Five real documents, drawn small enough to compare and clickable.

   >>> THE THUMBNAIL IS A FULL-SIZE DOCUMENT, SCALED. <<<
   Not a smaller document. A design has to be judged at its real proportions --
   a table that only just fits A4 must look like it only just fits, and a
   "responsive" render at 240px would reflow into something the printer never
   produces. So the inner box is laid out at the true 794px and `scale()`
   shrinks the picture afterwards.

   The scale is a fixed number rather than a fraction of the column, which is
   why the columns are a FIXED 244px and not `1fr`: with fluid columns the
   scaled document and its frame would disagree at every width but one, and the
   card would show a slice of the page or a band of empty. */
.lets-podesign {
    display: grid;
    grid-template-columns: repeat(auto-fill, 196px);
    /* 14, not 18: five 196px cards plus four gaps is 1036px, which is what a
       1366-wide laptop leaves once the page chrome and a scrollbar are out of
       the way. Four more pixels of gap is the difference between all five in a
       row and the fifth alone on the next one. Below about 1300 it wraps, and
       that is fine -- what matters is that the common case does not. */
    gap: 14px;
    justify-content: start;
}

.lets-podesign__card {
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius);
    background: var(--lets-surface, #fff);
    overflow: hidden;
    transition: border-color .12s, box-shadow .12s;
}
.lets-podesign__card:hover { border-color: #9fb6bd; box-shadow: var(--lets-shadow); }

/* The chosen one is unmistakable at a glance across five near-identical
   cards -- a tinted border alone is not enough on a page this busy. */
.lets-podesign__card.is-chosen {
    border-color: var(--lets-primary, #0e7490);
    box-shadow: 0 0 0 2px var(--lets-primary, #0e7490) inset;
}

.lets-podesign__pick { display: block; margin: 0; cursor: pointer; font-weight: 400; }

/* The radio itself is the accessible control and stays keyboard-reachable --
   it is placed over the card's name row rather than hidden, so tabbing through
   the group shows a real focus ring. */
.lets-podesign__pick input { position: absolute; opacity: 0; pointer-events: none; }
.lets-podesign__pick input:focus-visible + .lets-podesign__frame {
    outline: 2px solid var(--lets-primary, #0e7490);
    outline-offset: -2px;
}

/* >>> SMALL ENOUGH THAT ALL FIVE SIT ON ONE ROW. <<<
   194px of 794 is 0.2443. The width was chosen from the narrow end of the
   screens this is used on, not from what looked comfortable in isolation: at
   244px a 1366-wide laptop fitted four and wrapped the fifth onto a row of its
   own, and a design you have to scroll to reach is not being compared with the
   others -- which is the single thing this control exists to do.

   250px of height is a crop rather than the whole sheet. The designs run from
   975 to 1149px tall, so at this scale it holds all but the last centimetre of
   every one of them, and the fade at the bottom says there is more. */
.lets-podesign__frame {
    display: block;
    position: relative;
    width: 194px;
    height: 250px;
    overflow: hidden;
    background: #fff;
    border-bottom: 1px solid var(--lets-border);
}
.lets-podesign__scale {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 794px;
    transform: scale(.2443);
    transform-origin: top left;
    /* A thumbnail is a picture of a document, not a document: nothing inside
       it should be selectable, focusable or clickable in its own right. */
    pointer-events: none;
    -webkit-user-select: none;
    user-select: none;
}
/* Inside a card the sheet is the card, so it drops its own frame. */
.lets-podesign__scale .po-doc {
    border: 0; border-radius: 0; box-shadow: none; margin: 0;
}
/* Says "there is more page below this" without a scrollbar that cannot scroll. */
.lets-podesign__frame::after {
    content: '';
    position: absolute; left: 0; right: 0; bottom: 0; height: 34px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #fff);
    pointer-events: none;
}

.lets-podesign__name { display: block; padding: 9px 11px 3px; }
.lets-podesign__name strong { display: block; font-size: 13px; color: var(--lets-ink, #14201d); }
.lets-podesign__name em {
    display: block; font-style: normal; font-size: 11px; line-height: 1.35;
    color: var(--lets-muted, #6b7a85); margin-top: 2px;
}

.lets-podesign__zoom { display: block; padding: 4px 11px 10px; font-size: 11.5px; }

@media (max-width: 560px) {
    /* One per row rather than a cut-off second column. The frame keeps its
       fixed width -- see the note above about why it cannot be fluid. */
    .lets-podesign { grid-template-columns: 196px; justify-content: center; }
}

@media print {
    /* A settings screen's picker has no business on paper. */
    .lets-podesign { display: none; }
}

/* ==========================================================================
   THE ISSUE BAR  --  Preview, Create, and what it will print as   (D158q)
   ==========================================================================
   The design picker that stood here is gone: the design belongs to the company
   and is set by an Admin. What is left states it. */
.lets-po__issue { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.lets-po__issue label { margin: 0; white-space: nowrap; }
.lets-po__issue-spacer { flex: 1 1 auto; }

.lets-po__design { font-size: 12px; color: var(--lets-muted, #6b7a85); }
.lets-po__design strong { color: var(--lets-ink, #14201d); }
.lets-po__design small { display: block; font-size: 10.5px; opacity: .8; }

/* ==========================================================================
   THE DESIGN GALLERY
   ========================================================================== */
.lets-po__gallerybar { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.lets-po__pick-po { display: flex; align-items: center; gap: 9px; margin: 0; }
.lets-po__pick-po label { margin: 0; white-space: nowrap; }
.lets-po__pick-po select { width: auto; min-width: 250px; }

.lets-po__sample { margin-bottom: 26px; }

.lets-po__samplebar {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 9px 14px; margin-bottom: 10px;
    background: var(--lets-sunken);
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    font-size: 13px; color: var(--lets-ink-2);
}

.lets-po__samplekey {
    display: inline-block; margin-right: 8px; padding: 2px 9px;
    background: var(--lets-teal); color: #fff; border-radius: 20px;
    font-size: 11px; font-weight: 700; letter-spacing: .4px;
}

/* >>> "PRINT THIS ONE" PRINTS ONE. <<<
   The button stamps `data-print-only="design-x"` on <body>; everything that is
   not that panel is then hidden for the duration of the dialog. Without it,
   printing from this page produces all four designs across a dozen sheets,
   which is exactly what somebody comparing them does NOT want. The flag is
   cleared on `afterprint`, so an ordinary Ctrl+P still prints the lot. */
@media print {
    body[data-print-only] .lets-po__sample { display: none; }
    body[data-print-only] .lets-po__sample.is-printing { display: block; }
    body[data-print-only] .lets-po__samplebar,
    body[data-print-only] .lets-po__gallerybar,
    body[data-print-only] .lets-page,
    body[data-print-only] .box-default { display: none !important; }
}

/* The breakdown sub-row: the word buckets a line used, under it rather than
   beside it. Reads as a footnote to the row above, never as another line item
   -- so no border between them, and the type is a step down. */
.po-doc__bd > td {
    padding-top: 0 !important;
    border-top: 0 !important;
    font-size: 9.5px;
    color: #7b8a86;
}

.po-doc__bd span { display: inline-block; margin-right: 13px; white-space: nowrap; }
.po-doc__bd strong { color: #46555f; font-variant-numeric: tabular-nums; }

/* ==========================================================================
   THE WIREFRAME GLOBE  --  background art for `modern`             (D158h)

   Sits BEHIND the page content (`.po-doc__page` is z-index 1) and bleeds off
   the right edge, so it reads as a watermark rather than as a picture somebody
   pasted in. Big and very faint: at this weight it gives the sheet some depth
   without competing with a single figure on it.

   `currentColor` is what the SVG strokes with, so the tint lives here in one
   place next to the rest of the palette.
   ========================================================================== */
/* >>> THE BOX THAT CROPS THE GLOBE. <<<                             (D158u)
   It holds the art and nothing else, so hiding its overflow can never reach a
   line of the document -- which is exactly why `.po-doc` cannot do the
   cropping in print, where it must stay `overflow: visible` for page breaks.
   Before this, screen and paper cropped differently and the printed copy grew
   a whole sphere the screen never showed. */
.po-doc__globeclip {
    position: absolute;
    inset: 0;
    overflow: hidden;
    z-index: 0;
    pointer-events: none;
}

.po-doc__globe {
    position: absolute;
    /* >>> BOTTOM-RIGHT, AND MOSTLY OFF THE PAGE. <<<
       The owner: *"its looking nice but move it on bottom right instead of
       upper right"*. Anchored to the corner and pushed out past it by 46% of
       its own size, so the sheet crops away most of the sphere and what is
       left reads as the edge of something much larger. Centred and nearly
       whole, it read as a picture sitting on the page instead.

       Down here it also sits under the terms and the sign-off rather than
       under the banner, so more of the 31% is actually on white and visible --
       see the note in D158i about those being two different numbers. */
    bottom: 0;
    right: 0;
    transform: translate(46%, 46%);
    /* >>> 43% IS THE LARGEST THAT CLEARS THE TERMS TEXT. <<<
       The owner: *"can we make this glob little small it is coming over the
       text"*, then *"a bit bigger"*. Measured, not eyeballed -- the shot script
       walks the terms and sign-off with a Range and reports how far any LINE
       reaches into the disc:

            40%  sphere 301  no overlap
            43%  sphere 324  no overlap   <- here
            44%  sphere 331  37px over "3. Localized files will go through..."
            52%  sphere 391  69px

       Testing against the terms BLOCK instead reported "overlapping" at every
       size and was useless: that block is full width, so its box always meets
       anything in this corner. Where the LINES end is the question.

       >>> THE FOOTER MEETS IT, AND THAT IS ACCEPTED. <<<              (D158q)
       The three-part footer put "TEAM ..." in this exact corner, so the shot
       script now reports {terms: 0, footer: 145}. The terms -- the text the
       owner complained about -- are still completely clear, and no size above
       about 11% would clear the footer as well, because both are anchored to
       the same corner. Faint grey line art under faint grey 10px caps costs
       nothing; shrinking the globe to a token would. */
    width: 43%;
    height: auto;
    z-index: 0;
    pointer-events: none;
    /* >>> GREY, NOT THE BRAND TEAL. <<<
       The owner: *"dont add blue color and keep it light grey color ...
       currently its like someone added ink and wrote something"*. A coloured
       line at low alpha reads as a MARK somebody made on the sheet -- ink,
       a stamp, a smudge. A neutral grey at the same weight reads as part of
       the paper, which is what a watermark has to do.

       Slightly more opaque than the teal was, per the same note: grey carries
       less on white than teal does, so matching the old alpha would have made
       it nearly vanish. */
    color: rgba(140, 149, 152, .16);
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}

/* >>> A PREVIEW STILL SHOUTS, EVEN OVER THE ART. <<< When both are on the
   page the word has to win, or a preview gets printed and treated as real. */
.po-doc--draft .po-doc__globe { color: rgba(140, 149, 152, .10); }

@media print {
    /* Fainter on paper: an ink-jet lays this down heavier than a screen shows
       it, and the sheet has to stay readable at arm's length. */
    .po-doc__globe { color: rgba(140, 149, 152, .12); }
}

/* The sub-task id, running on from the task name rather than dropping below it
   -- the owner: *"first line will show Task name then task ID in same line"*.
   Muted and a size down, so the name still leads the row. */
.po-doc__id {
    margin-left: 7px;
    font-size: 10.5px;
    font-weight: 400;
    color: #7b8a86;
}

/* ===========================================================================
   THE INVOICE DESIGN CHOOSER                                          (D163)
   ===========================================================================
   The owner: *"show them in right side explore window like PDF Pages on left
   and Big pages on right or presentation style"*.

   A reader's rail on the left, the sheet being looked at on the right. Both
   sides are the SAME rendered document -- `vendor-invoices/document-body`,
   through the real template, against a draft built from the linguist's own
   purchase order.

   >>> THE THUMBNAILS ARE SCALED RENDERS, NOT IMAGES. <<<
   `transform: scale()` on a real document, inside a clipped box. A screenshot
   would have been less work and would start lying the first time a template
   changed -- silently, on the one screen whose entire job is showing somebody
   what they are choosing.

   `transform` rather than `zoom`: zoom is non-standard, and Firefox only
   gained it in 126. A scaled element also keeps its ORIGINAL box for layout,
   which is why the wrapper below sets an explicit height.
   =========================================================================== */
.lets-designer {
    display: flex;
    gap: 18px;
    align-items: flex-start;
}

/* ---- LEFT: the page rail ------------------------------------------------ */
.lets-designer__rail {
    flex: 0 0 210px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    /* Its own scroll, so a long rail never pushes the stage down the page. */
    max-height: 78vh;
    overflow-y: auto;
    padding-right: 4px;
}

.lets-designer__page {
    display: block;
    width: 100%;
    padding: 0;
    background: #fff;
    border: 2px solid var(--lets-line, #dfe6e4);
    border-radius: 6px;
    cursor: pointer;
    text-align: left;
    overflow: hidden;
    transition: border-color .15s, box-shadow .15s;
}

.lets-designer__page:hover { border-color: var(--lets-teal-bright, #22d3ee); }

.lets-designer__page.is-current {
    border-color: var(--lets-teal, #0e7490);
    box-shadow: 0 0 0 3px rgba(14, 116, 144, .15);
}

/* The clipped window the shrunken document sits in. The height is the A4
   aspect of the visible slice -- enough to recognise a layout by, not the
   whole page, because a full sheet at this width is unreadable anyway. */
.lets-designer__thumb {
    display: block;
    height: 190px;
    overflow: hidden;
    background: #fff;
    border-bottom: 1px solid var(--lets-line, #dfe6e4);
    position: relative;
}

.lets-designer__shrink {
    display: block;
    width: 860px;              /* roughly the document's natural width ... */
    transform: scale(.235);    /* ... brought down to fit the rail */
    transform-origin: top left;
    pointer-events: none;      /* the BUTTON is the target, not the document */
}

.lets-designer__caption {
    display: block;
    padding: 7px 10px 9px;
}

.lets-designer__caption strong {
    display: block;
    font-size: 13px;
    color: var(--lets-ink, #111b19);
}

.lets-designer__caption small {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    line-height: 1.35;
    color: var(--lets-ink-2, #5c6863);
}

/* ---- RIGHT: the sheet --------------------------------------------------- */
.lets-designer__stage {
    flex: 1 1 auto;
    min-width: 0;              /* lets a wide table scroll rather than push */
    max-height: 78vh;
    overflow: auto;
    background: #eef1f0;
    border: 1px solid var(--lets-line, #dfe6e4);
    border-radius: 6px;
    padding: 16px;
}

/* The document already carries its own page furniture; the stage only frames
   it. `.po-doc__page` inside keeps the white sheet and the shadow. */
.lets-designer__sheet { margin: 0 auto; }

/* ---- THE PHONE ----------------------------------------------------------
   The rail goes horizontal and the thumbnails shrink further. Two columns of
   anything this size do not survive 600px, and the stage is what matters --
   so the rail becomes a strip of tabs above it. */
@media (max-width: 991px) {
    .lets-designer { flex-direction: column; }

    .lets-designer__rail {
        flex: 0 0 auto;
        flex-direction: row;
        width: 100%;
        max-height: none;
        overflow-x: auto;
        overflow-y: hidden;
    }

    .lets-designer__page { flex: 0 0 150px; }
    .lets-designer__thumb { height: 110px; }
    .lets-designer__shrink { transform: scale(.165); }
    .lets-designer__stage { max-height: 60vh; }
}

/* ============================================================================
   NEW OFFERS: AN ALREADY-OPENED ROW READS LIGHTER                       (D160)
   ============================================================================
   The owner: "opened link should have shown light in color so they look
   like opened already." Faded via opacity rather than a hand-picked colour
   per cell -- the row mixes a status label (BID/AUCTION/HEADSUP, its own
   fixed colour), plain text and a link, and opacity dims all three the same
   way without fighting any of their more specific selectors. A touch of
   contrast returns on hover, so a faded row stays legible and clickable
   rather than looking disabled. */
.lets-offerrow--seen { opacity: .6; transition: opacity .15s ease; }
.lets-offerrow--seen:hover { opacity: .85; }

/* ============================================================================
   NEW OFFERS: THE TAB BAR, REDESIGNED                                (22 Aug 2026)
   ============================================================================
   Replaces AdminLTE's `nav-tabs-custom` in offers.php -- a Bootstrap 3
   underline with a grey top-of-box border, the stock look every AdminLTE tab
   strip has had since 2016. This is a "segmented control" instead: each of
   the four tabs is its own button, and the ACTIVE one is filled solid rather
   than picked out with a thin underline -- which of the four is open should
   read in one glance, not take a second scan for the highlighted word among
   four black ones.

   >>> BUTTON CORNERS, NOT PILLS. <<<                       (27 Aug 2026)
   The owner: *"should not be round .. please make their border slightly curv
   but not llook like pill."* They were `border-radius: 999px`, which fully
   rounds a 40px-tall tab into a lozenge. Now on --lets-radius-s, the SAME
   corner every .btn in the app already has, so the strip reads as a row of
   buttons that belongs to the rest of the interface.

   >>> AND IT FOLLOWS THE CHOSEN THEME, NOT A FIXED TEAL. <<<
   Written before the five themes existed, this block filled the active tab
   with --lets-teal-bright (#22d3ee, a neon cyan) whatever the person had
   picked -- so on Ember or Rose the one loud element on the page was a colour
   from no theme at all. Every accent below is now --th-accent/--th-accent-deep
   /--th-tint, the same three .btn-primary reads.

   Only the OUTER wrapper class changed in offers.php (nav-tabs-custom ->
   lets-offertabs); `.nav-tabs`, `.tab-content` and `.tab-pane` inside it are
   still plain Bootstrap, and Bootstrap's own tab.js still drives switching
   between them via `[data-toggle="tab"]` -- nothing here touches how a click
   is handled, only how the row of tabs looks. */
.lets-offertabs {
    background: var(--lets-surface);
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius);
    box-shadow: 0 1px 2px rgba(17,27,25,.04);
    padding: 18px 18px 0;
    margin-bottom: 20px;
}
.lets-offertabs > .nav-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    border-bottom: none;
    margin: 0 0 18px;
}
/* Bootstrap 3's own `.nav-tabs > li` floats left; the flex row above already
   places them, and a float left in would only fight it. */
.lets-offertabs > .nav-tabs > li { float: none; }
.lets-offertabs > .nav-tabs > li > a {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    margin: 0;
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    background: #fff;
    color: var(--lets-ink-2);
    font-weight: 650;
    font-size: 13.5px;
    transition: background .15s ease, color .15s ease, border-color .15s ease,
                box-shadow .15s ease;
}
.lets-offertabs > .nav-tabs > li > a:hover,
.lets-offertabs > .nav-tabs > li > a:focus {
    background: var(--th-tint);
    border-color: var(--th-accent);
    color: var(--th-accent-deep);
}
.lets-offertabs > .nav-tabs > li > a > .fa { font-size: 13px; opacity: .8; }

.lets-offertabs > .nav-tabs > li.active > a,
.lets-offertabs > .nav-tabs > li.active > a:hover,
.lets-offertabs > .nav-tabs > li.active > a:focus {
    background: linear-gradient(135deg, var(--th-accent), var(--th-accent-deep));
    border-color: transparent;
    color: #fff;
    /* Neutral, not a teal glow: the fill above changes colour with the theme
       and a shadow tinted to one of the five would be wrong under the other
       four. Same value .btn-primary uses in the THEMES block. */
    box-shadow: 0 3px 10px -3px rgba(31,56,77,.45);
}
.lets-offertabs > .nav-tabs > li.active > a > .fa { opacity: 1; }

/* The unread count -- a small solid circle riding the tab, not AdminLTE's
   square `.badge`. Dark-on-light for an inactive tab; translucent white on
   the active one, which is already dark, so it stays legible on either. */
.lets-offertabs > .nav-tabs > li > a > .badge {
    min-width: 20px; height: 20px; line-height: 20px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--lets-ink-2);
    color: #fff;
    font-size: 11px;
    font-weight: 800;
}
.lets-offertabs > .nav-tabs > li.active > a > .badge {
    background: rgba(255,255,255,.3);
    color: #fff;
}

/* `.tab-content` assumed `nav-tabs-custom`'s own box and padding, which this
   no longer wraps it in -- give the table/empty-state below its own room to
   breathe instead of sitting flush against the pills above it. */
.lets-offertabs > .tab-content { padding: 0 0 20px; }

/* ==========================================================================
   PROJECT LIBRARY  --  master > sub-project > folder > language        (D82+)
   --------------------------------------------------------------------------
   The owner's tree: "Every project should have folder and Subproject should
   have sub folders", four fixed folders inside each, languages under those.

   >>> WHY THIS WAS REDESIGNED. <<<  The first cut painted every level with
   --lets-tint (#e0f7fa) and --lets-teal, so all four depths arrived as the
   same wash of blue: "currently looking all blue and unable to distiguish
   the same". Colour was carrying no information at all.

   The rule now is that COLOUR IDENTIFIES A FOLDER'S KIND, and STRUCTURE
   (indent, rails, weight) identifies its DEPTH. Those are two different
   questions and they get two different visual channels:

     depth   -> left rails + indent + type-size + weight   (no colour)
     kind    -> one hue per folder type, on the icon chip  (no structure)

   So a Glossary is green whether it sits on a master or six levels of
   accordion down, and you can tell a master from a sub-project without
   reading either of them.

   Built on the --lets-* tokens plus a small set of folder hues declared
   below. One light palette; this app has no dark variant to mirror.

   >>> TWO KINDS OF COLOUR HERE, AND THEY ANSWER TO DIFFERENT THINGS. <<<
                                                            (24 Aug 2026)
   The CHROME -- the project/sub-project rows, their folder icon, the
   PROJECT tag, the accent rail, the picker's tabs and hovers -- follows
   the user's chosen skin, via --th-accent / --th-accent-deep / --th-tint.
   Pick Ember and the library is orange; pick Jade and it is green. It
   used to be hard-wired to --lets-teal, which read as "everything is
   blue" no matter what skin was selected.

   The FOLDER HUES below (--fold-style violet, --fold-gloss green,
   --fold-ref blue, ...) deliberately do NOT follow the skin. They are
   how you tell a Glossary from a Style Guide at a glance; if they moved
   with the theme they would collapse into one colour and stop carrying
   meaning. The skin colours the furniture, the folder hues label the
   contents.

   Each swapped value keeps var(--lets-teal) as its fallback, so a page
   that somehow renders without a theme class on <body> still looks
   deliberate rather than unstyled.
   ========================================================================== */

.lets-libtree,
.lets-lib .lets-filefolders {
    /* ---- one hue per folder KIND -----------------------------------------
       Deliberately not from the brand ramp: the four fixed folders need to be
       told apart at a glance, and four shades of the same teal is exactly the
       problem this replaced. Each is a mid-saturation hue that holds up as a
       12px icon on white, paired with a very light wash for the chip behind
       it. */
    --fold-style:      #7c3aed;  /* Style Guide -- violet  */
    --fold-style-bg:   #f3ecff;
    --fold-gloss:      #0f8a5f;  /* Glossary    -- green   */
    --fold-gloss-bg:   #e6f6ef;
    --fold-ref:        #2563eb;  /* Reference   -- blue    */
    --fold-ref-bg:     #e8effd;
    --fold-misc:       #64748b;  /* Misc        -- slate   */
    --fold-misc-bg:    #eef1f5;
    --fold-custom:     #b45309;  /* Others      -- amber   */
    --fold-custom-bg:  #fdf0e3;
}

/* ==========================================================================
   LEVEL 1  --  THE MASTER PROJECT
   The heaviest thing on the page, because it is the sharing boundary: what
   sits here reaches every sub-project underneath.
   ========================================================================== */
.lets-libtree {
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius);
    overflow: hidden;
    background: var(--lets-surface);
}

.lets-libmaster { border-bottom: 1px solid var(--lets-border); }
.lets-libmaster:last-child { border-bottom: none; }

.lets-libmaster__row {
    display: flex; align-items: center; gap: 10px;
    padding: 13px 16px 13px 13px;
    cursor: pointer; list-style: none;
    background: var(--lets-sunken);
    /* The accent bar is the master's signature. Drawn as a border rather
       than a pseudo-element so it survives the open/closed swap cleanly. */
    border-left: 3px solid var(--th-accent, var(--lets-teal));
    transition: background .12s ease;
}
.lets-libmaster__row::-webkit-details-marker { display: none; }
.lets-libmaster__row:hover { background: var(--th-tint, var(--lets-tint)); }
.lets-libmaster[open] > .lets-libmaster__row {
    background: var(--lets-surface);
    border-bottom: 1px solid var(--lets-border);
}
.lets-libmaster[open] > .lets-libmaster__row .lets-filefolder__chevron { transform: rotate(90deg); }

.lets-libmaster__icon { color: var(--th-accent, var(--lets-teal)); flex: 0 0 17px; display: inline-flex; font-size: 15px; }
.lets-libmaster__code {
    font-weight: 800; font-size: 13.5px; letter-spacing: .4px;
    color: var(--lets-ink); text-transform: uppercase;
}
.lets-libmaster__subs { font-size: 11.5px; color: var(--lets-muted); font-weight: 600; margin-left: auto; }

/* ---- the level pills: the one place a level is NAMED in words ---------- */
.lets-libmaster__tag {
    font-size: 9.5px; text-transform: uppercase; letter-spacing: .7px; font-weight: 800;
    padding: 2px 7px; border-radius: 4px;
    background: var(--th-accent, var(--lets-teal)); color: #fff;
}
.lets-libmaster__tag--sub {
    background: transparent; color: var(--lets-muted);
    border: 1px solid var(--lets-border); font-weight: 700;
}

/* >>> THE RAIL. <<< A single hairline down the left of the master's body,
   with each child hanging off it. This is what makes the nesting readable
   without needing another background colour. */
.lets-libmaster__body {
    padding: 12px 16px 16px 0;
    margin-left: 26px;
    border-left: 1px solid var(--lets-border);
}

/* ==========================================================================
   LEVEL 2  --  A SUB-PROJECT
   Lighter than its master in every channel: thinner rule, smaller code, an
   outline pill instead of a solid one.
   ========================================================================== */
.lets-libsub {
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    margin: 8px 0 0 16px;
    background: var(--lets-surface);
    position: relative;
}
/* The elbow joining this card to the master's rail -- cheap, and it does more
   for legibility than any amount of shading. */
.lets-libsub::before {
    content: ''; position: absolute;
    left: -16px; top: 19px; width: 15px; height: 1px;
    background: var(--lets-border);
}

.lets-libsub__row {
    display: flex; align-items: center; gap: 9px;
    padding: 9px 12px; cursor: pointer; list-style: none;
    border-radius: var(--lets-radius-s);
    transition: background .12s ease;
}
.lets-libsub__row::-webkit-details-marker { display: none; }
.lets-libsub__row:hover { background: var(--lets-sunken); }
.lets-libsub[open] > .lets-libsub__row {
    border-bottom: 1px solid var(--lets-hairline);
    border-radius: var(--lets-radius-s) var(--lets-radius-s) 0 0;
}
.lets-libsub[open] > .lets-libsub__row .lets-filefolder__chevron { transform: rotate(90deg); }

.lets-libsub__icon { color: var(--lets-ink-2); flex: 0 0 15px; display: inline-flex; }
.lets-libsub__code { font-weight: 700; font-size: 12.5px; color: var(--lets-ink); letter-spacing: .2px; }
.lets-libsub__inh {
    margin-left: auto; font-size: 10.5px; font-weight: 700;
    color: var(--fold-custom); background: var(--fold-custom-bg);
    border-radius: 4px; padding: 2px 7px;
}
.lets-libsub__body { padding: 6px 12px 10px; }

/* ---- the caption that states the sharing rule in words ------------------ */
.lets-libsection__head {
    display: flex; align-items: center; gap: 10px;
    font-size: 10px; text-transform: uppercase; letter-spacing: .7px;
    font-weight: 800; color: var(--lets-muted);
    margin: 4px 0 6px;
}
.lets-libsection__head::after {
    content: ''; flex: 1; height: 1px; background: var(--lets-hairline);
}
/* The button must not be stretched by the rule above. */
.lets-libsection__head .lets-libadd { flex: 0 0 auto; order: 3; }
.lets-libsection__head::after { order: 2; }

/* ==========================================================================
   LEVEL 3  --  A TYPED FOLDER  (Style Guide / Glossary / Reference / Misc)
   Where the colour lives. No background, no border -- just a coloured icon
   chip and a name, so a row of five folders reads as five kinds rather than
   five boxes.
   ========================================================================== */
.lets-lib .lets-filefolders {
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    background: var(--lets-surface);
    overflow: hidden;
}
/* Inside the library the folders are already within a card, so they drop the
   second frame and sit flush. */
.lets-libsub__body > .lets-libfolder,
.lets-libsection > .lets-libfolder { border-radius: 0; }

.lets-lib .lets-filefolder { border-bottom: 1px solid var(--lets-hairline); }
.lets-lib .lets-filefolder:last-child { border-bottom: none; }

.lets-lib .lets-filefolder__row {
    display: flex; align-items: center; gap: 9px;
    padding: 8px 12px; cursor: pointer; list-style: none;
    transition: background .12s ease;
}
.lets-lib .lets-filefolder__row::-webkit-details-marker { display: none; }
.lets-lib .lets-filefolder__row:hover { background: var(--lets-sunken); }
.lets-lib .lets-filefolder[open] > .lets-filefolder__row .lets-filefolder__chevron { transform: rotate(90deg); }

.lets-lib .lets-filefolder__chevron {
    color: var(--lets-muted); flex: 0 0 12px; display: inline-flex;
    font-size: 10px; transition: transform .15s ease;
}

/* >>> THE COLOURED CHIP. <<< The icon sits in a rounded square washed with
   its own hue -- the single strongest "these are different things" signal on
   the screen, and it costs one element. */
.lets-lib .lets-filefolder__icon {
    flex: 0 0 22px; height: 22px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 6px; font-size: 11px;
    background: var(--lets-hairline); color: var(--lets-ink-2);
}
.lets-lib .lets-filefolder[data-folder="STYLE_GUIDE"] > .lets-filefolder__row .lets-filefolder__icon { background: var(--fold-style-bg);  color: var(--fold-style); }
.lets-lib .lets-filefolder[data-folder="GLOSSARY"]    > .lets-filefolder__row .lets-filefolder__icon { background: var(--fold-gloss-bg);  color: var(--fold-gloss); }
.lets-lib .lets-filefolder[data-folder="REFERENCE"]   > .lets-filefolder__row .lets-filefolder__icon { background: var(--fold-ref-bg);    color: var(--fold-ref); }
.lets-lib .lets-filefolder[data-folder="MISC"]        > .lets-filefolder__row .lets-filefolder__icon { background: var(--fold-misc-bg);   color: var(--fold-misc); }
.lets-lib .lets-filefolder[data-folder="CUSTOM"]      > .lets-filefolder__row .lets-filefolder__icon { background: var(--fold-custom-bg); color: var(--fold-custom); }

.lets-lib .lets-filefolder__name { font-weight: 650; font-size: 12.5px; color: var(--lets-ink); }
.lets-lib .lets-filefolder__sub  { font-size: 10.5px; color: var(--lets-muted); }

/* An EMPTY folder recedes -- it is still there (that is the point, "show 0"),
   but it must not compete with the ones holding something. */
.lets-lib .lets-filefolder[data-empty="1"] > .lets-filefolder__row .lets-filefolder__icon { opacity: .45; }
.lets-lib .lets-filefolder[data-empty="1"] > .lets-filefolder__row .lets-filefolder__name { color: var(--lets-muted); font-weight: 600; }

.lets-lib .lets-filefolder__count {
    min-width: 20px; text-align: center;
    font-size: 10.5px; font-weight: 800;
    padding: 1px 6px; border-radius: 9px;
    background: var(--lets-ink); color: #fff;
}
.lets-lib .lets-filefolder__count--zero {
    background: transparent; color: var(--lets-muted);
    border: 1px solid var(--lets-border); font-weight: 700;
}
.lets-lib .lets-filefolder__size { margin-left: auto; font-size: 11px; color: var(--lets-muted); font-weight: 600; }
.lets-lib .lets-filefolder__body { padding: 2px 12px 10px 43px; }

/* ==========================================================================
   LEVEL 4  --  A LANGUAGE INSIDE A FOLDER
   The quietest level. A caption, not a container: the files are the content
   here and another box around them would be one box too many.
   ========================================================================== */
.lets-liblang { margin: 2px 0 8px; }
.lets-liblang:last-child { margin-bottom: 2px; }

.lets-liblang__head {
    display: flex; align-items: center; gap: 6px;
    font-size: 10px; font-weight: 800; letter-spacing: .6px;
    text-transform: uppercase; color: var(--lets-muted);
    padding: 6px 0 4px;
}
.lets-liblang__head i { font-size: 10px; opacity: .8; }
.lets-liblang__n {
    font-size: 9.5px; font-weight: 800; color: var(--lets-ink-2);
    background: var(--lets-hairline); border-radius: 8px; padding: 0 5px;
}

/* ---- the file rows ------------------------------------------------------ */
.lets-lib .lets-filetable { width: 100%; border-collapse: collapse; }
.lets-lib .lets-filetable thead th {
    font-size: 9.5px; text-transform: uppercase; letter-spacing: .6px;
    color: var(--lets-muted); font-weight: 800;
    padding: 4px 10px; border-bottom: 1px solid var(--lets-hairline); text-align: left;
}
.lets-lib .lets-filetable td {
    padding: 6px 10px; font-size: 12px; vertical-align: middle;
    border-bottom: 1px solid var(--lets-hairline);
}
.lets-lib .lets-filetable tr:last-child td { border-bottom: none; }
.lets-lib .lets-filetable tbody tr { transition: background .1s ease; }
.lets-lib .lets-filetable tbody tr:hover { background: var(--lets-sunken); }

.lets-lib .lets-filename {
    display: flex; align-items: center; gap: 7px;
    font-weight: 600; color: var(--lets-ink); font-size: 12px;
}
.lets-lib .lets-filename i { color: var(--lets-muted); font-size: 12px; }

.lets-lib .lets-dl-btn {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 11px; font-weight: 700; color: var(--th-accent-deep, var(--lets-teal-deep));
    padding: 3px 9px; border-radius: 5px;
    border: 1px solid var(--lets-border); background: var(--lets-surface);
    transition: background .12s ease, border-color .12s ease;
}
.lets-lib .lets-dl-btn:hover, .lets-dl-btn:focus {
    background: var(--th-tint, var(--lets-tint)); border-color: var(--th-accent, var(--lets-teal));
    color: var(--th-accent-deep, var(--lets-teal-deep)); text-decoration: none;
}

/* An inherited row is tinted, not just badged -- so you can see at a glance
   which half of a merged folder came from the level above. */
.lets-lib .lets-filetable tr[data-inherited="1"] { background: color-mix(in srgb, var(--fold-custom-bg) 55%, transparent); }
.lets-lib .lets-filetable tr[data-inherited="1"]:hover { background: var(--fold-custom-bg); }
.lets-lib .lets-filetable tr[data-inherited="1"] .label {
    background: var(--fold-custom-bg); color: var(--fold-custom);
    border: 1px solid color-mix(in srgb, var(--fold-custom) 25%, transparent);
    font-weight: 700; font-size: 9.5px; letter-spacing: .4px;
}

.lets-lib .lets-files-body { padding-top: 8px; padding-bottom: 14px; }

/* ==========================================================================
   THE GROUPED UPLOADER + ITS REVIEW STEP
   "before submit we should have review button that which file going where."
   The review is the point of the dialog, so it is the loudest thing in it.
   ========================================================================== */
.lets-libmodal {
    position: fixed; inset: 0; z-index: 1050;
    background: rgba(9, 30, 26, .5);
    display: flex; align-items: flex-start; justify-content: center;
    padding: 40px 16px; overflow-y: auto;
}
.lets-libmodal[hidden] { display: none; }

.lets-libmodal__panel {
    background: var(--lets-surface); color: var(--lets-ink);
    border-radius: var(--lets-radius);
    width: 100%; max-width: 880px;
    box-shadow: var(--lets-shadow-h);
    overflow: hidden;
}
.lets-libmodal__head {
    display: flex; align-items: center; gap: 12px;
    padding: 14px 18px; background: var(--lets-sunken);
    border-bottom: 1px solid var(--lets-border);
}
.lets-libmodal__head h4 { margin: 0; font-size: 14.5px; font-weight: 800; flex: 1; }
.lets-libmodal__head small { font-weight: 600; }
.lets-libmodal__body { padding: 16px 18px; }
.lets-libmodal__foot {
    display: flex; align-items: center; gap: 8px;
    padding: 12px 18px; background: var(--lets-sunken);
    border-top: 1px solid var(--lets-border); font-size: 12px;
}

.lets-libgroup {
    position: relative;
    border: 1px solid var(--lets-border); border-radius: var(--lets-radius-s);
    padding: 13px 14px; margin-bottom: 10px;
    background: var(--lets-surface);
    border-left: 3px solid var(--th-accent, var(--lets-teal));
}
.lets-libgroup__x {
    position: absolute; top: 7px; right: 9px;
    border: none; background: none; font-size: 16px; line-height: 1;
    color: var(--lets-muted); cursor: pointer;
}
.lets-libgroup__x:hover { color: var(--lets-red); }
.lets-libgroup__row { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.lets-libgroup__langs {
    display: flex; flex-wrap: wrap; gap: 5px 14px; align-items: center;
    margin-top: 11px; padding-top: 10px; border-top: 1px dashed var(--lets-border);
}
.lets-libgroup__lbl {
    font-size: 10px; text-transform: uppercase; letter-spacing: .7px;
    font-weight: 800; color: var(--lets-muted); margin-right: 2px;
}
.lets-libchk { font-weight: 500; font-size: 12px; margin: 0; cursor: pointer; }
.lets-libchk input { margin-right: 4px; }

.lets-libreview {
    margin-top: 14px; border-radius: var(--lets-radius-s);
    border: 1px solid var(--lets-green);
    background: color-mix(in srgb, var(--lets-green) 6%, var(--lets-surface));
    padding: 12px 14px;
}
.lets-libreview__head {
    font-size: 10px; text-transform: uppercase; letter-spacing: .7px;
    font-weight: 800; color: var(--lets-green); margin-bottom: 8px;
}
.lets-libreview__bad td { opacity: .5; text-decoration: line-through; }
.lets-libreview__warn {
    margin: 10px 0 0; padding: 8px 10px 8px 26px; list-style-position: outside;
    font-size: 11.5px; color: var(--lets-amber);
    background: color-mix(in srgb, var(--lets-amber) 8%, transparent);
    border-radius: 6px;
}


/* ============================================================================
   AVAILABILITY  --  "who is away", and the badge that says so   (23 Aug 2026)
   ============================================================================
   The owner: *"Make a calander where vendor and users can mark their holidays,
   so that at the time of assignment user get to know that vendor will be
   unavailable at this time."*

   Two surfaces, one vocabulary:

     .lets-avail__*   the calendar screens (staff and vendor portal)
     .lets-ms__away   the badge inside an assignment picker

   >>> THE COLOUR IS THE WHOLE MESSAGE, SO IT ONLY HAS TWO STATES. <<<
   AMBER means "away later" -- mind the deadline. RED (--now) means "away
   today" -- do not expect anything back this afternoon. Nothing here is
   green: this module never asserts that somebody IS available, only that
   they have said they are not, and a green badge would be the screen
   claiming the first from the absence of the second.

   >>> NOT A DISABLED STATE, ANYWHERE. <<< The owner chose to warn rather than
   block, so an option carrying one of these is still selectable and must
   still LOOK selectable. Greying it out would be the stylesheet quietly
   overruling a decision that was made deliberately.
   ============================================================================ */

.lets-avail__intro { margin: 0 0 10px; color: var(--lets-ink-2); }
.lets-avail__caveat { margin-bottom: 0; }

.lets-avail__empty { margin: 6px 0; font-size: 12.5px; }
.lets-avail__quiet { margin: 10px 0 0; font-size: 11.5px; }
.lets-avail__forwho { margin: 0 0 12px; font-size: 12.5px; color: var(--lets-ink-2); }

.lets-avail__table { margin-bottom: 0; }
.lets-avail__table > tbody > tr > td { vertical-align: middle; }

.lets-avail__dates { font-weight: 600; white-space: nowrap; }
.lets-avail__kind,
.lets-avail__note { color: var(--lets-ink-2); font-size: 12.5px; }

/* The action column: as narrow as its button and no wider. */
.lets-avail__act { width: 1%; white-space: nowrap; text-align: right; }
.lets-avail__act form { display: inline; margin: 0; }

/* A period running RIGHT NOW, on the person's own list. */
.lets-avail__row--now td { background: color-mix(in srgb, var(--lets-red) 5%, transparent); }

.lets-avail__tag {
    display: inline-block; margin-left: 6px; padding: 1px 6px;
    border-radius: 999px; font-size: 10px; font-weight: 800;
    text-transform: uppercase; letter-spacing: .5px;
}
.lets-avail__tag--now {
    color: var(--lets-red);
    background: color-mix(in srgb, var(--lets-red) 12%, transparent);
}

.lets-avail__who { font-weight: 600; white-space: nowrap; }
.lets-avail__what {
    display: block; font-weight: 400; font-size: 10.5px;
    text-transform: uppercase; letter-spacing: .6px; color: var(--lets-muted);
}

/* One period on the team calendar. Several may sit side by side on a row. */
.lets-avail__chip {
    display: inline-block; margin: 2px 6px 2px 0; padding: 3px 9px;
    border-radius: var(--lets-radius-s); font-size: 12px;
    color: var(--lets-amber);
    background: color-mix(in srgb, var(--lets-amber) 9%, transparent);
    border: 1px solid color-mix(in srgb, var(--lets-amber) 25%, transparent);
}
.lets-avail__chip--now {
    color: var(--lets-red);
    background: color-mix(in srgb, var(--lets-red) 9%, transparent);
    border-color: color-mix(in srgb, var(--lets-red) 28%, transparent);
}
.lets-avail__chip em {
    font-style: normal; font-size: 10px; text-transform: uppercase;
    letter-spacing: .5px; opacity: .75; margin-left: 4px;
}

/* The remove "x" that follows a chip when the viewer holds availability,edit. */
.lets-avail__chipform { display: inline; margin: 0 10px 0 -2px; }
.lets-avail__chipx {
    border: 0; background: none; padding: 0 2px; line-height: 1;
    color: var(--lets-muted); font-size: 15px; cursor: pointer;
}
.lets-avail__chipx:hover { color: var(--lets-red); }

/* The date-range control in the team calendar's header. */
.lets-avail__window {
    float: right; display: flex; align-items: center; gap: 6px;
    margin-top: -4px; font-size: 12px;
}
.lets-avail__window label { margin: 0; color: var(--lets-ink-2); font-weight: 600; }
.lets-avail__window input { width: 150px; }

/* Periods that have already passed, folded away under the live ones. */
.lets-avail__past { margin-top: 12px; }
.lets-avail__past > summary {
    cursor: pointer; font-size: 12px; font-weight: 600;
    color: var(--lets-ink-2); padding: 6px 0;
}
.lets-avail__past .lets-avail__table { opacity: .65; }

/* ----------------------------------------------------------------------------
   THE PICKER BADGE
   ----------------------------------------------------------------------------
   Rendered by lets-availability.js into `option.dataset.html`, which
   lets-multiselect prints for each of its rows. A plain <select> cannot show
   this and gets the same words in its option text instead -- see that file.

   `.lets-ms__` and not `.lets-avail__` on purpose: it is styled to sit inside
   a multiselect row, and the prefix is what says so to the next person who
   changes that widget's padding.
   ---------------------------------------------------------------------------- */
.lets-ms__away {
    display: inline-block; margin-left: 6px; padding: 1px 7px;
    border-radius: 999px; font-size: 10.5px; font-weight: 700;
    white-space: nowrap;
    color: var(--lets-amber);
    background: color-mix(in srgb, var(--lets-amber) 12%, transparent);
}
.lets-ms__away--now {
    color: var(--lets-red);
    background: color-mix(in srgb, var(--lets-red) 12%, transparent);
}

/* The linguist's OWN date, shown beside ours when the two calendars disagree.
   Quieter than the dates themselves -- it is context for a reader who might
   otherwise think our conversion is a mistake, not a second fact to read. */
.lets-avail__tz {
    font-style: normal; font-size: 10.5px; opacity: .8;
    margin-left: 6px; padding-left: 6px;
    border-left: 1px solid currentColor;
}

/* ============================================================================
   WORKING HOURS  --  the shop sign, and the badge that says so   (27 Aug 2026)
   ============================================================================
   The owner: *"vendor apni profile main apna time schedul bata sakta hai ...
   dukaan kab se kab tak chalu rahegi or kon kon se din bahnd rahati hai."*

   The RECURRING half of the block above. Two surfaces, same split:

     .lets-sched__*   the form (staff vendor screen and vendor portal)
     .lets-ms__hours  the badge inside an assignment picker
     .lets-cap__*     the capacity triangle on the task grid

   >>> IT BORROWS THE AVAILABILITY VOCABULARY RATHER THAN INVENTING ONE. <<<
   AMBER for "limited", RED for "shut right now" -- the same two states, the
   same two colours, because a PM reading a picker should not have to learn
   that this module's amber means something different from that one's. And
   nothing green, for the reason stated up there: this never asserts that
   somebody IS available, only what they have told us about when they are not.

   >>> NOT A DISABLED STATE, ANYWHERE, FOR THE SAME REASON. <<< The owner chose
   *"Warn only"*. An option carrying one of these is still selectable and must
   still LOOK selectable -- greying it out would be the stylesheet quietly
   overruling a decision that was made deliberately.
   ============================================================================ */

/* ---- the badge on a picker option -------------------------------------- */
.lets-ms__hours {
    display: inline-block; margin-left: 6px; padding: 1px 7px;
    border-radius: 999px; font-size: 10.5px; font-weight: 700;
    white-space: nowrap;
    color: var(--lets-amber);
    background: color-mix(in srgb, var(--lets-amber) 12%, transparent);
}
/* Shut at this moment -- "do not expect anything back this afternoon", which
   is the same claim .lets-ms__away--now makes and wears the same red. */
.lets-ms__hours--shut {
    color: var(--lets-red);
    background: color-mix(in srgb, var(--lets-red) 12%, transparent);
}

/* ---- the form ----------------------------------------------------------- */
.lets-sched__intro { margin: 0 0 12px; color: var(--lets-ink-2); font-size: 12.5px; }
.lets-sched__zonelink { margin-left: 4px; }
.lets-sched__unset { margin-bottom: 14px; }
.lets-sched__sibling {
    margin: 10px 0 0; font-size: 12.5px; color: var(--lets-ink-2);
}

.lets-sched__standing { margin-bottom: 6px; }
.lets-sched__label { display: block; font-weight: 600; margin-bottom: 6px; }
.lets-sched__help { font-size: 11.5px; margin-top: 6px; }

.lets-sched__radios { display: flex; flex-wrap: wrap; gap: 14px; padding-top: 4px; }
.lets-sched__radio { font-weight: 400; margin: 0; cursor: pointer; }
.lets-sched__radio input { margin-right: 5px; }

.lets-sched__capacity { max-width: 260px; }

.lets-sched__table { margin: 14px 0 0; }
.lets-sched__table > tbody > tr > td { vertical-align: middle; }
.lets-sched__c-day { width: 130px; }
.lets-sched__c-open { width: 110px; }

.lets-sched__dayname { font-weight: 600; }
/* Saturday and Sunday, tinted so the pair a linguist is most likely to switch
   off is findable without reading seven labels. */
.lets-sched__dayname--wknd { color: var(--lets-ink-2); }

.lets-sched__check { font-weight: 400; margin: 0; cursor: pointer; white-space: nowrap; }
.lets-sched__check input { margin-right: 5px; }
.lets-sched__check--allday { margin-right: 14px; }
.lets-sched__checktext { font-size: 12.5px; }

.lets-sched__hours { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.lets-sched__window { display: inline-flex; align-items: center; gap: 6px; }
.lets-sched__time { width: 116px; display: inline-block; }
.lets-sched__to { color: var(--lets-ink-2); font-size: 12px; }

/* A closed day, and a day whose hours do not apply (closed or round the
   clock). Faded rather than hidden: the boxes keep their values and keep
   posting them, which is what lets somebody untick "24 hours" and find the
   times they typed still there. See the panel's script for why they are
   `readonly` and never `disabled`. */
.lets-sched__row--shut .lets-sched__dayname { color: var(--lets-muted); }
.lets-sched__row--muted .lets-sched__window { opacity: .45; }
.lets-sched__row--muted .lets-sched__time { background: var(--lets-tint); cursor: default; }

.lets-sched__overnight {
    font-size: 11px; font-weight: 700; white-space: nowrap;
    color: var(--lets-amber);
    background: color-mix(in srgb, var(--lets-amber) 12%, transparent);
    border-radius: 999px; padding: 1px 8px;
}

.lets-sched__foot { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 8px; }

/* The fill-in buttons, on the table's header line rather than in the footer
   beside Save -- the owner: *"Copy Monday to everyday, monday firday only.
   should be in header line instead of bottom with save button"*. Sitting on
   the tint, tight to the top of the table, so it reads as part of the table's
   furniture and not as a second row of actions. */
.lets-sched__fillbar {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
    margin: 14px 0 0;
    padding: 8px 12px;
    background: var(--lets-tint);
    border: 1px solid var(--lets-border);
    border-bottom: 0;
    border-radius: var(--lets-radius-s) var(--lets-radius-s) 0 0;
}
.lets-sched__fillbar-label {
    font-size: 12px; font-weight: 600; color: var(--lets-ink-2);
    margin-right: 2px;
}
/* The table butts straight onto the bar above it. */
.lets-sched__fillbar + .lets-sched__table { margin-top: 0; }

/* ---- the capacity triangle, on the assignment grid ---------------------- */
/* The owner: *"esclamation mark with yellow triagnel shows that vendor
   assinmanet capacity is lower then you entered"*. Amber and not red, because
   it is the softer of the two warnings on that row: the overrun flag beside it
   means words have gone missing or been double-sold, which is money; this
   means somebody may be late. */
.lets-cap-flag {
    display: inline-flex; align-items: center; gap: 4px;
    margin-left: 6px; padding: 1px 7px;
    border-radius: 999px; font-size: 10.5px; font-weight: 700;
    white-space: nowrap;
    color: var(--lets-amber);
    background: color-mix(in srgb, var(--lets-amber) 12%, transparent);
    cursor: help;
}

/* ==========================================================================
   NESTED FOLDERS, SCOPE WARNINGS AND THE FOLDER DIALOGS            (23 Aug 26)
   "User can also create folders inside and upload files inside the folders."
   ========================================================================== */

/* ---- per-folder action buttons, revealed on hover ---------------------- */
.lets-lib .lets-foldacts {
    display: inline-flex; align-items: center; gap: 2px;
    margin-left: 8px; opacity: 0; transition: opacity .12s ease;
}
.lets-lib .lets-filefolder__row:hover .lets-foldacts,
.lets-lib .lets-filefolder__row:focus-within .lets-foldacts { opacity: 1; }

.lets-lib .lets-foldact {
    position: relative;
    border: none; background: none; cursor: pointer;
    color: var(--lets-muted); font-size: 11.5px;
    padding: 3px 5px; border-radius: 4px; line-height: 1;
}
.lets-lib .lets-foldact:hover { background: var(--th-tint, var(--lets-tint)); color: var(--th-accent-deep, var(--lets-teal-deep)); }
.lets-lib .lets-foldact--danger:hover { background: color-mix(in srgb, var(--lets-red) 12%, transparent); color: var(--lets-red); }
.lets-lib .lets-foldact__plus { font-size: 7px; position: absolute; top: 2px; right: 1px; }

/* A folder inherited from the master: same amber the inherited file rows use,
   so "came from above" is one colour wherever it appears. */
.lets-lib .lets-inh-chip {
    font-size: 9.5px; font-weight: 800; letter-spacing: .3px;
    color: var(--fold-custom); background: var(--fold-custom-bg);
    border-radius: 4px; padding: 1px 6px;
}

/* Nested folders step in, and the rail makes the nesting legible without
   another colour -- same device the master body uses one level up. */
.lets-lib .lets-filefolder__body > .lets-filefolder {
    margin-left: 4px; padding-left: 12px;
    border-left: 1px solid var(--lets-hairline);
    border-bottom: none;
}

/* ---- the language caption --------------------------------------------- */
.lets-lib .lets-liblang__head--global { color: var(--fold-ref); }
.lets-lib .lets-liblang__note {
    text-transform: none; letter-spacing: 0; font-weight: 600;
    font-size: 10px; color: var(--lets-muted); font-style: italic;
}

/* ==========================================================================
   THE SCOPE WARNINGS
   The owner asked to be TOLD, at upload time, when something is about to
   reach further than one language or one sub-project. So these are banners
   inside the dialog, not help text -- and they are repeated in the review,
   which is the last thing read before confirming.
   ========================================================================== */
.lets-libwarn {
    display: flex; gap: 10px; align-items: flex-start;
    padding: 10px 12px; margin-bottom: 12px;
    border-radius: var(--lets-radius-s); font-size: 12px; line-height: 1.5;
    border: 1px solid transparent;
}
.lets-libwarn i { font-size: 14px; margin-top: 1px; flex: 0 0 auto; }
.lets-libwarn strong { font-weight: 800; }

.lets-libwarn--master {
    background: color-mix(in srgb, var(--lets-amber) 10%, var(--lets-surface));
    border-color: color-mix(in srgb, var(--lets-amber) 35%, transparent);
    color: var(--lets-amber);
}
.lets-libwarn--global {
    background: var(--fold-ref-bg);
    border-color: color-mix(in srgb, var(--fold-ref) 30%, transparent);
    color: var(--fold-ref);
}

.lets-libreview__scope {
    display: flex; gap: 8px; align-items: center;
    font-size: 11.5px; font-weight: 600;
    padding: 7px 10px; margin-bottom: 8px; border-radius: 6px;
}
.lets-libreview__scope--master { background: color-mix(in srgb, var(--lets-amber) 12%, transparent); color: var(--lets-amber); }
.lets-libreview__scope--global { background: var(--fold-ref-bg); color: var(--fold-ref); }

.lets-globalpill {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 10.5px; font-weight: 700;
    background: var(--fold-ref-bg); color: var(--fold-ref);
    border-radius: 4px; padding: 1px 7px;
}

.lets-libchk--global { font-weight: 700; color: var(--fold-ref); }

/* ---- the small single-field dialogs (new folder / rename) -------------- */
.lets-libmodal__panel--sm { max-width: 460px; }

/* ==========================================================================
   THE TASK FORM'S REFERENCE PICKER + ITS CHIPS                  (23 Aug 2026)
   "select reference file button it should open a folder which show the task
   project tree as reference selection and checkbox"
   ========================================================================== */

/* ---- what has been picked, shown above the grid ------------------------ */
.lets-refchips { display: flex; flex-wrap: wrap; gap: 6px; }

.lets-refchip {
    display: inline-flex; align-items: center; gap: 6px;
    background: var(--th-tint, var(--lets-tint)); border: 1px solid var(--lets-border);
    border-radius: 14px; padding: 3px 6px 3px 10px;
    font-size: 12px; color: var(--lets-ink);
}
.lets-refchip i { color: var(--th-accent, var(--lets-teal)); font-size: 11px; }
.lets-refchip__where {
    font-size: 10.5px; color: var(--lets-muted); font-style: normal;
    padding-left: 6px; border-left: 1px solid var(--lets-border);
}
.lets-refchip__x {
    border: none; background: none; cursor: pointer; line-height: 1;
    font-size: 15px; color: var(--lets-muted); padding: 0 2px;
}
.lets-refchip__x:hover { color: var(--lets-red); }

/* ---- the tree inside the modal ---------------------------------------- */
.lets-picktree { max-height: 52vh; overflow-y: auto; }

.lets-picknode + .lets-picknode { margin-top: 14px; }
.lets-picknode__head {
    display: flex; align-items: center; gap: 8px;
    font-weight: 800; font-size: 13px; color: var(--lets-ink);
    text-transform: uppercase; letter-spacing: .3px;
    padding-bottom: 6px; margin-bottom: 4px;
    border-bottom: 1px solid var(--lets-border);
}
.lets-picknode__head i { color: var(--th-accent, var(--lets-teal)); }
.lets-picknode__tag {
    font-size: 9.5px; font-weight: 800; letter-spacing: .6px;
    color: var(--lets-muted); border: 1px solid var(--lets-border);
    border-radius: 4px; padding: 1px 6px; text-transform: uppercase;
}

.lets-pickfolder { margin-left: 8px; }
.lets-pickfolder > summary {
    display: flex; align-items: center; gap: 7px;
    cursor: pointer; list-style: none;
    font-size: 12.5px; font-weight: 700; color: var(--lets-ink-2);
    padding: 5px 0;
}
.lets-pickfolder > summary::-webkit-details-marker { display: none; }
.lets-pickfolder > summary i { color: var(--lets-muted); }
.lets-pickfolder__n {
    font-size: 10px; font-weight: 800; color: var(--lets-muted);
    background: var(--lets-hairline); border-radius: 8px; padding: 0 6px;
}
/* Nested folders keep stepping in, with the same rail the library uses. */
.lets-pickfolder__body {
    padding-left: 14px; margin-left: 6px;
    border-left: 1px solid var(--lets-hairline);
}

/* ---- one selectable document ------------------------------------------ */
.lets-pickdoc {
    display: flex; align-items: center; gap: 8px;
    padding: 5px 8px; margin: 0; border-radius: 5px;
    font-weight: 400; font-size: 12.5px; cursor: pointer;
}
.lets-pickdoc:hover { background: var(--lets-sunken); }
.lets-pickdoc input { margin: 0; }
.lets-pickdoc i { color: var(--lets-muted); font-size: 11px; }
.lets-pickdoc__name { flex: 1; color: var(--lets-ink); }
.lets-pickdoc__lang {
    font-size: 10.5px; font-weight: 700;
    color: var(--lets-ink-2); background: var(--lets-hairline);
    border-radius: 4px; padding: 1px 7px;
}
/* A global reference is marked in the same blue the library uses for it. */
.lets-pickdoc__lang.is-global { color: var(--fold-ref, #2563eb); background: var(--fold-ref-bg, #e8effd); }
.lets-pickdoc__v { font-size: 10.5px; color: var(--lets-muted); font-weight: 600; }

/* The picker modal reuses .lets-libmodal from the Project Library, so the two
   dialogs in this application that show a document tree look identical. The
   folder-hue tokens are declared on .lets-libtree/.lets-lib there, which this
   modal is not inside -- so the two it actually uses are restated here. */
.lets-libmodal { --fold-ref: #2563eb; --fold-ref-bg: #e8effd; }

/* "Your days are recorded as Asia/Kolkata time" -- on the linguist's own
   availability page. The --assumed variant is the one that needs acting on:
   their record carries no timezone, so we are guessing, and only they can
   correct it. Amber rather than red because nothing is broken yet. */
.lets-avail__zone {
    margin: 12px 0 0; font-size: 12.5px; color: var(--lets-ink-2);
}
.lets-avail__zone--assumed {
    color: var(--lets-amber); padding: 8px 10px; border-radius: 6px;
    background: color-mix(in srgb, var(--lets-amber) 8%, transparent);
}

/* The picker's "why is this empty?" note. Informational, not a warning --
   nothing has gone wrong, the material is simply filed somewhere this task
   cannot reach. See Tasks::libraryTree(). */
.lets-pickhint {
    display: flex; gap: 9px; align-items: flex-start;
    padding: 10px 12px; margin-bottom: 12px;
    border-radius: var(--lets-radius-s);
    background: var(--fold-ref-bg, #e8effd);
    border: 1px solid color-mix(in srgb, var(--fold-ref, #2563eb) 28%, transparent);
    color: var(--fold-ref, #2563eb);
    font-size: 12px; line-height: 1.55;
}
.lets-pickhint i { font-size: 14px; margin-top: 1px; flex: 0 0 auto; }

/* The picker's toolbar: filter + the language gate side by side. */
.lets-pickbar { display: flex; gap: 10px; align-items: flex-end; margin-bottom: 8px; }
.lets-pickbar > input[type=text] { flex: 1; }
.lets-pickbar__lang { margin: 0; display: flex; flex-direction: column; gap: 3px; min-width: 190px; }
.lets-pickbar__lang > span {
    font-size: 10px; text-transform: uppercase; letter-spacing: .6px;
    font-weight: 800; color: var(--lets-muted);
}
.lets-pickbar__note {
    margin: 0 0 10px; font-size: 11.5px; font-weight: 600;
    color: var(--fold-ref, #2563eb);
    background: var(--fold-ref-bg, #e8effd);
    border-radius: 6px; padding: 6px 10px;
}

/* A document outside the chosen language: shown, but plainly not choosable.
   Greying it states the rule; hiding it would look like an empty library --
   the exact confusion this screen has already caused once. */
.lets-pickdoc.is-locked { opacity: .4; cursor: not-allowed; }
.lets-pickdoc.is-locked:hover { background: transparent; }

/* "all in sub-projects" -- shown on a project row whose total comes entirely
   from its children, so a badge of 5 above four empty folders explains itself
   without needing the tooltip. */
.lets-libmaster__where {
    font-size: 10px; font-weight: 700; letter-spacing: .3px;
    color: var(--lets-muted); font-style: italic;
}

/* ==========================================================================
   THE PICKER'S TWO TABS                                         (23 Aug 2026)
   "one button for upload from drive and upload from library" -- so the dialog
   carries both, and only one is ever on screen.
   ========================================================================== */
.lets-picktabs {
    display: flex; gap: 4px; margin-bottom: 14px;
    border-bottom: 1px solid var(--lets-border);
}
.lets-picktab {
    border: none; background: none; cursor: pointer;
    padding: 8px 14px; margin-bottom: -1px;
    font-size: 12.5px; font-weight: 700; color: var(--lets-muted);
    border-bottom: 2px solid transparent;
}
.lets-picktab:hover { color: var(--lets-ink); }
.lets-picktab.is-active { color: var(--th-accent, var(--lets-teal)); border-bottom-color: var(--th-accent, var(--lets-teal)); }
.lets-picktab i { margin-right: 5px; }

/* ---- the "from my computer" pane -------------------------------------- */
.lets-pickdrop {
    border: 2px dashed var(--lets-border); border-radius: var(--lets-radius);
    padding: 26px 18px; text-align: center; cursor: pointer;
    background: var(--lets-sunken);
    transition: border-color .12s ease, background .12s ease;
}
.lets-pickdrop:hover { border-color: var(--th-accent, var(--lets-teal)); background: var(--th-tint, var(--lets-tint)); }
.lets-pickdrop > i { font-size: 26px; color: var(--th-accent, var(--lets-teal)); display: block; margin-bottom: 8px; }
.lets-pickdrop p { margin: 0 0 4px; }

.lets-picklocal { list-style: none; margin: 12px 0 0; padding: 0; }
.lets-picklocal li {
    display: flex; align-items: center; gap: 8px;
    padding: 6px 10px; font-size: 12.5px;
    border-bottom: 1px solid var(--lets-hairline);
}
.lets-picklocal li:last-child { border-bottom: none; }
.lets-picklocal li i { color: var(--lets-muted); }
.lets-picklocal li em { margin-left: auto; font-style: normal; font-size: 11px; color: var(--lets-muted); }
.lets-picklocal__clear { justify-content: flex-end; }

/* Picked library references, shown inside the grid cell they belong to.
   Narrower than the standalone chips above the grid, because a file-grid cell
   is a column, not a row. */
.lets-refcell { display: flex; flex-direction: column; gap: 3px; margin-top: 3px; }
.lets-refcell .lets-refchip {
    max-width: 150px; padding: 2px 4px 2px 7px;
    font-size: 10.5px; border-radius: 10px;
}
.lets-refcell .lets-refchip > span {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ==========================================================================
   EMAIL DESIGN PICKER  --  on the company page, beside the PO one
   ==========================================================================
   The owner, 23 Aug 2026: *"it should be show on company page so that i can
   choose this email template from there."*

   Lighter than `.lets-podesign` above and deliberately so. That picker draws
   five COMPLETE purchase orders, because a PO is a page and the layout is most
   of the decision. The email designs share one layout and differ in colour, so
   these cards show the real banner over two real fact-table rows and stop
   there -- see the note in companies/_email-picker.php.

   Every colour on a card comes from `Notifier::DESIGNS` as an inline style,
   never from here, so a palette edited in PHP repaints these in the same
   breath. This file only supplies the shape. */
.lets-emaildesign {
    display: flex; flex-wrap: wrap; gap: 12px; justify-content: start;
}

.lets-emaildesign__card {
    display: block; width: 208px; margin: 0; cursor: pointer; font-weight: 400;
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius);
    background: var(--lets-surface, #fff);
    overflow: hidden;
    transition: border-color .12s, box-shadow .12s;
}
.lets-emaildesign__card:hover { border-color: #9fb6bd; box-shadow: var(--lets-shadow); }

.lets-emaildesign__card.is-chosen {
    border-color: var(--lets-primary, #0e7490);
    box-shadow: 0 0 0 2px var(--lets-primary, #0e7490) inset;
}

/* The radio stays a real, focusable control -- moved into the corner rather
   than hidden, so the card is reachable by keyboard and announces its state.
   `display:none` here would make the whole picker invisible to a screen
   reader, which is the usual cost of a "pretty" radio group. */
.lets-emaildesign__card input[type="radio"] {
    position: absolute; margin: 8px 0 0 8px; z-index: 2;
}

.lets-emaildesign__banner {
    display: block; padding: 14px 14px 14px 30px;
    font-size: 13px; font-weight: 700; letter-spacing: .3px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Two rows of the fact table, at the real proportions: a 38% label column
   against the value, which is what `Notifier::tableStyles()` sets. */
.lets-emaildesign__rows { display: flex; border-top: 1px solid transparent; }
.lets-emaildesign__rows > span {
    display: block; padding: 5px 9px; font-size: 11px;
    border: 1px solid; border-top: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lets-emaildesign__rows > span:first-child { width: 38%; font-weight: 600; border-right: 0; }
.lets-emaildesign__rows > span:last-child  { flex: 1; }

.lets-emaildesign__meta { display: block; padding: 9px 11px 10px; }
.lets-emaildesign__meta strong { display: block; font-size: 13px; color: var(--lets-ink, #111b19); }
.lets-emaildesign__meta em {
    display: block; margin-top: 2px; font-style: normal;
    font-size: 11.5px; line-height: 1.35; color: var(--lets-muted, #5c6863);
}

/* "Default" -- said out loud, because an empty column means "nobody has
   chosen", not "no email goes out". See the migration. */
.lets-emaildesign__tag {
    display: inline-block; margin-top: 6px; padding: 1px 7px;
    border-radius: 999px; font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .4px;
    background: var(--lets-primary, #0e7490); color: #fff;
}


/* ##########################################################################
   THE SIDEBAR LOGO PICKER                                     (25 Aug 2026)
   ##########################################################################

   Four presets plus "as it is now", each drawing a small sidebar rather than
   describing one -- the rule `.lets-emaildesign` set next door. What differs
   is WHAT the card has to show: an email design is a colour, so one banner
   swatch says it. A sidebar preset is two slots that behave differently at two
   widths, so each card draws BOTH -- the wide panel and the narrow rail, side
   by side, at the proportions the real sidebar uses (230px against its 42px
   collapsed rail).

   >>> THE PREVIEW USES THE REAL LOGO BLOCK'S SURFACE, NOT A GREY BOX. <<<
   Which is the DARK accent gradient and white lettering set by "REFINEMENTS 1"
   above -- not the light glass the sidebar below it uses, and not the pale
   block this header had before that refinement landed.

   Getting that right is the whole point of drawing a preview at all. The
   commonest mistake with these images is a white-backed PNG: invisible on a
   white card, a glaring white rectangle on the real coloured header. A picker
   that previewed onto white would hide the exact fault it exists to reveal. */
.lets-sidebarstyle {
    display: flex; flex-wrap: wrap; gap: 12px; justify-content: start;
}

.lets-sidebarstyle__card {
    position: relative;
    display: block; width: 236px; margin: 0; cursor: pointer; font-weight: 400;
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius);
    background: var(--lets-surface, #fff);
    overflow: hidden;
    transition: border-color .12s, box-shadow .12s;
}
.lets-sidebarstyle__card:hover { border-color: #9fb6bd; box-shadow: var(--lets-shadow); }

.lets-sidebarstyle__card.is-chosen {
    border-color: var(--lets-primary, #0e7490);
    box-shadow: 0 0 0 2px var(--lets-primary, #0e7490) inset;
}

/* A real, focusable radio in the corner rather than `display:none` -- see the
   note on `.lets-emaildesign__card input`. Hiding it is the usual price of a
   "pretty" radio group and it costs the keyboard and the screen reader. */
.lets-sidebarstyle__card input[type="radio"] {
    position: absolute; margin: 8px 0 0 8px; z-index: 2;
}

.lets-sidebarstyle__preview {
    display: flex; align-items: stretch; height: 62px;
    background: linear-gradient(120deg, var(--th-accent) 0%,
                                        var(--th-accent-deep) 60%,
                                        var(--th-accent-deep) 100%);
    border-bottom: 1px solid var(--lets-border);
}

/* The open sidebar. Padded left past the radio so the two never overlap. */
.lets-sidebarstyle__wide {
    flex: 1; min-width: 0;
    display: flex; align-items: center; justify-content: center;
    padding: 0 8px 0 28px;
}

/* The collapsed rail, at the width it actually is. Marked off by a hairline
   rather than a different fill, because in the real header it is the SAME
   block of gradient -- the sidebar just gets narrower. A contrasting panel
   here would suggest a division that is not there. */
.lets-sidebarstyle__narrow {
    flex: 0 0 44px;
    display: flex; align-items: center; justify-content: center;
    box-shadow: inset 1px 0 0 rgba(255,255,255,.28);
}

.lets-sidebarstyle__preview img {
    max-width: 100%; max-height: 34px; width: auto; height: auto; display: block;
}
.lets-sidebarstyle__narrow img { max-height: 26px; }

/* Scaled down from `.lets-logo-text` / `.lets-logo-mark` rather than restated,
   so a card and the header it previews cannot end up in different typefaces or
   weights. Only the size differs, because the card is a third the width.

   White, and no text-shadow -- the state those two classes are actually in on
   the dark bar, per "Dark bar -- everything on it flips to white" above. Their
   base declarations higher up the file are for a header this application has
   not had since that refinement. */
.lets-sidebarstyle__word {
    font-size: 14px; font-weight: 800; letter-spacing: 2px;
    color: #fff;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lets-sidebarstyle__letters {
    font-size: 13px; font-weight: 800; letter-spacing: 1px;
    color: #fff;
}

.lets-sidebarstyle__meta { display: block; padding: 9px 11px 10px; }
.lets-sidebarstyle__meta strong { display: block; font-size: 13px; color: var(--lets-ink, #111b19); }
.lets-sidebarstyle__meta em {
    display: block; margin-top: 2px; font-style: normal;
    font-size: 11.5px; line-height: 1.35; color: var(--lets-muted, #5c6863);
}

.lets-sidebarstyle__tag {
    display: inline-block; margin-top: 6px; padding: 1px 7px;
    border-radius: 999px; font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .4px;
    background: var(--lets-primary, #0e7490); color: #fff;
}

/* Said on the card itself, not in a note at the bottom of the screen: this is
   the one thing that would make the choice not do what the picture shows, and
   it belongs next to the picture. */
.lets-sidebarstyle__warn {
    display: block; margin-top: 6px;
    font-size: 11px; line-height: 1.3; color: #8a6d1f;
}
.lets-sidebarstyle__warn i { margin-right: 3px; }

/* ============================================================================
   THE SIGN-IN THEME PICKER  --  Branding screen               (26 Aug 2026)
   ============================================================================
   Sibling of `.lets-sidebarstyle` above, and deliberately not a reuse of it:
   the two pickers draw different things. That one paints a sidebar, split into
   a wide panel and a narrow rail. This one paints a whole PAGE at postage-
   stamp size, and the thing being chosen is largely WHERE THE PANEL SITS on it
   -- hard right against a photograph, or centred on a gradient. Sharing the
   class would have meant one set of rules trying to be both shapes.

   The card chrome (size, border, chosen ring, the real focusable radio in the
   corner) is intentionally identical, so the two pickers on one screen read as
   the same kind of control. */
.lets-logintheme {
    display: flex; flex-wrap: wrap; gap: 12px; justify-content: start;
}

.lets-logintheme__card {
    position: relative;
    display: block; width: 236px; margin: 0; cursor: pointer; font-weight: 400;
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius);
    background: var(--lets-surface, #fff);
    overflow: hidden;
    transition: border-color .12s, box-shadow .12s;
}
.lets-logintheme__card:hover { border-color: #9fb6bd; box-shadow: var(--lets-shadow); }

.lets-logintheme__card.is-chosen {
    border-color: var(--lets-primary, #0e7490);
    box-shadow: 0 0 0 2px var(--lets-primary, #0e7490) inset;
}

/* A real, focusable radio rather than `display:none` -- hiding it is the usual
   price of a "pretty" radio group, and it costs the keyboard and the screen
   reader. Same as the sidebar picker. */
.lets-logintheme__card input[type="radio"] {
    position: absolute; margin: 8px 0 0 8px; z-index: 2;
}

/* The page. `is-right` / `is-centre` is the whole point of the preview. */
.lets-logintheme__preview {
    display: flex; align-items: center; height: 76px; padding: 0 10px;
    background-repeat: no-repeat;
    border-bottom: 1px solid var(--lets-border);
}
.lets-logintheme__preview.is-right  { justify-content: flex-end; }
.lets-logintheme__preview.is-centre { justify-content: center; }

/* The panel, at the proportion the real one takes: 400px of a 1440px page is
   a little over a quarter, and the card is 236px wide. */
.lets-logintheme__pane {
    display: flex; flex-direction: column; justify-content: center; gap: 4px;
    width: 62px; padding: 7px 6px;
    border-radius: 5px;
    border: 1px solid rgba(255, 255, 255, .34);
    box-shadow: 0 3px 10px rgba(0, 0, 0, .45);
}

.lets-logintheme__line {
    display: block; height: 5px; border-radius: 2px;
    background: rgba(255, 255, 255, .30);
}
.lets-logintheme__btn {
    display: block; height: 6px; border-radius: 2px; margin-top: 1px;
}

.lets-logintheme__meta { display: block; padding: 9px 11px 10px; }
.lets-logintheme__meta strong { display: block; font-size: 13px; color: var(--lets-ink, #111b19); }
.lets-logintheme__meta em {
    display: block; margin-top: 2px; font-style: normal;
    font-size: 11.5px; line-height: 1.35; color: var(--lets-muted, #5c6863);
}

.lets-logintheme__tag {
    display: inline-block; margin-top: 6px; padding: 1px 7px;
    border-radius: 999px; font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .4px;
    background: var(--lets-primary, #0e7490); color: #fff;
}

/* Next to the picture it is talking about, not in a note at the foot of the
   screen -- the one thing that would stop the choice doing what the card
   shows. */
.lets-logintheme__warn {
    display: block; margin-top: 6px;
    font-size: 11px; line-height: 1.3; color: #8a6d1f;
}
.lets-logintheme__warn i { margin-right: 3px; }

/* ==========================================================================
   REPORTS  --  the filter bar and the totals strip            (26 Aug 2026)
   ==========================================================================
   Both report screens are a filter bar, a row of totals and a grid. The grid
   sizes itself to what is left of the viewport (see lets-grid.css), so
   everything above it is charged rent: the bar is deliberately tight, and the
   totals are one wrapping line rather than a row of dashboard cards. */

.lets-report__filters { margin-bottom: 10px; }
.lets-report__filters .box-body { padding: 12px 15px 2px; }

.lets-report__filters .form-group { margin-bottom: 10px; }

.lets-report__filters label {
    font-size: 11.5px; font-weight: 600; letter-spacing: .2px;
    color: var(--lets-ink-2); margin-bottom: 3px;
}

/* ---- the totals strip -------------------------------------------------- */
.lets-report__totals {
    display: flex; flex-wrap: wrap; gap: 6px;
    margin: 0 0 10px;
}

/* Empty until the first reply lands. Collapsing it would make the page jump
   as the grid loads, so it holds its line whether or not it has tiles yet. */
.lets-report__totals:empty { min-height: 26px; }

.lets-report__tile {
    display: inline-block; padding: 3px 10px;
    border: 1px solid var(--lets-border); border-radius: 999px;
    background: var(--lets-surface);
    font-size: 12px; line-height: 18px; color: var(--lets-ink-2);
    white-space: nowrap;
}

.lets-report__tile b { color: var(--lets-ink); font-weight: 700; }

/* Money tiles read differently from volume tiles on purpose. Somebody
   scanning for a revenue figure among fifteen word-count totals should find
   it without reading every label. */
.lets-report__tile.is-money {
    border-color: var(--lets-tint);
    background: var(--lets-tint);
}

.lets-report__tile.is-money b { color: var(--lets-teal-deep, #0c6478); }

/* ---- "you cannot see money" ------------------------------------------- */
.lets-report__nomoney {
    margin: -4px 0 10px; font-size: 12px;
}
.lets-report__nomoney i { margin-right: 4px; }

/* The currency on a money tile. Set apart from the label so "Amount USD" reads
   as one thing at a glance -- there is one tile per currency and they sit next
   to each other, so the code is what tells them apart. */
.lets-report__tile em {
    font-style: normal; font-weight: 700;
    color: var(--lets-teal-deep, #0c6478); opacity: .75;
}

/* The fold control, centred on the filter card's bottom edge. A plain child of
   the .box, NOT of .box-body -- AdminLTE slides the body and footer up on
   collapse, and a control inside either would hide itself along with the
   filters and leave no way back. */
.lets-report__fold {
    text-align: center;
    border-top: 1px solid var(--lets-hairline, #edf2f0);
    padding: 4px 0 6px;
}
.lets-report__fold .btn { min-width: 64px; }

/* A collapsed card has nothing above the strip, so the border would draw a
   line under a line. */
.collapsed-box .lets-report__fold { border-top: 0; }

/* ==========================================================================
   ANNOUNCEMENTS  --  the banner, the bell, and the audience picker
   (27 Aug 2026)
   --------------------------------------------------------------------------
   The owner, 22 Aug: "this info will be display on receipints dashboard as
   headline and or in the bell icon as new info received."

   >>> `.lets-banner` IS THE SAME COMPONENT UNDER A SECOND NAME. <<<
                                                            (27 Aug 2026)
   The owner, looking at the working-hours screens: *"the banner showing on
   profiles should choose color something like we used for announcement .. some
   gredient some beautiful color not like current"*. Those screens had AdminLTE
   `.callout`s on them, which is the very thing the note below says this
   component exists to replace.

   Rather than copy the gradient, the rail and the chip into a second class --
   a second copy of a decision the owner already made once, and a second place
   for it to drift -- every selector in this block answers to BOTH names. The
   rules are defined once; `.lets-banner` is what a screen uses when the thing
   it is showing is not an announcement. Same three severities, same ink, same
   contrast argument below.

   If you restyle one, you have restyled the other. That is the point.

   >>> IT WAS AdminLTE'S .callout FOR A FEW HOURS. IT CANNOT BE. <<<
                                                        (27 Aug 2026)
   The house rule is "prefer AdminLTE's own classes" (HANDOVER 6), and that
   was the first attempt: `callout callout-danger` for URGENT,
   `callout-warning` for IMPORTANT, `callout-info` for INFO.

   >>> ALL THREE RENDERED IDENTICALLY, AND NOTHING SAID SO. <<< The theme
   accent layer at the very end of this file does

       .callout, .lets-page { border-left-color: var(--th-accent); }

   which is the same specificity as `.callout-danger` and comes LATER, so it
   wins. Every callout in the application takes the theme's accent stripe --
   correct for the notes and tips those were built for, and fatal for a
   control whose whole job is to look different when something is urgent. The
   markup said `callout-danger`, the DOM said `callout-danger`, and the stripe
   was teal.

   So the banner is its own block, which is what STYLE-GUIDE.md 4 prescribes
   for a new component anyway. It borrows no AdminLTE class, so no later rule
   can quietly repaint it.

   --------------------------------------------------------------------------
   THE COLOUR: DARK GROUND, NEAR-WHITE INK           [REVISED -- 27 Aug 2026]

   The owner had offered both branches: *"like some transparent light color or
   gredient color with text . or dark but beautiful color"*. The PALE branch
   was taken first, and the owner, looking at it: *"Use some dark colors ase
   these banners are not visible correctly"*. So it is the other branch now.

   >>> THE OLD ARGUMENT WAS RIGHT ABOUT THE FAILURE AND WRONG ABOUT THE FIX.
   <<< It warned that "white on a mid-saturation fill lands around 4.5:1",
   which is true and is why a mid-tone banner is unreadable. It concluded that
   the ground therefore had to be pale. It does not: it has to be far from the
   ink in EITHER direction, and these grounds are deep enough to be further
   from near-white than the tints ever were from near-black.

   Measured, not guessed -- worst case is the LIGHT end of each gradient,
   since the far end is darker still:

       title (--lets-on-dark)     11.5:1 info | 12.6:1 important | 13.7:1 urgent
       body  (--lets-on-dark-2)    7.3:1      |  8.1:1           |  8.8:1

   Every one passes AAA (7:1). The pale version's body ink did not manage that
   on amber, which is the screen the complaint came from.

   The two points the old note got right are kept and still hold:

     1. FIVE THEMES. The user picks their own accent (Config\AppSkins). These
        grounds are near-neutral -- a dark slate carrying a hint of the brand
        green -- so none of them fights a violet or a green theme. The
        SEVERITY is carried by the rail and the chip, which are 4px and 26px
        wide and may be as loud as they like.
     2. SEVERITY WITHOUT SHOUTING. Same division. The reading surface stays
        calm; the colour lives at the edges.

   >>> THE CHIP CARRIES *DARK* INK ON A BRIGHT DISC. <<< The inverse of the
   pale version, and not a detail: white on --lets-teal-glow measures under
   2:1. The glow colours are bright enough to be backgrounds and far too
   bright to be foregrounds.

   >>> THE INK RULES ARE SCOPED TO THE BANNER (`.lets-announce .lets-announce__
   body`), NOT SET ON THE BARE CLASS. <<< `announcements/view.php` prints
   __body on an ordinary white card outside any banner. Colouring the bare
   class near-white rendered that page as invisible text on white -- found by
   grepping for the class rather than by opening the screen, which is the only
   reason it did not ship.

   The gradient is deliberately shallow -- severity ground to --lets-dark-2 at
   135deg. Enough to stop a flat rectangle looking dead, not enough to move
   the contrast under the text as the eye travels across it.
   --------------------------------------------------------------------------

   Layout is FLEX with `min-width: 0` on the text, not a floated icon. Same
   lesson as .lets-notice a few hundred lines up: a float gives the browser no
   wrap boundary, so on a narrow screen the title collides with the icon
   instead of dropping below it.
   ========================================================================== */

.lets-announce,
.lets-banner {
    position: relative;
    margin-bottom: 12px;
    padding: 14px 16px 14px 18px;
    border: 1px solid var(--lets-dark-border);
    border-radius: var(--lets-radius);
    /* Every severity overrides this; the neutral ground is what a banner
       with no modifier on it gets, rather than a white flash. */
    background: linear-gradient(135deg, var(--lets-dark-1), var(--lets-dark-2) 70%);
    color: var(--lets-on-dark-2);
    box-shadow: var(--lets-shadow-h);
    overflow: hidden;   /* keeps the rail inside the rounded corner */
}

/* THE RAIL. A pseudo-element rather than `border-left`, so nothing that
   targets `border-left-color` on a generic selector can reach it -- which is
   the entire lesson of the .callout attempt above. */
.lets-announce::before,
.lets-banner::before {
    content: '';
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 4px;
    background: var(--lets-teal);
}

.lets-announce__row,
.lets-banner__row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}

/* The icon sits in a filled chip, which is where the severity colour is
   allowed to be loud. Fixed size so several stacked announcements line their
   text up down the left edge. */
.lets-announce__icon,
.lets-banner__icon {
    flex: 0 0 26px;
    width: 26px; height: 26px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    font-size: 13px;
    /* DARK ink on a BRIGHT disc -- the inverse of the pale version. White on
       these glows measures under 2:1; the dark ground it sits on measures 7+. */
    color: var(--lets-dark-2);
    background: var(--lets-teal-glow);
}

/* >>> min-width: 0 IS LOAD-BEARING. <<< A flex child's default minimum is its
   content width, so a long unbroken word (a URL somebody pasted into an
   announcement) would push the row wider than the page instead of wrapping. */
.lets-announce__main,
.lets-banner__main { flex: 1 1 auto; min-width: 0; }

/* --lets-ink, not the severity colour: this is the line that has to be
   READABLE, and near-black on a pale tint beats coloured-on-tint every time.
   The colour is carried by the rail and the chip beside it. */
.lets-announce__title,
.lets-banner__title {
    margin: 0 0 4px;
    font-size: 15px;
    font-weight: 700;
    line-height: 1.35;
}

/* >>> THE INK IS SCOPED TO THE BANNER, AND THAT IS NOT TIDINESS. <<<
   `announcements/view.php` prints .lets-announce__body on an ORDINARY WHITE
   CARD, outside any banner -- the staff detail page. Colouring the bare class
   near-white made that page render invisible text on white, which is the kind
   of thing nobody notices until somebody opens the one screen that has it.
   The descendant selector means only a body INSIDE a banner goes light. */
.lets-announce .lets-announce__title,
.lets-banner .lets-banner__title { color: var(--lets-on-dark); }

.lets-announce .lets-announce__body,
.lets-banner .lets-banner__body { color: var(--lets-on-dark-2); }

/* >>> `pre-line` IS WHAT RENDERS THE PARAGRAPH BREAKS, AND IT IS THE ONLY
      THING THAT MAY. <<<
   The body reaches the page as plain escaped text with no markup in it. This
   line is what turns the newlines the writer typed into breaks on screen.

   It shipped for a few hours with nl2br() in the PHP as well, and every break
   drew TWICE -- one newline in the database, two blank lines on screen. Found
   by screenshotting the page, which is the only way it was ever going to be
   found. If you add nl2br() back, delete this line, and vice versa; never
   both. See the banner partial for why the CSS half is the one to keep.

   `break-word` is the other half of the pasted-URL problem above. */
.lets-announce__body,
.lets-banner__body {
    margin: 0;
    color: var(--lets-ink-2);
    white-space: pre-line;
    overflow-wrap: break-word;
    line-height: 1.55;
}

/* >>> `--flow` IS FOR PROSE TYPED INTO THE MARKUP, NOT INTO THE DATABASE. <<<
                                                            (27 Aug 2026)
   `pre-line` above exists so an ANNOUNCEMENT's paragraph breaks survive -- the
   writer pressed Enter and meant it. Applied to a hand-written banner it does
   the opposite of what anybody wants: every newline in the SOURCE becomes a
   line break on screen.

   The owner saw exactly that and reported it as a blank heading. It was not a
   heading; it was the newline immediately after `<p class="...">`, rendered
   faithfully, with the sentence then chopped at each point the source happened
   to wrap:

       <p class="lets-banner__body">          <- this newline drew a blank line
           You have not set a working         <- and these two drew breaks
           schedule yet, so the week below...

   So a static banner says so, and gets the browser's ordinary wrapping back.
   The severity colours, the size and the leading are unchanged -- this
   modifier touches one property, deliberately. */
.lets-announce__body--flow,
.lets-banner__body--flow { white-space: normal; }

.lets-announce__meta,
.lets-banner__meta {
    margin: 8px 0 0;
    font-size: 11px;
    color: var(--lets-on-dark-3);
}
.lets-announce__meta i { margin-right: 3px; }

/* --------------------------------------------------------------------------
   THE THREE LEVELS. Each sets three things and nothing else: the tint the
   gradient runs from, the rail, and the chip. The ink never changes, which is
   what keeps all three equally readable.
   -------------------------------------------------------------------------- */

.lets-announce--info,
.lets-banner--info {
    background: linear-gradient(135deg, var(--lets-dark-info), var(--lets-dark-2) 70%);
}
.lets-announce--info::before,
.lets-banner--info::before,
.lets-banner--info .lets-banner__icon,
.lets-announce--info .lets-announce__icon { background: var(--lets-teal-glow); }

.lets-announce--important,
.lets-banner--important {
    background: linear-gradient(135deg, var(--lets-dark-important), var(--lets-dark-2) 70%);
    border-color: #4d3a14;
}
.lets-announce--important::before,
.lets-banner--important::before,
.lets-banner--important .lets-banner__icon,
.lets-announce--important .lets-announce__icon { background: var(--lets-amber-glow); }

/* URGENT is the one that may interrupt somebody, so it is allowed slightly
   more: a heavier rail and a border that carries the colour too. Still a pale
   surface -- an announcement nobody can read is not more urgent for being
   red. */
.lets-announce--urgent,
.lets-banner--urgent {
    background: linear-gradient(135deg, var(--lets-dark-urgent), var(--lets-dark-2) 70%);
    border-color: #5f2226;
}
.lets-announce--urgent::before,
.lets-banner--urgent::before {
    width: 5px;
    background: var(--lets-red-glow);
}
.lets-announce--urgent .lets-announce__icon,
.lets-banner--urgent .lets-banner__icon { background: var(--lets-red-glow); }

/* The title picks up the severity ink on URGENT only. Everywhere else the
   colour would be decoration; here it is the one case where a reader
   skim-reading a stack of notices should land on this one first. Still 8.9:1
   on its own tint. */
.lets-announce--urgent .lets-announce__title,
.lets-banner--urgent .lets-banner__title { color: var(--lets-red-glow); }

/* The body preview inside the bell dropdown. Two lines at most: the dropdown
   is a summary, and an announcement long enough to scroll turns the bell into
   a page. -webkit-line-clamp is the only cross-browser way to cap by LINE
   rather than by height, which is what keeps a one-line and a three-line
   notice looking like the same component. */
.lets-announce__snippet {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    white-space: normal;
    font-size: 12px;
    color: var(--lets-ink-2);
}

/* --------------------------------------------------------------------------
   THE AUDIENCE PICKER  --  the four "everyone / staff / linguists / clients"
   boxes on the compose form.

   A grid of labelled cards rather than a row of bare checkboxes, because each
   one needs its help text ATTACHED: "clients" and "the team for a client" are
   one word apart and reach opposite audiences, and a wrong guess mails the
   customer. A checkbox with its explanation somewhere else is a checkbox
   somebody ticks without the explanation.
   -------------------------------------------------------------------------- */

.lets-announce-pick {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 8px;
    margin-bottom: 4px;
}

.lets-announce-pick__box {
    display: block;
    margin: 0;
    padding: 10px 12px;
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    background: var(--lets-surface);
    cursor: pointer;
    font-weight: 400;
    transition: border-color .12s ease, background .12s ease;
}

.lets-announce-pick__box:hover { border-color: var(--th-accent, var(--lets-teal)); }

/* `:has()` so the whole card lights up when its own box is ticked, with no
   JavaScript keeping a class in step. Browsers without it simply show the
   checkbox state, which is the same information less loudly -- so this
   degrades rather than breaks. */
.lets-announce-pick__box:has(input:checked) {
    border-color: var(--th-accent, var(--lets-teal));
    background: var(--th-tint, #f2fbfd);
}

.lets-announce-pick__box input { margin-right: 6px; }

.lets-announce-pick__label {
    font-weight: 600;
    color: var(--lets-ink);
}

.lets-announce-pick__help {
    display: block;
    margin-top: 3px;
    padding-left: 20px;
    font-size: 11px;
    line-height: 1.4;
    color: var(--lets-muted);
}

/* --------------------------------------------------------------------------
   CLEARING A BANNER, AND THE BELL'S OWN SCROLL           (27 Aug 2026)

   The owner: "also if there are lots of messages on bell then it should have
   scrollbar" and "swipe can remove that announcement so that it will not hold
   on top eveytime".
   -------------------------------------------------------------------------- */

/* THE CLOSE BUTTON. Top-right of the card, out of the text's way. Quiet until
   hovered -- it is an escape hatch, not a call to action, and a bright x on
   every notice would read as "you are meant to dismiss this". */
.lets-announce__dismiss {
    margin: -4px -4px 0 0;
    flex: 0 0 auto;
    align-self: flex-start;
}

.lets-announce__dismiss button {
    border: 0;
    background: none;
    padding: 0 4px;
    font-size: 20px;
    line-height: 1;
    color: var(--lets-on-dark-3);
    opacity: .7;
    cursor: pointer;
    transition: opacity .12s ease, color .12s ease;
}

.lets-announce__dismiss button:hover,
.lets-announce__dismiss button:focus {
    opacity: 1;
    color: var(--lets-on-dark);
    outline: none;
}

/* >>> "swipe to clear" IS HIDDEN ON ANYTHING WITH A MOUSE. <<<
   `pointer: fine` is a real pointing device. Telling a desk user to swipe is
   telling them to do something their hardware cannot do, and they would go
   looking for the gesture rather than the x that is right there. Touch users
   get the hint; everyone else gets the button, which is always present. */
.lets-announce__hint { display: none; }

@media (pointer: coarse) {
    .lets-announce__hint { display: inline; }
}

/* A card mid-swipe must not select its own text under the finger, and must
   not fight the browser for the gesture -- `pan-y` keeps VERTICAL scrolling
   native (so the page still scrolls normally when a finger starts on a
   banner) while leaving the horizontal axis to us. */
.lets-announce[data-announce-id] {
    touch-action: pan-y;
    -webkit-user-select: none;
    user-select: none;
    will-change: transform;
}

/* The body still has to be selectable -- somebody copying a reference number
   out of an announcement is the ordinary case. */
.lets-announce__title,
.lets-announce__body {
    -webkit-user-select: text;
    user-select: text;
}

/* --------------------------------------------------------------------------
   THE BELL'S SCROLLING LIST

   AdminLTE's .notifications-menu .menu already sets max-height 200px and
   overflow-x hidden; it does NOT cap the height in a way that survives our
   two-line rows, so this restates the height and lets it scroll. Native
   scrollbar on purpose -- see the note at the call site.
   -------------------------------------------------------------------------- */
/* >>> AdminLTE CAPS THIS AT 200px WITH A FOUR-CLASS SELECTOR, SO OURS HAS
       TO MATCH ITS SPECIFICITY OR IT LOSES. <<<
   `.navbar-nav>.notifications-menu>.dropdown-menu>li .menu{max-height:200px}`
   is the rule in AdminLTE.min.css. A bare `.lets-announce-bell` is one
   class against four and never applied -- the list stayed at 200px and
   showed two notices out of seven, which looked exactly like the scroll
   not working. Same lesson as the .callout stripe a few hundred lines up:
   borrowing a vendor class means inheriting whatever else targets it. */
.navbar-nav > .notifications-menu > .dropdown-menu > li .menu.lets-announce-bell {
    max-height: 340px;
    overflow-y: auto;
    overflow-x: hidden;
    /* Firefox; WebKit takes the ::-webkit- rules below. Thin and in the house
       greys, so a list of notices does not sprout a chrome-coloured bar. */
    scrollbar-width: thin;
    scrollbar-color: var(--lets-border) transparent;
}

.lets-announce-bell::-webkit-scrollbar { width: 8px; }
.lets-announce-bell::-webkit-scrollbar-track { background: transparent; }

.lets-announce-bell::-webkit-scrollbar-thumb {
    background: var(--lets-border);
    border-radius: 4px;
}

.lets-announce-bell::-webkit-scrollbar-thumb:hover { background: var(--lets-muted); }

/* The count itself. AdminLTE puts a .label on the icon; this pins it to a
   circle so 1 and 12 are the same shape, and stops 99+ stretching the navbar
   item and shoving the user menu sideways. */
.lets-announce-bell__badge {
    position: absolute;
    top: 8px; right: 4px;
    min-width: 17px;
    padding: 2px 4px;
    border-radius: 9px;
    font-size: 10px;
    line-height: 1.2;
    text-align: center;
}

/* A little wider than AdminLTE's 280px default. Announcement titles are
   sentences somebody wrote ("New rate card effective 1 September"), not the
   two-word system strings the notification menu was designed for, and at
   280px most of them truncated mid-phrase. Same four-class specificity as
   the rule it is widening. */
.navbar-nav > .notifications-menu > .dropdown-menu.lets-announce-bell__menu {
    width: 360px;
    max-width: 92vw;   /* a 360px dropdown must not hang off a phone */
}

/* "Show cancelled work" -- a widening control, on its own line under the
   filter grid rather than among the dropdowns. */
.lets-report__check {
    display: inline-block; font-weight: 600; font-size: 12px;
    color: var(--lets-ink-2, #5c6863); cursor: pointer; margin: 0 0 6px;
}
.lets-report__check input { margin-right: 6px; vertical-align: -1px; }
.lets-report__check small { font-weight: 400; margin-left: 4px; }

/* ==========================================================================
   LINKS AND CREDENTIALS ON A TASK                              (27 Aug 2026)
   "ek chota model khulna chahiye text box wala jisme ham link ya credentials
    add kar sake, or add hone ke baad wo credentials or link Instruction card
    me niche alag se dikhe."

   Three surfaces, one feature: the chips in the file-grid cell where the
   button was pressed, the small dialog itself (which reuses .lets-libmodal
   wholesale -- see partials/library-picker.php), and the list under the
   instructions, which is the part the owner asked for by name.
   ========================================================================== */

/* ---- chips inside the LINKS / CREDENTIALS grid cells --------------------
   The twin of .lets-refcell above, and deliberately identical: a grid cell is
   a column, not a row, so both stack narrow and ellipsise. They are separate
   selectors rather than a shared class because the two cells hold different
   THINGS and one of them will eventually want to look different. */
.lets-linkcell { display: flex; flex-direction: column; gap: 3px; margin-top: 3px; }
.lets-linkcell .lets-refchip {
    max-width: 150px; padding: 2px 4px 2px 7px;
    font-size: 10.5px; border-radius: 10px;
}
.lets-linkcell .lets-refchip > span {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- the password box in the dialog ------------------------------------ */
.lets-linksecret { display: flex; gap: 6px; }
.lets-linksecret .form-control { flex: 1; }
.lets-linksecret .btn { flex: 0 0 auto; min-width: 58px; }

/* ---- the list under the instructions ----------------------------------- */
.lets-linksum {
    margin-top: 16px;
    border: 1px solid var(--lets-border);
    border-radius: var(--lets-radius-s);
    background: var(--lets-sunken);
    overflow: hidden;
}
.lets-linksum__head {
    padding: 9px 12px;
    background: var(--lets-surface);
    border-bottom: 1px solid var(--lets-border);
    font-size: 12px; font-weight: 800; letter-spacing: .02em;
    text-transform: uppercase; color: var(--lets-ink);
}
.lets-linksum__head i { color: var(--th-accent, var(--lets-teal)); margin-right: 5px; }
.lets-linksum__head small {
    text-transform: none; letter-spacing: 0; font-weight: 600;
    color: var(--lets-muted); margin-left: 6px;
}
.lets-linksum__group {
    padding: 8px 12px 3px;
    font-size: 11px; font-weight: 800; text-transform: uppercase;
    letter-spacing: .04em; color: var(--lets-muted);
}
.lets-linksum__group i { margin-right: 4px; }

.lets-linkrow {
    display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
    padding: 7px 12px; font-size: 12.5px;
    border-top: 1px solid var(--lets-hairline);
    background: var(--lets-surface);
}
.lets-linkrow__main {
    display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
    flex: 1; min-width: 0;
}
.lets-linkrow__main > strong { color: var(--lets-ink); }
.lets-linkrow__main > a {
    color: var(--th-accent, var(--lets-teal));
    word-break: break-all; font-size: 12px;
}
.lets-linkrow__user {
    font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
    font-size: 11.5px; color: var(--lets-ink);
    background: var(--th-tint, var(--lets-tint));
    border: 1px solid var(--lets-border); border-radius: 3px; padding: 0 5px;
}
/* The dots, and the real thing once Show is pressed. Same width-ish font, so
   revealing a password does not make the row jump. */
.lets-linkrow__secret {
    font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
    font-size: 11.5px; letter-spacing: .08em; color: var(--lets-ink);
}
.lets-linkrow__show { padding: 0 4px; font-size: 11px; }
.lets-linkrow__note { color: var(--lets-muted); font-style: normal; font-size: 11.5px; }
.lets-linkrow__who {
    font-size: 11px; color: var(--lets-muted); white-space: nowrap;
    padding-left: 8px; border-left: 1px solid var(--lets-border);
}

/* A row whose linguist has since left the task. It is NOT posted -- see
   renderLinkCells() -- and saying so is the whole point of still drawing it. */
.lets-linkrow--orphan { background: var(--lets-red-tint, #fdecea); }
.lets-linkrow--orphan .lets-linkrow__who { color: var(--lets-red); font-weight: 700; }


/* ==========================================================================
   BACKUP & ARCHIVAL                                          (27 Aug 2026)

   The screen that copies files onto the drive library and can then remove
   the B2 originals. See docs/BACKUP-ARCHIVAL-HANDOVER.md.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The scope tiles  --  eight things you might back up

   Real radios underneath, same as `.lets-choice`: the label is what gets
   painted, the input stays reachable by keyboard. `:has()` does the selected
   state in CSS so nothing depends on JavaScript having loaded -- a backup
   screen that cannot show you what you picked is worse than one with no
   styling at all.
   -------------------------------------------------------------------------- */
.lets-arc-scopes {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
}

@media (max-width: 1100px) { .lets-arc-scopes { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .lets-arc-scopes { grid-template-columns: 1fr; } }

.lets-arc-scope {
    position: relative;
    display: block;
    margin: 0;
    padding: 12px 14px;
    border: 1px solid var(--lets-border, #e1e7e5);
    border-radius: var(--lets-radius-s, 8px);
    background: var(--lets-surface, #fff);
    cursor: pointer;
    transition: border-color .12s, box-shadow .12s, background .12s;
}

.lets-arc-scope input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}

.lets-arc-scope:hover { border-color: var(--th-accent, #17726b); }
.lets-arc-scope:focus-within { outline: 2px solid var(--th-accent, #17726b); outline-offset: 2px; }

.lets-arc-scope:has(input:checked) {
    border-color: var(--th-accent, #17726b);
    background: var(--th-tint, #e0f7fa);
    box-shadow: 0 0 0 3px var(--th-tint, #e0f7fa);
}

.lets-arc-scope__t {
    display: block;
    font-weight: 600;
    font-size: 13px;
    color: var(--lets-ink, #111b19);
}

.lets-arc-scope__s {
    display: block;
    margin-top: 3px;
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--lets-muted, #8fa19b);
}

/* The master tile spans the row: "Everything" is not one choice among eight,
   it is the default and the one most runs will use. */
.lets-arc-scope--all { grid-column: 1 / -1; }


/* --------------------------------------------------------------------------
   The preview table  --  what this run would actually collect
   -------------------------------------------------------------------------- */
.lets-arc-preview { width: 100%; border-collapse: collapse; font-size: 13px; }

.lets-arc-preview th,
.lets-arc-preview td {
    padding: 7px 10px;
    border-bottom: 1px solid var(--lets-hairline, #edf2f0);
    text-align: left;
}

.lets-arc-preview th {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--lets-muted, #8fa19b);
    font-weight: 600;
}

.lets-arc-preview td.num,
.lets-arc-preview th.num { text-align: right; font-variant-numeric: tabular-nums; }

.lets-arc-preview tfoot td {
    font-weight: 700;
    border-top: 2px solid var(--lets-border, #e1e7e5);
    border-bottom: none;
}

.lets-arc-preview tr.is-empty td { color: var(--lets-muted, #8fa19b); }


/* --------------------------------------------------------------------------
   Status pills

   Four states, four colours, and REMOVED is the only red one. A run whose
   originals have been deleted must not be scannable as just another finished
   backup -- it is the one row on the list that cannot be undone.
   -------------------------------------------------------------------------- */
.lets-arc-pill {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .02em;
    white-space: nowrap;
}

.lets-arc-pill--staged     { background: var(--lets-amber-tint, #fdeccb); color: var(--lets-amber-deep, #7a4f10); }
.lets-arc-pill--downloaded { background: var(--lets-green-tint, #e6f5eb); color: var(--lets-green, #1f8a45); }
.lets-arc-pill--cleared    { background: var(--lets-green-tint, #e6f5eb); color: var(--lets-green, #1f8a45); }
.lets-arc-pill--removed    { background: var(--lets-red-tint, #fdebec);   color: var(--lets-red-deep, #9b1c23); }


/* --------------------------------------------------------------------------
   The remove panel

   Deliberately styled as a warning and placed at the BOTTOM of the archive
   page, below everything that describes what the archive contains. Somebody
   has to scroll past the full file list to reach the button that deletes
   what the list describes.
   -------------------------------------------------------------------------- */
.lets-arc-danger {
    border: 1px solid var(--lets-red, #e5484d);
    border-radius: var(--lets-radius-s, 8px);
    background: var(--lets-red-tint, #fdebec);
    padding: 14px 16px;
}

.lets-arc-danger h4 {
    margin: 0 0 8px;
    font-size: 13.5px;
    font-weight: 700;
    color: var(--lets-red-deep, #9b1c23);
}

.lets-arc-danger p { margin: 0 0 8px; font-size: 12.5px; line-height: 1.55; }

.lets-arc-danger__row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
    margin-top: 10px;
}

.lets-arc-danger__row input[type="text"] {
    width: 190px;
    padding: 6px 10px;
    border: 1px solid var(--lets-red, #e5484d);
    border-radius: 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
}

.lets-arc-blocked {
    border-left: 3px solid var(--lets-amber, #8a5d16);
    background: var(--lets-amber-tint, #fdeccb);
    color: var(--lets-amber-deep, #7a4f10);
    padding: 10px 14px;
    border-radius: 4px;
    font-size: 12.5px;
    line-height: 1.55;
}


/* --------------------------------------------------------------------------
   The file list on one archive
   -------------------------------------------------------------------------- */
.lets-arc-files { width: 100%; border-collapse: collapse; font-size: 12.5px; }

.lets-arc-files th,
.lets-arc-files td {
    padding: 6px 10px;
    border-bottom: 1px solid var(--lets-hairline, #edf2f0);
    text-align: left;
    vertical-align: top;
}

.lets-arc-files th {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--lets-muted, #8fa19b);
}

.lets-arc-files code {
    font-size: 11px;
    color: var(--lets-muted, #8fa19b);
    background: none;
    padding: 0;
    word-break: break-all;
}

.lets-arc-files tr.is-removed { background: var(--lets-red-tint, #fdebec); }

.lets-arc-hash {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10.5px;
    color: var(--lets-muted, #8fa19b);
    word-break: break-all;
}

/* ==========================================================================
   THE SUB-PROJECT'S INSTRUCTION TEMPLATE                       (27 Aug 2026)
   "he can save that template for the sub project and that template will be
    show in each new task creation for that sub project"

   One bar, above the task form's instructions editor. Its job is to explain
   why there is already text in a box the PM has not typed in yet -- without
   it, a pre-filled brief reads as the last task's leaking through.
   ========================================================================== */
.lets-tplbar {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: 0 0 10px;
    padding: 8px 12px;
    font-size: 12.5px; color: var(--lets-ink);
    background: var(--th-tint, var(--lets-tint));
    border: 1px solid var(--lets-border);
    border-left: 3px solid var(--th-accent, var(--lets-teal));
    border-radius: var(--lets-radius-s);
}
.lets-tplbar[hidden] { display: none; }
.lets-tplbar > i { color: var(--th-accent, var(--lets-teal)); }
.lets-tplbar > span { flex: 1; min-width: 220px; }

/* The editor on the sub-project screen. Taller than the notes one: this is a
   whole standing brief, and writing it in six lines of viewport is the reason
   people paste one in from elsewhere instead. */
#instructionTemplateEditor { min-height: 260px; }
#instructionTemplateEditor .ql-editor { min-height: 260px; }

/* --------------------------------------------------------------------------
   SECTION HEADINGS INSIDE THE BELL                       (27 Aug 2026)

   The owner: "there are two announcement buttons on vendor page ... vendor
   also getting new offer in announcments.. so merge them correctly."

   One bell now carries three streams -- work offered, announcements, personal
   notices. Merging them without labelling them would trade a duplicate icon
   for an undifferentiated list, which is the same problem one layer down: a
   linguist would not know whether "New rate card" was a broadcast to everyone
   or a change to THEIR rates.

   Deliberately quiet. These are signposts inside a list somebody is scanning,
   not headings competing with the notices under them.
   -------------------------------------------------------------------------- */
.lets-bell__section {
    padding: 7px 12px 3px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--lets-muted);
    background: var(--lets-sunken);
    border-bottom: 1px solid var(--lets-hairline);
}

/* Not the first one -- a rule directly under the dropdown's own header draws
   two lines a few pixels apart. */
.lets-bell__section + li a { border-top: 0; }

/* ---------------------------------------------------------------------------
   BANK ACCOUNTS TABLE  (companies/form.php)                      29 Aug 2026
   ---------------------------------------------------------------------------
   The company's accounts are edited as rows of inputs inside the company form.
   `table-layout: fixed` is what makes the <colgroup> widths authoritative --
   without it the browser sizes columns from their content, the drag on a
   heading's right edge changes a number nothing reads, and the column appears
   not to resize at all. */
.lets-bank { table-layout: fixed; }
.lets-bank td { padding: 4px 6px; vertical-align: middle; }
.lets-bank th { position: relative; white-space: nowrap; }
.lets-bank .form-control { min-width: 0; }

/* A row ticked for deletion. Struck through and faded rather than removed, so
   the deletion is visible -- and reversible -- before the form is saved. */
.lets-bank__row--gone td { opacity: .45; text-decoration: line-through; }

/* ===========================================================================
   RECORD HISTORY  --  the History tab on a View page
   ---------------------------------------------------------------------------
   partials/record-history.php. One entry per SAVE, with the changed fields
   nested under it -- unlike /audit, whose grid is necessarily one row per
   field because it spans every record in the application at once.

   The old/new colours are deliberately the SAME ones .lets-audit__old and
   .lets-audit__new use above, and unthemed for the same reason given there:
   a removed value tinted coral under Ember and green under Jade would read as
   a different kind of event depending on the colour scheme.
   ========================================================================== */
.lets-rechist__intro { font-size: 12px; margin-bottom: 4px; }

.lets-rechist__list { list-style: none; margin: 0; padding: 0; }

.lets-rechist__event {
    padding: 9px 0 9px 12px;
    border-top: 1px solid var(--lets-hairline);
    border-left: 3px solid var(--lets-tint);
}

.lets-rechist__event:first-child { border-top: 0; }

/* The one-line "who did what to which record, when". Wraps rather than
   scrolls on a narrow screen -- this sits inside a tab panel that is already
   as wide as it is going to get. */
.lets-rechist__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 7px;
    font-size: 12.5px;
}

.lets-rechist__where { font-weight: 600; color: var(--lets-ink); }
.lets-rechist__label { color: var(--lets-ink-2); }

/* Pushed to the right edge together, so the eye can run down "who" and "when"
   as columns even though this is a flex row and not a table. */
.lets-rechist__who  { margin-left: auto; color: var(--lets-ink-2); font-size: 11.5px; }
.lets-rechist__when { color: var(--lets-muted); font-size: 11.5px; white-space: nowrap; }

.lets-rechist__summary {
    margin-top: 4px;
    color: var(--lets-ink-2);
    font-style: italic;
    font-size: 12.5px;
}

/* auto on the value columns and 1% on the label: the field name takes exactly
   the width it needs and the two values share what is left, so a long note
   does not squeeze "Credit limit" onto three lines. */
.lets-rechist__fields { width: 100%; margin-top: 5px; border-collapse: collapse; }

.lets-rechist__fields td {
    padding: 2px 8px 2px 0;
    font-size: 12px;
    vertical-align: top;
    word-break: break-word;
}

.lets-rechist__field { color: var(--lets-muted); white-space: nowrap; width: 1%; }

.lets-rechist__old {
    color: var(--lets-muted, #6f7680);
    text-decoration: line-through;
    text-decoration-color: rgba(192, 57, 43, .5);
}

.lets-rechist__new   { color: #1d7a45; font-weight: 600; }
.lets-rechist__arrow { color: var(--lets-border, #c3cad1); width: 1%; font-size: 11px; }

/* "empty" is a statement about the value, not a value -- so it must not look
   like one that happened to be the word "empty". */
.lets-rechist__none { color: var(--lets-muted); font-style: italic; font-weight: 400; }

.lets-rechist__more { margin: 8px 0 0; font-size: 12px; }

/* ---------------------------------------------------------------------------
   LOGIN: THE FIRST-TIME NOTE
   ---------------------------------------------------------------------------
   Quieter than the form and quieter than the error, because it is standing
   guidance rather than a response to anything the person just did -- it is on
   the page before they type. See the long note in auth/login.php for why it is
   unconditional and why it does not mention a password being emailed.
   -------------------------------------------------------------------------- */
.lets-login__firsttime {
    margin: 14px 0 0;
    padding-top: 12px;
    border-top: 1px solid var(--lets-hairline, #e2e5e9);
    font-size: 12px;
    line-height: 1.55;
    color: var(--lets-muted, #6f7680);
}

.lets-login__firsttime strong { color: var(--lets-ink-2, #3d4852); }

/* A milestone step that NAVIGATES rather than switching a tab (1 Sep 2026).
   Used on the edit form, where Basic is the page you are on and every other
   step is a panel on the view screen. Styled identically to the button on
   purpose: it is the same road, and a step that looked different would read
   as a different kind of thing. */
.lets-milestone__btn--link { text-decoration: none; }
.lets-milestone__btn--link:hover,
.lets-milestone__btn--link:focus { text-decoration: none; }

/* ---------------------------------------------------------------------------
   RATE CARD: THE WORD / HOURLY BANNER                        (2 Sep 2026)
   ---------------------------------------------------------------------------
   One label over a span of columns instead of "(hr)" repeated under fifteen
   headings.

   >>> THE SELECTORS CARRY THE FULL `> thead > tr > th` PATH ON PURPOSE. <<<
   The first version of this styled `.lets-ratetable__band--word` alone and
   nothing changed on screen: `.table > thead > tr > th` already paints every
   header #e4f0ee and, at one class plus three elements, it outranked a lone
   class. Matching its shape and adding the band class wins on specificity
   without an !important.

   The two bands are tinted differently because the distinction they carry is
   the one mistake this table invites: an hourly figure typed into a per-word
   box is a rate 200x too high, and it looks perfectly normal next to 0.065.
   -------------------------------------------------------------------------- */
/* The BAND cells only. Source / Target / Rate card share this row via
   rowspan and must keep looking like ordinary headings. */
.lets-ratetable > thead > tr.lets-ratetable__banner > th.lets-ratetable__band {
    border-bottom: 0;
    padding: 5px 4px 3px;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
    text-align: center;
}

/* The three that span both rows sit centred against the taller header. */
.lets-ratetable > thead > tr.lets-ratetable__banner > th[rowspan] {
    vertical-align: middle;
}

.lets-ratetable > thead > tr > th.lets-ratetable__band {
    border-radius: 5px 5px 0 0;
    border-bottom: 2px solid rgba(0, 0, 0, .08);
}

.lets-ratetable > thead > tr > th.lets-ratetable__band--word {
    background: #d7e7f8;
    color: #1f4f85;
    border-bottom-color: #7aa9d8;
}

.lets-ratetable > thead > tr > th.lets-ratetable__band--hour {
    background: #fbe6c4;
    color: #8a5a0b;
    border-bottom-color: #d9a441;
}

/* The columns under each banner carry the same tint, a shade lighter, so a
   wide card still shows which section a box belongs to once the banner has
   scrolled out of sight. */
.lets-ratetable > thead > tr > th.is-word { background: #eef5fc; }
.lets-ratetable > thead > tr > th.is-hour { background: #fdf5e8; }

/* ---------------------------------------------------------------------------
   THE THIRD BAND: WHAT THE ROW IS ABOUT              (2 Sep 2026, owner ask)
   ---------------------------------------------------------------------------
   Source / Target / Domain used to span both header rows via rowspan, on the
   reasoning that they belong to neither money band. True, but it looked wrong:
   two banded groups sitting beside three tall unbanded cells reads as a table
   that has been patched, not designed, and the owner said so.

   They get a band of their own instead -- "Basic pair" -- so every heading in
   the row now sits under a band and the three groups line up. Grey, and
   deliberately the quietest of the three: these columns are what the row IS,
   while the coloured ones are what has to be typed and checked. The tint must
   not compete with the word/hour distinction, which is the one this table
   exists to make obvious.
   --------------------------------------------------------------------------- */
.lets-ratetable > thead > tr > th.lets-ratetable__band--basic {
    background: #e8ecec;
    color: #4a5757;
    border-bottom-color: #b6c2c0;
}

.lets-ratetable > thead > tr > th.is-basic { background: #f3f6f5; }

/* The actions column heads nothing -- it holds a delete button per row. Left
   with the default heading fill it showed as a stray coloured sliver off the
   right edge of the last band, which is what the owner spotted. It is blank,
   so it reads as blank. */
.lets-ratetable > thead > tr > th.lets-ratetable__act {
    background: transparent;
    border-bottom-color: transparent;
}

/* ---------------------------------------------------------------------------
   REVIEW YOUR RATES BEFORE UPDATE                       (2 Sep 2026, D191)
   ---------------------------------------------------------------------------
   The dialog a CSV import now goes through. It borrows the shared .lets-modal
   shell so it opens, closes and stacks like every other dialog; only the body
   is its own, because the body is a diff and nothing else in the application
   shows one.

   >>> IT IS THE SAME SHAPE AS THE CARD IT IS ABOUT. <<< A row per language
   pair, a column per activity -- the owner asked for exactly that after the
   first version listed one line per changed price: *"ek langauge ke sare rates
   ek row main dekhna easy hota hai"*. So the dialog is WIDE and short rather
   than narrow and long, and it scrolls sideways the way the rate card does.

   The change itself has to be unmissable -- *"jo change ho wo specially red
   light fir brown text main dikhaye ke pahale ye the ab ye ho rahe hai"* -- so
   a changed cell puts the new figure on a light red field in brown with the
   old one struck through above it. Colour alone is never the signal: the old
   value is struck and the new one is bold, so this reads the same to somebody
   who cannot tell the two apart.
   --------------------------------------------------------------------------- */
.lets-raterev__dialog {
    max-width: 1100px;
    width: calc(100vw - 40px);
}

.lets-raterev__body { padding-bottom: 4px; }

.lets-raterev__sum {
    margin: 0 0 10px;
    font-size: 13px;
    color: #46525a;
}

.lets-raterev__sum strong { color: #1f2b33; }

.lets-raterev__intro {
    color: #7c8791;
    font-size: 12px;
}

/* The zero question. Deliberately loud: it is the one thing in this dialog
   that can take a price away. */
.lets-raterev__zero {
    border: 1px solid #e6c9a8;
    background: #fdf6ec;
    border-radius: 6px;
    padding: 10px 12px;
    margin: 0 0 12px;
}

.lets-raterev__zerohead {
    margin: 0 0 8px;
    color: #8a5a0b;
    font-size: 13px;
}

.lets-raterev__opt {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-weight: 400;
    margin: 0 0 6px;
    font-size: 12.5px;
    line-height: 1.45;
    color: #46525a;
    cursor: pointer;
}

.lets-raterev__opt:last-child { margin-bottom: 0; }
.lets-raterev__opt input { margin: 3px 0 0; }

/* >>> THE LIST SCROLLS, THE DIALOG DOES NOT. <<< A card can put fifty pairs
   and fifteen activities in here, and a dialog that grows past the window puts
   its own Apply button off screen. Both directions, same as the rate card. */
.lets-raterev__scroll {
    max-height: 48vh;
    overflow: auto;
    border: 1px solid #dde5e3;
    border-radius: 6px;
}

.lets-raterev__table { margin: 0; }

.lets-raterev__table > thead > tr > th {
    position: sticky;
    top: 0;
    z-index: 2;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: .04em;
    white-space: nowrap;
    background: #eef2f1;
    vertical-align: bottom;
    padding: 6px 8px;
}

.lets-raterev__table > tbody > tr > td {
    font-size: 12.5px;
    vertical-align: middle;
    white-space: nowrap;
    padding: 5px 8px;
}

/* The pair names stay put while the activities scroll past them -- a row of
   figures with its label off screen is a row nobody can read. */
.lets-raterev__table > thead > tr > th.lets-raterev__pairhead,
.lets-raterev__table > tbody > tr > td.lets-raterev__pair {
    position: sticky;
    left: 0;
    z-index: 1;
    background: #fff;
    font-weight: 600;
}

.lets-raterev__table > thead > tr > th.lets-raterev__pairhead {
    z-index: 3;
    background: #eef2f1;
}

/* A cell stacks: what it was on top, what it becomes underneath. */
.lets-raterev__cell { text-align: center; line-height: 1.25; }

.lets-raterev__cell--changed { background: #fff7f6; }

.lets-raterev__same { color: #6c7781; }

.lets-raterev__was {
    display: block;
    color: #a3adb4;
    text-decoration: line-through;
    font-size: 11px;
}

/* The change itself. */
.lets-raterev__now {
    display: block;
    padding: 1px 6px;
    border-radius: 4px;
    background: #fdecec;
    color: #8a3b12;
    font-weight: 700;
}

.lets-raterev__new {
    display: block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: #1f7a5a;
    font-weight: 700;
}

/* The marker on a cell whose zero is being ignored. */
.lets-raterev__zmark {
    display: inline-block;
    margin-left: 5px;
    min-width: 15px;
    padding: 0 4px;
    border-radius: 3px;
    background: #f2e3cd;
    color: #8a5a0b;
    font-size: 10px;
    font-weight: 700;
    cursor: help;
}

.lets-raterev__key {
    margin: 8px 0 0;
    font-size: 11px;
    color: #7c8791;
    display: flex;
    align-items: center;
    gap: 5px;
    flex-wrap: wrap;
}

.lets-raterev__key .lets-raterev__was,
.lets-raterev__key .lets-raterev__now,
.lets-raterev__key .lets-raterev__zmark { display: inline-block; margin: 0; }

.lets-raterev__keysep { color: #c3cbd1; }

.lets-raterev__none {
    margin: 0;
    padding: 18px;
    text-align: center;
    color: #7c8791;
    font-size: 13px;
}

/* ---------------------------------------------------------------------------
   A BOX FILLED IN FROM THE CLIENT CARD                  (2 Sep 2026, D192)
   ---------------------------------------------------------------------------
   A price that appeared without being typed should say so, or the first
   question on seeing a filled card is "did I do that?". Quiet on purpose: it
   is an ordinary box holding an ordinary starting figure, not a warning and
   not a value that is locked.
   --------------------------------------------------------------------------- */
.lets-money__input.lets-prefilled,
input.lets-prefilled {
    background: #f2f8f5;
    box-shadow: inset 2px 0 0 #7fbfa3;
}

/* ---------------------------------------------------------------------------
   CLIENT RATES, BESIDE THE SUB-PROJECT'S            (2 Sep 2026, D192)
   ---------------------------------------------------------------------------
   Two tabs over one table shape, so the eye lands in the same place after
   switching -- the whole value of the dialog is that the two cards can be
   compared without holding one of them in memory. It reuses the review
   dialog's matrix styling for exactly that reason: two different-looking
   tables of the same numbers would be worse than one.
   --------------------------------------------------------------------------- */
.lets-ratecmp__dialog {
    max-width: 1100px;
    width: calc(100vw - 40px);
}

.lets-ratecmp__sum {
    margin: 0 0 10px;
    font-size: 13px;
    color: #46525a;
}

.lets-ratecmp__sum strong { color: #1f2b33; }
.lets-ratecmp__hint { color: #7c8791; font-size: 12px; }

.lets-ratecmp__tabs {
    display: flex;
    gap: 2px;
    margin: 0 0 -1px;
}

.lets-ratecmp__tab {
    border: 1px solid #dde5e3;
    border-bottom-color: transparent;
    background: #f3f6f5;
    color: #6c7781;
    font-size: 12px;
    font-weight: 600;
    padding: 6px 14px;
    border-radius: 6px 6px 0 0;
    cursor: pointer;
}

.lets-ratecmp__tab.is-on {
    background: #fff;
    color: #1f2b33;
    border-color: #dde5e3;
    border-bottom-color: #fff;
}

.lets-ratecmp__scroll {
    max-height: 46vh;
    overflow: auto;
    border: 1px solid #dde5e3;
    border-radius: 0 6px 6px 6px;
    background: #fff;
}

/* The warning under the table. It is about a write to ANOTHER record that
   happens immediately, which is the one thing on this screen that does. */
.lets-ratecmp__note {
    margin: 10px 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: #8a5a0b;
}

.lets-ratecmp__note:empty { display: none; }
.lets-ratecmp__note .fa { margin-right: 4px; }

/* The disagreement, stated on the rate card itself rather than only inside
   the dialog -- a button nobody presses is not a warning. */
.lets-ratecmp__flag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin: 0;
    padding: 3px 9px;
    border-radius: 4px;
    background: #fdf6ec;
    border: 1px solid #e6c9a8;
    color: #8a5a0b;
    font-size: 11.5px;
    font-weight: 600;
}

/* ---------------------------------------------------------------------------
   THE CLIENT'S HALF OF THE REVIEW DIALOG              (2 Sep 2026, D192)
   ---------------------------------------------------------------------------
   A mark under the figure, saying what this cell would mean for the client's
   own rate card, and one tickbox that decides all of them at once. Quieter
   than the change itself on purpose: the change is what is happening, the mark
   is what it implies somewhere else.
   --------------------------------------------------------------------------- */
.lets-raterev__cmark {
    display: block;
    margin-top: 2px;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: .03em;
    text-transform: uppercase;
    cursor: help;
}

.lets-raterev__cmark--add    { color: #1f7a5a; }
.lets-raterev__cmark--differ { color: #8a5a0b; text-transform: none; letter-spacing: 0; }

.lets-raterev__client {
    margin: 10px 0 0;
    padding: 9px 12px;
    border: 1px solid #cfe0d8;
    background: #f2f8f5;
    border-radius: 6px;
}

.lets-raterev__client .lets-raterev__opt { margin: 0; }

.lets-raterev__clientnote {
    margin: 6px 0 0;
    font-size: 11.5px;
    line-height: 1.5;
    color: #6c7781;
}

.lets-raterev__clientnote .fa { margin-right: 4px; color: #8a5a0b; }

/* ---------------------------------------------------------------------------
   WHAT A LINGUIST'S OWN CARD DOES NOT COVER          (2 Sep 2026, D194)
   ---------------------------------------------------------------------------
   Stated on the Vendors panel rather than used to hide the linguist. Hiding
   would deadlock: the missing rate can only be agreed once they are on a job,
   so a vendor with no Translation rate could never be given their first
   Translation job. Offering them SILENTLY is the other half of the mistake --
   so the panel says what is missing and who to ask.

   Amber, not red: nothing is broken and nothing is blocked. It is work for the
   VM, and the row can be priced by hand in the meantime.
   --------------------------------------------------------------------------- */
.lets-vendorgap {
    margin: 10px 0 0;
    padding: 10px 12px;
    border: 1px solid #e6c9a8;
    background: #fdf6ec;
    border-radius: 6px;
    font-size: 12.5px;
    color: #46525a;
}

.lets-vendorgap__head { margin: 0 0 6px; color: #8a5a0b; font-weight: 600; }
.lets-vendorgap__head .fa { margin-right: 5px; }
.lets-vendorgap__list { margin: 0 0 6px; padding-left: 20px; line-height: 1.6; }
.lets-vendorgap__foot { margin: 0; font-size: 11.5px; color: #7c8791; }
