/*
 * DocketDesk59 front-end app shell — Junction59 brand system.
 * Font: Arial only. Colours: brand gradient pairs (A = default flat,
 * B = contrast/legibility variant). This file is the entire visual
 * system for the front-end app; wp-admin chrome is not used anywhere
 * a logged-in DocketDesk59 user can see.
 */

:root {
    --ddesk-black: #000000;
    --ddesk-white: #FFFFFF;

    --ddesk-lime-a: #C6EC38;
    --ddesk-lime-b: #49EEA6;
    --ddesk-cyan-a: #01C5FF;
    --ddesk-cyan-b: #007EFF;
    --ddesk-magenta-a: #E70D70;
    --ddesk-magenta-b: #FA0ECB;
    --ddesk-purple-a: #CC51FF;
    --ddesk-purple-b: #7400EA;

    --ddesk-gradient-primary: linear-gradient(135deg, var(--ddesk-cyan-a), var(--ddesk-cyan-b));
    --ddesk-gradient-lime: linear-gradient(135deg, var(--ddesk-lime-a), var(--ddesk-lime-b));
    --ddesk-gradient-magenta: linear-gradient(135deg, var(--ddesk-magenta-a), var(--ddesk-magenta-b));
    --ddesk-gradient-purple: linear-gradient(135deg, var(--ddesk-purple-a), var(--ddesk-purple-b));

    --ddesk-border: #E2E2E2;
    --ddesk-bg: #F7F7F8;
    --ddesk-text: var(--ddesk-black);
    --ddesk-text-muted: #5B5B5B;
    --ddesk-danger: var(--ddesk-magenta-b);
    --ddesk-success: var(--ddesk-lime-b);

    /* ------------------------------------------------------------------ *
     * The scales underneath the brand. 0.76.0, design review V-1 to V-3.
     *
     * The palette above is the brand and does not change: Arial, the same
     * four gradient pairs, black and white doing the structural work. What
     * was missing was a scale beneath it. The stylesheet had grown 22 font
     * sizes, 31 spacing values, 13 corner radii and 25 colours outside the
     * tokens, because each screen was built when it was needed rather than
     * against a system.
     *
     * Six of those font sizes differed from a neighbour by half a pixel.
     * Nobody can see half a pixel, which means the distinction was doing no
     * work and guaranteed a twenty-third value next time.
     * ------------------------------------------------------------------ */

    /* Type: seven steps, each visibly different from the next. */
    --ddesk-text-label: 11px;   /* uppercase labels, chips, table headings */
    --ddesk-text-meta: 12px;    /* timestamps, helper text, muted detail   */
    --ddesk-text-body: 13px;    /* the default: form fields, table cells   */
    --ddesk-text-card: 15px;    /* a card's own heading                    */
    --ddesk-text-figure: 18px;  /* a money figure in a bar or a row        */
    --ddesk-text-screen: 23px;  /* the screen's h1                         */
    --ddesk-text-display: 30px; /* the one big number on a page            */

    /*
     * Spacing: 4px steps, each named for its multiple, so --ddesk-space-5 is
     * always 20px and nothing has to be looked up.
     *
     * The off-grid values - 6, 10, 14, 18 and a few others, 328 of the 646
     * spacing declarations in this file - were snapped to the step below
     * rather than the nearest one. Downward on purpose: the review's finding
     * about these screens was that they are long, and the sweep had to be
     * able to tighten them without being able to loosen them.
     *
     * 1px, 2px and 3px are not on the scale and were left alone. They are
     * hairlines and optical nudges - a badge sitting a pixel off its
     * baseline - not the rhythm of a page, and rounding them to 4px doubles
     * them.
     */
    --ddesk-space-1: 4px;
    --ddesk-space-2: 8px;
    --ddesk-space-3: 12px;
    --ddesk-space-4: 16px;
    --ddesk-space-5: 20px;
    --ddesk-space-6: 24px;
    --ddesk-space-7: 28px;
    --ddesk-space-8: 32px;
    --ddesk-space-10: 40px;
    --ddesk-space-12: 48px;

    /* Corners: three. */
    --ddesk-radius-control: 6px; /* buttons, inputs, chips  */
    --ddesk-radius-card: 12px;   /* cards, panels, banners  */
    --ddesk-radius-pill: 999px;  /* badges, toggles, dots   */

    /*
     * Greys. Three, named for the job they do, replacing eight near-identical
     * values that were invented one at a time.
     */
    --ddesk-grey-bg: #F7F7F8;
    --ddesk-grey-line: #E2E2E2;
    --ddesk-grey-text: #5B5B5B;
    /* A grey to FILL with, not to write in. 0.82.9: the approved-value bar
       draws bought-in cost as a block on black, and the three greys above are
       named for a background, a hairline and body text. Borrowing one of them
       for a fill is how a token stops meaning anything. Reads clearly against
       both the panel's black and the bar's own rgba(255,255,255,0.14) track;
       nothing is ever set in it. */
    --ddesk-grey-fill: #6E6E6E;

    /*
     * The readable end of each brand colour, for text and icons on white.
     *
     * These existed already as literals in nine places - #A10057, #4A5A0A,
     * #005A94, #5C0099 - because the A values in the palette are too light to
     * read as text. That instinct was right and it is what the B values are
     * for; what was missing was a name. A tint to sit each one on comes with
     * it, so a status chip is one pair rather than two more literals.
     */
    --ddesk-cyan-text: #005A94;
    --ddesk-cyan-tint: #F2FBFF;
    --ddesk-lime-text: #4A5A0A;
    --ddesk-lime-tint: #F4FBE3;
    --ddesk-magenta-text: #A10057;
    --ddesk-magenta-tint: #FDE7F2;
    --ddesk-purple-text: #5C0099;
    --ddesk-purple-tint: #F6EEFF;
}

/* ---------- Standalone page reset ---------- */
/* The app page uses its own bare template (no theme header/footer), but the
   theme's stylesheet can still apply global body/html rules via wp_head() -
   this resets the page root so the app owns the whole canvas. */
body.ddesk-standalone-body {
    margin: 0;
    padding: 0;
    background: var(--ddesk-grey-bg);
}

/* ---------- Reset / base ---------- */
.ddesk-app, .ddesk-app * {
    box-sizing: border-box;
}
.ddesk-app {
    min-height: 100vh;
}
.ddesk-app {
    font-family: Arial, Helvetica, sans-serif;
    color: var(--ddesk-text);
    background: var(--ddesk-bg);
    line-height: 1.5;
    font-size: var(--ddesk-text-card);
    -webkit-font-smoothing: antialiased;
}
.ddesk-app h1, .ddesk-app h2, .ddesk-app h3, .ddesk-app h4 {
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 700;
    color: var(--ddesk-black);
    margin: 0 0 var(--ddesk-space-3);
}
/* ---------- A heading needs room above it. 0.82.14 ----------
   Marc, 13 November: "lets also make sure that there is some padding around
   tables and sections, see how Add Credit is practicly touching the section
   above."

   Headings had no top margin at all, so one that followed a table, a form or
   a paragraph sat hard against it and the page read as a single column of
   stuff rather than as sections. On the client credit screen "Add credit"
   begins 1px under the last row of the history table.

   Only h2 and below, and only when something comes first. An h1 is the
   screen's own title and always the first thing on it, and a heading that
   opens its container has the container's padding above it already - adding
   a margin there would push it off its own panel. The card-head, link-card
   and view-block patterns further down set their own margins and are
   unaffected: they come later in this file at the same specificity. */
.ddesk-app h2, .ddesk-app h3, .ddesk-app h4 { margin-top: var(--ddesk-space-6); }
.ddesk-app h2:first-child, .ddesk-app h3:first-child, .ddesk-app h4:first-child { margin-top: 0; }
/* A table and the thing after it. The wrapper is a bordered white card, so
   without this the next heading's margin is all that separates two boxes.
   Not on the last one: that would add space inside the bottom of whatever
   panel the table closes. */
.ddesk-table-wrap:not(:last-child) { margin-bottom: var(--ddesk-space-5); }
.ddesk-app a { color: var(--ddesk-cyan-b); text-decoration: none; }
.ddesk-app a:hover { text-decoration: underline; }
.ddesk-app p { margin: 0 0 var(--ddesk-space-3); }
.ddesk-app p.description { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); }
.ddesk-muted { color: var(--ddesk-text-muted); font-weight: normal; font-size: 0.6em; }

/* ---------- Header / nav chrome ----------
   Sep 2, 2026: header made sticky (Marc: "...we make the navigation sticky
   as we scroll down the page it stays at the top") and given a new
   .ddesk-header-left wrapper around the logo + Company switcher, so the
   top-level flex row still distributes exactly 3 slots
   (left / nav / user) via justify-content: space-between - the switcher
   didn't get its own top-level slot, it rides inside the brand's. z-index
   40 clears normal page content but stays below the nav dropdown's own
   z-index: 20 context (fine - that's a child stacking context, not a
   competing sibling) and well below the modal overlay (z-index: 100). */
.ddesk-header {
    background: var(--ddesk-black);
    color: var(--ddesk-white);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--ddesk-space-3) var(--ddesk-space-7);
    flex-wrap: wrap;
    gap: var(--ddesk-space-3);
    position: sticky;
    top: 0;
    z-index: 40;
}
.ddesk-header-left {
    display: flex;
    align-items: center;
    gap: var(--ddesk-space-5);
    flex-wrap: wrap;
}
.ddesk-header-brand {
    display: flex;
    align-items: center;
    gap: var(--ddesk-space-2);
}
.ddesk-header-brand img { height: 30px; width: auto; display: block; }
.ddesk-company-switcher select {
    background: rgba(255,255,255,0.08);
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: var(--ddesk-radius-control);
    color: var(--ddesk-white);
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 700;
    font-size: var(--ddesk-text-body);
    padding: var(--ddesk-space-1) var(--ddesk-space-2);
}
.ddesk-company-switcher select:hover { border-color: rgba(255,255,255,0.32); }
.ddesk-header-nav {
    display: flex;
    gap: var(--ddesk-space-1);
    flex-wrap: wrap;
}
.ddesk-header-nav a {
    color: var(--ddesk-white);
    opacity: 0.75;
    padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border-radius: var(--ddesk-radius-control);
    font-weight: 700;
    font-size: var(--ddesk-text-body);
}
.ddesk-header-nav a:hover { opacity: 1; text-decoration: none; background: rgba(255,255,255,0.08); }
.ddesk-header-nav a.is-active { opacity: 1; background: var(--ddesk-gradient-primary); color: var(--ddesk-black); }
.ddesk-header-user {
    display: flex;
    align-items: center;
    gap: var(--ddesk-space-3);
    font-size: var(--ddesk-text-body);
    color: var(--ddesk-white);
    opacity: 0.85;
}
.ddesk-header-user a { color: var(--ddesk-white); opacity: 0.85; font-size: var(--ddesk-text-body); }
.ddesk-header-user a:hover { opacity: 1; }

/* ---------- Nav dropdowns (Settings/Billing parents) ---------- */
.ddesk-nav-item { position: relative; }
.ddesk-nav-caret { font-size: var(--ddesk-text-label); margin-left: 2px; opacity: 0.8; }
.ddesk-nav-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 200px;
    background: var(--ddesk-black);
    border: 1px solid rgba(255,255,255,0.12);
    border-radius: var(--ddesk-radius-control);
    padding: var(--ddesk-space-1);
    z-index: 20;
    box-shadow: 0 8px 24px rgba(0,0,0,0.35);
}
.ddesk-has-children:hover .ddesk-nav-dropdown,
.ddesk-has-children:focus-within .ddesk-nav-dropdown { display: block; }
.ddesk-nav-dropdown a {
    display: block;
    color: var(--ddesk-white);
    opacity: 0.85;
    padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border-radius: var(--ddesk-radius-control);
    font-weight: 700;
    font-size: var(--ddesk-text-body);
    white-space: nowrap;
}
.ddesk-nav-dropdown a:hover { opacity: 1; text-decoration: none; background: rgba(255,255,255,0.08); }
.ddesk-nav-dropdown a.is-active { opacity: 1; background: var(--ddesk-gradient-primary); color: var(--ddesk-black); }

/* ---------- Header icon links ---------- */
/* Consolidated notification bell (Sep 4, 2026, replaces the separate
   Approvals badge-link + Notifications bell): light grey/white like the
   other nav headings when there's nothing outstanding, red with the count
   overlapping its corner once there is - "red" is var(--ddesk-danger),
   which is the magenta-B token, since the Junction59 palette has no true
   red (flagged to Marc as a disclosed substitution). */
.ddesk-icon-link { position: relative; display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-bell-icon { width: 20px; height: 20px; color: var(--ddesk-white); opacity: 0.75; transition: color .15s ease, opacity .15s ease; }
.ddesk-icon-link:hover .ddesk-bell-icon { opacity: 1; }
.ddesk-bell.has-alerts .ddesk-bell-icon { color: var(--ddesk-danger); opacity: 1; }
.ddesk-bell-count {
    position: absolute;
    bottom: -3px;
    right: -6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    border-radius: var(--ddesk-radius-pill);
    background: var(--ddesk-danger);
    color: var(--ddesk-white);
    font-size: var(--ddesk-text-label);
    font-weight: 700;
    line-height: 1;
    border: 2px solid var(--ddesk-black);
}
.ddesk-badge-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 var(--ddesk-space-1);
    border-radius: var(--ddesk-radius-pill);
    background: var(--ddesk-magenta-a);
    color: var(--ddesk-black);
    font-size: var(--ddesk-text-label);
    font-weight: 700;
}

/* Help nav item (Sep 4, 2026): just the "?" glyph, styled as a compact
   round icon instead of a text label, to save space in the nav per Marc. */
.ddesk-nav-help-icon {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0 !important;
    border-radius: var(--ddesk-radius-pill) !important;
    font-size: var(--ddesk-text-card);
}

/* ---------- Settings/Billing link cards ---------- */
.ddesk-link-cards { display: flex; gap: var(--ddesk-space-4); flex-wrap: wrap; margin-top: var(--ddesk-space-5); }
.ddesk-link-card {
    display: block;
    width: 260px;
    padding: var(--ddesk-space-5);
    background: var(--ddesk-white);
    border: 1px solid var(--ddesk-border);
    border-radius: var(--ddesk-radius-card);
    color: var(--ddesk-text);
}
.ddesk-link-card:hover { text-decoration: none; border-color: var(--ddesk-cyan-a); }
.ddesk-link-card h3 { margin: 0 0 var(--ddesk-space-1); font-size: var(--ddesk-text-card); }
.ddesk-link-card p { margin: 0; color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); }

/* ---------- Filter bar (Estimates list, etc.) ---------- */
.ddesk-filter-bar { display: flex; gap: var(--ddesk-space-2); margin-bottom: var(--ddesk-space-4); flex-wrap: wrap; }
.ddesk-filter-bar select { padding: var(--ddesk-space-2) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); font-family: Arial, Helvetica, sans-serif; }

/* ---------- Screen-reader-only text: this front end has no WP theme/admin
   stylesheet loaded, so unlike wp-admin screens, .screen-reader-text isn't
   provided for free - defined here for the toggle labels below. ---------- */
.screen-reader-text {
    border: 0; clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%);
    height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute;
    width: 1px; word-wrap: normal !important;
}

/* ---------- Settings two-column layout (Alerts / notification preferences) ---------- */
.ddesk-settings-grid { display: grid; grid-template-columns: 1.3fr 1fr; gap: var(--ddesk-space-5); align-items: start; margin-top: var(--ddesk-space-5); }
@media (max-width: 900px) { .ddesk-settings-grid { grid-template-columns: 1fr; } }
.ddesk-settings-card { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-5); }
.ddesk-settings-card h2 { margin: 0 0 2px; font-size: var(--ddesk-text-figure); }
.ddesk-settings-card > p.description { margin: 0 0 var(--ddesk-space-2); }
.ddesk-settings-subhead { font-weight: 700; font-size: var(--ddesk-text-body); margin: var(--ddesk-space-4) 0 var(--ddesk-space-1); }

.ddesk-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--ddesk-space-4); padding: var(--ddesk-space-3) 0; border-bottom: 1px solid var(--ddesk-border); }
.ddesk-toggle-row:last-of-type { border-bottom: none; }
.ddesk-toggle-row h4 { margin: 0 0 2px; font-size: var(--ddesk-text-body); }
.ddesk-toggle-row p { margin: 0; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
/* Toggle-first layout (toggle, then icon+label, both left-aligned with a
   small gap) instead of the default space-between spread - used for the
   "Show discount column on line items" toggle to match production's real
   order, confirmed by direct live inspection Sep 5, 2026. */
.ddesk-toggle-row-reversed { justify-content: flex-start; gap: var(--ddesk-space-2); border-bottom: none; padding: var(--ddesk-space-1) 0; }
.ddesk-toggle-row-reversed h4 { margin: 0; }

/* A native checkbox styled as a switch - the input stays in the tab order and
   keeps its focus outline, so this is keyboard- and screen-reader-usable,
   not a purely visual re-skin. */
.ddesk-toggle { position: relative; display: inline-flex; flex: 0 0 auto; width: 40px; height: 22px; }
.ddesk-toggle input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.ddesk-toggle-slider { position: absolute; inset: 0; background: var(--ddesk-grey-line); border-radius: var(--ddesk-radius-card); transition: background 0.15s; pointer-events: none; }
.ddesk-toggle-slider::before { content: ""; position: absolute; height: 16px; width: 16px; left: 3px; top: 3px; background: var(--ddesk-white); border-radius: 50%; transition: transform 0.15s; }
.ddesk-toggle input:checked + .ddesk-toggle-slider { background: var(--ddesk-purple-b); }
.ddesk-toggle input:checked + .ddesk-toggle-slider::before { transform: translateX(18px); }
.ddesk-toggle input:focus-visible + .ddesk-toggle-slider { outline: 2px solid var(--ddesk-cyan-b); outline-offset: 2px; }

.ddesk-btn-block { width: 100%; justify-content: center; margin-top: var(--ddesk-space-1); }

.ddesk-icon-btn-row { display: flex; gap: var(--ddesk-space-2); }
.ddesk-icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control);
    background: var(--ddesk-white); color: var(--ddesk-black);
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-meta); font-weight: 700;
    cursor: pointer; text-decoration: none;
}
.ddesk-icon-btn:hover { border-color: var(--ddesk-cyan-a); text-decoration: none; }

.ddesk-mini-notif { text-align: center; padding: var(--ddesk-space-7) var(--ddesk-space-3); color: var(--ddesk-text-muted); }
.ddesk-mini-notif p { margin: 0 0 var(--ddesk-space-1); font-size: var(--ddesk-text-body); }
.ddesk-mini-notif-list { list-style: none; margin: var(--ddesk-space-2) 0; padding: 0; }
.ddesk-mini-notif-list li { display: flex; justify-content: space-between; gap: var(--ddesk-space-2); padding: var(--ddesk-space-2) 0; border-bottom: 1px solid var(--ddesk-border); font-size: var(--ddesk-text-body); }
.ddesk-mini-notif-list li:last-child { border-bottom: none; }
.ddesk-mini-notif-when { color: var(--ddesk-text-muted); font-weight: 400; white-space: nowrap; }

/* ---------- Content area ---------- */
/* 0.62.0: 1360 to 1760. Marc, 18 October 2026: "if you need to make the tool
   wider you can even on my laptop screen there is quite a bit of margin on
   either side." A 1512-wide laptop was giving back 76px each side to a cap set
   when this app had six columns on its widest screen; the estimate row now has
   ten and wants every pixel. The page still centres, so a wider screen gets a
   wider app rather than a longer line of text. */
.ddesk-content { max-width: 1760px; margin: 0 auto; padding: var(--ddesk-space-5) var(--ddesk-space-6) var(--ddesk-space-10); }
.ddesk-page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--ddesk-space-3);
    margin-bottom: var(--ddesk-space-6);
}
.ddesk-page-head h1 { margin: 0; font-size: var(--ddesk-text-screen); }
.ddesk-page-head-main { display: flex; flex-direction: column; gap: var(--ddesk-space-1); }
.ddesk-page-head-title { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-page-head-title h1 { margin: 0; }
.ddesk-page-subtitle { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); }
.ddesk-page-head-actions { display: flex; gap: var(--ddesk-space-2); flex-wrap: wrap; align-items: center; }

/* THE HEAD THAT MATCHES ITS CARD. 0.79.0.
 *
 * Marc, 12 November 2026: "Some pages use full width cards, and some only
 * partial, like the edit docket page, with the back to dockets and request
 * deletion being on the opposite side of the page... would it make sense to
 * either centre the card, and anchor the top button to the top right of the
 * card and not the page, or reorganize the card so it is full width."
 *
 * Both, as it turns out, because the pages are not all the same shape.
 *
 * The page is 1760px wide and a form card is 960px, so on a screen wider
 * than about 1100px the head's right-hand buttons were floating 800px away
 * from the card they belong to, over empty background. On the 18 screens
 * that are nothing but a form card, the head stops where the card stops, so
 * the buttons sit above the card's own right edge. Both stay left: centring
 * the pair would have moved every one of those screens' headings away from
 * the left edge every other screen in the app aligns to.
 *
 * The 12 screens that mix a narrow card with full-width tables take the
 * other half of Marc's own suggestion instead - the card goes wide, matching
 * the tables under it - and keep the full-width head. See ddesk-wide-card.
 */
.ddesk-page-head-narrow { max-width: 960px; }

/* Generic inline icon used in card/section headers and buttons */
.ddesk-icon { width: 17px; height: 17px; flex: 0 0 auto; }

/* ---------- Card head (icon + title + optional subtitle/right slot) ---------- */
/* 0.56.0, second density pass: the head's bottom margin and the subtitle's
   top margin were both paying for the same gap, which is why a negative
   margin had to be bolted on below to cancel it. One owner now. */
.ddesk-card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--ddesk-space-3); margin-bottom: var(--ddesk-space-2); }
.ddesk-card-head-left { display: flex; align-items: center; gap: var(--ddesk-space-2); }
.ddesk-card-head-left .ddesk-icon { color: var(--ddesk-cyan-b); width: 19px; height: 19px; }
.ddesk-card-head h2, .ddesk-card-head h3 { margin: 0; }
.ddesk-card-subtitle { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); margin: var(--ddesk-space-1) 0 var(--ddesk-space-3); }
/* A list that is prose in bullets rather than a set of records. 0.82.15. */
.ddesk-plain-list { margin: 0; padding-left: var(--ddesk-space-5); }
.ddesk-plain-list li { margin-bottom: var(--ddesk-space-2); line-height: 1.55; }
.ddesk-plain-list li:last-child { margin-bottom: 0; }

/* ---------- Viewing as somebody else. 0.82.15 ----------
   Across the top of every screen until it is stopped. Solid rather than a
   tinted note: this is a state the whole app is in, not a remark about the
   screen it sits on, and the failure worth preventing is an admin coming
   back an hour later, forgetting it is on, and reading a half-empty Dockets
   list as the truth about their own account.

   BLACK GROUND, MAGENTA EDGE, and the edge is not decoration. White on
   magenta A measures 4.48:1, which fails small text at 4.5 - and the span
   on this bar is 12px, the smallest thing on it. Magenta B is lighter still
   and worse. There is no darker magenta to reach for and no new hex is
   allowed below :root (the 0.76.0 rule), so the colour moves to the edge
   where it carries the meaning without carrying the text. Black is this
   app's ground for "these are the figures that matter" already, which is
   the right register: it is a statement about the whole screen.

   Magenta because that is this app's "something here wants your attention",
   and the same colour the read-only refusal uses - so the bar and the wall
   an admin meets on pressing Save read as one fact rather than two
   surprises. */
.ddesk-view-as-bar {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: var(--ddesk-space-2) var(--ddesk-space-4);
    background: var(--ddesk-black); color: var(--ddesk-white);
    border-left: 5px solid var(--ddesk-magenta-a);
    border-radius: var(--ddesk-radius-card);
    padding: var(--ddesk-space-3) var(--ddesk-space-4);
    margin-bottom: var(--ddesk-space-4);
}
.ddesk-view-as-bar-text { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ddesk-space-1) var(--ddesk-space-3); min-width: 0; }
.ddesk-view-as-bar-text strong { font-size: var(--ddesk-text-card); }
.ddesk-view-as-bar-text span { font-size: var(--ddesk-text-meta); color: rgba(255,255,255,0.78); }
.ddesk-view-as-bar form { margin: 0; flex: 0 0 auto; }
/* White on magenta, so the way out is the brightest thing in the bar. */
.ddesk-view-as-bar .ddesk-btn {
    background: var(--ddesk-white); color: var(--ddesk-black);
    border-color: var(--ddesk-white); font-weight: 700;
}
.ddesk-view-as-bar .ddesk-btn:hover { background: var(--ddesk-black); color: var(--ddesk-white); border-color: var(--ddesk-black); }
/* A figure the user did not choose, saying so. 0.82.14.
   Purple, which is this app's "waiting on somebody": the panel is working
   off an assumption and it wants a decision. A rule rather than a filled
   banner, because it sits above a dense table of figures and must not read
   as an error. */
.ddesk-inherited-note {
    margin: 0 0 var(--ddesk-space-3); padding-left: var(--ddesk-space-3);
    border-left: 3px solid var(--ddesk-purple-a);
    font-size: var(--ddesk-text-meta); line-height: 1.5; color: var(--ddesk-text-muted);
}

/* ---------- Summary / stat bar (e.g. estimate Grand Total row) ---------- */
/* 0.65.1: the bar is the one set of figures on this screen that is not live.
   See the note beside it in estimates-edit.php. */
.ddesk-summary-note { margin: var(--ddesk-space-1) 0 0; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
.ddesk-summary-bar { display: flex; flex-wrap: wrap; gap: var(--ddesk-space-7); align-items: center; background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-4) var(--ddesk-space-6); margin-top: var(--ddesk-space-5); }
.ddesk-summary-item { display: flex; flex-direction: column; gap: 3px; }
.ddesk-summary-item.ddesk-summary-push { margin-left: auto; text-align: right; }
/* 0.65.0 marked the money columns of the Client View and the estimate view
   with this and 0.66.0 marks the billing request strip's, and it had no rule
   behind it at all - so those columns were left-aligned on screen while the
   same columns in the PDF are right-aligned. One line, and the preview that
   exists to match the PDF matches it. */
.ddesk-table th.ddesk-cell-right, .ddesk-table td.ddesk-cell-right { text-align: right; }
/* ---------- A second figure under the first. 0.82.14 ----------
   The flex target beside its retainer target, and the annual figure under
   the monthly one. Small, muted and on its own line, because it qualifies
   the number above rather than competing with it. Only drawn where it says
   something the number above does not. */
.ddesk-cell-sub { display: block; font-size: var(--ddesk-text-meta); font-weight: normal; color: var(--ddesk-text-muted); }
/* A subtotal belongs to the rows above it; a total closes the table. The
   difference is a rule, not a shade: a tinted row in a dense table reads as
   a selected row. */
.ddesk-table tr.ddesk-row-subtotal td { border-top: 1px solid var(--ddesk-grey-line); color: var(--ddesk-text-muted); }
.ddesk-table tr.ddesk-row-total td { border-top: 2px solid var(--ddesk-black); }
/* A logged email's body, read back. 0.66.0. Monospaced because it is the text
   that was sent, and pre-wrapped because a billing email's facts block is
   lines rather than paragraphs. */
.ddesk-email-body {
    white-space: pre-wrap; word-break: break-word; margin: var(--ddesk-space-2) 0 0;
    padding: var(--ddesk-space-2) var(--ddesk-space-3); background: var(--ddesk-bg); border-radius: var(--ddesk-radius-control);
    font-size: var(--ddesk-text-meta); max-height: 320px; overflow: auto;
}
/* The request being looked at, in a list of several. 0.66.0. */
.ddesk-billing-requests tr.is-current > td { background: var(--ddesk-bg); font-weight: 600; }
.ddesk-summary-label { font-size: var(--ddesk-text-label); color: var(--ddesk-text-muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.ddesk-summary-value { font-size: var(--ddesk-text-figure); font-weight: 700; display: flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-summary-value .ddesk-icon { width: 15px; height: 15px; color: var(--ddesk-text-muted); }
.ddesk-summary-grand { font-size: var(--ddesk-text-screen); }
.ddesk-summary-item.ddesk-summary-push .ddesk-summary-value { justify-content: flex-end; }

/* Empty-state note (e.g. Budget Allocation & Hours Tracker at 0 hours) */
/* 0.56.0: this sentence sits inside a card that already has 20px of padding,
   so "Breakdown hidden for this task" cost 72px of white space around one
   line of text. */
.ddesk-empty-note { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); padding: var(--ddesk-space-2) 0; margin: 0; }

/*
 * Big centered empty-state prompt (Sep 4, 2026 production parity pass) -
 * used for a task with zero resources and for an estimate with zero
 * tasks. The task-level use is a touch smaller than the estimate-level
 * one (ddesk-empty-state-block-large), matching production's own visual
 * hierarchy between "no resources on this task" and "no tasks at all".
 */
.ddesk-empty-state-block { text-align: center; padding: var(--ddesk-space-7) var(--ddesk-space-5); border: 1px dashed var(--ddesk-border); border-radius: var(--ddesk-radius-card); background: var(--ddesk-bg-subtle, var(--ddesk-grey-bg)); }
.ddesk-empty-state-block p { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); margin: 0 0 var(--ddesk-space-3); }
.ddesk-empty-state-block-large { padding: var(--ddesk-space-12) var(--ddesk-space-5); margin-bottom: var(--ddesk-space-5); }
.ddesk-empty-state-block-large p { font-size: var(--ddesk-text-body); max-width: 480px; margin: 0 auto var(--ddesk-space-4); }

/* Plain full-width "+ Add Task" bar below the task list, added Sep 5, 2026
   to replace the previous name-and-toggle form card there - production's
   real bottom control (confirmed live) is exactly this: a plain dashed bar
   that adds a "Task N" task the moment it's clicked, no fields to fill in
   first. Same look production's own resting state uses (its darker fill
   only shows up on hover, not by default). */
/* 0.64.0: the margin used to be on the little form around this button. The
   button is a submit on the estimate's own form now - the Tasks card is inside
   that form, and forms cannot nest - so the spacing lives on the bar itself.
   Only on the bar: the same partial also renders as a small button in the card
   head, where 20px underneath it would float it off its own row. */
/*
 * 0.71.0. Marc, 11 November 2026: "the design of the Add Task on the initial
 * estimate is hard to find, being grey on grey it gets lost, and it is one of
 * the most important steps."
 *
 * He is right. Grey text on a grey fill inside a grey dashed box, on a grey
 * page, for the control that starts the work. The dashed outline is kept,
 * because it reads as "something goes here" rather than as a finished thing,
 * but it now uses the brand cyan and ordinary body-text weight so it is a
 * control rather than a suggestion.
 */
.ddesk-add-task-bar {
    display: block; width: 100%; text-align: center; padding: var(--ddesk-space-3) var(--ddesk-space-3);
    margin: 0 0 var(--ddesk-space-5);
    border: 2px dashed var(--ddesk-cyan-a, var(--ddesk-cyan-a)); border-radius: var(--ddesk-radius-control);
    background: var(--ddesk-cyan-tint); color: var(--ddesk-cyan-text);
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); font-weight: 700;
    cursor: pointer;
}
.ddesk-add-task-bar:hover { background: var(--ddesk-cyan-a, var(--ddesk-cyan-a)); color: var(--ddesk-white); border-color: var(--ddesk-cyan-a, var(--ddesk-cyan-a)); border-style: solid; }
.ddesk-add-task-bar:focus-visible { outline: 3px solid var(--ddesk-black); outline-offset: 2px; }

/* ---------- Budget & Funding Summary rows (estimate Build form) ---------- */
.ddesk-funding-row { display: flex; align-items: center; gap: var(--ddesk-space-3); padding: var(--ddesk-space-3) 0; border-bottom: 1px solid var(--ddesk-border); }
.ddesk-funding-row:last-of-type { border-bottom: none; }
.ddesk-funding-label { flex: 1 1 auto; font-size: var(--ddesk-text-body); }
.ddesk-funding-amount { display: flex; align-items: center; gap: var(--ddesk-space-2); font-size: var(--ddesk-text-body); flex: 0 0 auto; }
.ddesk-funding-amount input[type="number"] { width: 110px; padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); }
.ddesk-funding-remaining { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); white-space: nowrap; }
.ddesk-funding-notes { padding: 0 0 var(--ddesk-space-3); }
/* 0.68.0: the credit row's three stacked controls - source pot, client-facing
   reason, internal note - each need saying which is which, since two textareas
   with nothing between them is how an internal note ends up on a client's
   estimate. */
.ddesk-funding-sublabel {
    display: block; margin: var(--ddesk-space-2) 0 var(--ddesk-space-1); font-size: var(--ddesk-text-label); font-weight: 700;
    letter-spacing: 0.04em; text-transform: uppercase; color: var(--ddesk-text-muted);
}
/* Same missing-width bug as .ddesk-tab-panel textarea above, and the same
   fix (Sep 7, 2026, Marc's direct follow-up): these two notes fields sit
   directly in .ddesk-funding-notes, not a .ddesk-field wrapper, so they
   never picked up any of the .ddesk-field textarea rules - shown at the
   bare browser default width/font regardless of Budget & Funding Summary's
   own width. Filled to the card's edge, "almost as wide as the Budget and
   Funding Summary block" as asked. */
.ddesk-funding-notes textarea {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    min-height: 44px;
    font-family: Arial, Helvetica, sans-serif;
    font-size: var(--ddesk-text-body);
    padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border: 1px solid var(--ddesk-border);
    border-radius: var(--ddesk-radius-control);
    background: var(--ddesk-white);
    color: var(--ddesk-text);
    resize: vertical;
}
.ddesk-funding-notes textarea:focus {
    outline: none; border-color: var(--ddesk-cyan-b); box-shadow: 0 0 0 3px rgba(1,197,255,0.15);
}

/* ---------- Tasks / Resources (estimate Build form) - Sep 2, 2026 rebuild against real production behaviour ---------- */
/* Task cards and their resource table/add-form override the generic
   .ddesk-form-card 960px cap: Marc flagged this section as reading too
   narrow/cramped against production, and a live re-check found production's
   own resource table runs close to its full content width (~1240px measured
   directly on docketdesk59.lovable.app), not a narrow centred form. Letting
   these run the full width of .ddesk-content (1760px since 0.62.0) gives the table's many
   columns, and the two controls stacked in Resource/Item, real room. */
.ddesk-form-card.ddesk-task-card { max-width: none; }

/* Estimate Details / Estimate Content / Budget & Funding Summary widened
   Sep 5, 2026 at Marc's request to match the Tasks section above - same
   scoped-override pattern as .ddesk-task-card just above, so the shared
   .ddesk-form-card class (960px/28px padding) stays untouched for the
   many other, unrelated screens in the plugin that still use it plainly.
   Padding and field spacing are tightened at the same time per his
   "minimize vertical height where possible" - less scrolling on a page
   that's already long. */
.ddesk-form-card.ddesk-wide-card { max-width: none; padding: var(--ddesk-space-4) var(--ddesk-space-6); }
.ddesk-form-card.ddesk-wide-card + .ddesk-form-card.ddesk-wide-card { margin-top: var(--ddesk-space-4); }
.ddesk-form-card.ddesk-wide-card .ddesk-field { margin-bottom: var(--ddesk-space-3); }
.ddesk-form-card.ddesk-wide-card .ddesk-field-row { gap: var(--ddesk-space-4); }
/* WHAT A WIDE CARD DOES TO A FIELD ROW. 0.79.0.
 *
 * .ddesk-field-row is a flex row and .ddesk-field is flex:1, so two fields in
 * a 960px card take about 460px each. In a card that now runs to 1760px they
 * take about 860px each, which is a text box the width of a paragraph holding
 * a client name. Eight more screens went wide in this release, so the ceiling
 * is worth having: the fields keep their natural size and the row packs left
 * rather than stretching to fill a width nothing in it wants.
 *
 * 380px is the measured width of the longest thing these rows hold - a client
 * name, a docket title, a date pair - with room over. Below 380px nothing
 * changes at all, because flex-basis and the 200px floor still decide, which
 * is why a narrow window behaves exactly as it did.
 *
 * A field that should be wide is a field on its own outside a row, which is
 * how every long description in this app is already written. */
.ddesk-form-card.ddesk-wide-card .ddesk-field-row { justify-content: flex-start; }
.ddesk-form-card.ddesk-wide-card .ddesk-field-row > .ddesk-field { flex: 1 1 240px; max-width: 380px; }

/* ---------- Estimate Details, compact (8 October) ----------
   Three columns instead of two, so the right half of a wide screen is not
   empty on every row, and the card is about half the height it was. It falls
   back to two columns on a tablet and one on a phone, which is the only reason
   a grid is used rather than fixed widths. */
.ddesk-details-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ddesk-space-2) var(--ddesk-space-4); align-items: start; }
/* Specific enough to beat `.ddesk-form-card.ddesk-wide-card .ddesk-field`,
   which sets a 12px bottom margin. Without the extra class the grid's own
   row-gap would be added to that margin and the rows would sit 22px apart,
   which is most of the height this card was meant to save. */
.ddesk-form-card.ddesk-wide-card .ddesk-details-grid > .ddesk-field { margin-bottom: 0; }
.ddesk-details-span { grid-column: 1 / -1; }
@media (max-width: 1040px) { .ddesk-details-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 680px) { .ddesk-details-grid { grid-template-columns: minmax(0, 1fr); } }

/* The header carries the estimate's name, its version and Save, so the card no
   longer opens with a read-only field row and no longer ends with a row holding
   one button. */
.ddesk-form-card.ddesk-wide-card .ddesk-details-head { flex-wrap: wrap; gap: var(--ddesk-space-2); margin-bottom: var(--ddesk-space-3); }
.ddesk-details-head .ddesk-card-head-left { flex-wrap: wrap; gap: var(--ddesk-space-2); }
.ddesk-details-head h2 { font-size: var(--ddesk-text-figure); }
.ddesk-details-head-actions { display: flex; align-items: center; gap: var(--ddesk-space-2); }
.ddesk-details-id { font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }

/* The one sentence that explains a field, opened from its label rather than
   standing under the row for good. */
/* A label and its hint on one line. .ddesk-field label is display:block, so
   without this the button lands on a line of its own and pushes the control
   below its neighbours in the row. The label keeps its own bottom margin here
   so the field's vertical rhythm is unchanged. */
.ddesk-label-row { display: flex; align-items: center; gap: 2px; margin-bottom: var(--ddesk-space-1); }
.ddesk-form-card .ddesk-label-row label, .ddesk-label-row label { margin-bottom: 0; }

.ddesk-hint {
    display: inline-flex; align-items: center; justify-content: center;
    width: 15px; height: 15px; padding: 0; margin-left: 3px; flex: none;
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-label); font-weight: 700; line-height: 1;
    color: var(--ddesk-text-muted); background: none;
    border: 1px solid var(--ddesk-border); border-radius: 50%; cursor: pointer;
}
.ddesk-hint:hover, .ddesk-hint[aria-expanded="true"] { color: var(--ddesk-cyan-b); border-color: var(--ddesk-cyan-b); }
.ddesk-hint:focus-visible { outline: 2px solid var(--ddesk-cyan-b); outline-offset: 1px; }

/* Chips beside the button rather than stacked above it. */
.ddesk-j59-picker-inline { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-j59-picker-inline .ddesk-j59-chips { margin-bottom: 0; min-height: 0; }

/* The closed state, on a locked estimate only: nobody can change these fields
   while it is locked, so the form folds to one line of what they say. */
.ddesk-details-card[data-ddesk-collapsed] .ddesk-details-body { display: none; }
.ddesk-details-card:not([data-ddesk-collapsed]) .ddesk-details-summary { display: none; }
.ddesk-details-summary { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-details-summary .ddesk-icon { color: var(--ddesk-cyan-b); }
.ddesk-details-summary-title { font-size: var(--ddesk-text-body); }
.ddesk-details-summary code { font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
.ddesk-details-spacer { flex-grow: 1; }
.ddesk-details-fact { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); font-size: var(--ddesk-text-meta); background: var(--ddesk-bg); border-radius: var(--ddesk-radius-pill); padding: 3px var(--ddesk-space-2); }
.ddesk-details-fact b { font-size: var(--ddesk-text-label); font-weight: 700; color: var(--ddesk-text-muted); }
.ddesk-form-card.ddesk-wide-card .ddesk-card-head { margin-bottom: var(--ddesk-space-2); }
.ddesk-task-card { position: relative; }
.ddesk-task-card.is-dragging { opacity: 0.4; }
.ddesk-section-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ddesk-space-4); margin-bottom: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-task-title-row { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-drag-handle { cursor: grab; color: var(--ddesk-text-muted); display: inline-flex; }
.ddesk-drag-handle .ddesk-icon { width: 16px; height: 16px; }
.ddesk-task-name-display { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-task-rename-form { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-task-rename-form input[type="text"] { padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-card); font-weight: 700; }
.ddesk-task-actions { display: flex; align-items: center; gap: var(--ddesk-space-3); flex-wrap: wrap; }
.ddesk-inline-toggle-form { margin: 0; }
.ddesk-inline-toggle-label { display: flex; align-items: center; gap: var(--ddesk-space-2); font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); font-weight: 700; }
/* While a task switch is in flight. 0.59.0: it saves over the network now
   rather than reloading the page, and on a slow connection the only thing
   worse than a wait is a wait with no sign of itself. The switch is disabled
   for the same moment, so this is the visible half of that. */
.ddesk-inline-toggle-label.is-saving { opacity: 0.55; cursor: progress; }
.ddesk-inline-toggle-label.is-saving .ddesk-toggle { pointer-events: none; }
/* Task Subtotal + icon-only Delete task, added Sep 5, 2026 to match
   production exactly (direct live inspection): the Subtotal moved out of
   the title row (it used to render as a bare "- 0.00" right next to the
   task name) and into this actions row as "Subtotal: $X,XXX.XX", and
   "Delete task" collapsed from a bordered/labelled button to one plain
   trash icon - both sit at the end of .ddesk-task-actions. */
.ddesk-task-subtotal { font-size: var(--ddesk-text-body); color: var(--ddesk-text-muted); white-space: nowrap; }
.ddesk-icon-btn-danger { border: none; background: none; padding: var(--ddesk-space-1); color: var(--ddesk-magenta-b); }
.ddesk-icon-btn-danger:hover { background: rgba(231,13,112,0.08); border-radius: var(--ddesk-radius-control); }
.ddesk-icon-btn-danger .ddesk-icon { width: 17px; height: 17px; }
.ddesk-icon-btn-tiny { padding: var(--ddesk-space-1); }
.ddesk-icon-btn-tiny .ddesk-icon { width: 13px; height: 13px; margin: 0; }
.ddesk-task-notes-panel { background: var(--ddesk-bg); border-radius: var(--ddesk-radius-control); padding: var(--ddesk-space-3) var(--ddesk-space-3); margin: 0 0 var(--ddesk-space-4); }
/* Same missing-width bug as .ddesk-tab-panel/.ddesk-funding-notes textareas
   above (Sep 7, 2026): Internal Notes also sits outside any .ddesk-field
   wrapper, so it was rendering at the bare browser default too. Caught and
   fixed proactively alongside Marc's two reported instances, same pattern. */
.ddesk-task-notes-panel textarea {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    font-family: Arial, Helvetica, sans-serif;
    font-size: var(--ddesk-text-body);
    padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border: 1px solid var(--ddesk-border);
    border-radius: var(--ddesk-radius-control);
    background: var(--ddesk-white);
    color: var(--ddesk-text);
    resize: vertical;
}
.ddesk-task-notes-panel textarea:focus {
    outline: none; border-color: var(--ddesk-cyan-b); box-shadow: 0 0 0 3px rgba(1,197,255,0.15);
}
.ddesk-muted-inline { color: var(--ddesk-text-muted); font-style: italic; }
.ddesk-icon-inline { width: 13px; height: 13px; vertical-align: -2px; color: var(--ddesk-text-muted); }
/* Compact Tasks/Resources row (Sep 4, 2026): replaces the old boxed,
   fully-labelled "Add a resource" form-card (each field with its own label
   above it took ~3x the vertical space of one table row, per task, and
   Marc flagged the whole Estimate screen as needing far less scrolling to
   match production's tighter layout). Both the always-visible "Add a
   resource" row and the per-row "Edit" toggle now share this single-line,
   unlabelled layout - column widths mirror the read-only table's own
   colgroup percentages (10/22/17/7/9/9/9/8/9) so the row lines up under
   the same Type/Resource/Description/Qty/Unit/Rate/Cost/Subtotal headers
   instead of reading as a separate, unrelated form. Rarely-used fields
   (Notes, Discount %) move into the existing collapsible notes panel
   rather than taking a column of their own. */
.ddesk-resource-add-row-wrap { margin-top: var(--ddesk-space-1); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); background: var(--ddesk-bg); }
.ddesk-resource-edit-row td { background: var(--ddesk-bg); padding: var(--ddesk-space-2) !important; }
.ddesk-resource-row-compact { display: flex; align-items: flex-start; padding: var(--ddesk-space-2) var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-rrc-cell { padding: 0 var(--ddesk-space-1); box-sizing: border-box; }
.ddesk-rrc-cell:first-child { padding-left: 0; }
.ddesk-rrc-type { width: 10%; min-width: 92px; }
.ddesk-rrc-item { width: 20%; min-width: 162px; }
.ddesk-rrc-desc { width: 13%; min-width: 120px; }
.ddesk-rrc-qty { width: 7%; min-width: 56px; }
/* 0.61.0: 9% to 11%, and the two points come from Resource / Item. The Unit
   options are the source app's words now - "Cost Per Piece" is fourteen
   characters where "Units" was five - and a <select> gives its text the cell
   less its padding, its border and the room its own arrow takes. Measured in a
   headless browser at 1440 and 1280 rather than guessed at: 11% is 136px and
   127px there, and the longest label needs 87px of text.

   The min-width stays where it was. A min-width is what the cell demands when
   the percentage cannot deliver it, and demanding 124px of a 1,150px row is
   what pushes the whole row over and wraps the buttons onto a second line. On
   a narrow window the longest label clips, which is a far smaller problem than
   a row that breaks in half. */
.ddesk-rrc-unit { width: 11%; min-width: 92px; }
.ddesk-rrc-unit select { padding-left: var(--ddesk-space-1); padding-right: var(--ddesk-space-1); }
.ddesk-rrc-rate { width: 9%; min-width: 84px; display: flex; align-items: center; gap: 3px; }
.ddesk-rrc-cost { width: 9%; min-width: 84px; display: flex; align-items: center; gap: 3px; }
.ddesk-rrc-rate input, .ddesk-rrc-cost input { width: auto; flex: 1; min-width: 0; }
/* 0.65.1: the cost-over-rate marker takes the CURRENCY SYMBOL'S place.

   It was a third flex child first, on the reasoning that flex: 0 0 auto would
   take its 13px from the cell. It does not: the only flexible thing in the
   cell is the input, so the input gave up all 16px of the glyph and the gap,
   and a review measured the figure clipped to "25" where it had read "250.0" -
   on the one money field the warning is telling somebody to look at. Widening
   the cell is not available either: the row's widths have to total 100.

   So the glyph stands where the "$" stands, and the "$" steps aside while it
   is there. Nothing moves, nothing is covered, the tooltip and the focus ring
   work because it is still a real element in the flow, and the column is
   headed Cost so the row loses nothing by not repeating the symbol on it. The
   two are held to one width so the field does not shift by a few pixels as the
   warning comes and goes. */
.ddesk-rrc-cost-warn,
.ddesk-rrc-cost > .ddesk-rrc-prefix {
    flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
    width: 14px;
}
.ddesk-rrc-cost-warn { cursor: help; color: var(--ddesk-danger); }
.ddesk-rrc-cost-warn .ddesk-icon-inline { color: inherit; }
.ddesk-rrc-cost-warn:focus-visible { outline: 2px solid var(--ddesk-danger); outline-offset: 1px; border-radius: var(--ddesk-radius-control); }

/* THE NO-COST ANSWER. 0.68.0.
   A full-width strip under the row, not a control inside the Cost cell: that
   cell is 14px of currency symbol, 14px of warning glyph and whatever is left
   for the figure, and a checkbox and a sentence do not go in it. Wraps on a
   narrow window rather than pushing the sentence off the side, and the whole
   strip is hidden by the [hidden] attribute when the row owes nothing, so a
   normal row looks exactly as it did. */
.ddesk-rrc-cost-answer {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--ddesk-space-2);
    padding: var(--ddesk-space-1) var(--ddesk-space-2) var(--ddesk-space-2); margin-top: -2px;
    border-top: 1px dashed var(--ddesk-border);
    background: var(--ddesk-surface-sunken, rgba(0, 0, 0, 0.02));
}
.ddesk-rrc-cost-answer[hidden] { display: none; }
.ddesk-rrc-cost-answer .ddesk-inline-toggle-label { font-size: var(--ddesk-text-meta); white-space: nowrap; }
/* Danger, like the cost-over-rate glyph above, and for the same reason: this
   one does stop the save, so it had better not read as a suggestion. */
.ddesk-rrc-cost-answer-msg { font-size: var(--ddesk-text-meta); line-height: 1.45; color: var(--ddesk-danger); font-weight: 600; }
.ddesk-rrc-cost-answer-msg[hidden] { display: none; }
/* PAYABLE TO. 0.79.0.
   The same full-width strip as the no-cost answer above and for the same
   reason - a supplier name does not fit in a 9% column - so it borrows that
   strip's padding, dashed rule and sunken background. It sits above the
   no-cost answer because it is the question asked of every bought-in line and
   the answer below is asked only of the ones that owe one; when both are on
   screen they read as one block, which is why only the upper one carries the
   rule. */
.ddesk-rrc-payable {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--ddesk-space-2);
    padding: var(--ddesk-space-1) var(--ddesk-space-2) var(--ddesk-space-1); margin-top: -2px;
    border-top: 1px dashed var(--ddesk-border);
    background: var(--ddesk-surface-sunken, rgba(0, 0, 0, 0.02));
}
.ddesk-rrc-payable[hidden] { display: none; }
.ddesk-rrc-payable:not([hidden]) + .ddesk-rrc-cost-answer { border-top: 0; margin-top: 0; }
.ddesk-rrc-payable label { display: flex; align-items: center; gap: var(--ddesk-space-2); margin: 0; }
.ddesk-rrc-payable-label { font-size: var(--ddesk-text-meta); white-space: nowrap; font-weight: 600; }
/* 240px holds the longest supplier name in the live data with room to spare,
   and flex-wrap puts the note underneath rather than squeezing the box on a
   narrow window. */
.ddesk-rrc-payable input { width: 240px; max-width: 100%; }
.ddesk-rrc-payable-note { font-size: var(--ddesk-text-meta); line-height: 1.45; color: var(--ddesk-text-muted); }
/* 0.65.1: 74px to 100px. The cell was empty until this release, so its width
   had never had to hold anything; $123,456.78 is 72px of text at 13px/700 and
   spilled over the Actions buttons beside it. 100px leaves an 88px content box,
   which holds every figure this app can produce - measured in Chromium down to a
   900px window, -$1,234,567.89 included. A min-width raises the floor rather than taking
   percentage from another cell, so the list still totals 100 and the only cost
   is that a narrow window wraps the Actions buttons onto a second line a little
   sooner, which this row already does. */
.ddesk-rrc-subtotal { width: 8%; min-width: 100px; padding-top: var(--ddesk-space-2); font-weight: 700; font-size: var(--ddesk-text-body); white-space: nowrap; }
/* 0.54.0: 9% to 13%, min 64px to 124px. The tick that saved a row became a
   labelled Add/Save button, which with the two glyphs beside it needs about
   113px - and an explicit min-width suppresses the flex auto-minimum, so the
   cell did not grow and did not wrap: the contents simply painted through the
   edge of the card. The four points come from Description, and the list still
   has to sum to 100. flex-wrap as well, so a narrow window puts the buttons on
   a second line rather than outside the box. */
.ddesk-rrc-actions { width: 13%; min-width: 124px; display: flex; flex-wrap: wrap; gap: var(--ddesk-space-1); padding-top: 2px; position: relative; }
.ddesk-rrc-save { white-space: nowrap; }
/* The Disc column, 0.60.0. Only on an estimate whose "Show discount column on
   line items" toggle is on, so the row below is the same as it always was for
   everybody else - which is why these widths are an override rather than the
   defaults above. The seven points come from Type, Resource / Item,
   Description, Unit, Rate and Cost, one or two each; nothing comes out of
   Subtotal or Actions, both of which were measured to the pixel at 0.52.0 and
   0.54.0. The matching read-only table widths live in
   DDesk_Estimate_Columns::table(), which a test adds up. */
/* 0.62.0: five more points for the Disc column, which now holds two boxes
   rather than one. Taken from Resource / Item and Description, the two that
   wrap and keep reading, and one each from Type and Unit, which hold a word.
   Still totals 100 - see DDesk_Estimate_Columns for the read-only table's own
   list and the test that adds them up. */
.ddesk-resource-row-compact.has-disc .ddesk-rrc-type { width: 7%; min-width: 80px; }
.ddesk-resource-row-compact.has-disc .ddesk-rrc-item { width: 16%; min-width: 142px; }
.ddesk-resource-row-compact.has-disc .ddesk-rrc-desc { width: 11%; min-width: 104px; }
.ddesk-resource-row-compact.has-disc .ddesk-rrc-unit { width: 10%; min-width: 92px; }
.ddesk-resource-row-compact.has-disc .ddesk-rrc-rate { width: 8%; min-width: 80px; }
.ddesk-resource-row-compact.has-disc .ddesk-rrc-cost { width: 8%; min-width: 80px; }
.ddesk-rrc-disc { width: 12%; min-width: 124px; }
/* Two boxes, 0.62.0: a percentage and an amount, side by side, each carrying
   the symbol that says which it is. One box could not say what it wanted and
   the answer arrived in a sentence under the row; these fill each other in,
   in line. */
.ddesk-rrc-disc { display: flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-rrc-disc-pair { display: flex; align-items: center; gap: 2px; flex: 1; min-width: 0; }
.ddesk-resource-row-compact .ddesk-rrc-disc-input { width: auto; flex: 1; min-width: 0; text-align: right; padding-left: var(--ddesk-space-1); padding-right: var(--ddesk-space-1); }
.ddesk-rrc-suffix { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); }
/* A figure the save would refuse, said where it was typed - the box that was
   filled in from it is cleared at the same time, so nothing reads as agreed. */
.ddesk-resource-row-compact .ddesk-rrc-disc-input.is-refused {
    border-color: var(--ddesk-magenta-a);
    background: var(--ddesk-magenta-tint);
}
.ddesk-rrc-disc-input:disabled { background: var(--ddesk-border); color: var(--ddesk-text-muted); }
/* The read-only table's own Disc cell. Each figure holds together on its own
   line - a percentage and an amount on the same line do not fit the column -
   but the cell itself wraps between them. */
.ddesk-disc-cell span { white-space: nowrap; }
/* The switch that puts that column there, 0.61.0, sitting under the Tasks
   subtitle rather than in a form most of a page above it. */
.ddesk-discount-switch, .ddesk-row-switch {
    display: flex; align-items: flex-start; gap: var(--ddesk-space-2);
    margin: 0 0 var(--ddesk-space-3); font-size: var(--ddesk-text-body); color: var(--ddesk-text);
}
.ddesk-discount-switch .description, .ddesk-row-switch .description { margin: 2px 0 0; }
.ddesk-resource-row-compact input,
.ddesk-resource-row-compact select {
    width: 100%; font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body);
    padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control);
    background: var(--ddesk-white); color: var(--ddesk-text); max-width: none;
}
.ddesk-resource-row-compact input:focus, .ddesk-resource-row-compact select:focus {
    outline: none; border-color: var(--ddesk-cyan-b); box-shadow: 0 0 0 3px rgba(1,197,255,0.15);
}
.ddesk-resource-row-compact input:disabled { background: var(--ddesk-border); color: var(--ddesk-text-muted); }
.ddesk-rrc-prefix { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); }
.ddesk-rrc-item .ddesk-role-name-custom, .ddesk-rrc-item .ddesk-field-assignee { margin-top: var(--ddesk-space-1); }
.ddesk-field-assignee select { margin: 0; }
/* Non-Staff Resource/Item column (Sep 5, 2026, Marc's deliberate departure
   from production - see resource-row-form.php docblock): stands in for the
   rate-card dropdown at the same size/position so the free-text field
   below it doesn't jump. */
.ddesk-rrc-custom-label { display: block; font-size: var(--ddesk-text-body); padding: var(--ddesk-space-1) 0 2px; color: var(--ddesk-text-muted); font-weight: 400; }
.ddesk-rrc-owner-entered { display: block; font-size: var(--ddesk-text-label); font-style: italic; color: var(--ddesk-text-muted); }
/* The merge screen. One card per candidate role, each carrying its own rate
   grid, because "no rate moved" should be checkable rather than promised. */
.ddesk-merge-option { margin-top: var(--ddesk-space-4); }
.ddesk-merge-option h3 { margin: 0 0 var(--ddesk-space-1); font-size: var(--ddesk-text-card); display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-table-tight { margin-top: var(--ddesk-space-2); }
.ddesk-table-tight th, .ddesk-table-tight td { padding: var(--ddesk-space-1) var(--ddesk-space-2); font-size: var(--ddesk-text-meta); }
.ddesk-merge-suggestion { font-size: var(--ddesk-text-label); color: var(--ddesk-text-muted); margin-top: 3px; }
/* A Staff row still carrying a typed role from before 0.45.0. The dropdown
   above it has nothing selected, so without this the row reads as blank and
   nobody can tell what the line was for. */
.ddesk-rrc-legacy-role { font-size: var(--ddesk-text-label); line-height: 1.4; color: var(--ddesk-text-muted); margin: var(--ddesk-space-1) 0 0; padding: var(--ddesk-space-1) var(--ddesk-space-2); border-left: 2px solid var(--ddesk-magenta-a); background: var(--ddesk-bg); }
/* Discount % / line-item notes: a small anchored popover off the row's
   notes-toggle icon, not a full-width bar - it's nested inside the narrow
   .ddesk-rrc-actions cell (so the existing toggle JS, which looks up the
   panel via btn.parentElement.querySelector(), can find it) and escapes
   that cell visually with absolute positioning instead. */
.ddesk-resource-row-secondary {
    position: absolute; top: 100%; right: 0; margin-top: var(--ddesk-space-1); z-index: 30;
    width: 260px; display: flex; flex-direction: column; gap: var(--ddesk-space-2);
    background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control);
    box-shadow: 0 6px 16px rgba(0,0,0,0.14); padding: var(--ddesk-space-2);
}
.ddesk-resource-row-secondary textarea { width: 100%; box-sizing: border-box; font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); }
.ddesk-resource-row-secondary .ddesk-field { margin-bottom: 0; }
.ddesk-resource-row-secondary .ddesk-field label { display: block; font-size: var(--ddesk-text-label); margin-bottom: 3px; color: var(--ddesk-text-muted); }
.ddesk-resource-row-secondary .ddesk-field input { width: 100%; box-sizing: border-box; font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); }
/* Headings for the add-resource row on a task that has nothing in it yet, where
   the read-only table above - and therefore its header row - is not rendered.
   Same widths as the row itself, so the labels sit over their own fields. */
.ddesk-rrc-headings {
    padding-bottom: 0;
    font-size: var(--ddesk-text-label);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--ddesk-text-muted);
}
.ddesk-rrc-headings .ddesk-rrc-subtotal { padding-top: 0; font-size: var(--ddesk-text-label); }

/* The "Needs you" band, 0.41.0. Sits directly under the summary tiles because an
   unanswered request is the one thing on the dashboard with a clock on it. */
.ddesk-requests-band .ddesk-form-card {
    border-left: 3px solid var(--ddesk-magenta-a);
    background: var(--ddesk-magenta-tint);
}

/* Save as Template, 0.41.0: one row across the width rather than four stacked
   rows in a narrow column, on a page whose length is already the complaint. */
.ddesk-template-row { display: flex; align-items: flex-end; gap: var(--ddesk-space-3); flex-wrap: wrap; }
.ddesk-template-row .ddesk-field { flex: 1 1 200px; margin-bottom: 0; }
/* Specificity, not source order: the action cell carries both classes. */
.ddesk-template-row .ddesk-field.ddesk-template-row .ddesk-field input,
.ddesk-template-row .ddesk-field select { width: 100%; box-sizing: border-box; }

/* Internal Notes, 0.41.0. The one tab of Estimate Content that never leaves the
   building, coloured so it cannot be mistaken for the five that do. */
/* The base .ddesk-tab-btn rules live in an inline <style> later in the body, so
   they win on source order at equal specificity. These have to out-specify
   them, not merely come first. */
.ddesk-tabs-nav .ddesk-tab-btn.ddesk-tab-btn-internal { color: var(--ddesk-purple-b); }
.ddesk-tabs-nav .ddesk-tab-btn.ddesk-tab-btn-internal.is-active { border-bottom-color: var(--ddesk-purple-b); color: var(--ddesk-purple-b); }

/* [hidden] has to beat .ddesk-btn's own display, or a hidden button renders. */
[hidden] { display: none !important; }
.ddesk-internal-warning {
    display: flex; align-items: flex-start; gap: var(--ddesk-space-1);
    padding: var(--ddesk-space-2) var(--ddesk-space-2); margin-bottom: var(--ddesk-space-2);
    border-left: 3px solid var(--ddesk-purple-a);
    background: var(--ddesk-purple-tint); color: var(--ddesk-purple-text);
    font-size: var(--ddesk-text-label); line-height: 1.5;
}

/* Work Period, 0.41.0. Its own band rather than a line at the foot of Estimate
   Content, because it is when the work happens, not more narrative about it. */
.ddesk-workperiod-band {
    margin-top: var(--ddesk-space-4);
    padding: var(--ddesk-space-3) var(--ddesk-space-4);
    border: 1px solid var(--ddesk-border, var(--ddesk-grey-line));
    border-left: 3px solid var(--ddesk-cyan-a);
    border-radius: var(--ddesk-radius-card);
    background: var(--ddesk-bg-subtle, var(--ddesk-grey-bg));
}
.ddesk-workperiod-band label.ddesk-workperiod-label { font-weight: 700; display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-workperiod-hint { margin: var(--ddesk-space-1) 0 var(--ddesk-space-2); font-size: var(--ddesk-text-label); }

/* Task quantity, 0.40.0. Resource hours are per unit; these two controls say how
   many units there are. The estimate-level bar is the one cell that re-prices a
   whole quote; the per-task box is where the exceptions live. */
.ddesk-estimate-qty-bar {
    display: flex;
    align-items: center;
    gap: var(--ddesk-space-2);
    flex-wrap: wrap;
    margin: var(--ddesk-space-2) 0 var(--ddesk-space-4);
    padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border: 1px solid var(--ddesk-border, var(--ddesk-grey-line));
    border-radius: var(--ddesk-radius-card);
    background: var(--ddesk-bg-subtle, var(--ddesk-grey-bg));
    font-size: var(--ddesk-text-body);
}
.ddesk-estimate-qty-bar label { font-weight: 600; }
.ddesk-estimate-qty-bar input[type="number"] { width: 90px; }
.ddesk-estimate-qty-bar .ddesk-muted-inline { flex-basis: 100%; font-size: var(--ddesk-text-label); }

.ddesk-task-qty-form { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-task-qty-label { font-size: var(--ddesk-text-label); color: var(--ddesk-text-muted, var(--ddesk-grey-text)); text-transform: uppercase; letter-spacing: 0.03em; }
.ddesk-task-qty-form input[type="number"] { width: 72px; font-size: var(--ddesk-text-body); }
.ddesk-extended-cell { font-weight: 600; }

@media (max-width: 900px) {
    .ddesk-estimate-qty-bar { align-items: flex-start; flex-direction: column; gap: var(--ddesk-space-2); }
}

/* The app's own footer: what version is running, and whether this is demo data.
   Quiet by design - it is there to be found when somebody goes looking, not to
   compete with the page. Same shape as the hub's own footer line. */
.ddesk-app-footer {
    display: flex;
    align-items: center;
    gap: var(--ddesk-space-2);
    margin-top: var(--ddesk-space-8);
    padding: var(--ddesk-space-3) 0 var(--ddesk-space-6);
    border-top: 1px solid var(--ddesk-border, var(--ddesk-grey-line));
    font-family: Arial, Helvetica, sans-serif;
    font-size: var(--ddesk-text-meta);
    color: var(--ddesk-text-muted, var(--ddesk-grey-text));
}
.ddesk-version-pill {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--ddesk-space-2);
    border-radius: var(--ddesk-radius-pill);
    background: var(--ddesk-grey-line);
    color: var(--ddesk-text-muted, var(--ddesk-grey-text));
    font-size: var(--ddesk-text-label);
    font-weight: 700;
    letter-spacing: 0.02em;
}
.ddesk-version-pill-demo { background: linear-gradient(135deg, var(--ddesk-magenta-a), var(--ddesk-magenta-b)); color: var(--ddesk-white); }

/* A credit line reads as money going the other way, so it is coloured and
   tagged rather than left to be told apart by a minus sign alone. */
.ddesk-credit-amount { color: var(--ddesk-magenta-text); font-weight: 700; }
.ddesk-badge-credit { background: var(--ddesk-magenta-tint); color: var(--ddesk-magenta-text); border: 1px solid var(--ddesk-grey-line); }

@media (max-width: 900px) {
    .ddesk-rrc-cell { width: 100% !important; padding: var(--ddesk-space-1) 0; }
    /* Stacked on a phone, each field already carries its own placeholder, and
       a column of headings above a column of fields helps nobody. */
    .ddesk-rrc-headings { display: none; }
    /* Nothing to expand into once the row is a stack: the negative margin
       that pulls the discount box over the Cost field would pull it off the
       left of the screen instead. The note below it still appears. */
    }

/* Resource table: fixed proportions (set via <colgroup>) so the Resource /
   Item column - which stacks a second control (assignee or name field)
   under the item picker, same as production - gets enough width to show
   both comfortably instead of squeezing them together. */
.ddesk-resource-table { table-layout: fixed; }

/* ---------- Resource table density and reach (0.52.0) ----------
   Three changes, all measured in a headless browser at 1920 and 1440 against
   the real stylesheet before being written here.

   The row padding is the single biggest vertical win on the Build screen
   because it multiplies by row count: 8px off every row, header included.
   .ddesk-table-tight at the top of this file already proves a tighter row
   reads fine, and scoping it to this table leaves the other sixty alone.

   The tiny icon button was rendering at 16px rather than the 13px its own
   modifier asks for: .ddesk-actions .ddesk-icon (below) and
   .ddesk-icon-btn-tiny .ddesk-icon have identical specificity, so source
   order decided it, and the resource table's pencil came out a different
   size from the identical button in the task title row.

   The last column is sticky as a backstop, not as the fix. The columns above
   are sized so nothing scrolls on a desktop; this is what keeps Delete
   reachable on a narrow screen, where the table deliberately does scroll. */
.ddesk-resource-table th, .ddesk-resource-table td { padding: var(--ddesk-space-2) var(--ddesk-space-3); }
.ddesk-actions .ddesk-icon-btn-tiny .ddesk-icon { width: 13px; height: 13px; }
.ddesk-resource-table .ddesk-btn-small { padding: var(--ddesk-space-1) var(--ddesk-space-2); }
.ddesk-resource-table th:last-child,
.ddesk-resource-table td:last-child {
    position: sticky;
    right: 0;
    background: var(--ddesk-white);
}
/* The header cell's own grey, not the body white, or the sticky column would
   show a white notch in the header strip as the table scrolls under it. */
.ddesk-resource-table thead th:last-child { background: var(--ddesk-grey-bg); }
@media (max-width: 900px) {
    .ddesk-resource-table { table-layout: auto; min-width: 760px; }
}

/* ---------- More Options menu (estimate Build screen header) ---------- */
.ddesk-more-options { display: inline-flex; }
.ddesk-more-options-menu { left: auto; right: 0; background: var(--ddesk-white); border-color: var(--ddesk-border); box-shadow: 0 8px 24px rgba(0,0,0,0.15); }
.ddesk-more-options-menu a { color: var(--ddesk-black); opacity: 1; display: flex; align-items: center; gap: var(--ddesk-space-2); padding: var(--ddesk-space-2) var(--ddesk-space-3); font-size: var(--ddesk-text-body); }
.ddesk-more-options-menu a:hover { background: var(--ddesk-bg); text-decoration: none; }
.ddesk-more-options-menu .ddesk-icon { width: 15px; height: 15px; }
.ddesk-disabled-menu-item { opacity: 0.45 !important; cursor: not-allowed; pointer-events: none; }

/* ---------- Per-row "..." action menu (Billing list, Sep 3, 2026 parity pass) ----------
   Reuses the same hover/focus-within dropdown pattern as the header nav and
   the estimate Build screen's More Options menu - no JS needed here either. */
.ddesk-row-menu { display: inline-flex; vertical-align: middle; }
.ddesk-row-menu > button { padding: var(--ddesk-space-1) var(--ddesk-space-2); font-size: var(--ddesk-text-card); line-height: 1; }
.ddesk-row-menu .ddesk-nav-dropdown { min-width: 210px; }
/* .ddesk-table-wrap scrolls horizontally (overflow-x: auto), which per the
   CSS overflow spec also forces its vertical overflow to a computed 'auto' -
   so an absolutely-positioned dropdown taller than the row it opens from
   gets clipped by the wrap's own edge, in whichever direction it opens.
   The Billing list's per-row "..." menu needs to escape that box entirely,
   so its wrap opts out of the clipping with this modifier rather than
   changing the shared .ddesk-table-wrap and risking the wide tables
   (e.g. Resources) that rely on its horizontal scroll. */
.ddesk-table-wrap.ddesk-table-wrap-visible { overflow: visible; }

/* 0.75.0, review finding UX-01: a wrap that actually scrolls becomes a
   focusable region (assets/js/frontend.js adds the class and the role). The
   focus ring matters more here than anywhere else in the app - it is the only
   thing that tells somebody arriving by keyboard that the arrow keys will now
   move the table rather than the page. */
.ddesk-table-wrap.is-scrollable:focus-visible {
    outline: 2px solid var(--ddesk-cyan-b, var(--ddesk-cyan-b));
    outline-offset: 2px;
    border-radius: var(--ddesk-radius-control);
}

/* ---------- Billing Request form (Sep 3, 2026 Billing production parity) ---------- */
.ddesk-form-section-title { margin: var(--ddesk-space-6) 0 var(--ddesk-space-2); font-size: var(--ddesk-text-card); }
.ddesk-billing-type-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--ddesk-space-2); margin-bottom: var(--ddesk-space-1); }
.ddesk-billing-type-option {
    display: flex; gap: var(--ddesk-space-2); align-items: flex-start; padding: var(--ddesk-space-3) var(--ddesk-space-3);
    border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); cursor: pointer;
}
.ddesk-billing-type-option input { margin-top: 3px; }
.ddesk-billing-type-option span { display: flex; flex-direction: column; gap: 2px; }
.ddesk-billing-type-option em { font-style: normal; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
.ddesk-radio-inline { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); margin-right: var(--ddesk-space-5); font-weight: 600; font-size: var(--ddesk-text-body); }

/* A choice with a reason attached: the radio, a short label, and the sentence
   that says what picking it actually does. Used where the options differ in
   consequence rather than only in name. */
.ddesk-radio-block {
	display: flex;
	align-items: flex-start;
	gap: var(--ddesk-space-2);
	padding: var(--ddesk-space-3) var(--ddesk-space-3);
	border: 1px solid var(--ddesk-border);
	border-radius: var(--ddesk-radius-control);
	cursor: pointer;
	font-size: var(--ddesk-text-body);
}
.ddesk-radio-block:hover { border-color: var(--ddesk-cyan-a); }
.ddesk-radio-block input { margin-top: 2px; flex: 0 0 auto; }
.ddesk-radio-block strong { display: block; margin-bottom: 3px; }
.ddesk-radio-block .description { display: block; margin: 0; }
.ddesk-radio-block:has(input:checked) { border-color: var(--ddesk-cyan-b); background: rgba(1,197,255,0.06); }

/* The billing amount: two ways of saying the same thing, one of them chosen. */
.ddesk-amount-modes { display: grid; gap: var(--ddesk-space-2); margin-bottom: var(--ddesk-space-2); }
.ddesk-inline-input { display: flex; align-items: center; gap: var(--ddesk-space-1); margin-top: var(--ddesk-space-1); flex-wrap: wrap; }
.ddesk-inline-input input { width: 160px; }
.ddesk-inline-input input:disabled { background: var(--ddesk-grey-bg); color: var(--ddesk-text-muted); }
.ddesk-percent-preview { font-weight: 700; color: var(--ddesk-cyan-b); }

/* The versions of one estimate, and which of them counts. */
.ddesk-version-strip {
	display: flex;
	align-items: center;
	gap: var(--ddesk-space-2);
	flex-wrap: wrap;
	padding: var(--ddesk-space-2) var(--ddesk-space-3);
	margin-bottom: var(--ddesk-space-4);
	border: 1px solid var(--ddesk-border);
	border-radius: var(--ddesk-radius-control);
	background: var(--ddesk-grey-bg);
}
.ddesk-version-strip .description { margin: 0; }
.ddesk-version-chip {
	display: inline-flex;
	align-items: center;
	gap: var(--ddesk-space-1);
	padding: var(--ddesk-space-1) var(--ddesk-space-2);
	border: 1px solid var(--ddesk-border);
	border-radius: var(--ddesk-radius-pill);
	background: var(--ddesk-white, var(--ddesk-white));
	font-size: var(--ddesk-text-meta);
	font-weight: 700;
	text-decoration: none;
	color: var(--ddesk-text);
}
.ddesk-version-chip:hover { border-color: var(--ddesk-cyan-a); }
.ddesk-version-chip.is-here { border-color: var(--ddesk-cyan-b); box-shadow: inset 0 0 0 1px var(--ddesk-cyan-b); }
.ddesk-version-tag {
	font-size: var(--ddesk-text-label);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.5px;
	color: var(--ddesk-white);
	background: linear-gradient(135deg, var(--ddesk-cyan-a), var(--ddesk-cyan-b));
	border-radius: var(--ddesk-radius-pill);
	padding: 2px var(--ddesk-space-1);
}
.ddesk-checkbox-inline { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); font-weight: 600; font-size: var(--ddesk-text-body); white-space: nowrap; }
.ddesk-collapsible-section { margin-top: var(--ddesk-space-6); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-1) var(--ddesk-space-4); }
.ddesk-collapsible-section summary { cursor: pointer; padding: var(--ddesk-space-3) 0; font-weight: 700; font-size: var(--ddesk-text-body); }
.ddesk-collapsible-section[open] summary { border-bottom: 1px solid var(--ddesk-border); margin-bottom: var(--ddesk-space-3); }

/* ---------- Month picker (Budget Allocation & Hours Tracker) ---------- */
.ddesk-month-picker { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); padding: var(--ddesk-space-1) var(--ddesk-space-2); font-size: var(--ddesk-text-body); font-weight: 700; }

/* ---------- Choice cards (New Estimate 3-way picker) ---------- */
.ddesk-choice-grid { display: flex; flex-wrap: wrap; gap: var(--ddesk-space-4); margin-top: var(--ddesk-space-5); }
.ddesk-choice-card {
    display: block; flex: 1 1 260px; min-width: 240px;
    border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-5);
    text-decoration: none; color: inherit; background: var(--ddesk-white);
    transition: border-color .15s ease, box-shadow .15s ease;
}
.ddesk-choice-card:hover { border-color: var(--ddesk-cyan-a); box-shadow: 0 2px 10px rgba(0,0,0,.06); text-decoration: none; }
.ddesk-choice-card .ddesk-icon { width: 22px; height: 22px; color: var(--ddesk-cyan-b); margin-bottom: var(--ddesk-space-3); }
.ddesk-choice-card h3 { margin: 0 0 var(--ddesk-space-2); font-size: var(--ddesk-text-card); }
.ddesk-choice-card p { margin: 0; font-size: var(--ddesk-text-body); color: var(--ddesk-text-muted); }
.ddesk-choice-card-disabled { opacity: .5; cursor: default; pointer-events: none; }

/* ---------- Modal (e.g. "+ New Docket" popup on New Estimate) ---------- */
.ddesk-modal-overlay {
    display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5);
    z-index: 100; align-items: center; justify-content: center; padding: var(--ddesk-space-5);
}
.ddesk-modal-overlay.is-open { display: flex; }
.ddesk-modal {
    background: var(--ddesk-white); border-radius: var(--ddesk-radius-card); width: 100%; max-width: 560px;
    max-height: 90vh; overflow-y: auto; padding: var(--ddesk-space-7); position: relative;
}
.ddesk-modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ddesk-space-3); margin-bottom: var(--ddesk-space-4); }
.ddesk-modal-head h2 { margin: 0 0 var(--ddesk-space-1); font-size: var(--ddesk-text-figure); }
.ddesk-modal-head p { margin: 0; color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); }
.ddesk-modal-close {
    background: none; border: none; cursor: pointer; font-size: var(--ddesk-text-figure); line-height: 1;
    color: var(--ddesk-text-muted); padding: var(--ddesk-space-1); flex: 0 0 auto;
}
.ddesk-modal-close:hover { color: var(--ddesk-black); }
.ddesk-modal-highlight { background: rgba(1,197,255,0.08); border: 1px dashed var(--ddesk-cyan-a); border-radius: var(--ddesk-radius-control); padding: var(--ddesk-space-3) var(--ddesk-space-3); margin-bottom: var(--ddesk-space-4); }
.ddesk-modal-highlight label { color: var(--ddesk-cyan-b); }

/* Export Estimate to PDF modal (Sep 7, 2026) - see DDesk_Estimate_Export. */
.ddesk-export-summary-card {
    background: var(--ddesk-bg); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control);
    padding: var(--ddesk-space-3) var(--ddesk-space-4); margin-bottom: var(--ddesk-space-4);
}
.ddesk-export-summary-card > strong { display: block; margin-bottom: var(--ddesk-space-2); font-size: var(--ddesk-text-body); }
.ddesk-export-summary-row { display: flex; align-items: center; justify-content: space-between; gap: var(--ddesk-space-3); font-size: var(--ddesk-text-body); color: var(--ddesk-text-muted); }
.ddesk-export-summary-row + .ddesk-export-summary-row { margin-top: var(--ddesk-space-1); }
.ddesk-export-summary-total { font-weight: 700; color: var(--ddesk-text); }
.ddesk-export-options { margin-bottom: var(--ddesk-space-4); }
/* Reuses the plugin's existing .ddesk-toggle-row/.ddesk-toggle pill-switch
   component (Notifications Preferences, Budget & Funding Summary) instead of
   plain checkboxes, matching both this plugin's own established toggle
   pattern and the toggle-switch style shown in production's real Export
   Estimate to PDF modal (Marc's screenshots, Sep 7 2026). */
.ddesk-export-tax-rate-field { margin: 0 0 var(--ddesk-space-2); max-width: 160px; }

/* ---------- Work Period popover (button + floating month-grid picker) ---------- */
.ddesk-wp-popover { position: relative; display: inline-block; }
.ddesk-wp-trigger {
    display: inline-flex; align-items: center; gap: var(--ddesk-space-2); padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); background: var(--ddesk-white);
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); font-weight: 700; cursor: pointer;
}
.ddesk-wp-trigger:hover { border-color: var(--ddesk-cyan-a); }
.ddesk-wp-trigger .ddesk-icon { width: 15px; height: 15px; color: var(--ddesk-cyan-b); }
.ddesk-wp-trigger-count { color: var(--ddesk-text-muted); font-weight: 400; }
/* Trigger button + separate "N month(s) selected" pill + split-value
   caption - three distinct elements (not one combined trigger label),
   matching production per direct live inspection Sep 5, 2026. */
.ddesk-wp-trigger-row { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-wp-count-badge { font-weight: 400; }
.ddesk-wp-split-note { margin: var(--ddesk-space-1) 0 0; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
.ddesk-wp-panel {
    display: none; position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
    background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card);
    box-shadow: 0 8px 24px rgba(0,0,0,0.12); padding: var(--ddesk-space-4); width: 280px;
}
.ddesk-wp-panel.is-open { display: block; }
.ddesk-wp-panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--ddesk-space-2); font-weight: 700; font-size: var(--ddesk-text-body); }
.ddesk-wp-year-nav { background: none; border: none; cursor: pointer; font-size: var(--ddesk-text-card); padding: var(--ddesk-space-1) var(--ddesk-space-2); border-radius: var(--ddesk-radius-control); }
.ddesk-wp-year-nav:hover { background: var(--ddesk-bg); }
.ddesk-wp-month-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--ddesk-space-1); }
.ddesk-wp-month {
    background: none; border: 1px solid transparent; border-radius: var(--ddesk-radius-control); padding: var(--ddesk-space-2) var(--ddesk-space-1);
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-meta); cursor: pointer; text-align: center;
}
.ddesk-wp-month:hover { background: var(--ddesk-bg); }
.ddesk-wp-month.is-selected { background: var(--ddesk-gradient-primary); color: var(--ddesk-black); font-weight: 700; }
.ddesk-wp-month.is-in-range { background: rgba(1,197,255,0.12); }
.ddesk-wp-panel-note { margin: var(--ddesk-space-2) 0 0; font-size: var(--ddesk-text-label); color: var(--ddesk-text-muted); }

/* Junction59 Contacts chip picker (Sep 8, 2026) - replaces a native
   <select multiple> that always showed every team member in a scrollable
   box; reuses the .ddesk-wp-trigger/.ddesk-wp-panel popover look above. */
.ddesk-j59-picker { position: relative; }
.ddesk-j59-chips { display: flex; flex-wrap: wrap; gap: var(--ddesk-space-1); align-items: center; margin-bottom: var(--ddesk-space-2); min-height: 26px; }
.ddesk-j59-chips-empty { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); }
.ddesk-j59-chip { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-j59-chip-remove {
    background: none; border: none; cursor: pointer; padding: 0; line-height: 1;
    font-size: var(--ddesk-text-body); color: inherit; opacity: 0.7;
}
.ddesk-j59-chip-remove:hover { opacity: 1; }
.ddesk-j59-panel { width: 260px; padding: var(--ddesk-space-2); }
.ddesk-j59-search {
    width: 100%; box-sizing: border-box; padding: var(--ddesk-space-1) var(--ddesk-space-2); margin-bottom: var(--ddesk-space-2);
    border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); font-size: var(--ddesk-text-body);
    font-family: Arial, Helvetica, sans-serif;
}
.ddesk-j59-options { max-height: 220px; overflow-y: auto; }
.ddesk-j59-option { display: flex; align-items: center; gap: var(--ddesk-space-2); padding: var(--ddesk-space-1) var(--ddesk-space-1); border-radius: var(--ddesk-radius-control); font-size: var(--ddesk-text-body); cursor: pointer; }
.ddesk-j59-option:hover { background: var(--ddesk-bg); }
.ddesk-j59-option input[type="checkbox"] { margin: 0; }

/* ---------- Flash messages ---------- */
.ddesk-flash {
    padding: var(--ddesk-space-3) var(--ddesk-space-4);
    border-radius: var(--ddesk-radius-control);
    margin-bottom: var(--ddesk-space-5);
    font-weight: 700;
    font-size: var(--ddesk-text-body);
}
.ddesk-flash-success { background: rgba(73,238,166,0.18); border: 1px solid var(--ddesk-lime-b); color: var(--ddesk-lime-text); }
.ddesk-flash-error { background: rgba(250,14,203,0.10); border: 1px solid var(--ddesk-magenta-a); color: var(--ddesk-magenta-text); }
/* A caution rather than a failure: something is about to happen that is hard
   to take back. Amber is not in the Junction59 palette, so this uses the
   cyan-blue end of it at low weight - plainly a notice, plainly not an error. */
.ddesk-flash-warning { background: rgba(1,197,255,0.10); border: 1px solid var(--ddesk-cyan-b); color: var(--ddesk-cyan-text); }
.ddesk-flash-warning strong { display: block; margin-bottom: 2px; }
/* Actions offered inside a flash, e.g. "go to billing" right after approving.
   Wraps onto its own line on a phone rather than squeezing the buttons. */
/* Hours under the money in the Budget Awareness table, 0.82.31. The amount
   is the figure; the hours are how it was estimated, so they read as a note
   rather than a second column. */
.ddesk-ba-hours { display: block; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }

/* Allocated against what the estimate is worth, 0.82.30. Ordinary muted
   type until it is wrong, because a line that shouts every time is a line
   people stop reading. */
.ddesk-alloc-against { margin: 0 0 var(--ddesk-space-3); color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); }
.ddesk-alloc-against.ddesk-alloc-over { color: var(--ddesk-magenta-b); }

/* The retainer position, 0.82.30. A read-only screen that also has to work
   in a small window opened beside an estimate, so the chrome is minimal and
   the table is the thing. No colours of its own beyond the two states that
   mean something: pending money, and a cap that has been passed. */
.ddesk-app-popup { padding: var(--ddesk-space-5); max-width: 1100px; margin: 0 auto; }
.ddesk-retainer-head { display: flex; flex-wrap: wrap; gap: var(--ddesk-space-4); align-items: flex-end; justify-content: space-between; margin-bottom: var(--ddesk-space-3); }
.ddesk-retainer-head h1 { margin: 0; }
.ddesk-retainer-sub { margin: var(--ddesk-space-1) 0 0; color: var(--ddesk-text-muted); }
.ddesk-retainer-month { display: flex; flex-direction: column; gap: var(--ddesk-space-1); }
.ddesk-retainer-note { margin: 0 0 var(--ddesk-space-4); color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); max-width: 70ch; }
.ddesk-retainer-h2 { margin: var(--ddesk-space-6) 0 var(--ddesk-space-3); }
.ddesk-retainer-foot { margin-top: var(--ddesk-space-5); color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); }
.ddesk-retainer-group { text-align: center; border-bottom: 1px solid var(--ddesk-border); }
.ddesk-retainer-hours { display: block; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
.ddesk-retainer-title { display: block; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
/* Pending is not spent yet, so it is marked rather than left to read as
   committed money. Magenta B rather than A: on white, A measures 4.48:1 and
   fails the 4.5 small-text rule - the same reason the View as bar is black. */
.ddesk-retainer-pending { color: var(--ddesk-magenta-b); }
.ddesk-retainer-over { color: var(--ddesk-magenta-b); font-weight: 700; }
.ddesk-retainer-spent { font-weight: 700; }
.ddesk-retainer-unbudgeted td { background: rgba(0,0,0,0.03); }
.ddesk-retainer-link { margin: 0 0 var(--ddesk-space-4); display: flex; flex-wrap: wrap; gap: var(--ddesk-space-2); align-items: baseline; }
.ddesk-retainer-link .ddesk-muted { font-size: var(--ddesk-text-meta); }
@media (max-width: 782px) {
    .ddesk-retainer-table { font-size: var(--ddesk-text-meta); }
    .ddesk-app-popup { padding: var(--ddesk-space-3); }
}

/* The pre-flight panel on the estimate, 0.82.27. A flash carries one
   sentence; this one carries up to thirteen across three groups, so the
   weight comes off everything below the heading and the list indents under
   it. No colours of its own - it is a warning flash and reads as one. */
.ddesk-preflight .ddesk-preflight-lead { font-weight: 400; margin: var(--ddesk-space-2) 0 var(--ddesk-space-3); }
.ddesk-preflight-group { margin-bottom: var(--ddesk-space-3); }
.ddesk-preflight-heading { display: block; font-size: var(--ddesk-text-meta); text-transform: uppercase; letter-spacing: 0.04em; }
.ddesk-preflight-group ul { margin: var(--ddesk-space-1) 0 0 var(--ddesk-space-5); padding: 0; font-weight: 400; }
.ddesk-preflight-group li { margin-bottom: var(--ddesk-space-1); }
.ddesk-preflight-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ddesk-space-2); margin-top: var(--ddesk-space-3); }
.ddesk-preflight-note { font-size: var(--ddesk-text-meta); font-weight: 400; color: var(--ddesk-text-muted); }

/* The "here is what this will move" list, 0.77.0. Used by the two screens
   that ask somebody to confirm something irreversible - Change number and
   Void - where the honest way to ask is to say how much of it there is. */
.ddesk-impact-list { margin: var(--ddesk-space-2) 0 var(--ddesk-space-4) var(--ddesk-space-5); }
.ddesk-impact-list li { margin-bottom: var(--ddesk-space-1); }
/* And the part a cascade cannot reach. Not an error: nothing has gone wrong,
   it is a fact about the world outside this database. */
.ddesk-warning-note {
    display: flex; align-items: flex-start; gap: var(--ddesk-space-2);
    padding: var(--ddesk-space-3);
    background: var(--ddesk-magenta-tint);
    border-left: 3px solid var(--ddesk-magenta-a);
    border-radius: var(--ddesk-radius-control);
    font-size: var(--ddesk-text-meta);
}
.ddesk-warning-note .ddesk-icon { flex: none; margin-top: 1px; color: var(--ddesk-magenta-text); }

/* BILLING REQUEST DETAILS. 0.79.0.
   The neutral twin of the warning note above: the same block, in the cyan
   tint rather than the magenta, for a sentence that is important without
   anything being wrong. "This job is entirely outside cost" uses the warning
   version, because that one is the fact accounting most needs to not miss. */
.ddesk-lead-note {
    padding: var(--ddesk-space-3);
    background: var(--ddesk-cyan-tint);
    border-left: 3px solid var(--ddesk-cyan-a);
    border-radius: var(--ddesk-radius-control);
    font-size: var(--ddesk-text-meta);
}
/* A note under a figure or beside a heading, where the sentence explains the
   figure rather than labelling it. Muted and small so the figure keeps the
   eye. */
.ddesk-field-note { font-size: var(--ddesk-text-meta); line-height: 1.45; color: var(--ddesk-text-muted); }
.ddesk-payable-group { margin: var(--ddesk-space-4) 0 var(--ddesk-space-1); font-size: var(--ddesk-text-body); display: flex; align-items: baseline; gap: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-payable-group:first-of-type { margin-top: var(--ddesk-space-2); }
/* Four labels and four figures. In a full-width card the label column grew to
   about 950px and threw the figure to the far right of the screen, so the eye
   had to travel the width of a laptop to read "Agency fees $0.00". Capped to
   the width the longest label actually needs. */
.ddesk-money-split { max-width: 520px; margin-top: var(--ddesk-space-3); }
.ddesk-money-split th { width: 70%; }

/* The "accounting has this" banner on a submitted billing request. 0.77.0,
   DD-02. The icon sits beside the sentence rather than above it, and the
   sentence is long enough that it needs the room. */
.ddesk-frozen-banner { display: flex; align-items: flex-start; gap: var(--ddesk-space-2); }
.ddesk-frozen-banner .ddesk-icon { flex: none; margin-top: 2px; }
.ddesk-frozen-banner strong { display: block; margin-bottom: 2px; }
.ddesk-flash-actions { display: inline-flex; gap: var(--ddesk-space-2); margin-left: var(--ddesk-space-2); flex-wrap: wrap; vertical-align: middle; }
@media (max-width: 600px) {
	.ddesk-flash-actions { display: flex; margin: var(--ddesk-space-2) 0 0; }
}

/* ---------- Buttons ---------- */
.ddesk-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--ddesk-space-1);
    font-family: Arial, Helvetica, sans-serif;
    font-weight: 700;
    font-size: var(--ddesk-text-body);
    border-radius: var(--ddesk-radius-control);
    padding: var(--ddesk-space-2) var(--ddesk-space-4);
    border: none;
    cursor: pointer;
    text-decoration: none;
    line-height: 1.2;
}
.ddesk-btn:hover { text-decoration: none; opacity: 0.92; }
.ddesk-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.ddesk-btn:disabled:hover { opacity: 0.45; }
/* .ddesk-btn.ddesk-btn-* (two classes) is deliberate, not stylistic: the
   general ".ddesk-app a" link colour rule above is one class plus the "a"
   element, which beats a single-class ".ddesk-btn-primary" selector on
   specificity alone. That let the blue link colour win on every button
   rendered as an <a>, putting blue text on the primary button's blue-to-
   cyan gradient - unreadable, and the bug Marc flagged. Two classes here
   outrank it so the intended button text colour always applies. */
.ddesk-btn.ddesk-btn-primary { background: var(--ddesk-gradient-primary); color: var(--ddesk-black); }
.ddesk-btn.ddesk-btn-secondary { background: var(--ddesk-white); color: var(--ddesk-black); border: 1px solid var(--ddesk-black); }
.ddesk-btn.ddesk-btn-danger { background: var(--ddesk-white); color: var(--ddesk-magenta-b); border: 1px solid var(--ddesk-magenta-b); }
.ddesk-btn-small { padding: var(--ddesk-space-1) var(--ddesk-space-3); font-size: var(--ddesk-text-meta); border-radius: var(--ddesk-radius-control); }

/* ---------- Cards (dashboard stats etc.) ---------- */
.ddesk-cards { display: flex; gap: var(--ddesk-space-4); flex-wrap: wrap; }
.ddesk-card {
    background: var(--ddesk-white);
    border: 1px solid var(--ddesk-border);
    border-radius: var(--ddesk-radius-card);
    padding: var(--ddesk-space-5);
    min-width: 160px;
}
.ddesk-card .ddesk-card-num { font-size: var(--ddesk-text-display); font-weight: 700; margin: 0 0 var(--ddesk-space-1); }
.ddesk-card .ddesk-card-label { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); margin: 0; }
.ddesk-card-accent-top { border-top: 4px solid transparent; border-image: var(--ddesk-gradient-primary) 1; }

/* ---------- Tables ---------- */
.ddesk-table-wrap { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); overflow-x: auto; }
table.ddesk-table { width: 100%; border-collapse: collapse; }
table.ddesk-table th, table.ddesk-table td { padding: var(--ddesk-space-3) var(--ddesk-space-4); text-align: left; font-size: var(--ddesk-text-body); border-bottom: 1px solid var(--ddesk-border); }
table.ddesk-table th { font-weight: 700; background: var(--ddesk-grey-bg); color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); text-transform: uppercase; letter-spacing: 0.04em; }

/* A sortable column heading. Inherits the heading's own type so a sortable
   column does not read as a different kind of thing from the ones beside it -
   the only difference is that it responds. */
table.ddesk-table th a.ddesk-sort-head {
	color: inherit;
	font: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
	text-decoration: none;
	white-space: nowrap;
}
table.ddesk-table th a.ddesk-sort-head:hover,
table.ddesk-table th a.ddesk-sort-head:focus { color: var(--ddesk-text); text-decoration: underline; }
table.ddesk-table th a.ddesk-sort-head.is-active { color: var(--ddesk-text); }
table.ddesk-table th a.ddesk-sort-head span { font-size: 9px; /* a glyph, not type: the only size here off the scale */ }
table.ddesk-table tr:last-child td { border-bottom: none; }
table.ddesk-table tr:hover td { background: var(--ddesk-grey-bg); }
.ddesk-table-empty { padding: var(--ddesk-space-8); text-align: center; color: var(--ddesk-text-muted); }

/* ---------- Lists on a phone. 0.76.0, design review W-6 ----------

   Billing, Dockets and Estimates are nine- and ten-column tables. On a phone
   they were a horizontal scroll region, which is correct and is not a list:
   reading one row meant swiping across it, and comparing two meant swiping
   back. 0.75.0 at least made that region reachable from a keyboard; it did
   not make it a thing anybody wants to read on a train.

   Below 782px each row becomes a card, drawn as Marc chose on 12 November:
   who it is for, what it comes to and where it stands on the first line,
   with reference numbers, dates and the rest folded underneath and labelled.
   Nothing is dropped. The folded lines carry their column heading as a
   prefix, because the <thead> that named them is not on screen any more.

   The markup stays one table, so sorting, the empty state, the row menus and
   every test that reads this markup are untouched - a <td> gains a class or
   a data-label and nothing else. */
@media (max-width: 782px) {
    .ddesk-stack-table thead { display: none; }
    .ddesk-stack-table,
    .ddesk-stack-table tbody,
    .ddesk-stack-table tr,
    .ddesk-stack-table td { display: block; width: auto; }
    /* The wrap no longer scrolls sideways, so it must not claim to: 0.75.0's
       script measures this box and makes it a tab stop when it overflows. */
    .ddesk-table-wrap:has(.ddesk-stack-table) { overflow-x: visible; border: none; background: none; }
    .ddesk-stack-table tr {
        display: grid;
        /* No grid-template-columns. The three cells on line one are placed
           in columns 1, 2 and 3 explicitly and the grid creates those columns
           at their own width, so declaring them was decoration: measured
           three ways - minmax(0, 1fr), auto, and gone - the layout is
           identical to the pixel. What keeps a long client name inside the
           card is overflow-wrap on the lead cell below, not column sizing,
           and the mutation run says so: breaking the wrap fails the suite and
           breaking the columns does not. Same finding as the nav's min-width
           in 0.75.0, and the same answer: say what actually holds. */
        gap: var(--ddesk-space-1) var(--ddesk-space-2);
        align-items: baseline;
        padding: var(--ddesk-space-3);
        margin-bottom: var(--ddesk-space-2);
        background: var(--ddesk-white);
        border: 1px solid var(--ddesk-grey-line);
        border-radius: var(--ddesk-radius-card);
    }
    table.ddesk-stack-table td { padding: 0; border-bottom: none; font-size: var(--ddesk-text-meta); }
    table.ddesk-stack-table tr:hover td { background: none; }

    /* Line one. */
    table.ddesk-stack-table td.ddesk-cell-lead {
        grid-column: 1; grid-row: 1;
        /* A client name with nothing in it to break on - and there are
           several in this data - is wider than a phone. The column is
           allowed to shrink below it (the 0 floor above); this is what makes
           the name itself wrap instead of sticking out of the card. */
        overflow-wrap: anywhere;
        font-size: var(--ddesk-text-card); font-weight: 700;
    }
    table.ddesk-stack-table td.ddesk-cell-amount {
        grid-column: 2; grid-row: 1;
        font-size: var(--ddesk-text-card); font-weight: 700; white-space: nowrap;
    }
    table.ddesk-stack-table td.ddesk-cell-state { grid-column: 3; grid-row: 1; }

    /* Everything else, underneath, labelled. */
    table.ddesk-stack-table td[data-label] {
        grid-column: 1 / -1;
        color: var(--ddesk-grey-text);
    }
    table.ddesk-stack-table td[data-label]::before {
        content: attr(data-label) ": ";
        font-weight: 700;
        color: var(--ddesk-grey-text);
    }
    /* A fold line with nothing in it is a label with a dash after it, which
       is nine lines of nothing on a request that has not been submitted. */
    table.ddesk-stack-table td[data-label][data-empty] { display: none; }

    table.ddesk-stack-table td.ddesk-actions {
        grid-column: 1 / -1; justify-self: end;
        margin-top: var(--ddesk-space-2);
    }
    /* The empty state is a message, not a row of a card. */
    table.ddesk-stack-table tr:has(.ddesk-table-empty) { display: block; padding: 0; }
    table.ddesk-stack-table td.ddesk-table-empty { padding: var(--ddesk-space-6); text-align: center; }
}
/* The actions cell, 0.56.0.
   It was display:flex ON the <td>, which takes the cell out of normal table
   layout: it stops filling the row's height and its contents stop lining up
   with the text in every other column. Measured on the estimates list, the
   icons sat 9px above the text beside them and the cell was 46px tall in a
   65px row. A table cell again, with the controls laid out inline inside it,
   so the row reads as one line across. Used on twenty screens, and the
   alignment was wrong on all of them. */
td.ddesk-actions { display: table-cell; vertical-align: middle; white-space: nowrap; }
td.ddesk-actions > * { vertical-align: middle; }
td.ddesk-actions > * + * { margin-left: var(--ddesk-space-2); }
.ddesk-actions { display: flex; gap: var(--ddesk-space-2); }

/* ---------- Badges / pills ---------- */
.ddesk-badge { display: inline-block; padding: 3px var(--ddesk-space-2); border-radius: var(--ddesk-radius-pill); font-size: var(--ddesk-text-meta); font-weight: 700; }
.ddesk-badge-lime { background: rgba(198,236,56,0.25); color: var(--ddesk-lime-text); }
.ddesk-badge-cyan { background: rgba(1,197,255,0.15); color: var(--ddesk-cyan-text); }
.ddesk-badge-magenta { background: rgba(231,13,112,0.12); color: var(--ddesk-magenta-text); }
.ddesk-badge-purple { background: rgba(204,81,255,0.15); color: var(--ddesk-purple-text); }
.ddesk-badge-grey { background: var(--ddesk-grey-line); color: var(--ddesk-text-muted); }

/* Vendor Cost Reconciliation variance (Sep 7, 2026) - reuses the same lime/magenta
   pair as the badge colours above, for a positive/negative pair that reads
   consistently with the rest of the brand palette rather than a generic red/green. */
.ddesk-variance-positive { color: var(--ddesk-lime-text); font-weight: 700; }
.ddesk-variance-negative { color: var(--ddesk-magenta-text); font-weight: 700; }

/* ---------- Lock banner (Milestone 6: invoice-driven estimate lock) ---------- */
.ddesk-lock-banner { border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-4) var(--ddesk-space-5); margin-bottom: var(--ddesk-space-5); font-size: var(--ddesk-text-body); }
.ddesk-lock-banner-locked { background: rgba(231,13,112,0.08); border: 1px solid rgba(231,13,112,0.3); color: var(--ddesk-magenta-text); }
.ddesk-lock-banner-unlocked { background: rgba(198,236,56,0.2); border: 1px solid rgba(198,236,56,0.5); color: var(--ddesk-lime-text); }
.ddesk-lock-banner form { margin-top: var(--ddesk-space-2); display: flex; gap: var(--ddesk-space-2); align-items: flex-end; flex-wrap: wrap; }
.ddesk-lock-banner .ddesk-field { margin-bottom: 0; }

/* ---------- Forms ---------- */
/* 0.52.0: padding 28 -> 20, and the sibling margin 24 -> 16. A card's bottom
   padding and the next card's top margin were paying for the same gap twice,
   52px of it, on every stacked card in the app. */
.ddesk-form-card { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-5); max-width: 960px; }
.ddesk-form-card + .ddesk-form-card { margin-top: var(--ddesk-space-4); }
.ddesk-field { margin-bottom: var(--ddesk-space-3); }
.ddesk-field label { display: block; font-weight: 700; font-size: var(--ddesk-text-body); margin-bottom: var(--ddesk-space-1); }
.ddesk-field .description { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); margin-top: var(--ddesk-space-1); margin-bottom: 0; }
.ddesk-field input[type=text],
.ddesk-field input[type=email],
.ddesk-field input[type=url],
.ddesk-field input[type=number],
.ddesk-field input[type=date],
.ddesk-field input[type=password],
.ddesk-field select,
.ddesk-field textarea {
    width: 100%;
    max-width: 460px;
    font-family: Arial, Helvetica, sans-serif;
    font-size: var(--ddesk-text-body);
    padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border: 1px solid var(--ddesk-border);
    border-radius: var(--ddesk-radius-control);
    background: var(--ddesk-white);
    color: var(--ddesk-text);
}
.ddesk-field input.ddesk-wide, .ddesk-field textarea.ddesk-wide { max-width: 100%; }
.ddesk-field input:focus, .ddesk-field select:focus, .ddesk-field textarea:focus {
    outline: none; border-color: var(--ddesk-cyan-b); box-shadow: 0 0 0 3px rgba(1,197,255,0.15);
}
/* Estimate Content's Description/Specs/Resources/Assumptions/Footer
   textareas (Sep 7, 2026, Marc's follow-up on the ddesk-wide-card pass
   above): these sit directly in .ddesk-tab-panel, not inside a .ddesk-field
   wrapper, so none of the .ddesk-field textarea rules above ever applied to
   them - they were rendering at the browser's bare default size (~20 cols)
   even after Estimate Content itself was widened, leaving a large empty gap
   next to a tiny box. Sized to fill the tab panel edge to edge instead (the
   panel already sits inside .ddesk-wide-card's own padding, so this reads
   as "almost as wide as the content block" without touching that padding). */
.ddesk-tab-panel textarea {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    font-family: Arial, Helvetica, sans-serif;
    font-size: var(--ddesk-text-body);
    padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border: 1px solid var(--ddesk-border);
    border-radius: var(--ddesk-radius-control);
    background: var(--ddesk-white);
    color: var(--ddesk-text);
    resize: vertical;
}
.ddesk-tab-panel textarea:focus {
    outline: none; border-color: var(--ddesk-cyan-b); box-shadow: 0 0 0 3px rgba(1,197,255,0.15);
}
.ddesk-field-row { display: flex; gap: var(--ddesk-space-6); flex-wrap: wrap; }
.ddesk-field-row .ddesk-field { flex: 1; min-width: 200px; }
.ddesk-inline-fields { display: flex; gap: var(--ddesk-space-4); flex-wrap: wrap; align-items: center; }
.ddesk-inline-fields label { display: flex; align-items: center; gap: var(--ddesk-space-1); font-weight: 400; font-size: var(--ddesk-text-body); }
.ddesk-checkbox-field { display: flex; align-items: center; gap: var(--ddesk-space-2); }
.ddesk-checkbox-field label { margin: 0; font-weight: 400; }
/* 22 -> 16: this sat above a card's own 20px of bottom padding, so the last
   button row of every form had 42px under it. */
.ddesk-form-actions { margin-top: var(--ddesk-space-4); display: flex; gap: var(--ddesk-space-3); }
/* ---------- The estimate's one Save bar. 0.76.0, design review W-3 ----------

   0.64.0 made the estimate screen a single form and then repeated its Save
   button four times down the page so that one of them would always be nearby.
   None of them ever was: on a real estimate the gap between two of them is
   forty resource lines, and a person building a task could not see any Save
   or tell whether what they were looking at had been written.

   One bar instead, pinned to the foot of the window for as long as the form
   is on screen, carrying the project total and - driven entirely by the
   dirty flag the 0.52.0 unsaved-work guard already publishes on <body> - a
   marker when something in front of you is unsaved.

   sticky, not fixed. The bar belongs to the form: it floats while the form is
   on screen and comes to rest at the foot of it, rather than hovering over
   the Budget, Template and Change Log sections underneath, which it has
   nothing to do with. */
.ddesk-save-bar {
    position: sticky; bottom: 0; z-index: 30;
    display: flex; align-items: center; gap: var(--ddesk-space-4); flex-wrap: wrap;
    margin-top: var(--ddesk-space-6);
    padding: var(--ddesk-space-3) var(--ddesk-space-4);
    background: var(--ddesk-white);
    border: 1px solid var(--ddesk-grey-line);
    border-radius: var(--ddesk-radius-card);
    /* Lifted off the page, because it sits over content while it floats and a
       flat bar reads as part of whatever happens to be behind it. */
    box-shadow: 0 -2px 10px rgba(0, 0, 0, .07);
}
.ddesk-save-bar-figure { display: flex; flex-direction: column; line-height: 1.2; }
.ddesk-save-bar-label {
    font-size: var(--ddesk-text-label); font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em;
    color: var(--ddesk-grey-text);
}
.ddesk-save-bar-value { font-size: var(--ddesk-text-figure); font-weight: 700; }
/* The marker is CSS only. Whether there is unsaved work is decided in one
   place - the dirty guard at the foot of estimates-edit.php - and this reads
   that decision rather than forming a second opinion about it. */
.ddesk-save-bar-state {
    display: none; align-items: center; gap: var(--ddesk-space-1);
    font-size: var(--ddesk-text-meta); font-weight: 700;
    color: var(--ddesk-magenta-text);
}
body[data-ddesk-estimate-dirty] .ddesk-save-bar-state { display: inline-flex; }
/* The button to the right, whatever else is on the bar. */
.ddesk-save-bar .ddesk-btn-primary { margin-left: auto; }

@media (max-width: 782px) {
    /* On a phone the bar is the button, with the total beside it. The label
       goes: "Project Total" above a figure costs a line of a screen that has
       none to spare, and the figure is self-evidently the total. */
    .ddesk-save-bar { gap: var(--ddesk-space-3); padding: var(--ddesk-space-3); }
    .ddesk-save-bar-label { display: none; }
    .ddesk-save-bar-state { order: 3; width: 100%; }
}

/* ---------- Sections inside edit pages (e.g. Divisions under a Client) ---------- */
.ddesk-section { margin-top: var(--ddesk-space-5); }
.ddesk-section h2 { font-size: var(--ddesk-text-figure); margin-bottom: var(--ddesk-space-2); }
.ddesk-section-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--ddesk-space-3); }

/* ---------- Tabs (used on edit pages with multiple sub-panels) ---------- */
.ddesk-tabs { display: flex; gap: var(--ddesk-space-1); border-bottom: 1px solid var(--ddesk-border); margin-bottom: var(--ddesk-space-3); flex-wrap: wrap; }
.ddesk-tabs a { padding: var(--ddesk-space-2) var(--ddesk-space-4); font-weight: 700; font-size: var(--ddesk-text-body); color: var(--ddesk-text-muted); border-bottom: 2px solid transparent; }
.ddesk-tabs a:hover { text-decoration: none; color: var(--ddesk-black); }
.ddesk-tabs a.is-active { color: var(--ddesk-black); border-bottom-color: var(--ddesk-cyan-b); }
.ddesk-tabs-compact { margin: var(--ddesk-space-2) 0 0; }
.ddesk-tabs-compact a { padding: var(--ddesk-space-2) var(--ddesk-space-3) var(--ddesk-space-2); font-size: var(--ddesk-text-meta); }

/* ---------- Login gate ---------- */
.ddesk-login-gate { max-width: 380px; margin: 90px auto; text-align: center; padding: 0 var(--ddesk-space-5); }
.ddesk-login-gate img { height: 40px; margin-bottom: var(--ddesk-space-6); }
.ddesk-login-gate .ddesk-flash { text-align: left; }
.ddesk-login-form { text-align: left; margin-top: var(--ddesk-space-2); background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-7); }
.ddesk-login-form .ddesk-field input { max-width: 100%; }
.ddesk-login-form .ddesk-form-actions { margin-top: var(--ddesk-space-1); }
.ddesk-login-form .ddesk-form-actions .ddesk-btn { width: 100%; justify-content: center; }
.ddesk-login-gate .description { margin-top: var(--ddesk-space-4); }

/* ==========================================================================
   Component kit (Sep 2, 2026) - built against a full interactive audit of
   production (docketdesk59.lovable.app), see UI-PARITY-AUDIT.md. These are
   the reusable pieces every list/detail screen should draw on from here on,
   agreed with Marc as step 1 before rebuilding individual screens: status
   pills (colour rule centralised in DDesk_Helpers::status_pill()), stat
   tiles, a list toolbar, an activity feed, entity cards, and a view/edit
   split shell. Structure and interaction match production; colour stays the
   Junction59 gradient system, never production's blue/purple defaults.
   Entity cards and the view/edit split are foundational only in this pass -
   not wired into a screen yet, reserved for Clients/Rate Cards and the
   Docket/Client/Estimate detail rebuild that follow the Dashboard+Dockets
   demonstration.
   ========================================================================== */

/* ---------- Stat tiles (Dashboard; Billing next) ---------- */
/* Sep 2, 2026 revision: Marc flagged the original version's gradient top
   border as too distracting for six tiles in a row - colour was fighting
   for attention instead of helping scan the row. Corners are rounder
   (14px, up from 10px) and the border is a plain neutral line again;
   colour now lives only in a small dot next to the label - present, but a
   differentiator rather than a decoration. */
/* 240px, not 175px. 0.58.2, measured against this stylesheet rather than
   guessed.

   .ddesk-content caps the page at 1760px (1360 before 0.62.0), so this grid is 1712px wide on any
   screen from a laptop upwards and the arithmetic is fixed: at 175px, seven
   tiles fitted one row at 182px each, which leaves 142px inside the padding,
   and "$2,917,784.66" at 26px Arial bold needs 166px. The headline number ran
   out through its own border by 26px, and by 24px at 1024 and 15px at 1280 -
   every width from 1024 up was wrong, which is every width anybody here uses.
   Adding the Awaiting Billing tile is what made it visible; six tiles had
   been one pixel inside the limit.

   240px gives five tiles a row at 261px, leaving 221px inside for a number
   that needs 166px. Seven tiles land as 5+2, as 4+3 at 1280 and 3+3+1 at
   1024. Wrapping to a second row is the right trade: these are the headline
   figures on the screen and shrinking them to fit seven across is the
   opposite of what they are for. */
/* 0.75.0, review finding UX-03: tighter.
   The "Needs you" band now sits above these, and Marc asked for both to fit
   without scrolling. Seven tiles at 240px wrap to three rows on a 1280px
   window; at 200px they make two. The padding and the number come down a
   little with them. Nothing is removed - the chips and the note are still
   there - so the tiles say exactly what they said before in about two thirds
   of the height. */
.ddesk-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--ddesk-space-3); margin-top: var(--ddesk-space-4); }
.ddesk-stat-tile {
    background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card);
    padding: var(--ddesk-space-3) var(--ddesk-space-4); display: block; color: inherit;
    /* A grid item's min-width is auto, so it refuses to shrink below its
       widest child and pushes the row out instead. */
    min-width: 0;
}
a.ddesk-stat-tile { transition: box-shadow .15s ease; }
a.ddesk-stat-tile:hover { box-shadow: 0 2px 10px rgba(0,0,0,.07); text-decoration: none; }
.ddesk-stat-tile-label { display: flex; align-items: center; gap: var(--ddesk-space-1); font-size: var(--ddesk-text-label); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ddesk-text-muted); margin: 0; }
.ddesk-stat-tile-label::before { content: ""; width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: var(--ddesk-text-muted); }
.ddesk-stat-tile-cyan .ddesk-stat-tile-label::before { background: var(--ddesk-cyan-a); }
.ddesk-stat-tile-lime .ddesk-stat-tile-label::before { background: var(--ddesk-lime-b); }
.ddesk-stat-tile-magenta .ddesk-stat-tile-label::before { background: var(--ddesk-magenta-a); }
.ddesk-stat-tile-purple .ddesk-stat-tile-label::before { background: var(--ddesk-purple-a); }
.ddesk-stat-tile-num { font-size: var(--ddesk-text-screen); font-weight: 700; margin: var(--ddesk-space-1) 0 0; line-height: 1.15; }
/* A money figure is one unbreakable token, so it cannot wrap its way out of
   trouble: past thirteen characters it has to get smaller. Set from the string
   itself in PHP - DDesk_Money::tile_size() - because CSS cannot count
   characters, and measured so that each step fits the 168px a tile gives it:
   fourteen characters at 21px, eighteen at 17px. Nothing on the dashboard
   reaches them today; they are the net under the next big number. */
.ddesk-stat-tile-num.is-long { font-size: var(--ddesk-text-figure); }
.ddesk-stat-tile-num.is-longer { font-size: var(--ddesk-text-card); }
.ddesk-stat-tile-chips { display: flex; flex-wrap: wrap; gap: var(--ddesk-space-1); margin-top: var(--ddesk-space-1); }
.ddesk-stat-tile-chips .ddesk-badge { font-size: var(--ddesk-text-label); padding: 2px var(--ddesk-space-2); }
.ddesk-stat-tile-note { font-size: var(--ddesk-text-label); color: var(--ddesk-text-muted); margin-top: var(--ddesk-space-1); }

/* ---------- Dashboard filter bar (Client/Company/Docket/Status/Date range) ---------- */
.ddesk-dash-filter-bar { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; margin: var(--ddesk-space-4) 0 var(--ddesk-space-1); }
.ddesk-dash-filter-bar select,
.ddesk-dash-filter-bar input[type="date"] {
    padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); background: var(--ddesk-white);
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-meta); color: var(--ddesk-text);
}
.ddesk-dash-filter-date-sep { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); }
.ddesk-dash-filter-toggle { display: flex; align-items: center; gap: var(--ddesk-space-1); font-size: var(--ddesk-text-meta); font-weight: 700; color: var(--ddesk-text-muted); }
.ddesk-dash-filter-bar .ddesk-btn-small { padding: var(--ddesk-space-1) var(--ddesk-space-3); }
/* .ddesk-dash-company-row removed Sep 2, 2026 - the Dashboard-local
   Company selector it styled was replaced by the global header switcher
   (.ddesk-company-switcher, above) per Marc's "persistent on all pages"
   direction. */

/* ---------- List toolbar: search + filters + count (list screens) ---------- */
.ddesk-list-toolbar { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; margin-bottom: var(--ddesk-space-4); }
.ddesk-list-toolbar-search { position: relative; flex: 1 1 220px; min-width: 180px; }
.ddesk-list-toolbar-search .ddesk-icon { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--ddesk-text-muted); pointer-events: none; }
.ddesk-list-toolbar-search input[type="search"] {
    width: 100%; padding: var(--ddesk-space-2) var(--ddesk-space-3) var(--ddesk-space-2) var(--ddesk-space-8); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control);
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); background: var(--ddesk-white);
}
.ddesk-list-toolbar-search input[type="search"]:focus { outline: none; border-color: var(--ddesk-cyan-b); box-shadow: 0 0 0 3px rgba(1,197,255,0.15); }
.ddesk-list-toolbar select {
    padding: var(--ddesk-space-2) var(--ddesk-space-3); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); background: var(--ddesk-white);
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); color: var(--ddesk-text);
}
.ddesk-list-toolbar-count { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); white-space: nowrap; margin-left: auto; }
@media (max-width: 720px) { .ddesk-list-toolbar-count { margin-left: 0; } }

/* ---------- List toolbar row 2: date/amount range (Estimates, Sep 3, 2026) ---------- */
.ddesk-list-toolbar-row2 { display: flex; align-items: flex-end; gap: var(--ddesk-space-2); flex-wrap: wrap; width: 100%; margin-top: var(--ddesk-space-1); }
.ddesk-range-label { display: flex; flex-direction: column; gap: 3px; font-size: var(--ddesk-text-label); color: var(--ddesk-text-muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; }
.ddesk-range-label input { padding: var(--ddesk-space-2) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); width: 130px; }
.ddesk-range-sep { color: var(--ddesk-text-muted); padding-bottom: var(--ddesk-space-2); }

/* ---------- Status tab bar (Estimates list, Sep 3, 2026) ---------- */
.ddesk-status-tabs { display: flex; flex-wrap: wrap; background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card) var(--ddesk-radius-card) 0 0; border-bottom: none; overflow: hidden; }
.ddesk-status-tab {
    flex: 1 1 auto; text-align: center; padding: var(--ddesk-space-3) var(--ddesk-space-2); font-size: var(--ddesk-text-body); font-weight: 700; color: var(--ddesk-text-muted);
    border-right: 1px solid var(--ddesk-border); border-bottom: 3px solid transparent;
}
.ddesk-status-tab:last-child { border-right: none; }
.ddesk-status-tab:hover { text-decoration: none; color: var(--ddesk-text); background: var(--ddesk-bg); }
.ddesk-status-tab.is-active { color: var(--ddesk-black); border-bottom-color: var(--ddesk-cyan-b); background: var(--ddesk-bg); }
.ddesk-status-tab-count { color: var(--ddesk-text-muted); font-weight: 400; }
.ddesk-status-tab.is-active .ddesk-status-tab-count { color: inherit; }

/* Table directly follows the tab bar in the Estimates list, so its wrap
   picks up the tab bar's rounded bottom corners instead of its own top
   ones - the pagination bar sits between them, so this applies to the
   pagination row's own bottom edge / table wrap's top only via normal flow;
   kept simple rather than fighting border-radius across three stacked
   elements. */
.ddesk-list-pagination { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--ddesk-space-2); background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-top: none; padding: var(--ddesk-space-2) var(--ddesk-space-4); font-size: var(--ddesk-text-body); color: var(--ddesk-text-muted); }
.ddesk-list-pagination-controls { display: flex; align-items: center; gap: var(--ddesk-space-2); }
.ddesk-page-size { padding: 2px var(--ddesk-space-1); border-radius: var(--ddesk-radius-control); color: var(--ddesk-text-muted); font-weight: 700; }
.ddesk-page-size:hover { text-decoration: none; background: var(--ddesk-bg); }
.ddesk-page-size.is-active { background: var(--ddesk-gradient-primary); color: var(--ddesk-black); }
.ddesk-page-nav { display: flex; align-items: center; gap: var(--ddesk-space-2); margin-left: var(--ddesk-space-2); padding-left: var(--ddesk-space-2); border-left: 1px solid var(--ddesk-border); white-space: nowrap; }

/* ---------- Estimate Defaults panel (Sep 3, 2026) ---------- */
.ddesk-defaults-panel { margin-bottom: var(--ddesk-space-5); }
.ddesk-defaults-scope { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; margin: var(--ddesk-space-1) 0 var(--ddesk-space-1); }
.ddesk-defaults-scope-label { font-size: var(--ddesk-text-meta); font-weight: 700; color: var(--ddesk-text-muted); }

/* ---------- Billing deferred (8 October) ---------- */
.ddesk-defer-card { border-left: 3px solid var(--ddesk-purple-a); }
.ddesk-defer-card-overdue { border-left-color: var(--ddesk-magenta-a); }
.ddesk-defer-summary p { margin: 0 0 var(--ddesk-space-1); }
.ddesk-defer-release { margin-top: var(--ddesk-space-3); padding-top: var(--ddesk-space-3); border-top: 1px solid var(--ddesk-border); }
.ddesk-defer-answers { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; margin: var(--ddesk-space-2) 0 var(--ddesk-space-1); }
.ddesk-watchlist-group { margin-top: var(--ddesk-space-6); }
.ddesk-watchlist-group h2 { font-size: var(--ddesk-text-figure); margin: 0 0 2px; }
.ddesk-watchlist-group-alarm h2 { color: var(--ddesk-magenta-b); }
.ddesk-watchlist-total { font-size: var(--ddesk-text-body); font-weight: 700; color: var(--ddesk-text-muted); }
/* The watchlist summarised on the Billing page. Side by side on a desktop,
   stacked on a phone, with the action under the words rather than beside
   them - the button is the point of the card and must not be scrolled off. */
.ddesk-watchlist-card { display: flex; gap: var(--ddesk-space-5); align-items: center; background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-left: 4px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-4) var(--ddesk-space-5); margin-top: var(--ddesk-space-4); }
.ddesk-watchlist-card-alarm { border-left-color: var(--ddesk-magenta-a); }
.ddesk-watchlist-card-main { flex: 1 1 auto; min-width: 0; }
.ddesk-watchlist-card-action { flex: 0 0 auto; }
.ddesk-watchlist-card-label { font-size: var(--ddesk-text-label); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ddesk-text-muted); margin: 0; }
.ddesk-watchlist-card-head { font-size: var(--ddesk-text-card); font-weight: 700; margin: var(--ddesk-space-1) 0 0; line-height: 1.3; }
.ddesk-watchlist-card-alarm .ddesk-watchlist-card-head { color: var(--ddesk-magenta-b); }
.ddesk-watchlist-card-breakdown { font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); margin: var(--ddesk-space-1) 0 0; }
.ddesk-watchlist-card-quiet { font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); margin: var(--ddesk-space-2) 0 0; padding-top: var(--ddesk-space-2); border-top: 1px solid var(--ddesk-border); }
@media (max-width: 782px) {
	.ddesk-watchlist-card { flex-direction: column; align-items: stretch; gap: var(--ddesk-space-3); }
	.ddesk-watchlist-card-action .ddesk-btn { width: 100%; justify-content: center; }
}
.ddesk-defaults-inherited { background: var(--ddesk-surface, var(--ddesk-grey-bg)); border: 1px dashed var(--ddesk-border); border-radius: var(--ddesk-radius-control); padding: var(--ddesk-space-2) var(--ddesk-space-3); margin: var(--ddesk-space-1) 0 0; font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); white-space: pre-wrap; max-height: 180px; overflow: auto; }
.ddesk-subtabs { display: flex; gap: var(--ddesk-space-1); border-bottom: 1px solid var(--ddesk-border); margin: var(--ddesk-space-3) 0 var(--ddesk-space-4); }
.ddesk-subtab { padding: var(--ddesk-space-2) var(--ddesk-space-3); font-size: var(--ddesk-text-body); font-weight: 700; color: var(--ddesk-text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-subtab:hover { text-decoration: none; color: var(--ddesk-text); }
.ddesk-subtab.is-active { color: var(--ddesk-black); border-bottom-color: var(--ddesk-cyan-b); }
/* Subtabs that switch a panel in place rather than loading a page are buttons,
   so that typing in one panel is not thrown away by a navigation. They need the
   browser's button chrome removed to sit level with the link version. */
button.ddesk-subtab { background: none; border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer; font-family: inherit; }
.ddesk-defaults-panel textarea.ddesk-wide { width: 100%; padding: var(--ddesk-space-2) var(--ddesk-space-3); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); resize: vertical; }

/* ---------- Hours Tracker repeatable rows (Record Hours tab, Sep 3, 2026) ---------- */
.ddesk-repeat-grid { display: flex; flex-direction: column; gap: var(--ddesk-space-2); margin: var(--ddesk-space-2) 0; }
.ddesk-repeat-row { display: grid; grid-template-columns: 1fr 120px 40px; gap: var(--ddesk-space-2); align-items: center; }
.ddesk-repeat-row input, .ddesk-repeat-row select { padding: var(--ddesk-space-2) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body); width: 100%; }
.ddesk-repeat-row .ddesk-btn { padding: var(--ddesk-space-1); justify-content: center; }
.ddesk-repeat-row .ddesk-btn .ddesk-icon { width: 14px; height: 14px; }
@media (max-width: 600px) { .ddesk-repeat-row { grid-template-columns: 1fr 90px 36px; } }

/* ---------- Per-row icon actions (Estimates list Actions column, Sep 3, 2026) ---------- */
.ddesk-actions a { display: inline-flex; align-items: center; color: var(--ddesk-text-muted); }
.ddesk-actions a:hover { color: var(--ddesk-cyan-b); }
.ddesk-actions a.ddesk-danger-icon:hover { color: var(--ddesk-magenta-b); }
.ddesk-actions .ddesk-icon { width: 16px; height: 16px; }

/* ---------- Activity feed (Dashboard "Recent Activity") ---------- */
.ddesk-activity-list { list-style: none; margin: 0; padding: 0; }
.ddesk-activity-item { display: flex; align-items: center; justify-content: space-between; gap: var(--ddesk-space-3); padding: var(--ddesk-space-2) 0; border-bottom: 1px solid var(--ddesk-border); min-height: 40px; flex-wrap: wrap; }
.ddesk-activity-item:last-child { border-bottom: none; }
.ddesk-activity-left { display: flex; align-items: center; gap: var(--ddesk-space-2); min-width: 0; flex-wrap: wrap; }
.ddesk-activity-title { font-weight: 700; font-size: var(--ddesk-text-body); white-space: nowrap; }
.ddesk-activity-entity { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 360px; }
.ddesk-activity-right { display: flex; align-items: center; gap: var(--ddesk-space-3); flex: 0 0 auto; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
.ddesk-activity-when { white-space: nowrap; }

/* ---------- Entity cards - foundational, not wired up yet (Clients / Rate Cards next) ---------- */
.ddesk-entity-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: var(--ddesk-space-4); }
.ddesk-entity-card { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-5); display: flex; flex-direction: column; gap: var(--ddesk-space-3); }
.ddesk-entity-card-head { display: flex; align-items: center; gap: var(--ddesk-space-3); }
.ddesk-entity-card-avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--ddesk-gradient-primary); color: var(--ddesk-black); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: var(--ddesk-text-body); flex: 0 0 auto; }
.ddesk-entity-card-title { font-weight: 700; font-size: var(--ddesk-text-card); }
.ddesk-entity-card-tags { display: flex; gap: var(--ddesk-space-1); flex-wrap: wrap; margin-top: var(--ddesk-space-1); }
.ddesk-entity-card-stats { display: flex; gap: var(--ddesk-space-4); padding: var(--ddesk-space-3) 0; border-top: 1px solid var(--ddesk-border); border-bottom: 1px solid var(--ddesk-border); }
.ddesk-entity-card-stat { font-size: var(--ddesk-text-label); color: var(--ddesk-text-muted); }
.ddesk-entity-card-stat strong { display: block; color: var(--ddesk-black); font-size: var(--ddesk-text-card); font-weight: 700; }
.ddesk-entity-card-footer { display: flex; gap: var(--ddesk-space-2); }

/* ---------- View/edit split shell - foundational, not wired up yet (Docket / Client / Estimate detail next) ---------- */
.ddesk-view-card { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-6); }
.ddesk-view-row { display: flex; justify-content: space-between; gap: var(--ddesk-space-4); padding: var(--ddesk-space-2) 0; border-bottom: 1px solid var(--ddesk-border); font-size: var(--ddesk-text-body); margin: 0; }
.ddesk-view-row:last-child { border-bottom: none; }
.ddesk-view-row dt { color: var(--ddesk-text-muted); font-weight: 700; font-size: var(--ddesk-text-label); text-transform: uppercase; letter-spacing: 0.04em; margin: 0; }
.ddesk-view-row dd { margin: 0; font-weight: 400; text-align: right; }

@media (max-width: 720px) {
    .ddesk-header { padding: var(--ddesk-space-3) var(--ddesk-space-4); }
    .ddesk-content { padding: var(--ddesk-space-5) var(--ddesk-space-3) var(--ddesk-space-10); }
    .ddesk-form-card { padding: var(--ddesk-space-4); }
    .ddesk-activity-item { justify-content: flex-start; }
    .ddesk-activity-right { width: 100%; justify-content: space-between; }
}

/* ==========================================================================
   Rate Cards / Billing production parity (Sep 3, 2026) - see UI-PARITY-AUDIT
   follow-up. Rate Cards moves from a single flat table to production's
   grouped, collapsible-card layout (Default / Client Specific); Billing gets
   stat tiles and an outstanding-balance banner on top of the existing kit.
   ========================================================================== */

/* ---------- Rate Cards: grouped collapsible cards ---------- */
.ddesk-ratecard-group { margin-top: var(--ddesk-space-7); }
.ddesk-ratecard-group h2 { font-size: var(--ddesk-text-card); margin-bottom: 2px; }
.ddesk-ratecard-group-subtitle { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); margin: 0 0 var(--ddesk-space-3); }
.ddesk-ratecard-group-count { display: flex; align-items: center; gap: var(--ddesk-space-2); font-size: var(--ddesk-text-body); color: var(--ddesk-text-muted); margin-bottom: var(--ddesk-space-2); }
.ddesk-ratecard-group-empty { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); background: var(--ddesk-white); border: 1px dashed var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-4); }
.ddesk-ratecard-card { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); margin-bottom: var(--ddesk-space-3); overflow: hidden; }
.ddesk-ratecard-card summary {
    list-style: none; cursor: pointer; padding: var(--ddesk-space-4) var(--ddesk-space-5); display: flex; align-items: center;
    gap: var(--ddesk-space-3); flex-wrap: wrap;
}
.ddesk-ratecard-card summary::-webkit-details-marker { display: none; }
.ddesk-ratecard-card summary::before {
    content: ""; width: 9px; height: 9px; flex: 0 0 auto; align-self: center;
    border-right: 2px solid var(--ddesk-text-muted); border-bottom: 2px solid var(--ddesk-text-muted);
    transform: rotate(-45deg); transition: transform .15s ease; margin-right: 2px;
}
.ddesk-ratecard-card[open] summary::before { transform: rotate(45deg); }
.ddesk-ratecard-card-name { font-weight: 700; font-size: var(--ddesk-text-card); }
.ddesk-ratecard-card-meta { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); }
.ddesk-ratecard-card-actions { margin-left: auto; display: flex; gap: var(--ddesk-space-2); align-items: center; flex-wrap: wrap; }
.ddesk-ratecard-card-body { padding: 0 var(--ddesk-space-5) var(--ddesk-space-5); border-top: 1px solid var(--ddesk-border); }
.ddesk-ratecard-card-notes { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); margin: var(--ddesk-space-3) 0; }
.ddesk-ratecard-card-toolbar { display: flex; align-items: center; gap: var(--ddesk-space-3); margin: var(--ddesk-space-3) 0; flex-wrap: wrap; }
.ddesk-ratecard-card-toolbar .ddesk-ratecard-role-count { font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); font-weight: 700; }
/* Edit Rates inline grid (Sep 3, 2026 correction pass) */
.ddesk-ratecard-inline-input {
    width: 90px; padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control);
    font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body);
}
.ddesk-ratecard-inline-input:focus { outline: none; border-color: var(--ddesk-cyan-b); box-shadow: 0 0 0 3px rgba(1,197,255,0.15); }

/* ---------- Billing: outstanding balance banner ---------- */
.ddesk-outstanding-banner {
    display: flex; align-items: center; justify-content: space-between; gap: var(--ddesk-space-4); flex-wrap: wrap;
    background: var(--ddesk-black); color: var(--ddesk-white); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-5) var(--ddesk-space-6); margin-top: var(--ddesk-space-5);
}
.ddesk-outstanding-banner-label { font-size: var(--ddesk-text-meta); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.7); }
.ddesk-outstanding-banner-value { font-size: var(--ddesk-text-display); font-weight: 700; margin-top: var(--ddesk-space-1); }
.ddesk-outstanding-banner-note { font-size: var(--ddesk-text-meta); color: rgba(255,255,255,0.7); max-width: 420px; text-align: right; }

/* The three states money can be in on the Billing screen. 0.75.0, UX-04.
   Replaces the single black "Total Outstanding" banner. Same black bar, three
   compartments, and the receivable gets the large figure because it is the one
   that is actually owed. Stacks on a phone. */
.ddesk-money-states {
    /* 0.76.0: four compartments now that Paid this month has moved in from the
       stat tiles above, which are gone. 190px keeps all four on one row on a
       laptop and folds to two on a tablet. */
    display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 1px;
    background: rgba(255,255,255,0.14);
    border-radius: var(--ddesk-radius-card); overflow: hidden; margin-top: var(--ddesk-space-5);
}
.ddesk-money-state { background: var(--ddesk-black); color: var(--ddesk-white); padding: var(--ddesk-space-4) var(--ddesk-space-5); }
.ddesk-money-state-label { font-size: var(--ddesk-text-meta); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.7); }
.ddesk-money-state-value { font-size: var(--ddesk-text-screen); font-weight: 700; margin-top: var(--ddesk-space-1); }
.ddesk-money-state-primary .ddesk-money-state-value { font-size: var(--ddesk-text-display); }
.ddesk-money-state-note { font-size: var(--ddesk-text-meta); color: rgba(255,255,255,0.65); margin-top: var(--ddesk-space-1); line-height: 1.35; }

/* ---------- The dashboard's approved-value panel. 0.82.9 ----------
   Built on .ddesk-money-states' idiom rather than beside it: black ground,
   white figures, the device this app already uses for "these are the numbers
   that matter". One panel, because there is one headline here, not four. */
.ddesk-money-panel {
    background: var(--ddesk-black); color: var(--ddesk-white);
    border-radius: var(--ddesk-radius-card);
    padding: var(--ddesk-space-5) var(--ddesk-space-6);
    margin-bottom: var(--ddesk-space-3);
}
.ddesk-money-panel-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--ddesk-space-2); }
/* ---------- The same panel, laid across instead of down. 0.82.13 ----------
   Marc, 13 November: "lets compact the black bar on the dashboard, it forces
   the estimate list pretty far down the page and that is the most likely
   needed list on a day to day execution."

   Four stacked things - heading, figure, bar, legend - were 188px for one
   headline number. Side by side they are the height of the taller pair:
   heading over figure on the left, bar over its legend on the right. Nothing
   is dropped and nothing shrinks except the legend's figures, which are a
   key to the bar rather than numbers anybody reads off the dashboard.

   900px because below it the left column cannot hold $6,842,110.42 at
   display size beside anything at all; under that it stacks exactly as it
   did, which is also the phone layout. */
@media (min-width: 900px) {
    .ddesk-money-panel {
        display: grid;
        grid-template-columns: auto minmax(260px, 1fr);
        grid-template-areas: "head bar" "num parts";
        column-gap: var(--ddesk-space-6); row-gap: var(--ddesk-space-2);
        align-items: center;
        padding: var(--ddesk-space-4) var(--ddesk-space-5);
    }
    .ddesk-money-panel-head { grid-area: head; justify-content: flex-start; gap: var(--ddesk-space-3); }
    .ddesk-money-panel-num { grid-area: num; margin-top: 0; }
    .ddesk-money-bar { grid-area: bar; margin-top: 0; align-self: end; }
    .ddesk-money-parts { grid-area: parts; margin-top: 0; align-self: start; }
    /* The legend's figures step down from the screen size to the body size:
       beside a 30px headline they were competing with it, and they are there
       to name the three segments, not to be read as the page's numbers. */
    .ddesk-money-panel .ddesk-money-part dd { font-size: var(--ddesk-text-body); }
    /* Margin rate loses its push to the right edge here. Across a full-width
       list that put it at the end of the row it summarises; in a band it left
       it marooned the better part of a screen away from the Margin figure it
       is the percentage of. It reads as the fourth item in the key instead. */
    .ddesk-money-panel .ddesk-money-part-rate { margin-left: 0; text-align: left; }
}
.ddesk-money-panel-label {
    margin: 0; font-size: var(--ddesk-text-meta); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.7);
}
.ddesk-money-panel-context { margin: 0; font-size: var(--ddesk-text-meta); color: rgba(255,255,255,0.65); }
.ddesk-money-panel-num {
    margin: var(--ddesk-space-1) 0 0; font-size: var(--ddesk-text-display); font-weight: 700;
    letter-spacing: -0.02em; line-height: 1.15;
}
/* THE BAR. Heights in px, not a scale step: this is a drawn object whose job
   is to be read as a proportion, and 24px is the smallest that keeps a three
   percent segment visible at all. */
.ddesk-money-bar {
    display: flex; height: 24px; margin-top: var(--ddesk-space-4);
    border-radius: var(--ddesk-radius-control); overflow: hidden;
    background: rgba(255,255,255,0.14);
}
.ddesk-money-bar-seg { display: block; height: 100%; }
/* Bought in is grey, not a brand colour, because it is money passing through
   rather than the agency's own. The two that ARE the agency's get the brand. */
.ddesk-money-bar-cost { background: var(--ddesk-grey-fill); }
.ddesk-money-bar-fees { background: var(--ddesk-cyan-a); }
.ddesk-money-bar-margin { background: var(--ddesk-lime-a); }
.ddesk-money-parts {
    display: flex; flex-wrap: wrap; gap: var(--ddesk-space-2) var(--ddesk-space-6);
    margin: var(--ddesk-space-3) 0 0;
}
.ddesk-money-part dt { font-size: var(--ddesk-text-meta); color: rgba(255,255,255,0.65); }
.ddesk-money-part dd { margin: 2px 0 0; font-size: var(--ddesk-text-screen); font-weight: 700; }
/* The label is the legend: its colour names the segment it belongs to, so the
   bar needs no separate key. Both read against black above 4.5:1. */
.ddesk-money-part-fees dt { color: var(--ddesk-cyan-a); }
.ddesk-money-part-margin dt { color: var(--ddesk-lime-a); }
.ddesk-money-part-margin dd { color: var(--ddesk-lime-a); }
/* The Reports panel's two segments. Lime for money in, magenta for money
   still owed - the same two meanings those colours carry in the status
   pills. The dt uses magenta B rather than A: A is #E70D70, which is about
   4.0:1 on black and fails small text, where B is comfortably past 4.5. */
.ddesk-money-bar-paid { background: var(--ddesk-lime-a); }
.ddesk-money-bar-owing { background: var(--ddesk-magenta-a); }
.ddesk-money-part-owing dt { color: var(--ddesk-magenta-b); }
.ddesk-money-part-rate { margin-left: auto; text-align: right; }

/* ---------- Billed by month. 0.82.9, the first chart in Reports ----------
   CSS columns rather than SVG: twelve stacked bars need no path maths, they
   reflow on a phone, and they inherit the type scale. A chart that needed a
   library would have been a reason not to have one. */
.ddesk-chart {
    display: flex; align-items: flex-end; gap: var(--ddesk-space-2);
    height: 200px; padding: var(--ddesk-space-4) var(--ddesk-space-5) 0;
}
.ddesk-chart-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.ddesk-chart-value {
    margin: 0 0 var(--ddesk-space-1); font-size: var(--ddesk-text-label);
    color: var(--ddesk-grey-text); text-align: center; white-space: nowrap;
}
/* The bar's height is a percentage of this box, so the box has to be the
   thing that is 100% - flex-grow would make every column equal instead. */
.ddesk-chart-bar { display: flex; flex-direction: column; justify-content: flex-end; min-height: 2px; border-radius: 2px 2px 0 0; overflow: hidden; }
.ddesk-chart-seg { display: block; width: 100%; }
.ddesk-chart-seg-paid { background: var(--ddesk-lime-a); }
.ddesk-chart-seg-owing { background: var(--ddesk-magenta-a); }
.ddesk-chart-label {
    margin: var(--ddesk-space-1) 0 0; font-size: var(--ddesk-text-label);
    color: var(--ddesk-grey-text); text-align: center;
}
.ddesk-chart-key { display: flex; gap: var(--ddesk-space-3); margin: 0; font-size: var(--ddesk-text-meta); color: var(--ddesk-grey-text); }
.ddesk-chart-key-item { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-chart-key-item::before { content: ""; width: 10px; height: 10px; border-radius: 2px; }
.ddesk-chart-key-paid::before { background: var(--ddesk-lime-a); }
.ddesk-chart-key-owing::before { background: var(--ddesk-magenta-a); }
@media (max-width: 782px) {
    /* Twelve columns in 390px is 24px each, which the month labels cannot
       sit under. Shorter, and the value labels come off. */
    .ddesk-chart { height: 140px; padding-inline: var(--ddesk-space-3); gap: 3px; }
    .ddesk-chart-value { display: none; }
}
@media (max-width: 782px) {
    .ddesk-money-part-rate { margin-left: 0; text-align: left; }
}

.ddesk-invoice-status-form { display: flex; align-items: center; gap: var(--ddesk-space-1); }
.ddesk-invoice-status-form select { padding: var(--ddesk-space-1) var(--ddesk-space-2); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-control); font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-meta); }
.ddesk-egnyte-link { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); font-size: var(--ddesk-text-meta); }
.ddesk-egnyte-link .ddesk-icon { width: 13px; height: 13px; }

/* ---------- Header search box (Sep 4, 2026 - Reports/Search/Permissions/Help build) ---------- */
.ddesk-header-search { position: relative; }
.ddesk-header-search .ddesk-icon {
    position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
    width: 15px; height: 15px; color: rgba(255,255,255,0.6); pointer-events: none;
}
.ddesk-header-search input[type="search"] {
    width: 220px; padding: var(--ddesk-space-1) var(--ddesk-space-2) var(--ddesk-space-1) var(--ddesk-space-8); border-radius: var(--ddesk-radius-control); border: 1px solid rgba(255,255,255,0.2);
    background: rgba(255,255,255,0.08); color: var(--ddesk-white); font-family: Arial, Helvetica, sans-serif; font-size: var(--ddesk-text-body);
}
.ddesk-header-search input[type="search"]::placeholder { color: rgba(255,255,255,0.55); }
.ddesk-header-search input[type="search"]:focus { outline: none; border-color: var(--ddesk-cyan-a); background: rgba(255,255,255,0.14); }

/* ---------- Search results page ---------- */
.ddesk-search-page-bar { display: flex; gap: var(--ddesk-space-2); align-items: center; margin-bottom: var(--ddesk-space-4); }
.ddesk-search-page-bar .ddesk-list-toolbar-search { flex: 1 1 auto; }
.ddesk-search-results { display: flex; flex-direction: column; gap: var(--ddesk-space-2); margin-top: var(--ddesk-space-4); }
.ddesk-search-result {
    display: flex; align-items: center; gap: var(--ddesk-space-4); background: var(--ddesk-white);
    border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-4) var(--ddesk-space-5);
}
.ddesk-search-result-icon {
    flex: 0 0 auto; width: 36px; height: 36px; border-radius: var(--ddesk-radius-control); display: flex; align-items: center; justify-content: center;
}
.ddesk-search-result-icon .ddesk-icon { width: 18px; height: 18px; }
.ddesk-search-icon-client { background: rgba(1,197,255,0.12); color: var(--ddesk-cyan-text); }
.ddesk-search-icon-docket { background: rgba(198,236,56,0.25); color: var(--ddesk-lime-text); }
.ddesk-search-icon-estimate { background: rgba(204,81,255,0.15); color: var(--ddesk-purple-text); }
.ddesk-search-result-body { flex: 1 1 auto; min-width: 0; }
.ddesk-search-result-title { font-weight: 700; font-size: var(--ddesk-text-body); margin-bottom: var(--ddesk-space-1); }
.ddesk-search-result-meta { display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); }
.ddesk-search-match-tag {
    background: var(--ddesk-bg); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-pill);
    padding: 2px var(--ddesk-space-2); font-size: var(--ddesk-text-label); font-style: italic;
}
.ddesk-empty-state { padding: var(--ddesk-space-10); text-align: center; color: var(--ddesk-text-muted); background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); }

/* ---------- Permissions guide ---------- */
.ddesk-role-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ddesk-space-5); margin-top: var(--ddesk-space-5); }
.ddesk-role-card { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-6); }
.ddesk-role-card-head { display: flex; align-items: center; gap: var(--ddesk-space-3); margin-bottom: var(--ddesk-space-2); }
.ddesk-role-card-icon {
    flex: 0 0 auto; width: 40px; height: 40px; border-radius: var(--ddesk-radius-card); display: flex; align-items: center; justify-content: center;
    background: rgba(1,197,255,0.12); color: var(--ddesk-cyan-text);
}
.ddesk-role-card-icon .ddesk-icon { width: 20px; height: 20px; }
.ddesk-role-card-title { font-size: var(--ddesk-text-figure); font-weight: 700; display: flex; align-items: center; gap: var(--ddesk-space-2); }
.ddesk-role-card-desc { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); margin-bottom: var(--ddesk-space-4); }
.ddesk-role-card h4 { font-size: var(--ddesk-text-body); text-transform: uppercase; letter-spacing: 0.04em; margin: var(--ddesk-space-4) 0 var(--ddesk-space-2); }
.ddesk-role-card ul { margin: 0; padding-left: var(--ddesk-space-5); }
.ddesk-role-card li { font-size: var(--ddesk-text-body); margin-bottom: var(--ddesk-space-1); line-height: 1.4; }
.ddesk-info-card { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-6); margin-top: var(--ddesk-space-5); }
.ddesk-info-card h2 { margin-top: 0; }
.ddesk-info-card ul { padding-left: var(--ddesk-space-5); }
.ddesk-info-card li { font-size: var(--ddesk-text-body); margin-bottom: var(--ddesk-space-2); line-height: 1.5; }
@media (max-width: 900px) { .ddesk-role-grid { grid-template-columns: 1fr; } }

/* ---------- Help & Documentation ---------- */
.ddesk-help-getting-started { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); padding: var(--ddesk-space-6); }
.ddesk-help-getting-started h2 { margin-top: 0; display: flex; align-items: center; gap: var(--ddesk-space-2); }
.ddesk-help-getting-started-cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ddesk-space-6); margin-top: var(--ddesk-space-4); }
.ddesk-help-getting-started-cols h4 { margin: 0 0 var(--ddesk-space-2); font-size: var(--ddesk-text-body); text-transform: uppercase; letter-spacing: 0.04em; }
.ddesk-help-getting-started-cols ul { margin: 0; padding-left: var(--ddesk-space-4); }
.ddesk-help-getting-started-cols li { font-size: var(--ddesk-text-body); margin-bottom: var(--ddesk-space-1); }
.ddesk-help-protip { border-top: 1px solid var(--ddesk-border); margin-top: var(--ddesk-space-4); padding-top: var(--ddesk-space-4); display: flex; gap: var(--ddesk-space-2); align-items: flex-start; }
.ddesk-help-protip strong { display: block; margin-bottom: var(--ddesk-space-1); }
.ddesk-help-features { margin-top: var(--ddesk-space-6); }
.ddesk-help-feature { background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); margin-bottom: var(--ddesk-space-2); overflow: hidden; }
.ddesk-help-feature summary {
    list-style: none; cursor: pointer; padding: var(--ddesk-space-4) var(--ddesk-space-5); display: flex; align-items: center; gap: var(--ddesk-space-3);
}
.ddesk-help-feature summary::-webkit-details-marker { display: none; }
.ddesk-help-feature summary::before {
    content: ''; flex: 0 0 auto; width: 8px; height: 8px; border-right: 2px solid var(--ddesk-text-muted); border-bottom: 2px solid var(--ddesk-text-muted);
    transform: rotate(-45deg); transition: transform .15s ease; margin-right: 2px;
}
.ddesk-help-feature[open] summary::before { transform: rotate(45deg); }
.ddesk-help-feature-icon {
    flex: 0 0 auto; width: 34px; height: 34px; border-radius: var(--ddesk-radius-control); display: flex; align-items: center; justify-content: center;
    background: rgba(198,236,56,0.25); color: var(--ddesk-lime-text);
}
.ddesk-help-feature-icon .ddesk-icon { width: 17px; height: 17px; }
.ddesk-help-feature-title { font-weight: 700; font-size: var(--ddesk-text-body); display: flex; align-items: center; gap: var(--ddesk-space-2); }
.ddesk-help-feature-desc { color: var(--ddesk-text-muted); font-size: var(--ddesk-text-body); margin-top: 2px; }
.ddesk-help-feature-body { padding: 0 var(--ddesk-space-5) var(--ddesk-space-5) 68px; border-top: 1px solid var(--ddesk-border); }
.ddesk-help-feature-body h4 { font-size: var(--ddesk-text-body); text-transform: uppercase; letter-spacing: 0.04em; margin: var(--ddesk-space-4) 0 var(--ddesk-space-2); }
.ddesk-help-feature-body ol, .ddesk-help-feature-body ul { margin: 0; padding-left: var(--ddesk-space-5); }
.ddesk-help-feature-body li { font-size: var(--ddesk-text-body); margin-bottom: var(--ddesk-space-1); line-height: 1.45; }

/* ---------- Reports: one column of destinations. 0.82.12 ----------
   Replaces two rows of tabs, .ddesk-reports-cat-tabs and
   .ddesk-reports-sub-tabs, both removed with their markup.

   The row wraps rather than being a fixed two-column grid, so the pane keeps
   its own min-width and the column drops above it on a phone - the same
   pattern the rest of the app uses below 782px. `flex: 999 1 560px` on the
   pane is what does it: it takes every spare pixel beside the column and
   wraps the moment 560px is not there. */
.ddesk-reports-layout {
    display: flex; flex-wrap: wrap; gap: var(--ddesk-space-5);
    align-items: flex-start; margin-top: var(--ddesk-space-3);
}
.ddesk-reports-nav {
    flex: 1 1 210px; max-width: 240px;
    border-right: 1px solid var(--ddesk-border);
    padding-right: var(--ddesk-space-4);
}
/* min-width: 0 so a wide table inside its own scroll box cannot push the
   pane past the row and send the column off the screen. */
.ddesk-reports-pane { flex: 999 1 560px; min-width: 0; }
.ddesk-reports-nav-group {
    margin: var(--ddesk-space-4) 0 var(--ddesk-space-1);
    font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted);
}
.ddesk-reports-nav-group:first-child { margin-top: 0; }
.ddesk-reports-nav-list { list-style: none; margin: 0; padding: 0; }
.ddesk-reports-nav-link {
    display: block; padding: var(--ddesk-space-2) var(--ddesk-space-3);
    border-left: 3px solid transparent; border-radius: 0 var(--ddesk-radius-control) var(--ddesk-radius-control) 0;
    font-size: var(--ddesk-text-body); color: var(--ddesk-text); text-decoration: none;
}
.ddesk-reports-nav-link:hover { background: var(--ddesk-grey-bg); }
/* Where you are, said twice: the rule and the weight. Colour alone would
   leave it on one cue, and aria-current carries it for a screen reader. */
.ddesk-reports-nav-link.is-here {
    border-left-color: var(--ddesk-black); background: var(--ddesk-grey-bg); font-weight: 700;
}
.ddesk-reports-nav-link:focus-visible { outline: 2px solid var(--ddesk-cyan-b); outline-offset: -2px; }
@media (max-width: 782px) {
    /* Stacked: no rule down the side of a full-width block, and the list
       sits above the report rather than beside it.

       AND BOUNDED, which a screenshot at 400px is what caught. Fifteen
       destinations and four headings stack to about 1,100px, so the report
       itself began below the fold and the column was WORSE on a phone than
       the two rows of tabs it replaced. Capped at 40vh with its own scroll:
       the report starts within a thumb's reach, and the whole list is still
       reachable without leaving the page.

       A native select with optgroups would be better than a nested scroll
       on touch, and is the follow-up. It needs the routing to take one
       parameter instead of cat plus sub, which is not a change to make two
       days before the team arrives. */
    .ddesk-reports-nav {
        max-width: none; border-right: 0; padding-right: 0;
        border-bottom: 1px solid var(--ddesk-border); padding-bottom: var(--ddesk-space-3);
        max-height: 40vh; overflow-y: auto;
    }
}
.ddesk-badge-tiny { font-size: var(--ddesk-text-label); padding: 2px var(--ddesk-space-1); margin-left: var(--ddesk-space-1); }
.ddesk-stat-grid-single { grid-template-columns: minmax(175px, 320px); }
.ddesk-stat-grid-3 { grid-template-columns: repeat(auto-fit, minmax(175px, 1fr)); }
.ddesk-table-total-row td { border-top: 2px solid var(--ddesk-border); background: var(--ddesk-bg); }

.ddesk-report-download-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--ddesk-space-3); margin-top: var(--ddesk-space-4); }
.ddesk-report-download-card {
    display: block; padding: var(--ddesk-space-4); background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card); color: var(--ddesk-text);
}
.ddesk-report-download-card:hover { text-decoration: none; border-color: var(--ddesk-cyan-a); }
.ddesk-report-download-card h3 { margin: 0 0 var(--ddesk-space-1); font-size: var(--ddesk-text-body); }
.ddesk-report-download-card p { margin: 0 0 var(--ddesk-space-3); color: var(--ddesk-text-muted); font-size: var(--ddesk-text-meta); line-height: 1.45; }

.ddesk-top-clients-bars { display: flex; flex-direction: column; gap: var(--ddesk-space-2); margin-top: var(--ddesk-space-4); }
.ddesk-top-clients-bar-row { display: grid; grid-template-columns: 160px 1fr 130px; align-items: center; gap: var(--ddesk-space-3); }
.ddesk-top-clients-bar-label { font-size: var(--ddesk-text-body); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ddesk-top-clients-bar-track { height: 14px; border-radius: var(--ddesk-radius-control); background: var(--ddesk-bg); overflow: hidden; }
.ddesk-top-clients-bar-fill { height: 100%; border-radius: var(--ddesk-radius-control); background: linear-gradient(90deg, var(--ddesk-cyan-a), var(--ddesk-cyan-b)); }
.ddesk-top-clients-bar-value { font-size: var(--ddesk-text-body); font-weight: 700; text-align: right; }
/* The gross figure beside the net one. 0.82.14, Clients report.
   Net is the bar and the bold number; gross rides under it in muted type so
   the pair reads as "we kept this, of that" without a second bar inviting a
   comparison of the wrong lengths. */
.ddesk-bar-value-sub { display: block; font-size: var(--ddesk-text-meta); font-weight: normal; color: var(--ddesk-text-muted); }

@media (max-width: 900px) {
    .ddesk-top-clients-bar-row { grid-template-columns: 1fr; gap: var(--ddesk-space-1); }
    .ddesk-top-clients-bar-value { text-align: left; }
    .ddesk-bar-value-sub { display: inline; margin-left: var(--ddesk-space-2); }
}
@media (max-width: 900px) { .ddesk-help-getting-started-cols { grid-template-columns: 1fr; } .ddesk-help-feature-body { padding-left: var(--ddesk-space-5); } }

/* -------------------------------------------------------------------------
 * The J59 Hub top bar.
 *
 * One shape across every app on the hub: black bar, white Junction59 logo home
 * to the hub, the app's name beside it, then this app's own nav, then the
 * person and the way out. The only thing that changes app to app is the
 * accent, which the hub hands down as --j59-app-a: the same colour DocketDesk
 * wears on its card on the hub landing page. Learn it once there and the nav
 * tells you where you are without reading a word.
 *
 * Pills become an underline here. With eight nav items, two dropdowns, a
 * company switcher and a search box, pills at this density read as a wall, and
 * a pill changes an item's width when it becomes active so the whole row
 * reflows as you move between screens. An underline holds still.
 *
 * Only applies on the hub (.is-hub). Standalone, everything above is untouched.
 * ------------------------------------------------------------------------- */

.ddesk-header.is-hub {
    /* The logo lines up with the page content, as it does in every app. This
       bar carries far more than the others though - eight nav items, two
       dropdowns, a company switcher and a search - so it is allowed to use the
       rest of the window on the right rather than wrapping to a second row.
       Alignment is a promise about where the eye starts, not about where a
       dense bar has to stop. */
    padding-left: max( 24px, calc( 50% - 680px + 24px ) );
    padding-right: var(--ddesk-space-6);
    border-bottom: 3px solid transparent;
    border-image: linear-gradient( 90deg, var(--j59-app-a, var(--ddesk-cyan-a)), var(--j59-app-b, var(--ddesk-cyan-b)) ) 1;
    /*
     * WRAP, 0.75.0, review finding UX-01.
     *
     * This was nowrap with a 1200px media query to turn it back on, and the
     * reviewer measured the result: the page overflowed horizontally at an
     * ordinary desktop width. A breakpoint is a guess about when content stops
     * fitting, and this one guessed 1200 when the real figure is nearer 1400 -
     * it moves with the nav labels, the person's name and the translation.
     * Measured in tests/responsive-test.js: at 1280 the bar ran 98px past the
     * window.
     *
     * Wrapping is not a guess. The groups sit on one row exactly as long as
     * they fit and drop to a second row when they do not, at whatever width
     * that turns out to be. The note above about this bar using the rest of
     * the window still holds; it just no longer does it by overflowing.
     */
    flex-wrap: wrap;
    gap: var(--ddesk-space-5);
}

/* Three groups on one row: the brand holds its size, the nav takes the slack,
   the right rail holds its size. Only the nav may shrink. */
.ddesk-header.is-hub .ddesk-header-left { flex: 0 0 auto; gap: var(--ddesk-space-3); }
.ddesk-header.is-hub .ddesk-header-nav {
    flex: 1 1 auto;
    justify-content: center;
    flex-wrap: wrap;
    gap: 0;
    /* A flex item will not shrink below its content without this. With the
       bar wrapping it is belt and braces rather than the thing that holds -
       kept because the nav is the group that is meant to take the slack, and
       removing it makes that depend on the wrap alone. */
    min-width: 0;
}
.ddesk-header.is-hub .ddesk-header-user { flex: 0 1 auto; gap: var(--ddesk-space-2); min-width: 0; }

.ddesk-header.is-hub .ddesk-header-search input { width: 170px; max-width: 100%; }
.ddesk-header.is-hub .ddesk-company-switcher select { max-width: 150px; }

/* On a phone the nav starts at the left rather than centred, and the search
   box gives up its fixed width before anything is clipped. */
@media (max-width: 760px) {
    .ddesk-header.is-hub .ddesk-header-nav { justify-content: flex-start; }
    .ddesk-header.is-hub .ddesk-header-search { flex: 1 1 140px; }
    .ddesk-header.is-hub .ddesk-header-search input { width: 100%; }
    .ddesk-header.is-hub .ddesk-header-user { flex: 1 1 100%; }
}

.ddesk-header.is-hub .ddesk-header-nav a {
    border-radius: 0;
    padding: var(--ddesk-space-2) var(--ddesk-space-2);
    border-bottom: 2px solid transparent;
    white-space: nowrap;
}

.ddesk-header.is-hub .ddesk-header-nav a:hover {
    background: transparent;
    opacity: 1;
}

.ddesk-header.is-hub .ddesk-header-nav a.is-active {
    background: transparent;
    color: var(--ddesk-white);
    opacity: 1;
    border-bottom-color: var(--j59-app-a, var(--ddesk-cyan-a));
}

/* The dropdowns sit on their own panel, so they keep a filled active state. */
/*
 * Between a laptop and a wide monitor, tighten rather than wrap. 0.75.0.
 *
 * Wrapping is what stops the bar overflowing, and on a 1280px laptop the
 * three groups come to about 1430px at their natural size - so the first
 * thing the fix did was put every laptop's header on two rows. Measured in
 * tests/responsive-test.js.
 *
 * About 55px has to come off to get back to one row at 1280, and it comes
 * off the three places with slack: the gaps, the search box, and the name of
 * the person already looking at their own screen. Nothing is clipped, and
 * Log out stays beside where the name was. Above this range there is room to
 * be generous again.
 *
 * Below about 1200 the bar is two rows and that is the honest answer: nine
 * nav items, a company switcher, a search box and a right rail do not fit a
 * narrow laptop on one line without hiding something somebody needs.
 */
@media (min-width: 761px) and (max-width: 1500px) {
    .ddesk-header.is-hub { gap: var(--ddesk-space-3); }
    .ddesk-header.is-hub .ddesk-header-search input { width: 124px; }
    .ddesk-header.is-hub .ddesk-header-user { gap: var(--ddesk-space-2); }
    .ddesk-header.is-hub .ddesk-header-username { display: none; }
}
/*
 * The brace above, and the one deleted a few rules up, 0.76.0.
 *
 * 0.75.0 replaced a `@media (max-width: 1200px)` block with `flex-wrap: wrap`
 * and took the block's opening line without its closing brace. The orphaned
 * `}` then closed nothing, and this media query - the next one to open - was
 * never closed, so EVERY RULE AFTER IT, about six hundred lines, became part
 * of it.
 *
 * Which is to say: from the asked-for-hours panel to the end of this file,
 * the stylesheet only applied between 761px and 1500px. On a phone and on
 * anything wider than 1500px those screens were drawn with no styling at all
 * - including, at the very bottom, the print stylesheet, which can never
 * match a width rule and a print rule at once.
 *
 * It survived because a 15-inch laptop is inside 761-1500 and that is what
 * everybody builds on. Found while measuring the collapsible rows for W-3,
 * which worked at 1280 and not at 390. tests/responsive-test.js now asserts
 * the file is balanced, which is the only form in which a test can catch a
 * missing brace.
 */

.ddesk-header.is-hub .ddesk-nav-dropdown a.is-active {
    background: var(--j59-app-a, var(--ddesk-cyan-a));
    color: var(--ddesk-black);
}

/* ---------- Asked for hours: the mark on a line ---------- */

/*
 * One character per line, because an estimate already runs tall. The legend
 * sits once above the task list rather than on every row, and the whole
 * conversation is one click away inside a native <details> - the same
 * mechanism Version History uses, so there is no JavaScript to keep working.
 */
/* The panel below is absolute, so this has to be what it hangs from. Without
   it the panel anchored to whatever positioned ancestor it found - the table
   wrap, or the page - and opened somewhere other than beside its own dot. */
.ddesk-asked { position: relative; display: inline-block; vertical-align: middle; margin-left: var(--ddesk-space-1); }
.ddesk-asked > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; }
.ddesk-asked > summary::-webkit-details-marker { display: none; }
.ddesk-asked > summary:focus-visible { outline: 2px solid var(--ddesk-cyan-b); outline-offset: 2px; border-radius: 50%; }

.ddesk-asked-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; }
/* Grey: asked, nobody has answered. */
.ddesk-asked-waiting { background: var(--ddesk-grey-line); }
/* Lime: answered, and the line still carries the figure that came back. */
.ddesk-asked-matches { background: var(--ddesk-lime-a); }
/* Magenta: answered, and the figure has moved since. */
.ddesk-asked-changed { background: var(--ddesk-magenta-a); }
/* A grey SQUARE: asked, never answered, and the request has closed. A shape
   rather than another shade, because at 9px a pale grey circle and a hollow
   one are the same dot to anybody not comparing them side by side - and this
   one means something different from both: nothing is coming, and the line has
   to be asked again. */
.ddesk-asked-lapsed { background: var(--ddesk-grey-line); border-radius: var(--ddesk-radius-control); }
/* A hollow magenta SQUARE: answered, the figures differ, and the owner has
   looked and kept their own. The colour of the question it answers, drained of
   its fill and squared off, because at 9px a ring and a disc of the same hue
   are the weakest pairing in this set - and this is the one pairing that
   separates "settled" from "needs you". .ddesk-asked-lapsed uses a shape for
   the same reason. */
.ddesk-asked-kept { background: transparent; border: 2px solid var(--ddesk-magenta-a); border-radius: var(--ddesk-radius-control); }
/* Hollow: the person said it was not their work. */
.ddesk-asked-passed { background: transparent; border: 2px solid var(--ddesk-grey-line); }

.ddesk-asked-panel {
    position: absolute;
    left: 0;
    /* Above the action and assignee dropdowns (30) and the sticky nav (40),
       below the modal overlay (100), which must stay on top of everything. */
    z-index: 50;
    min-width: 240px;
    /* Capped against the viewport as well as in pixels: a resource table is
       already wide, and a fixed 420px panel on a phone ran off the screen
       with no way to scroll back to it. */
    width: max-content;
    max-width: min(420px, 78vw);
    background: var(--ddesk-white);
    border: 1px solid var(--ddesk-border);
    border-radius: var(--ddesk-radius-control);
    box-shadow: 0 8px 28px rgba(0, 0, 0, .14);
    padding: var(--ddesk-space-3) var(--ddesk-space-3);
    margin-top: var(--ddesk-space-1);
    font-size: var(--ddesk-text-meta);
    font-weight: normal;
    white-space: normal;
}
/* A long conversation on one line stays on the screen rather than running off
   the bottom of it. */
.ddesk-asked-panel { max-height: 50vh; overflow-y: auto; }

/* The panel opens inside .ddesk-table-wrap, which scrolls horizontally and so
   clips vertically too - the same trap documented above for the Billing row
   menu, which cut this panel in half on the last row of every task. The wrap
   stops clipping only while a panel is open, so the Resources table keeps the
   horizontal scroll it relies on the rest of the time. */
.ddesk-table-wrap:has(.ddesk-asked[open]) { overflow: visible; }

/* Where :has() is not available, the panel opens in the row rather than over
   it. A taller row, never a panel cut in half. */
@supports not selector(:has(*)) {
    .ddesk-asked-panel { position: static; width: auto; min-width: 0; max-width: none; }
}

/* On a phone the same thing, for a different reason: unclipping the wrap also
   drops the horizontal scroll the Resources table needs, which would let a
   wide table spill out of its card. In the row instead - the row is already
   the full width of a narrow screen. */
@media (max-width: 782px) {
    .ddesk-asked-panel { position: static; width: auto; min-width: 0; max-width: none; }
}

.ddesk-asked-entry { padding: 3px 0; }
.ddesk-asked-entry + .ddesk-asked-entry { border-top: 1px solid var(--ddesk-border); margin-top: 3px; padding-top: var(--ddesk-space-1); }
.ddesk-asked-entry .description { margin: 2px 0 0; }

/* The two buttons at the foot of an open panel. Side by side, because they
   are one decision with two answers, not two separate things to do. */
.ddesk-asked-actions { display: flex; gap: var(--ddesk-space-2); margin-top: var(--ddesk-space-2); padding-top: var(--ddesk-space-2); border-top: 1px solid var(--ddesk-border); }
.ddesk-asked-actions form { margin: 0; }
.ddesk-asked-panel .description { margin: var(--ddesk-space-1) 0 0; }

.ddesk-asked-legend { display: flex; flex-wrap: wrap; gap: var(--ddesk-space-3); align-items: center; font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); margin: 0 0 var(--ddesk-space-2); }
.ddesk-asked-legend span { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); }

/* ---------- Formatting toolbar (0.52.0) ----------
   Sits directly on top of the box it formats, sharing its border radius, so
   the two read as one control rather than a strip of buttons that happens to
   be above a textarea. Buttons are square and quiet: this is a tool somebody
   reaches for occasionally, not the main event on the screen. */
.ddesk-format-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    padding: var(--ddesk-space-1) var(--ddesk-space-1);
    background: var(--ddesk-bg);
    border: 1px solid var(--ddesk-border);
    border-bottom: 0;
    border-radius: var(--ddesk-radius-card) var(--ddesk-radius-card) 0 0;
}
.ddesk-format-btn {
    min-width: 26px;
    height: 26px;
    padding: 0 var(--ddesk-space-1);
    border: 1px solid transparent;
    border-radius: var(--ddesk-radius-control);
    background: transparent;
    color: var(--ddesk-text-muted);
    font-size: var(--ddesk-text-body);
    line-height: 1;
    cursor: pointer;
}
.ddesk-format-btn:hover { background: var(--ddesk-white); border-color: var(--ddesk-border); color: var(--ddesk-black); }
.ddesk-format-btn:focus-visible { outline: 2px solid var(--ddesk-cyan-b); outline-offset: 1px; }
.ddesk-format-bold { font-weight: 700; }
.ddesk-format-italic { font-style: italic; }
.ddesk-format-underline { text-decoration: underline; }
/* The box below the bar loses its own top corners, for the same reason. */
.ddesk-format-bar + textarea { border-top-left-radius: 0; border-top-right-radius: 0; }

/* The resource-row variant: one row high, growing as it is typed into, with
   the bar behind a single button so a forty-row table is not mostly toolbar. */
/* Up and down beside the drag handle. Stacked, so the pair takes one control's
   worth of width in a header row that is already full. */
/* A submit button that has to sit in a row of links and look like one of
   them. The Download on the estimates list is a POST, because the export reads
   its options from the post, but nothing about that is the reader's problem. */
.ddesk-inline-form { display: inline; margin: 0; }
.ddesk-actions .ddesk-inline-form button {
    /* Laid out like the anchors beside it rather than as a button. line-height
       on its own left it sitting 3px higher than its neighbours. */
    display: inline-flex;
    align-items: center;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ddesk-text-muted);
    cursor: pointer;
    line-height: 1;
    vertical-align: middle;
}
.ddesk-actions .ddesk-inline-form button:hover { color: var(--ddesk-cyan-b); }

/* ---------- Landing under a sticky header (0.55.1) ----------
   Every "come back to where you were" redirect on the Build screen points at
   a fragment, and the browser scrolls that element's TOP to the top of the
   window - which is underneath .ddesk-header, because the header is sticky.
   So adding a task landed on the new card with its name, its toggles and its
   subtotal hidden behind the black bar, and the first thing visible was "No
   resources added yet": Marc, 16 October 2026, "the heading task 3 is not
   visiable after i click add a task, so i might just start adding the
   resources and miss that step."

   scroll-margin-top is the browser's own answer to this. It changes nothing
   about the layout; it only tells the scroll-into-view machinery to stop
   short. The value clears the header at every width, including the one where
   it wraps to two rows on a narrow window, and the slack reads as deliberate
   spacing above the thing you were sent to. */
.ddesk-app [id] { scroll-margin-top: 96px; }

/* A line that is in the price but not on the client's copy. Muted, because it
   is a fact about the row rather than a problem with it. */
.ddesk-client-hidden-mark { display: inline-flex; vertical-align: middle; color: var(--ddesk-text-muted); margin-left: 3px; }

/* Two tables on the Build screen that run out of room. 0.56.0.
   Budget Awareness is twelve columns of figures under grouped headings, and
   Hour Allocations is eleven with a free-text Notes column that nothing
   truncates - so one real note pushed it past its card. Tighter cells for the
   first, a capped Notes cell for the second. The full note is still there on
   hover and in the export; what is clipped is a column, not a record. */
/* A label inside a panel that is not a .ddesk-field. */
.ddesk-field-label { display: block; font-weight: 700; font-size: var(--ddesk-text-body); margin-bottom: var(--ddesk-space-1); }

.ddesk-table-dense th, .ddesk-table-dense td { padding: var(--ddesk-space-1) var(--ddesk-space-2); font-size: var(--ddesk-text-meta); }
.ddesk-table-dense th { font-size: var(--ddesk-text-label); }
.ddesk-cell-clip {
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ddesk-task-move { display: inline-flex; flex-direction: column; gap: 1px; margin-right: 2px; }
.ddesk-task-move form { margin: 0; line-height: 0; }
.ddesk-task-move .ddesk-icon-btn-tiny { padding: 1px 3px; }
.ddesk-task-move button[disabled] { opacity: .3; cursor: default; }

.ddesk-format-bar-hidden { display: none; }
.ddesk-rrc-desc { position: relative; }
.ddesk-rrc-desc-input {
    width: 100%;
    min-height: 34px;
    max-height: 160px;
    padding-right: var(--ddesk-space-6);
    /* Both ways, 0.60.0. Marc, 18 October 2026: "when editing in the
       Description box, we need to be able to pull it wider as well as taller
       ... so a user can read the description in approximately the same width
       as the customer would see it on their estimate." A client's estimate
       gives the description about 95mm, which is roughly 360px on screen, and
       this column is 112px - so a sentence that reads as three lines to the
       client read as eight here with no way to widen it.

       Dragging the corner writes an inline width onto the element. It
       overflows its cell rather than widening it, because the cell's width is
       a percentage of a row that has to keep adding up: the box paints over
       the fields to its right (z-index below) and nothing moves. The inline
       width is then cleared on blur by the script on the Build screen, which
       is the snap-back Marc asked for - the height it was dragged to is kept,
       since that was already how this box behaved. */
    resize: both;
    max-width: 560px;
    font-family: inherit;
    font-size: var(--ddesk-text-body);
    line-height: 1.35;
    overflow-y: auto;
}
/* Above its neighbours only while it is being used, so a widened box is
   readable rather than painted under the Qty field. The cap comes off while
   it is focused too, because the 160px ceiling is there to stop a long
   description making the row enormous on its own - not to stop somebody
   dragging the box to the size they want to read it at. */
.ddesk-rrc-desc-input:focus { position: relative; z-index: 30; max-height: 70vh; }
/* A box whose height was set by hand keeps it, and stops auto-sizing itself:
   without this the cap comes back on blur and a box dragged to 300px snaps to
   160px, which is a snap-back nobody asked for. */
.ddesk-rrc-desc-input.ddesk-desc-tall { max-height: none; }
/* A staff name typed where the client reads. 0.82.13.
   Magenta, which is this app's "something here wants your attention", and a
   rule down the left rather than a filled box: it sits inside a dense row of
   forty and must be noticeable without becoming the loudest thing on the
   screen. It is advice, and nothing is blocked. */
.ddesk-desc-name-warn {
    margin: var(--ddesk-space-1) 0 0; padding-left: var(--ddesk-space-2);
    border-left: 2px solid var(--ddesk-magenta-a);
    font-size: var(--ddesk-text-meta); line-height: 1.4; color: var(--ddesk-text-muted);
}
.ddesk-desc-name-warn[hidden] { display: none; }
/* The formatting button. 0.60.1: a z-index, and out of the corner.
   Marc, 18 October 2026: "you removed the formatting for the description box,
   it needs to be able to be toggeld on." Nothing was removed - the button was
   buried. 0.60.0 gave the focused textarea position:relative and z-index:30 so
   a widened box would paint over its neighbours, and this button had no
   z-index at all, so clicking into the description put the text box on top of
   the one control you reach for while writing in it. The same release made the
   box resize: both, which puts the browser's own drag handle in the
   bottom-right corner - where this button used to sit, vertically centred on a
   one-row box. So it moves to the top right and sits above the text box, and
   the drag handle keeps the corner to itself. */
.ddesk-format-toggle {
    position: absolute;
    right: 8px;
    top: 7px;
    z-index: 31;
    width: 18px;
    height: 18px;
    padding: 0;
    border: 0;
    border-radius: var(--ddesk-radius-control);
    background: transparent;
    color: var(--ddesk-text-muted);
    font-size: var(--ddesk-text-label);
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}
.ddesk-format-toggle:hover { background: var(--ddesk-border); color: var(--ddesk-black); }
.ddesk-format-bar-hidden + .ddesk-rrc-desc-input + .ddesk-format-toggle { opacity: .65; }
/* With the bar open the button sits under it, not floating over the text. */
.ddesk-rrc-desc .ddesk-format-bar:not(.ddesk-format-bar-hidden) + .ddesk-rrc-desc-input { padding-right: var(--ddesk-space-2); }
/* 0.62.0: NOT display:none. Marc, 18 October 2026: "the toggle to turn on the
   description format editor is there but i cant turn it off." Hiding the one
   control that closes the bar the moment the bar opens is a one-way door, and
   the bar then sits over every row it was opened on for the rest of the
   session. It stays where it is and sits on the bar instead - the bar keeps a
   strip of its right-hand edge clear so no formatting button can land under
   it. */
.ddesk-rrc-desc .ddesk-format-bar:not(.ddesk-format-bar-hidden) { padding-right: var(--ddesk-space-8); }
.ddesk-rrc-desc .ddesk-format-bar:not(.ddesk-format-bar-hidden) ~ .ddesk-format-toggle {
    background: var(--ddesk-white);
    border: 1px solid var(--ddesk-border);
    color: var(--ddesk-black);
}

/* A card you open when you want it. 0.63.0, for the Change Log, which on an
   estimate that has been worked on for a month is most of the page and is
   almost never what anybody scrolled down for. A native <details>, like
   Version History and the per-line asked history above it. */
.ddesk-collapsible-card > summary {
    display: flex; align-items: center; justify-content: space-between; gap: var(--ddesk-space-3);
    cursor: pointer; list-style: none; padding: 2px 0;
}
.ddesk-collapsible-card > summary::-webkit-details-marker { display: none; }
.ddesk-collapsible-card > summary .ddesk-card-head-left { display: flex; align-items: center; gap: var(--ddesk-space-2); }
.ddesk-collapsible-card > summary h2 { margin: 0; }
.ddesk-collapsible-card > summary::after {
    content: '';
    width: 8px; height: 8px; flex: none;
    border-right: 2px solid var(--ddesk-text-muted);
    border-bottom: 2px solid var(--ddesk-text-muted);
    transform: rotate(45deg);
    margin-right: var(--ddesk-space-1);
}
.ddesk-collapsible-card[open] > summary::after { transform: rotate(-135deg); }
.ddesk-collapsible-count { font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted); margin-left: auto; }

/* One-line rows at the foot of the estimate. 0.76.0, design review W-3.

   Marc, 12 November, choosing "everything on the board": Budget & Funding,
   the Hours Tracker, Save as Template and the Change Log were four
   full-height panels between the tasks and the bottom of the page, and on a
   normal estimate none of them is what anybody scrolled down for. Each is
   now the row that was already at the top of it.

   A row you can see is a row, closed: the border and the hover tell you it
   opens, and the chevron says which way. Without this a collapsed <details>
   is a heading with a small arrow beside it, which reads as a heading. */
.ddesk-collapsible-card {
    border: 1px solid var(--ddesk-grey-line);
    border-radius: var(--ddesk-radius-card);
    background: var(--ddesk-white);
    padding: var(--ddesk-space-3) var(--ddesk-space-4);
    margin-bottom: var(--ddesk-space-3);
}
.ddesk-collapsible-card > summary:hover .ddesk-card-head-left h2 { color: var(--ddesk-cyan-text); }
.ddesk-collapsible-card > summary:focus-visible {
    outline: 2px solid var(--ddesk-cyan-b); outline-offset: 2px;
    border-radius: var(--ddesk-radius-control);
}
/* Open: the summary becomes the card's heading and needs a line under it,
   which a closed row must not have. */
.ddesk-collapsible-card[open] > summary {
    padding-bottom: var(--ddesk-space-3);
    margin-bottom: var(--ddesk-space-3);
    border-bottom: 1px solid var(--ddesk-grey-line);
}
/* The funding panel is both a collapsible and a form card and would
   otherwise carry two borders and two lots of padding. */
.ddesk-collapsible-card.ddesk-form-card { box-shadow: none; }
/* Manage Templates, which used to sit on the row beside the disclosure. */
.ddesk-template-manage { margin: var(--ddesk-space-3) 0 0; font-size: var(--ddesk-text-meta); }

/* ---------- The estimate, read. 0.63.0 ---------- */
/* An estimate opens here now: compact, and with nothing on it that writes.
   See includes/views/estimates-view.php for why it is a template of its own
   rather than the Build screen with its controls hidden. */
.ddesk-view .ddesk-form-card { margin-bottom: var(--ddesk-space-3); }
.ddesk-view h2 { margin: 0 0 var(--ddesk-space-2); font-size: var(--ddesk-text-card); }
.ddesk-view-meta {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: var(--ddesk-space-3) var(--ddesk-space-5);
    font-size: var(--ddesk-text-body);
}
.ddesk-view-meta-label {
    display: block; font-size: var(--ddesk-text-label); text-transform: uppercase; letter-spacing: 0.03em;
    color: var(--ddesk-text-muted); margin-bottom: 2px;
}
.ddesk-view-block {
    background: var(--ddesk-white); border: 1px solid var(--ddesk-border); border-radius: var(--ddesk-radius-card);
    padding: var(--ddesk-space-4) var(--ddesk-space-5); margin-bottom: var(--ddesk-space-3);
}
.ddesk-view-block h3 { margin: 0 0 var(--ddesk-space-1); font-size: var(--ddesk-text-body); text-transform: uppercase; letter-spacing: 0.03em; color: var(--ddesk-text-muted); }
/* INTERNAL NOTES ARE NOT A PROBLEM. 0.82.9, the last of V-4's four magenta
   meanings. This block carried a magenta rule, the same colour an overdue
   invoice carries, for a note that is simply not shown to the client. It is
   a visibility flag, not a status: grey rule, and the lock icon beside the
   heading is what actually says so. */
.ddesk-view-block.is-internal { border-left: 3px solid var(--ddesk-grey-fill); background: var(--ddesk-bg); }

/* NEEDS ACTION IS A MARK, NOT A COLOUR. 0.82.9.
   Urgency cuts across every state - a request can be moving and late - so it
   cannot be one of the five tones. A black rule and a bold note carry it,
   which also reads for anyone who cannot separate the hues. */
.ddesk-table tr.ddesk-needs-action > td:first-child { box-shadow: inset 3px 0 0 var(--ddesk-black); }
.ddesk-needs-action-note { font-weight: 700; }
/* PAYMENT TERMS. 0.68.0. Its own line above the footer, bold, and not inside a
   .ddesk-view-block card: a card carries an uppercase heading, and "PAYMENT
   TERMS / Payment terms: Net 15 days from invoice date." says it twice. Same
   left padding as the cards above and below so it lines up with them. */
.ddesk-view-terms {
    padding: 0 var(--ddesk-space-5) var(--ddesk-space-3); font-size: var(--ddesk-text-body); font-weight: 700;
    color: var(--ddesk-text); line-height: 1.55;
}
/* THE PROSE BLOCKS. 0.82.9.
   Two columns where there is room, one below 900px. The blocks are
   independent cards, so they need no row alignment and `align-items: start`
   keeps a short Assumptions card from stretching to match a long
   Description. margin-bottom on the card itself is what the gap replaces. */
.ddesk-view-prose-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
    gap: var(--ddesk-space-3); align-items: start;
}
.ddesk-view-prose-grid .ddesk-view-block { margin-bottom: 0; }
/* MEASURE, CAPPED. The cap is the point of 0.82.9: 62 characters, not the
   width of the window. In `ch` so it tracks the type size rather than a
   pixel guess, and `max-width` not `width`, so a narrow column still fills.
   Scoped to the block, so the same class inside a task row is untouched. */
.ddesk-view-block .ddesk-view-prose { max-width: 62ch; }
.ddesk-view-prose { font-size: var(--ddesk-text-body); line-height: 1.55; }
.ddesk-view-task { padding: var(--ddesk-space-3) 0; border-top: 1px solid var(--ddesk-border); }
.ddesk-view-task:first-of-type { border-top: 0; padding-top: 0; }
.ddesk-view-task-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--ddesk-space-3); }
.ddesk-view-task-head h3 { margin: 0; font-size: var(--ddesk-text-body); display: flex; align-items: center; gap: var(--ddesk-space-2); flex-wrap: wrap; }
.ddesk-view-task-total { font-weight: 700; white-space: nowrap; }
.ddesk-view-task-note { margin: var(--ddesk-space-1) 0 var(--ddesk-space-2); color: var(--ddesk-text-muted); }
.ddesk-view-table th, .ddesk-view-table td { font-size: var(--ddesk-text-meta); padding: var(--ddesk-space-1) var(--ddesk-space-2); vertical-align: top; }
.ddesk-view-totals { display: flex; flex-direction: column; gap: var(--ddesk-space-1); font-size: var(--ddesk-text-body); }
.ddesk-view-totals > div { display: flex; justify-content: space-between; gap: var(--ddesk-space-4); }
.ddesk-view-totals .is-strong { font-weight: 700; }
/* The credit's reason, under the row it explains rather than inside its
   label. 0.82.3. Indented and quiet, matching what the PDF draws, so a
   client reading the screen and a client reading the document see the same
   thing in the same place. It is the one line in this box that is a
   sentence, so it is the one line that does not sit in two columns. */
.ddesk-view-totals .ddesk-view-credit-reason {
    display: block;
    padding-left: var(--ddesk-space-3);
    margin-top: calc(-1 * var(--ddesk-space-1));
    font-size: var(--ddesk-text-label);
    font-style: italic;
    line-height: 1.4;
    color: var(--ddesk-text-muted);
}
/* 0.68.0: the credit row. Muted rather than red - this is good news for the
   client, not a warning - and the label is allowed to wrap because it carries
   the client-facing reason, while the figure beside it never does. */
.ddesk-view-totals .is-credit { color: var(--ddesk-text-muted); align-items: flex-start; }
.ddesk-view-totals .is-credit > span:first-child { max-width: 70%; line-height: 1.45; }
.ddesk-view-totals .is-credit > span:last-child { white-space: nowrap; }
.ddesk-view-totals .is-total {
    font-size: var(--ddesk-text-figure); font-weight: 700; color: var(--ddesk-cyan-b);
    border-top: 1px solid var(--ddesk-border); padding-top: var(--ddesk-space-2); margin-top: 2px;
}
/* Printing the preview is one of the two things it is for, so the chrome that
   cannot be printed goes. */
/* The letterhead on the estimate view. 0.80.0.
   Quiet on screen, where the company is rarely the question; the top of the
   document in print, where it is the first thing a client reads. */
.ddesk-view-letterhead {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ddesk-space-1) var(--ddesk-space-3);
    margin-bottom: var(--ddesk-space-3);
    font-size: var(--ddesk-text-meta); color: var(--ddesk-text-muted);
}
.ddesk-view-letterhead strong { color: var(--ddesk-text); }

@media print {
    .ddesk-page-head-actions, .ddesk-modal-overlay { display: none !important; }
    .ddesk-view .ddesk-form-card, .ddesk-view-block { break-inside: avoid; }
    /* A printed estimate reads top to bottom. 0.82.9: the two-column prose
       grid is for the window, where the measure would otherwise be the full
       width; on paper the sheet already sets the measure, and two columns
       would put Assumptions beside Description across a page break. */
    .ddesk-view-prose-grid { display: block; }
    .ddesk-view-prose-grid .ddesk-view-block { margin-bottom: var(--ddesk-space-3); }
    .ddesk-view-block .ddesk-view-prose { max-width: none; }
    /* Full size at the head of the printed page, and repeated on each sheet so
       a page that arrives on its own still says whose it is. */
    .ddesk-view-letterhead {
        display: block;
        margin-bottom: var(--ddesk-space-4);
        padding-bottom: var(--ddesk-space-2);
        border-bottom: 1px solid var(--ddesk-border);
        font-size: var(--ddesk-text-body); color: var(--ddesk-text);
    }
    .ddesk-view-letterhead strong { display: block; font-size: var(--ddesk-text-section); }
    .ddesk-view-letterhead span { display: block; color: var(--ddesk-text-muted); }
}
/* A task that is not in the estimate's total says so rather than being read
   as part of it. */
.ddesk-view-task-total.is-excluded { color: var(--ddesk-text-muted); font-weight: 400; text-decoration: line-through; }

/* 0.71.0: the note beside a disabled Ask for hours. Wraps under the button on
   a narrow screen rather than pushing the toolbar sideways. */
.ddesk-ask-hours-note { display: inline-block; max-width: 340px; line-height: 1.35; }

/* 0.72.0: the "Anyone" tick beside a narrowed assignee picker. Small, quiet,
   and only drawn when the role actually has people mapped to it. */
.ddesk-assignee-all { display: inline-flex; align-items: center; gap: var(--ddesk-space-1); margin-top: var(--ddesk-space-1); font-size: var(--ddesk-text-label); color: var(--ddesk-text-muted); cursor: pointer; }
.ddesk-assignee-all input { margin: 0; }

/* 0.73.0: the credit allocation box on a billing request. The warning line is
   the one case where a figure already saved is no longer allowed, so it is
   drawn as a warning rather than as another grey description. */
.ddesk-credit-allocation .description { margin-top: var(--ddesk-space-1); }
.ddesk-credit-warning { color: var(--ddesk-magenta-text); font-weight: 600; }

/* 0.75.0, review finding UX-05: the two export options that put agency-only
   material into a file meant for a client. A small flag rather than a colour
   alone, because colour on its own carries no meaning for a reader who cannot
   see it - the rule Weekly Counts already states for itself. */
.ddesk-internal-flag {
    display: inline-block; margin-left: var(--ddesk-space-1); padding: 1px var(--ddesk-space-1); border-radius: var(--ddesk-radius-pill);
    background: var(--ddesk-magenta-a, var(--ddesk-magenta-a)); color: var(--ddesk-white, var(--ddesk-white));
    font-size: var(--ddesk-text-label); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
    vertical-align: middle;
}
