/* ==========================================================================
   Kinetiq — Premium RTL design system
   Palette sampled from the Kinetiq logo.
   ONE SHEET, TWO PRODUCTS (2026-09-23). It dresses KIVOICE and KICHAT alike and is served
   to accounts that hold either or both, so it is named for the house. Where a comment below
   names a product it is because that rule really is about that product, or about its logo
   file — those names are accurate and stay.
   ========================================================================== */

:root {
    /* ═══ THE SUBSTRATE (2026-08-04) ═══════════════════════════════════════════════════
       A six-specialist design audit counted what this file ACTUALLY contained rather than
       what :root claimed: 36 font sizes across 103 declarations with ZERO referencing a
       token, 30 spacing values across 289 with two, 18 radius values bypassing the three
       tokens 27 times, and 76 distinct colours under 26 colour tokens — including two
       "success greens" two hex digits apart doing the same job 525 lines from each other.

       That is the answer to "לא מסודר" and to "אני לא רואה שהשתנה משהו": nothing lined up
       between components because nothing shared an increment, and nothing was decisively
       larger than anything else because five "card title" sizes differed by 0.3px. Neither
       is fixable by eye. The token blocks below are the fix.

         --n-*    ONE cool neutral ramp. The ground left #EEEEEE — the only fully achromatic
                  grey in the product, which made every cool surface above it read warm.
                  One step of it was EVERY control boundary until 2026-09-19, when the four
                  surfaces a control sits on stopped agreeing and --control-edge became a
                  literal of its own — see the note beside that token.
         --*-ink  The ink weight of a status, separate from its fill. A fill sits under a
                  white glyph at 3:1; a word sits on the page and must clear 4.5:1. Deriving
                  them per component is how two identical greens got into one file.
         --s1..7  One 4px scale. 1px and 2px are deliberately NOT on it — at that size the
                  value is optical correction, not spacing, and snapping it is a visible
                  misalignment rather than a tidier one.
         --r      One corner for everything with an edge. There were six on a single page.
         --t-*    Seven steps, with the body→title gap widened on purpose. Deduplicating the
                  five title sizes onto ~1rem would have been tidier and invisible.

       TWO RULES FOR EDITING THIS BLOCK, both learned the hard way on 2026-08-04:
       1. A bulk find-and-replace must never run over :root. One did, and left
          `--ok-ink: var(--ok-ink)` — a self-reference, which CSS treats as guaranteed-
          invalid, so every status word fell back to inherited grey while the file parsed
          perfectly and nothing warned.
       2. If you stash comments to protect them from a rewrite, restore in a LOOP. A single
          re.sub pass does not re-scan its own replacements, so comments nested inside a
          stashed :root came back as literal placeholder text sitting in a declaration block. */

    --kq-cyan-rgb: 0, 173, 181;
    --kq-ink-rgb: 34, 40, 49;

    --kq-cyan: #00ADB5;
    --kq-cyan-bright: #1AC4CC;
    --kq-azure: #0E8F96;
    --kq-blue: #00ADB5;
    --kq-blue-600: #0E8F96;
    --kq-indigo: #222831;
    --kq-violet: #00ADB5;

    --grad-brand: linear-gradient(135deg, #00ADB5 0%, #0E8F96 100%);
    --grad-brand-soft: linear-gradient(135deg, rgba(0,173,181,.13), rgba(0,173,181,.07));
    --grad-sidebar: linear-gradient(185deg, #222831 0%, #393E46 100%);
    --grad-success: linear-gradient(135deg, #18BE66, #4BC140);
    --grad-violet: linear-gradient(135deg, #1AC4CC, #00ADB5);

    /* RE-SPACED FOR THE DARK FRAME (2026-09-19). One cool ramp still, moved so the three jobs
       that have a floor each land on their own step: --n-8 is the muted tier (7.6:1 on paper,
       6.6:1 on the flat ground), --n-7 the faint tier (5.4:1 / 4.7:1), and the control boundary
       lives between --n-6 and --n-7 as it did before. */
    --n-0: #FFFFFF;    --n-1: #F7F9FB;    --n-2: #EDF1F5;    --n-3: #E3E8EE;    --n-4: #D5DCE4;    --n-5: #BFC8D2;    --n-6: #8A94A1;    --n-7: #616B79;    --n-8: #4B5563;    --n-9: #333B45;
    --ink: #12171D;
    /* SIDEBAR INK. Defaults are the values that used to be hard-coded below, so a dark
       brand renders byte-identically. BrandTheme overrides them only when the reseller
       picks a LIGHT secondary, where white text would erase the menu. --sb-veil is an
       rgb TRIPLE, not a colour: the rail's overlays are rgba() of it. */
    --sb-ink: #fff;
    --sb-ink-2: #c2cad2;
    /* The rail's group heading is TEXT and needs 4.5:1. It reads --frame-ink-dim now, which is a
       literal measured against the frame rather than against a gradient that moved under it, so
       this token has no reader left in the sheet; it stays declared because Web/BrandTheme still
       emits it and a token a brand block writes must have a home here. */
    --sb-ink-dim: color-mix(in srgb, var(--n-5) 60%, var(--n-6));
    --sb-veil: 255,255,255;
    /* The rail's own background, as rgb TRIPLES because the frame is a color-mix OF them: 18% of
       this colour over #121821. Defaults are the exact values .sidebar used to hard-code
       (#222831 / #393E46), which is why the reseller's secondary never actually painted the rail:
       --grad-sidebar was overridden and read by nobody. */
    --sb-bg-rgb: 34,40,49;
    /* The rail gradient's tail. The frame is flat, so no rule reads this any more; it stays
       declared because Web/BrandTheme still emits it and a token a brand block writes must have
       a home here. */
    --sb-bg2-rgb: 57,62,70;

    /* ══ THE FRAME («מסגרת כהה», 2026-09-19) ════════════════════════════════════════════════
       The rail, the top bar, the user menu and the save bar are ONE continuous deep frame and
       everything inside it is bright, flat paper. The frame is 18% of the reseller's rail colour
       over #121821. The percentage is not a taste: it is the largest tint that keeps the DIM tier
       — the rail's group headings, which are text and need 4.5:1 — above the floor for a
       near-white brand secondary. Measured at 26% a #F2F2F2 brand lifted the frame to L .044 and
       the dim tier fell to 3.5:1; at 18% it is 4.52:1 with the ink-2 tier at 6.4:1 and white at
       11.5:1, and Kinetiq's own rail sits at 17.3:1 / 9.6:1 / 7.0:1. A brand is allowed to colour
       the frame. It is not allowed to lighten it.
       It is read through color-mix on --sb-bg-rgb AT USE TIME, so the reseller's colour still
       paints the frame whichever block wrote that triple — BrandTheme.StyleBlock() is emitted by
       _BrandHead AFTER this stylesheet. Everything else in the frame is decided HERE — a literal,
       or one of the --frame-* names this sheet owns and StyleBlock() never writes — and never an
       --sb-* token, so a brand block cannot flip the frame's ink to dark on a frame that is always
       dark. The one other brand token the frame reads is --rail-ink, and that is deliberate:
       BrandTheme derives it AGAINST the frame, which is the whole of why it may be read here. */
    /* THE BASE, NAMED. --frame-bg below keeps the literal INSIDE its color-mix: that exact text
       is what frame-test.sh's formula check reads and what contrast-test.sh replaces to plant its
       «the base is lightened» control, so it may not become a var(). This restates the same base
       for the one other place that has to mix against it — the branding preview's rail, which is
       a miniature of the frame and was painting the raw brand colour instead. */
    --frame-base: #121821;
    --frame-bg: color-mix(in srgb, rgb(var(--sb-bg-rgb)) 18%, #121821);
    --frame-ink: #FFFFFF;
    --frame-ink-2: #B8C2CE;
    --frame-ink-dim: #9AA6B4;
    /* THE VEIL — every overlay on the frame is white at an alpha, never a second colour. Two
       steps and no more: hover and selected. */
    --frame-hover: rgba(255, 255, 255, .07);
    --frame-sel: rgba(255, 255, 255, .11);
    /* A CONTROL ON THE FRAME IS SUNK, NOT LIFTED — a black veil, where a selected nav item takes
       a white one. That is the material rule («you operate this» goes down, «you are here» comes
       up), and it is also the only version that survives a white label: a white-veiled control
       gets LIGHTER as the brand gets paler, and its dim text fell to 4.15:1 on the account pill
       under Kinetiq's own frame before this changed. Sunk, the control face is darker than the
       bar whatever the brand did to the bar, so light ink on it can only improve.
       The edge is still white, and it is what identifies the control: .45 measures 4.6:1 against
       the face it encloses and 4.1:1 against the bar around it. At .18 — what a tasteful hairline
       wants to be — it was 1.40:1, i.e. no boundary at all on the search field and the pill. */
    --frame-ctl-bg: rgba(0, 0, 0, .22);
    --frame-ctl-edge: rgba(255, 255, 255, .45);
    /* THE FRAME'S OWN STATUS COLOURS AND ITS FOCUS HALO, on :root like every other colour in this
       sheet. The status tokens above are measured on PAPER and not one of them reads on a
       near-black bar (--warn-ink is 2.00:1 on the chip's own wash), so the frame carries its own
       — but they were written as literals ON the element, which put six findings on ds-gate's
       DS08 against a baseline of one. A NAME is as safe here as a literal: the ORDERING CONTRACT
       above forbids reading a token Web/BrandTheme re-emits, and it emits none of these, so no
       brand block can reach them. Measured on the bar (#151B24): the dots 9.93:1 and 10.36:1;
       the chip's ink 8.27:1 on its own wash and its edge 4.52:1 against the bar. */
    --frame-dot-ok: #4ADE80;
    --frame-dot-warn: #FBBF24;
    --frame-warn-bg: rgba(217, 130, 7, .20);
    --frame-warn-edge: rgba(238, 213, 165, .55);
    --frame-warn-ink: #F6C87A;
    --frame-focus-halo: 0 0 0 4px rgba(10, 14, 20, .75);

    --ink-2: var(--n-9);
    --muted: var(--n-8);
    --line-strong: var(--n-6);
    --row-hover: #F2F6FA;
    --line: #DBE2EA;
    --line-2: #E3E8EE;
    --bg: var(--n-1);
    --card: var(--n-0);
    --panel: var(--n-0);

    /* ═══ ACTION vs BRAND (redesign 2026-09-17, A11Y-01 / A11Y-10 / DS-03) ══════════════════
       The brand teal is DECORATION: the rail indicator, chart bars, a live dot. White on it is
       2.6:1 and it is 2.75:1 as text on white, so it can never carry a word, a button face or
       a status. Everything the reader acts on reads the action tokens below instead — 5.6:1
       for white on the fill, 5.4:1 as link text on the page ground.
       Web/BrandTheme.StyleBlock() re-emits these tokens for a reseller with the values that
       reseller has today, so no white label changes colour because of this block. */
    /* Deepened one step for the dark frame and given a two-stop fill — the lighter stop is the
       one that has to hold white text and measures 5.38:1; the flat token under it is 6.0:1 and
       is what the edge and the hover read, so a gradient is never the thing proving a contrast. */
    --kq-btn-bg: #0B6E75;
    --kq-btn-bg-hover: #07555B;
    --kq-btn-on: #fff;
    --kq-btn-fill: linear-gradient(180deg, #0C767D 0%, #0A666C 100%);
    --kq-link-ink: #0A6B72;
    --kq-link-hover: #07555B;
    --kq-action-soft: #E2F2F3;
    --kq-brand-raw: var(--kq-cyan);
    --kq-brand-on: #fff;
    /* THE WORDMARK SQUARE, and the one thing --kq-brand-raw is not allowed to be. The square is
       the only surface in the product that puts a LETTER on the brand colour, and at 15px in the
       rail that letter is ordinary text — so it cannot read a token whose whole job is to stay
       the reseller's exact colour whether or not anything can be read on it. White on the brand
       teal is 2.75:1; the action pair below is 5.6:1, and Web/BrandTheme replaces it with the
       reseller's own colour for every brand a letter CAN be read on. The square is never painted
       for Kinetiq itself — its own mark is an image — so this default is what the guard measures
       rather than what the product shows. */
    --kq-mark-bg: var(--kq-btn-bg);
    --kq-mark-on: var(--kq-btn-on);
    --rail-ink: var(--kq-cyan-bright);
    --link: var(--kq-link-ink);

    /* STATUS. A FILL (--ok, --warn, --danger) paints bars and dots and needs 3:1; an INK carries
       a word and needs 4.5:1 on its own background; an EDGE frames a notice or a badge. One
       triple per meaning, so no component derives its own shade again. Neutral is a status
       too: zero, paused and "not known" are facts, not alarms. */
    /* The inks are re-measured on the flat paper: ok 6.2:1, warn 6.5:1, danger 6.8:1, info 8.0:1,
       neutral 7.6:1 — every one of them a real body-text contrast, so a status word is as
       readable as the sentence around it. */
    --ok: #17A05A;
    --warn: #D98207;
    --danger: #DC2626;
    --ok-ink: #0A7040;      --ok-bg: #E9F7EF;      --ok-edge: #B5E2C8;
    --warn-ink: #8A5100;    --warn-bg: #FDF3E3;    --warn-edge: #EED5A5;
    --danger-ink: #B02318;  --danger-bg: #FDECEB;  --danger-edge: #D9544C;  --danger-wash: #FFF8F7;
    /* A BADGE'S EDGE, one step lighter than the notice's: --danger-edge round a pale tint reads as
       an alert a single word is not. It is a colour, so it is named here and not written into the
       badge (ds-gate DS08). */
    --danger-edge-soft: #EDB6B1;
    --danger-strong: #8B1C14;
    --info-ink: #14528F;    --info-bg: #E8EFF9;    --info-edge: #B6CCE8;
    --info: var(--info-ink);
    --neutral-ink: var(--n-8);  --neutral-bg: var(--n-2);  --neutral-edge: var(--n-4);
    /* «יומנים» (2026-10-07): one hue per calendar, by its place in the account's own order — eight,
       chosen to stay apart from each other and to read as text on a white card. */
    --cal-0: #1A73E8; --cal-1: #0B8043; --cal-2: #D9620B; --cal-3: #8E24AA;
    --cal-4: #C5221F; --cal-5: #A07800; --cal-6: #3F51B5; --cal-7: #00838F;

    /* ── THE DATA RAMP ──────────────────────────────────────────────────────────────────────
       Five hues, fixed, assigned BY POSITION and never by meaning — the dashboard's first tile
       is always teal whatever it counts. Each clears 4.2:1 on white, so a bar, a chip glyph and
       a 32px figure are all legible in it, and no two neighbours are within 25 degrees of hue.
       Status keeps its own ramp above: green here would be a promise this ramp cannot keep. */
    --dv-1: #0E7C86;  --dv-2: #3C5CCC;  --dv-3: #B4690E;  --dv-4: #A8375A;  --dv-5: #4E6076;
    --dv-1-soft: color-mix(in srgb, var(--dv-1) 13%, var(--n-0));
    --dv-2-soft: color-mix(in srgb, var(--dv-2) 12%, var(--n-0));
    --dv-3-soft: color-mix(in srgb, var(--dv-3) 14%, var(--n-0));
    --dv-4-soft: color-mix(in srgb, var(--dv-4) 12%, var(--n-0));
    --dv-5-soft: color-mix(in srgb, var(--dv-5) 13%, var(--n-0));

    --shadow-sm: 0 1px 2px rgba(16, 22, 32, .07);
    --shadow: 0 6px 18px rgba(16, 22, 32, .10);
    --shadow-lg: 0 20px 48px rgba(10, 14, 20, .30);
    --shadow-dialog: 0 28px 70px -20px rgba(10, 14, 20, .50);
    --shadow-drawer: 18px 0 44px -18px rgba(10, 14, 20, .38);
    --shadow-savebar: 0 -12px 26px -18px rgba(10, 14, 20, .55);
    --shadow-lift: 0 6px 16px -10px rgba(10, 14, 20, .45);
    --shadow-pop: 0 14px 34px -12px rgba(10, 14, 20, .30);
    --knob-shadow: 0 1px 3px rgba(0, 0, 0, .35);
    --ring: 0 0 0 3px color-mix(in srgb, var(--kq-btn-bg) 26%, transparent);
    /* the white half of the two-tone focus ring — see the keyboard-focus block at the end */
    --focus-halo: 0 0 0 2px var(--n-0);

    /* A dark scrim that stays dark under every brand: a scrim derived from a light reseller
       secondary would brighten the page it is meant to push back. */
    /* Deeper than the light shell's, because the thing it pushes back is now framed by something
       dark and needs a harder step to read as behind. Still no blur. */
    --scrim: rgba(10, 14, 20, .58);

    /* THE PAGE GROUND, AND IT IS FLAT (2026-09-19). The three radials are off, not faded: with a
       dark frame giving the page its structure, a two-hue wash under white cards is the one thing
       on the screen that says «template». Turning it off also ends the per-pixel contrast
       argument — muted ink on the ground is 6.62:1 at every point of every page under every
       brand, where the wash made it a range that had to be re-measured each time a radial moved.
       The --wash-* tokens are gone with it. Web/BrandTheme still emits --wash-1-rgb/--wash-2-rgb;
       nothing reads them. */
    --ground: #EDF0F4;

    /* ONE CONTROL BOUNDARY FOR EVERYTHING YOU OPERATE (2026-09-19, «מסגרת כהה»). #7A8593 is the
       single value that clears 3:1 against all four surfaces a control is ever drawn on: white
       paper 3.75:1, the sunk field face 3.55:1, the ghost face 3.41:1 and the page ground 3.34:1.
       It is a literal and not a mix of two ramp steps, because those four numbers are what it has
       to hold and a derivation moves whenever the ramp under it moves. The ground it is measured
       against is FLAT now, so there is one number per surface instead of a per-pixel range.
       The ghost face is its own step as well: --surface-sunk is the field's face, and a ghost
       button beside a field must not be the same surface as the field.

       ── --control-edge IS EVERY CONTROL BOUNDARY (2026-09-19) ────────────────────────────────
       That fix reached the field and stopped there, and the split it implies was never written
       down — so the ghost button, the icon button and the agent card's five actions kept drawing
       themselves with --line and measured 1.24-1.35:1 against their own face. A ghost button's
       fill is --surface-sunk on a white card: 1.06:1. The frame is not decoration on those
       controls, it is the ONLY thing that says a control is there, and at 1.3:1 it is not there.

       So the two neutral edges mean two different things, and nothing may borrow the other's:
         --control-edge  the boundary of something you OPERATE — a field, a select, a ghost or
                         icon button, a card action, a choice card, a quick-link tile, the
                         account button, the month pager. 3:1 minimum, WCAG 1.4.11.
         --line / --line-2  a RULE BETWEEN THINGS — a divider under a card head, the hairline
                         between two rows, a frame round a group of fields, the edge of a notice
                         or a read-only box. It is not a control, it has no floor, and raising it
                         to 3:1 would box the product in.
       A hover on a control edge darkens to --n-7: with the resting edge at --n-6, the old hover
       (--n-5, --line-strong) either lightened it or did nothing at all. */
    --control-bg: #F7F9FB;
    --control-edge: #7A8593;
    --ghost-bg: #F3F6F9;
    --premium-ink: #6B4300;
    --premium-fill: linear-gradient(135deg, #FFE7A0, #F2B63A);


    /* DENSITY PASS 2026-08-05 (owner: "תקטין לי את כל הדפים בממשק בהתאם להקטנה שביצענו בתפריט").
       The rail was tightened first and he asked for the rest of the product to follow it.
       Done at the TOKENS, not rule by rule: 298 declarations reference these seven values, so a
       scale change here moves the whole interface at once and keeps it internally consistent —
       which is the entire reason the substrate was built. Editing paddings screen by screen is
       how the 30-spacing-value mess it replaced came about in the first place.
       ~0.85 of the old scale. --s1 stays 4px: it is the atom the others are multiples of, and
       below 4 a gap stops reading as deliberate and starts reading as a mistake. */
    /* 2026-09-19: the 4px atom is kept and the middle of the scale comes in by about a step.
       This is a console somebody reads for eight hours, and every row recovered is a row they do
       not scroll for. Rhythm before decoration — 298 declarations read these seven values. */
    --s1: 4px;
    --s2: 6px;
    --s3: 10px;
    --s4: 13px;
    --s5: 18px;
    --s6: 26px;
    --s7: 38px;

    /* ── THE MATERIAL: PAPER AND FLOAT (owner 2026-09-18, «זכוכית מדויקת») ─────────────────
       Supersedes the 2026-08-05 glass block ("תעשה לי זכוכית, ככה בכל הממשק בכל מקום קומפלט").
       Glass is a statement about DEPTH — "this surface is in front of that one". Sampled off a
       screenshot of the tenant dashboard at 1440x900, the product was making that statement TEN
       times in one viewport (the rail, the bar, four figure tiles, four cards): 1,632,146 blurred
       pixels, 81.4% of the page, for about eight surfaces that are not in front of anything. When
       everything floats, nothing does.

       So the material splits in two, and the split IS the design:

         PAPER — everything that sits IN the page: cards, figure tiles, panels, the filter bar,
                 the table, the tenant cards, the phone's call rows. Opaque, one real edge, a
                 short lift, no blur at all. Its fill is --surface-material, which ~40 rules in
                 this file already reference, so they went opaque without being touched.
         FLOAT — the five surfaces page content genuinely passes under, and no others: .topbar,
                 .kq-savebar, .kq-drawer, .usermenu__panel and .cselect__list. Translucent, with a
                 SHORT blur. The rail is NOT one of them any more: it is opaque at every width, so
                 the blur it used to take as a phone overlay was rastering a layer whose result is
                 hidden by the surface on top of it. The bar, the save bar and the user menu take
                 the FRAME's glass (--frame-float*); the drawer and the select list keep the light
                 one (--float-*), because they belong to the page and not to the tool.

       Measured after: 1 blurred layer over 3.6% of the dashboard at 1440 (2.0% at 390), 2 over
       9.7% of a form with its save bar. Area × blur radius — which is how a Gaussian's cost grows
       — falls 41× on the dashboard and on the call journal, 76× on a phone dashboard.

       THE SCRIM DOES NOT GET A BLUR. It is the one place a blur would look dramatic and the one
       place it would cost a full-viewport raster; --scrim at .48 already pushes the page back,
       and the thesis here is fewer blurred pixels, not better-placed ones. */

    /* #FFFFFF on the #EEF1F5 ground is 1.13:1 — a fill cannot draw its own edge, so the edge has
       to. --n-5 measured 1.34-1.43:1 against the washed ground under the five brand hues tested
       and 1.65:1 against the paper itself: visible from BOTH sides. The rgba(255,255,255,.55) it
       replaces never was — 1.04-1.08:1 against the card's own face, i.e. an edge that existed
       only on its outside, which is why the shadow had to be 36px tall to find the card at all.
       No new colour is born here: face, sunk face and edge are all steps of the --n-* ramp. */
    /* THE DARK FRAME DOES THE SEPARATING, so the paper can stop shouting: one real edge at
       1.41:1 against its own face and 1.24:1 against the ground — visible from both sides — and a
       shadow short enough to seat the card rather than float it. */
    --surface-material: var(--n-0);
    --surface-sunk: var(--n-1);
    --surface-edge: #D2DAE3;
    /* A SHORT LIFT, NOT A HALO. The 36px-radius shadow this replaces was re-rasterised on every
       repaint, nine times over on one dashboard. With a real edge the shadow only has to seat the
       panel, so it comes down to 8px. --surface-seat is the strip form: five tiles in a row are
       one strip, not five objects, and a drop under each one made the row read as a pile. */
    --surface-lift: 0 1px 2px rgba(16, 22, 32, .05), 0 6px 14px -10px rgba(16, 22, 32, .24);
    --surface-seat: 0 1px 2px rgba(16, 22, 32, .05);
    /* THE BAND BEHIND THE COLUMN NAMES IS THE FRAME (2026-09-19). The densest object in the
       product gets a hard top edge the eye can return to from row 25, and it is the same colour
       as the rail and the bar, so the journal reads as part of the tool rather than as a box that
       happens to be on the page. It stays a token because forced-colors has to replace it and
       because the callbacks group band (.cbk-group-head) is the same strip.
       THIS TOKEN CREATED THE CALLBACKS DEFECT, and an earlier note in this place said the
       opposite. .cbk-group-head fills with the band and its heading and its count went on writing
       the PAGE's inks on what had become the frame — 1.53:1 and 2.29:1, measured on painted
       pixels. Repointing --tbl-head-band here is what did that; the callbacks screen had not
       changed. Both inks moved to the frame's tiers on 2026-09-19 (:4918 and :4922), which are
       the same two tiers the column names take at :947 and :953. */
    --tbl-head-band: var(--frame-bg);

    /* 90% for the light float and 92% for the frame's, and both are floors rather than averages:
       the number is chosen against the LIGHTEST thing that can pass under the surface, so every
       other thing that passes under it only improves the contrast. blur(10px) rather than 18px:
       10px is already past the point where type behind a bar stops being readable as type, and a
       Gaussian's cost grows with its radius. saturate(1.2) rather than 1.6: 1.6 amplified whatever
       hue the reseller chose, so the more saturated the brand the more colour the surface took. */
    /* TWO MATERIALS NOW, NOT ONE. --float-* stays LIGHT: the cselect list and the browser-call
       drawer are content and must not go dark. --frame-float-* is the chrome's own glass, and its
       opacity is set by the worst case rather than by taste: at 92% a white card scrolling under
       the top bar lifts it to #282E34, where white still reads 13.7:1 and the dim tier 6.5:1.
       Anything darker under the bar only raises those numbers, so 92% is a floor, not an average
       — which the light bar it replaces could never say. */
    --float-bg: color-mix(in srgb, var(--n-0) 90%, transparent);
    --float-bg-strong: color-mix(in srgb, var(--n-0) 94%, transparent);
    --float-blur: saturate(1.2) blur(10px);
    --frame-float: color-mix(in srgb, var(--frame-bg) 92%, transparent);
    --frame-float-strong: color-mix(in srgb, var(--frame-bg) 97%, transparent);
    /* THE ONE EDGE THAT CATCHES LIGHT: an inset hairline along the side the light comes from, the
       top. The top bar does not take it — its top edge is the viewport's, so that line is a row of
       pixels nobody sees — and gets a defined bottom hairline and --float-drop instead, which is
       the edge it actually has to prove against whatever is scrolling under it. */
    --float-shine: inset 0 1px 0 rgba(255, 255, 255, .9);
    --float-drop: 0 6px 16px -10px rgba(10, 14, 20, .40);

    --r: 8px;
    --r-card: 12px;
    --r-pill: 999px;
    --r-circle: 50%;
    --radius: var(--r);
    --radius-sm: var(--r);
    --radius-xs: var(--r);

    /* Body holds at 14px — Hebrew loses legibility faster than Latin and this is the size the
       owner reads all day. What moves (2026-09-19) is the DISTANCE between steps: the page title
       goes 21.6→23px and a figure 31.2→32px, while the card title comes 17→16px and takes 700
       instead. Hierarchy is carried by weight and colour first, size second; five title sizes
       0.3px apart is what made the old screens read flat, not the sizes themselves.
       NOT a duration — see the SHELL-V01 note further down; the motion token is --dur-base. */
    --t-xs: .6875rem;    --t-sm: .75rem;    --t-md: .8125rem;    --t-base: .875rem;    --t-lg: 1rem;    --t-xl: 1.4375rem;    --t-2xl: 2rem;
    --sidebar-w: 240px;

    --kq-x: 1;

    /* DURATIONS ARE NOT SIZES. The 200ms token used to be called --t-base, which is also the
       name of the .89rem body step above — the second declaration won, so every
       `font-size: var(--t-base)` in the file was invalid and silently inherited its parent's
       size (SHELL-V01). Motion now has its own prefix. */
    --dur-fast: 120ms;
    --dur-base: 200ms;
    --dur-slow: 320ms;
    --e-out: cubic-bezier(.22, .61, .36, 1);
    --e-in: cubic-bezier(.55, 0, .68, .53);
    --e-standard: cubic-bezier(.4, 0, .2, 1);

    /* the sticky save bar's height, so a field focused near the bottom scrolls clear of it */
    --kq-savebar-h: 58px;
}
/* ── THE TWO ESCAPES ────────────────────────────────────────────────────────────────────────
   Each must produce the OPAQUE version of THIS design, not a leftover of the previous one. Paper
   needs no branch — it is already opaque, and the rail is opaque at every width now — so only the
   float surfaces are restated, and they are restated as tokens so no rule is written twice. The
   chrome's own glass (--frame-float*) falls back to the flat frame; the content glass falls back
   to paper. The ground needs no branch either: it has no wash left to turn off. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    :root {
        --frame-float: var(--frame-bg); --frame-float-strong: var(--frame-bg);
        --float-bg: var(--surface-material); --float-bg-strong: var(--surface-material);
        --float-blur: none;
    }
}
/* A reader who asked the system for less transparency gets no translucent surface anywhere. */
@media (prefers-reduced-transparency: reduce) {
    :root {
        --frame-float: var(--frame-bg); --frame-float-strong: var(--frame-bg);
        --float-bg: var(--surface-material); --float-bg-strong: var(--surface-material);
        --float-blur: none;
    }
}
/* THE REDUCED-MOTION GUARD. Nothing in this stylesheet honoured prefers-reduced-motion —
   the only such rule anywhere was inside vendored bootstrap. For a person who has asked
   their operating system to stop moving things, an interface that animates anyway is not a
   style choice, it is a symptom trigger.
   Motion is reduced to a hair rather than removed: a 0.01ms transition still fires its
   transitionend, so anything waiting on that event keeps working, and a state change still
   reads as a change rather than a jump-cut. */
@media (prefers-reduced-motion: reduce) {
    /* NOT the blanket `transition-duration: .01ms` reset. That kills the safe half too — an
       opacity fade carries no motion and is exactly what should survive — and it turns every
       state change into a jump-cut, which removes the causality cue the animation was there
       to give. Instead: keep the colour and opacity transitions, drop the travel. */
    *, *::before, *::after {
        transition-property: opacity, color, background-color, border-color, box-shadow,
                             outline-color, fill, stroke !important;
        animation-duration: var(--dur-fast) !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }
    /* the handful of places that displace an element on hover or press — decoration, so it goes */
    .btn:hover, .btn:active, .card:hover, .nav__link:hover,
    .stat:hover, .tool-item:hover { transform: none !important; }
    /* The frame's own hover is a colour change, so it survives the guard intact and a reader who
       asked for stillness still sees the rail answer the pointer. */
    .kq-fig, .card, .btn { transition-property: opacity, color, background-color, border-color, box-shadow !important; }
}
[dir="rtl"] { --kq-x: -1; }
/* ==========================================================================
   Page transitions
   ========================================================================== */
/* Clicking a rail item blinks the whole window white today — the navy rail, the logo and
   the top bar all disappear and come back byte-identical. Pinning the shell with a
   view-transition-name and giving its group a 0s duration makes it HOLD STILL across a real
   HTTP navigation, so only the content cross-fades. Pure CSS: `script-src 'self'` never sees
   it, and browsers without support just navigate the way they do now.
   The exit is one step shorter than the enter and opacity-only — on the way out the user has
   already decided, and every millisecond of outro is dead time they paid for. */
@view-transition { navigation: auto; }
.sidebar { view-transition-name: kq-rail; }
.topbar  { view-transition-name: kq-topbar; }
::view-transition-group(kq-rail),
::view-transition-group(kq-topbar) { animation-duration: 0s; }
::view-transition-old(root) { animation: kq-vt-out  90ms var(--e-in)  both; }
::view-transition-new(root) { animation: kq-vt-in  180ms var(--e-out) both; }
@keyframes kq-vt-out { to   { opacity: 0; } }
@keyframes kq-vt-in  { from { opacity: 0; translate: 0 6px; } }
/* Travel is the vestibular trigger, opacity is not — keep the fade, null the movement. The
   global reduced-motion block whitelists transition-property only; a keyframe still travels,
   so it has to be said again here. */
@media (prefers-reduced-motion: reduce) {
    ::view-transition-old(root),
    ::view-transition-new(root) { animation-duration: var(--dur-fast); translate: none; }
}
/* The top bar is sticky, and nothing set scroll-padding — so every #anchor, every
   scrollIntoView and every focus() after a validation failure landed the target UNDERNEATH
   73px of frosted white. The user sees the ring vanish and assumes focus was lost. */
/* AUTHORITATIVE, NOT OBSERVED. This was a measurement of what the topbar happened to come
   out to, and the density pass of 2026-08-05 made it 61px while this still said 73 — so the
   bot editor's sticky header, which pins itself to this number, floated 12px below the bar
   with a band of page content scrolling through the gap. The topbar now TAKES its height
   from this token, so the number cannot drift from the thing it describes again. */
:root { --kq-topbar-h: 58px; }
/* ONE HEIGHT FOR EVERY CONTROL IN THE TOP BAR (SHELL-30, owner 2026-09-18). The account chip,
   the platform search and the user pill were each sized by their own padding — 31px, 37px and
   40px, three nested pill sizes on one row — and the pill had no height of its own at all, so
   the tallest thing in a 61px bar was whatever the current user's name happened to do: 40px,
   54.55px or 67.59px for the same rule with three different names, the last of them painting
   3.8px above the viewport and 2.8px over the page below.
   The number is the bar's content box less a hair: --kq-topbar-h minus its padding-block and its
   hairline is 58 − 10 − 10 − 1 = 37, and the control stands one pixel inside it. Raise one and
   the other follows, which is what the two blocks below do. */
:root { --kq-bar-ctl-h: 36px; }
/* THE BAR'S CONTROLS GO BACK TO 40px ON A PHONE — the hamburger, the folded search and the user
   pill are three neighbours at one size — and the bar's height follows them. This block must sit
   AFTER the two :root declarations above (same specificity, source order decides) and BEFORE the
   coarse-pointer block at the foot of this file, which raises both again for a thumb. */
@media (max-width: 820px) {
    :root { --kq-bar-ctl-h: 40px; --kq-topbar-h: 62px; }
}
html { scroll-padding-block-start: calc(var(--kq-topbar-h) + 12px); }
* { box-sizing: border-box; }
/* ═══ TYPEFACES — SELF-HOSTED (owner chose this 2026-08-04) ════════════════════════════
   Was Heebo + Rubik off Google Fonts. Two problems, one of them not cosmetic:

   1. That pair is the default free Google Hebrew pairing — the Hebrew equivalent of Inter +
      Poppins, what every template and every generated mockup ships — and the market research
      named it the single loudest "AI-made" tell in the product.
   2. Typography was the ONLY system in this application still loading from a third party,
      while everything else runs under script-src 'self'. Two preconnects and a stylesheet
      link on every page, to a host we do not control, that can see every visitor.

   ASSISTANT for text (owner chose it 2026-08-05 from a live five-way specimen). It is the face
   most serious Israeli products set their interfaces in, and the reason is the reason he picked
   it: even colour and generous counters make it legible at 13px without asking to be noticed.
   "לא מקצועי מספיק" in a dense admin UI is nearly always a request for a QUIETER face, not a more
   characterful one.

   ASSISTANT IS THE ONLY FACE IN THE PRODUCT, digits included (owner 2026-08-05). IBM Plex Mono
   carried the figures until then; see the note at the @font-face block for what that cost and
   why he took it. OFL.

   ONE VARIABLE FILE PER SUBSET, not four static weights: Assistant ships variable, so 400
   through 700 come from 7KB of Hebrew instead of four separate downloads. Two files total.

   Hebrew and Latin are separate files by unicode-range so a browser fetches only what the
   page actually contains: the Hebrew subset is 7KB for every weight. font-display:swap, because
   a console that is unreadable for 200ms is worse than one that reflows once. */
@font-face { font-family: "Assistant"; font-weight: 200 800; font-style: normal; font-display: swap;
             src: url("/fonts/assistant-he.woff2") format("woff2");
             unicode-range: U+0590-05FF, U+200F, U+FB1D-FB4F; }
@font-face { font-family: "Assistant"; font-weight: 200 800; font-style: normal; font-display: swap;
             src: url("/fonts/assistant-latin.woff2") format("woff2");
             unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+20AA, U+20AC, U+2122, U+2212; }
/* THE FIGURES FACE IS GONE (owner 2026-08-05: "שגם הפונט של המספרים בכל הממשק
   יהיה ASSISTANT"). IBM Plex Mono used to carry every digit so that columns of
   timestamps and durations aligned. Assistant has NO tabular figures — measured on
   the live journal, its "1" is 9.02px against "0" at 9.84px, and font-variant-numeric
   changes nothing because the font ships no `tnum` feature. Those columns are now
   slightly ragged, and that is a trade the owner made knowingly in exchange for one
   typeface across the whole interface.
   The tabular-nums declarations are deliberately LEFT IN PLACE: they cost nothing,
   they document the intent, and they start working the day a face that supports
   them is chosen. */

html { -webkit-text-size-adjust: 100%; }
/* FORM CONTROLS DO NOT INHERIT TYPE. Browsers set their own family on button/input/select, so a
   control that misses .btn or .control renders in Arial while the page around it is Assistant —
   and several did: the icon buttons and the user menu. Found by listing every distinct first
   font-family on a live page rather than by reading the sheet, because the sheet looked right.
   Declared once, on the elements, so no future control can be born without it. */
button, input, select, textarea, optgroup { font: inherit; }
body {
    margin: 0;
    font-family: "Assistant", "Segoe UI", system-ui, -apple-system, sans-serif;
    /* FLAT GROUND, ONE COLOUR. The dark frame gives the page its structure, so the wash it used
       to carry is gone — see the --ground note in :root for what that buys. */
    background: var(--ground);
    color: var(--ink);
    font-size: 14px;
    line-height: 1.55;
    /* NOT antialiased. It thins strokes, and Hebrew recognition depends on stroke weight far
       more than Latin does — there are no ascenders and descenders to identify a letter by. */
    text-rendering: optimizeLegibility;
}
h1, h2, h3, h4, h5, .display {
    font-family: "Assistant", sans-serif;
    color: var(--ink);
    font-weight: 700;
    /* no negative tracking: Hebrew has no optical gaps to close and tightening
       shears the letterforms */
    margin: 0 0 .4em;
}
a { color: var(--link); text-decoration: none; }
/* --kq-indigo is the reseller's SECONDARY, which may be light — a hover that makes the link
   disappear. The hover is the action ink one step darker instead. */
@media (hover: hover) { a:hover { color: var(--kq-link-hover); } }
.muted { color: var(--muted); }
.text-center { text-align: center; }
.small { font-size: var(--t-sm); }
/* ==========================================================================
   App shell
   ========================================================================== */
.app { display: flex; min-height: 100vh; }
.sidebar {
    width: var(--sidebar-w);
    flex: 0 0 var(--sidebar-w);
    /* THE RAIL IS PART OF THE FRAME, AND IT IS OPAQUE AT EVERY WIDTH. Flat, not a gradient: the
       frame is one continuous colour from the rail through the top bar to the save bar, and a
       gradient down the rail is that seam showing. Every value here is a LITERAL and never an
       --sb-* token, so a brand block emitted after this sheet cannot flip the frame's ink to dark
       on a frame that is always dark. */
    background: var(--frame-bg);
    border-inline-start: 0;
    /* the seam that faces the work — the only edge of the rail anyone ever sees */
    border-inline-end: 1px solid rgba(255, 255, 255, .07);
    color: var(--frame-ink-2);
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0; /* RTL: right edge */
    display: flex;
    flex-direction: column;
    z-index: 40;
    /* no lift on a desktop: the rail is flush against the viewport and has nothing to lift off.
       The ≤820px block gives it one, where it really is an overlay. */
    box-shadow: none;
}
/* A STATED HEIGHT (SHELL-30), and it is the top bar's own: the rail's head and the bar beside it
   are one line across the top of the frame. The block had none and the rail's
   logo cap had been removed, so the first row of the menu — and with it the top of every item
   below — sat at a different height for every tenant: 55.89px with the KIVOICE logo, 71px with a
   reseller wordmark that wrapped, 175px with a square logo, which pushed the first nav item
   118.72px down a rail that was densified precisely to avoid a scrollbar. */
.sidebar__brand {
    display: flex;
    align-items: center;
    justify-content: center;
    block-size: var(--kq-topbar-h); flex: none;
    padding-block: 0; padding-inline: var(--s4);
    border-bottom: 1px solid rgba(255, 255, 255, .09);
}
.sidebar__logo { width: 150px; max-width: 100%; max-height: 40px; height: auto; display: block; margin: 0 auto; }
/* IMPERSONATION (a platform owner or a reseller acting inside a customer account).
   OPAQUE AND LIGHT, not a tint over the rail (A11Y-02). A translucent orange wash took whatever
   the rail was underneath it, so on a reseller with a light secondary the white customer name
   sat on near-white and vanished — the one line on the screen that says whose data you are
   editing. On its own warm ground every word clears 4.5:1 whatever the rail colour is. */
.imp-banner {
    margin: var(--s3) var(--s3) var(--s1);
    padding: var(--s3);
    background: var(--warn-bg);
    border: 1px solid var(--warn-edge);
    border-inline-start: 4px solid var(--warn-ink);
    border-radius: var(--r);
}
.imp-banner__label { display: flex; align-items: center; gap: var(--s2); color: var(--warn-ink); font-size: var(--t-xs); font-weight: 600; }
.imp-banner__label svg { width: 15px; height: 15px; }
.imp-banner__tenant { color: var(--ink-2); font-size: var(--t-xs); margin-top: var(--s2); }
/* ONE LINE, then cut, with the full name on the element's title. It used to take two lines when
   the customer's name was long, which moved the banner from 127.86px to 144.94px and every nav
   item under it with it — the rail's whole rhythm decided by a string a customer typed. */
.imp-banner__name {
    color: var(--ink); font-weight: 700; font-size: var(--t-base); line-height: 1.2;
    min-inline-size: 0; overflow-wrap: normal;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.imp-banner__exit {
    display: flex; align-items: center; justify-content: center; gap: var(--s2); width: 100%;
    min-block-size: var(--kq-bar-ctl-h);
    margin-top: var(--s3); padding: var(--s2) var(--s3); border-radius: var(--radius-xs); cursor: pointer;
    background: var(--n-0); color: var(--warn-ink); border: 1px solid var(--warn-edge); font: inherit; font-weight: 600; font-size: var(--t-md);
    transition: background var(--dur-fast) var(--e-standard);
}
@media (hover: hover) { .imp-banner__exit:hover { background: var(--warn-bg); } }
.imp-banner__exit svg { width: 16px; height: 16px; }
/* RAIL DENSITY (owner 2026-08-05: "שהכפתורים בתפריט הראשי יהיו פחות גדולים שיכנסו יותר כפתורים
   בלי שיפתח SCROLL"). Each row was 12px padding + a 20px icon + 12px = 44px, plus a 4px gap —
   48px per item. Ten items plus the brand block and the scope card overflowed a laptop viewport,
   so the last items in the rail were reachable only by scrolling a menu, which is the one thing
   a menu is supposed to save you from.
   Now 34px per row on a 2px gap: 36px per item, a quarter off, and the whole rail fits.
   Density is not the opposite of professional here — it is what dense admin products look like;
   what read as unprofessional was a navigation that ran out of room. */
.nav { padding: var(--s2) var(--s2) var(--s4); display: flex; flex-direction: column; gap: 2px; overflow-y: auto; }
.nav__section {
    color: var(--frame-ink-dim); font-size: var(--t-xs); font-weight: 600;
    padding: var(--s4) var(--s3) var(--s2);
}
.nav__link {
    display: flex; align-items: center; gap: var(--s2);
    min-block-size: 34px;
    padding: var(--s2) var(--s3);
    border-radius: var(--r);
    color: var(--frame-ink-2);
    font-size: var(--t-md);
    font-weight: 500;
    transition: background-color var(--dur-fast) var(--e-standard),
                color var(--dur-fast) var(--e-standard),
                border-color var(--dur-fast) var(--e-standard);
    position: relative;
}
.nav__link svg { width: 18px; height: 18px; flex: 0 0 18px; opacity: .78; }
/* the row is 35px and nothing in it may change that: the two items that carry a trailing element
   were 37.22px, a 2.22px step in a list whose gap is 2px. No label clips at 244px today, but
   nothing guarded it either, so a longer one would have moved the row instead of cutting. */
.nav__link > span { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* THE SELECTED ITEM IS A FILLED PILL AND A BAR, not a bar alone. On a flat frame a 3px accent
   with no field behind it is the only thing marking the row, and at rail density that is 3px of
   colour against 240px of dark; the pill is what the eye finds, the bar is what names the edge. */
.nav__link.active { background: var(--frame-sel); color: var(--frame-ink); font-weight: 600; box-shadow: none; }
.nav__link.active svg { opacity: 1; color: var(--rail-ink); }
/* THE INDICATOR SITS ON THE EDGE THAT FACES THE WORK, not on the rail's outer edge. The rail is
   flush against the viewport, so a bar at inset-inline-start is a stripe of colour along the side
   of the screen — it reads as a rendering artefact rather than as a mark, and an overscroll clips
   it. On the inner edge it sits on the seam the eye crosses to get back to the menu. */
.nav__link.active::before {
    content: ""; position: absolute; inset-inline-start: auto; inset-inline-end: 0; top: 5px; bottom: 5px;
    width: 3px; border-radius: var(--r) 0 0 var(--r); background: var(--rail-ink);
}
.sidebar__foot { margin-top: auto; padding: var(--s2); border-top: 1px solid rgba(255, 255, 255, .09); }
.main {
    flex: 1;
    min-width: 0;
    margin-inline-start: var(--sidebar-w);
    display: flex; flex-direction: column;
}
.topbar {
    position: sticky; top: 0; z-index: 30;
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--s4);
    height: var(--kq-topbar-h); box-sizing: border-box;
    padding: var(--s3) var(--s5);
    /* DARK GLASS, and part of the frame. What passes under it is unknown — a white card, the
       ground, the one dark code panel — so the opacity is chosen against the LIGHTEST of those
       and the bottom hairline is the edge it has to prove: white at .12 measures 2.0:1 against
       the bar and, over a white card, the bar itself is 3.9:1 against the page below. The light
       bar this replaces proved its edge at 1.65:1 and only against a surface it happened to be
       over. No shine: an inset line along the top edge of a bar whose top edge IS the viewport's
       paints a row of pixels nobody sees. A short drop instead, so the bar has a below. */
    background: var(--frame-float);
    -webkit-backdrop-filter: var(--float-blur);
    backdrop-filter: var(--float-blur);
    border-bottom: 1px solid rgba(255, 255, 255, .12);
    box-shadow: var(--float-drop);
    color: var(--frame-ink);
}
/* THE BAR'S TITLE SLOT, ONE RULE (SHELL-30). The <h1> a page opts into with KqTopbarHeading and
   the <p> phones get are the same slot in the same place, and they used to render at two sizes:
   at 768px the paragraph was 16.96px and the h1 14.24px, because the phone block overrode one
   and not the other. They also carried two separate copies of the ellipsis rule. One rule now,
   so the first page that opts the h1 in cannot inherit a mismatch.
   The groups themselves (.topbar__start / .topbar__end) are owned by the SHELL section at the
   foot of this file — they are shell markup, and a second copy here silently lost to it. */
.topbar__start > h1,
.topbar__title {
    margin: 0; min-inline-size: 0;
    font-size: var(--t-base); font-weight: 600; line-height: 1.3; color: var(--frame-ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.content { padding: var(--s5) var(--s6) var(--s7); max-width: 1240px; width: 100%; }
/* USER MENU
   The pill is a fixed-height control (--kq-bar-ctl-h) with two single lines in it. Before
   SHELL-30 it had no height and no wrapping guard outside the phone block: «בר יחזקאל» broke
   across two lines and «בעל פלטפורמה» across two more — `overflow-wrap: anywhere` let the name
   break between any two letters and the role had no `white-space`, so the column collapsed to
   the min-content of its longest word and the pill grew to 67.59px inside a 40px content box.
   It stays one line now and cuts with an ellipsis; the full text is still in the DOM, so the
   button's accessible name and its tooltip both carry it whole. */
.usermenu { position: relative; flex: none; }
.usermenu__btn {
    display: flex; align-items: center; gap: var(--s2);
    block-size: var(--kq-bar-ctl-h); max-inline-size: 16rem;
    background: var(--frame-ctl-bg); border: 1px solid var(--frame-ctl-edge); color: var(--frame-ink);
    border-radius: var(--r-pill); padding-block: 0; padding-inline: var(--s2) var(--s3); cursor: pointer;
    transition: background-color var(--dur-fast) var(--e-standard),
                color var(--dur-fast) var(--e-standard),
                border-color var(--dur-fast) var(--e-standard);
}
@media (hover: hover) {
    .usermenu__btn:hover { background: rgba(0, 0, 0, .34); border-color: rgba(255, 255, 255, .62); box-shadow: none; }
}
.usermenu__btn .who { display: flex; flex-direction: column; text-align: start; line-height: 1.2; min-inline-size: 0; }
.usermenu__btn .who .n,
.usermenu__btn .who .r {
    min-inline-size: 0; overflow-wrap: normal; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
}
.usermenu__btn .who .n { font-weight: 600; font-size: var(--t-md); color: var(--frame-ink); }
.usermenu__btn .who .r { font-size: var(--t-xs); color: var(--frame-ink-dim); }
/* the chevron is 16px like the account chip's glyph beside it (approved Topbar board); without
   `flex: none` it was the first thing the squeeze took and rendered 18.73px, then 14.70px, then
   24px on three screens — the same glyph at three sizes, set by the length of a person's name */
.usermenu__btn > svg { flex: none; inline-size: 16px; block-size: 16px; color: var(--frame-ink-dim); }
/* Anchored to the BAR, not to the button: at `top: calc(100% + 8px)` the panel moved down with
   every extra line the name took, and in the healthy state it opened 7px ABOVE the bar's bottom
   edge, with the hairline running across its top. The containing block starts at the bar's
   padding edge, so the bar's height less that padding lands the panel on the edge, plus s2. */
.usermenu__panel {
    position: absolute; inset-inline-end: 0;
    inset-block-start: calc(var(--kq-topbar-h) - var(--s3) + var(--s2));
    /* THE MENU THAT HANGS OFF THE BAR IS PART OF THE FRAME. It is chrome — it commands the tool,
       not the page — so it takes the frame's own glass rather than the light float the cselect
       list and the call drawer keep. A pop-over is opened OVER whatever was there and nothing
       reflows for it, so the blur is what keeps a list of commands off the text behind it.
       No --float-shine: a white inset hairline on a dark panel is the light material showing. */
    background: var(--frame-float-strong);
    -webkit-backdrop-filter: var(--float-blur); backdrop-filter: var(--float-blur); border: 1px solid rgba(255, 255, 255, .14); border-radius: var(--r-card);
    box-shadow: var(--shadow-lg); inline-size: 15rem; min-inline-size: 0; padding: var(--s2); display: none;
}
.usermenu.open .usermenu__panel { display: block; }
.usermenu__panel a, .usermenu__panel button {
    display: flex; align-items: center; gap: var(--s2); width: 100%;
    min-block-size: 34px;
    padding: var(--s2) var(--s3); border-radius: var(--r);
    background: none; border: 0; cursor: pointer; color: var(--frame-ink-2); font: inherit; text-align: start;
}
/* the only 24px icons in the product were in here — 18px is the step .nav__link and .btn use */
.usermenu__panel a svg, .usermenu__panel button svg { flex: none; inline-size: 18px; block-size: 18px; color: var(--frame-ink-dim); }
.usermenu__sep { height: 1px; background: rgba(255, 255, 255, .12); margin: var(--s1) var(--s1); }
/* avatar */
.avatar {
    width: 38px; height: 38px; border-radius: var(--r-circle);
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--kq-btn-fill); color: var(--kq-btn-on); font-weight: 700; font-size: var(--t-md);
    font-family: "Assistant", sans-serif; flex: 0 0 auto; box-shadow: none;
}
.avatar.lg { width: 52px; height: 52px; font-size: var(--t-lg); }
.avatar.sm { width: 30px; height: 30px; font-size: var(--t-xs); }
/* ==========================================================================
   Cards, stats
   ========================================================================== */
/* PAPER. The page's main surface: opaque, one real edge, the full (short) lift. It is the one
   panel a reader looks THROUGH nothing to read, and it carries the densest text in the product,
   so it is the surface that gains most from being brand-invariant — #FFFFFF under every reseller
   hue, body ink 17.67:1 and muted 6.67:1, where the 68% version drifted to #F5F7FC…#FAF6FB and
   6.17-6.36:1 depending on which colour the customer picked. */
.card {
    background: var(--surface-material);
    border: 1px solid var(--surface-edge);
    border-radius: var(--r-card);
    box-shadow: var(--surface-lift);
}
.card__head {
    display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
    padding: var(--s4) var(--s5); border-bottom: 1px solid var(--line);
}
.card__head h2, .card__head h3 { margin: 0; font-size: var(--t-lg); font-weight: 700; display: inline-flex; align-items: center; gap: var(--s2); }
.card__head h2 svg, .card__head h3 svg { flex: 0 0 auto; }
/* The head's glyph: 17px in the faint ink. Eight screen sections below restate it at 18px at a
   higher specificity and win wherever they apply — that was already true before this language was
   merged in, and it is left alone rather than silently changed. The eighth, the call page's
   `.call-sec__h`, was BELOW this rule at (0,1,1) until 2026-09-19 and lost to it. */
.card__head h2 > svg, .card__head h3 > svg { width: 17px; height: 17px; color: var(--n-7); }
.card__body { padding: var(--s5); }
.card__body.tight { padding: 0; }
/* A card whose body is a table clips to its own radius, so the dark column-name rule is the top
   of the card rather than a rectangle sitting inside a rounded one. Scoped by :has so nothing
   with a dropdown in it — an agent card's number picker — is ever given overflow. */
.card:has(> .card__body.tight),
.card.cl-card, .card:has(> .table-wrap) { overflow: hidden; }
.grid { display: grid; gap: var(--s4); }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
/* A row of figure tiles is a STRIP, not a row of objects: with an opaque face and a real edge the
   drop under each one is what made the row read as a pile, so it comes down to the seat. */
.stat {
    background: var(--surface-material); border: 1px solid var(--surface-edge);
    border-radius: var(--radius); padding: var(--s4); box-shadow: var(--surface-seat);
    position: relative; overflow: hidden;
}
.stat__icon {
    width: 46px; height: 46px; border-radius: var(--r);
    display: flex; align-items: center; justify-content: center; margin-bottom: var(--s3);
}
.stat__icon svg { width: 24px; height: 24px; }
/* one fill for every variant: white on the brand teal was 2.6:1, and the colour names were never
   a meaning a reader could act on */
.stat__icon, .stat__icon.blue, .stat__icon.green, .stat__icon.orange, .stat__icon.cyan {
    background: var(--kq-btn-fill); color: var(--kq-btn-on);
}
.stat__value { font-family: "Assistant", sans-serif; font-size: var(--t-2xl); font-weight: 700; line-height: 1; }
.stat__label { color: var(--muted); font-size: var(--t-md); margin-top: var(--s1); }
/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
    font: inherit; font-weight: 600; font-size: var(--t-md);
    min-block-size: 34px;
    padding: var(--s2) var(--s4); border-radius: var(--r);
    border: 1px solid transparent; cursor: pointer;
    position: relative;
    /* box-shadow is deliberately NOT in this list. Its blur radius is re-rasterised every
       frame and cannot be composited, so a hover over a toolbar of buttons repaints the
       whole strip; the shadow moved to the ::after layer below, where only opacity moves.
       `translate` rather than `transform` so the press composes with anything else. */
    transition: translate    var(--dur-fast) var(--e-standard),
                background   var(--dur-fast) var(--e-standard),
                border-color var(--dur-fast) var(--e-standard),
                color        var(--dur-fast) var(--e-standard);
    white-space: nowrap; text-decoration: none;
}
/* Down at 0ms, back up over --dur-fast. A transitioned press-down reads as lag on the one
   interaction where latency is most obvious — every save button in the product. */
.btn:active { transition-duration: 0ms; translate: 0 1px; }
.btn::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    pointer-events: none; opacity: 0;
    transition: opacity var(--dur-fast) linear;
}
/* A neutral lift, never a brand-coloured glow: a teal halo under a reseller's violet button
   was Kinetiq showing through the white label. */
.btn-primary::after { box-shadow: var(--shadow-lift); }
.btn svg { width: 17px; height: 17px; }
/* THE ACTION PAIR (A11Y-01). The face reads --kq-btn-fill (a flat colour for Kinetiq, the
   reseller's own gradient through the bridge); the edge and the hover read the solid tokens, so
   a gradient never has to be the thing that proves the contrast. */
.btn-primary {
    background: var(--kq-btn-fill); color: var(--kq-btn-on);
    border-color: var(--kq-btn-bg); box-shadow: var(--shadow-sm);
}
/* A GHOST BUTTON IS A BUTTON, and its frame is the only thing that says so: the fill is
   --surface-sunk, 1.06:1 against the white card it sits on. On --line it measured 1.31:1 against
   its own face — a control whose boundary has to be guessed (A11Y / WCAG 1.4.11). */
.btn-ghost { background: var(--ghost-bg); border-color: var(--control-edge); color: var(--ink-2); }
/* DEPRECATED — kept so no screen loses its styling before the screens move to .btn-ghost.
   A frame in the status ink, never a filled green button: a fill says "the main action here". */
.btn-success { background: var(--ghost-bg); border-color: var(--ok-ink); color: var(--ok-ink); }
/* Consequential actions. Framed, never subtle (SHELL-21): the old #f3c9c9 edge was 1.4:1 and the
   button read as plain text. The FILLED variant is the confirm button of a danger dialog only. */
.btn-danger { background: var(--n-0); border-color: var(--danger-edge); color: var(--danger-ink); }
.btn-danger-solid { background: var(--danger-ink); border-color: var(--danger-ink); color: var(--n-0); }
/* DEPRECATED, like .btn-success: the ink carries the word and the frame, both at 4.5:1+. */
.btn-warn { background: var(--n-0); border-color: var(--warn-ink); color: var(--warn-ink); }
.btn-sm { min-block-size: 28px; padding: var(--s1) var(--s3); font-size: var(--t-sm); border-radius: var(--r); }
.btn-block { width: 100%; }
/* aria-disabled too: a control that must stay focusable so its reason can be read. A BUSY button
   is also aria-disabled (kq-feedback) and keeps full strength — it is working, not refused. */
.btn[disabled], .btn[aria-disabled="true"]:not([aria-busy="true"]) { opacity: .55; cursor: not-allowed; }
.btn-lg { min-block-size: 42px; padding: var(--s3) var(--s5); font-size: var(--t-base); }
/* CENTRING IS NOT INHERITED FROM .btn. Every icon button in the product is `class="btn-icon"`
   WITHOUT `btn` — so the `display:inline-flex; align-items:center; justify-content:center` that
   .btn declares never reached any of them. The glyph sat wherever the default inline flow put
   it: baseline-aligned, and nudged off-centre by the descender space under it. Invisible on the
   34px desktop button, obvious on the 40px hamburger (owner 2026-08-06: "לא ממוקם באמצע").
   Declared here rather than by adding `btn` to every call site: this is what the class is for,
   and a markup fix would have to be repeated on each of them and remembered on the next one. */
.btn-icon {
    width: 34px; height: 34px; padding: 0; border-radius: var(--r);
    /* An icon button carries no word, so the box IS the control: on --line it was a 1.35:1 frame
       round a glyph, which reads as an icon printed on the page rather than a thing to press. */
    background: var(--n-0); border: 1px solid var(--control-edge); color: var(--ink-2);
    display: inline-flex; align-items: center; justify-content: center;
    flex: none; line-height: 0;
}
.btn-icon > svg { display: block; }
.topbar .btn-icon {
    background: var(--frame-ctl-bg); border-color: var(--frame-ctl-edge); color: var(--frame-ink);
    width: var(--kq-bar-ctl-h); height: var(--kq-bar-ctl-h);
}
/* ==========================================================================
   Tables
   ========================================================================== */
.table-wrap { overflow-x: auto; }
table.tbl { width: 100%; border-collapse: collapse; }
/* THE COLUMN NAMES SIT ON THE FRAME. That is this language's one structural move on the content
   side: the densest object in the product gets a hard top edge the eye can return to from row 25,
   and it is the same colour as the rail and the bar, so the journal reads as part of the tool
   rather than as a box that happens to be on the page.
   Contrast on the band: the names at --frame-ink-2 measure 9.4:1 and a sort link's hover at white
   17.3:1 — both far past what a grey band could give at any tone light enough to sit under black
   text. Scoped to `> thead > tr > th` and not to `th`: below 820px .is-cardable hides the thead
   and every value carries its own column name, so the band must not be able to follow the names
   into the cards. */
table.tbl > thead > tr > th {
    /* through the token, not a second copy of its value: the token is what the forced-colours
       branch and the phone card layout both have to be able to take away */
    background: var(--tbl-head-band);
    color: var(--frame-ink-2);
    font-size: var(--t-sm); font-weight: 600;
    padding: 9px var(--s4);
    border-bottom: 0;
    white-space: nowrap;
}
table.tbl > thead > tr > th a, table.tbl > thead > tr > th .cl-sort { color: var(--frame-ink-2); }
table.tbl > thead > tr > th a svg { opacity: .55; }
table.tbl > thead > tr > th .cl-sort.is-sorted { color: var(--frame-ink); }
table.tbl > thead > tr > th .cl-sort.is-sorted svg { opacity: 1; color: var(--rail-ink); }
table.tbl th {
    text-align: start; font-size: var(--t-sm);
    color: var(--n-8); font-weight: 500; padding: var(--s2) var(--s4) var(--s3); border-bottom: 1px solid var(--n-5);
    white-space: nowrap;
}
/* Sort-link headers (call journal) look exactly like every other th — muted, not link-teal
   (owner screenshot 2026-07-22); the accent appears only on hover / the active sort arrow. */
table.tbl th a {
    color: var(--muted); text-decoration: none;
}
@media (hover: hover) {
    table.tbl th a:hover {
        color: var(--kq-link-hover);
    }
}
/* 36px rows: 7px of lead, 13px text at 1.6 and a hairline. That is four more rows per 900px of
   viewport than the 10px padding it replaces — on a journal somebody reads all day, four rows is
   a scroll they do not make. The coarse-pointer block at the foot of this file puts the room back
   where a finger needs it. */
table.tbl td { padding: 7px var(--s4); vertical-align: middle; }
/* The row separator is drawn on the ROW, not on each cell. A cell that is itself a flex
   container (.row-actions) is pulled out of table-cell layout, so it sizes to its own content
   instead of the row — its cell border then landed above the neighbouring cells' and the
   separator looked stepped whenever another cell was taller (a role <select>, say).
   Bordering the row makes the line span the full width at the row's true bottom regardless. */
/* colour only, and no lift: a 1px translate on a 40px row makes a 50-row log shimmer
   like water as the pointer crosses it */
table.tbl tbody tr { transition: background-color var(--dur-fast) linear; border-bottom: 1px solid var(--line-2); }
table.tbl tbody tr:last-child { border-bottom: 0; }
.cell-user { display: flex; align-items: center; gap: var(--s3); }
.cell-user .n { font-weight: 600; }
.cell-user .e { font-size: var(--t-sm); color: var(--muted); }
.cell-user .n, .cell-user .e { min-width: 0; overflow-wrap: anywhere; }
/* a figure column: aligned on its end edge so the digits line up under one another */
.cell-num { text-align: end; white-space: nowrap; font-variant-numeric: tabular-nums slashed-zero; }
.row-actions { display: flex; gap: var(--s1); justify-content: flex-end; align-items: center; }
/* ==========================================================================
   Tabs (in-page section switch, e.g. the credential vault)
   ========================================================================== */
.tabs { display: flex; gap: var(--s1); margin-bottom: var(--s5); border-bottom: 1px solid var(--line); }
.tab {
    display: inline-flex; align-items: center; gap: var(--s2);
    padding: var(--s3) var(--s4); margin-bottom: -1px;
    font-weight: 600; font-size: var(--t-md); color: var(--muted); text-decoration: none;
    border-bottom: 2px solid transparent;
    transition: color var(--dur-fast) var(--e-standard), border-color var(--dur-fast) var(--e-standard);
}
.tab svg { width: 17px; height: 17px; }
/* The active tab is the action ink with an underline in the action fill — the brand teal was
   2.75:1 as the one word telling the reader which view they are in. aria-current is the state a
   screen reader hears; .active stays for the tabs that have not moved to it yet. */
.tab.active, .tab[aria-current="page"] { color: var(--kq-link-ink); border-bottom-color: var(--kq-btn-bg); }
/* --neutral-bg is 1.01:1 against the page ground, and the tab strip sits ON the ground — so the
   pill the kit asked for was a number floating in space on every list in the product. It gets a
   step of its own and an edge. */
.tab .count {
    font-size: var(--t-xs); font-weight: 700; color: var(--n-8);
    background: var(--n-3); border: 1px solid var(--n-4);
    border-radius: var(--r-pill); padding: 0 6px; line-height: 1.6;
    font-variant-numeric: tabular-nums;
}
.tab.active .count, .tab[aria-current="page"] .count {
    color: var(--kq-link-ink); background: var(--kq-action-soft);
    border-color: color-mix(in srgb, var(--kq-btn-bg) 26%, transparent);
}
/* a count that is a job waiting — late callbacks — rather than a size */
.tab .count.is-warn, .count.is-warn { color: var(--warn-ink); background: var(--warn-bg); border-color: var(--warn-edge); }
/* Search box with an icon inside the field (magnifier at the leading edge).
   The text starts after the icon's offset + the glyph + one gap (VLAB-34): the old s6 padding
   was 28px against a glyph that ends at 30px, so the first typed letter touched the magnifier. */
.search-box { position: relative; }
.search-box .control { padding-inline-start: calc(13px + 17px + var(--s2)); }
.search-box .search-icon {
    position: absolute; inset-inline-start: 13px; top: 50%; transform: translateY(-50%);
    color: var(--muted); pointer-events: none; display: inline-flex;
}
.search-box .search-icon svg { width: 17px; height: 17px; }
/* Tool parameters editor rows */
.params-rows { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s2); }
.param-row { display: grid; grid-template-columns: 150px 128px 1fr auto auto; gap: var(--s2); align-items: center; }
.param-row .control { margin: 0; }
.param-row .checkbox { white-space: nowrap; }
@media (max-width: 720px) { .param-row { grid-template-columns: 1fr 1fr; } }
/* Tools list on the bot editor */
.tool-item { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); padding: var(--s3) var(--s3); border: 1px solid var(--line); border-radius: var(--radius-sm); margin-bottom: var(--s2); }
.tool-item .tool-meta { min-width: 0; }
.tool-item .tool-name { font-weight: 600; display: flex; align-items: center; gap: var(--s2); min-width: 0; overflow-wrap: anywhere; }
.tool-item .tool-method { font-size: var(--t-xs); font-weight: 700; padding: 2px var(--s2); border-radius: var(--r); background: var(--kq-action-soft); color: var(--kq-link-ink); letter-spacing: .04em; }
/* Two lines, not one clipped line (GAP3-12): a 520px ellipsis cut most descriptions after a few
   words, and the description is the only place the list says what a tool is for. */
.tool-item .tool-desc {
    color: var(--muted); font-size: var(--t-md); margin-top: 2px; max-width: 72ch;
    overflow-wrap: anywhere;
    display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tool-item .tool-lead { display: flex; align-items: center; gap: var(--s3); min-width: 0; }
/* Circular service badge — marks an HTTP/API item (a credential, a connection, or a tool). */
.svc-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; border-radius: var(--r-circle); flex-shrink: 0;
    background: var(--kq-btn-fill); color: var(--kq-btn-on);
}
.svc-icon svg { width: 19px; height: 19px; }
.svc-icon.sm { width: 28px; height: 28px; }
.svc-icon.sm svg { width: 16px; height: 16px; }
/* ==========================================================================
   Badges / chips
   ========================================================================== */
/* STATUS BADGES (A11Y-10, DS-17). The word is in the status INK on its own tint, framed by its
   edge: the old fills as text measured 3.3:1 (green), 3.2:1 (amber) and 2.9:1 (the teal info).
   A badge is a 7px dot and a word — never the dot alone, never colour alone. */
.badge {
    display: inline-flex; align-items: center; gap: var(--s2);
    font-size: var(--t-sm); font-weight: 600; padding: 2px var(--s3); border-radius: var(--r-pill); line-height: 1.5;
    border: 1px solid transparent;
}
/* the dot inside a badge — one rule now that .dot, the older alias, is gone from every screen */
.kq-dot { display: inline-block; flex: none; width: 7px; height: 7px; border-radius: var(--r-circle);
          background: currentColor; }
.badge-ok { color: var(--ok-ink); background: var(--ok-bg); border-color: var(--ok-edge); }
/* a lighter edge than the shared --danger-edge, which stays the heavier line a danger FRAME
   needs; a badge is a word, and #D9544C around a pale tint reads as an alert the word is not */
.badge-danger { color: var(--danger-ink); background: var(--danger-bg); border-color: var(--danger-edge-soft); }
.badge-warn { color: var(--warn-ink); background: var(--warn-bg); border-color: var(--warn-edge); }
.badge-info { color: var(--info-ink); background: var(--info-bg); border-color: var(--info-edge); }
.badge-muted { color: var(--neutral-ink); background: var(--neutral-bg); border-color: var(--neutral-edge); }
/* Premium feature marker — a warm gold gradient, distinct from the status colours. */
.badge-premium {
    color: var(--premium-ink); background: var(--premium-fill); border: 0;
    font-size: var(--t-xs); font-weight: 800; padding: var(--s1) var(--s2); line-height: 1.4;
}
/* SIZED, NOT SCALED. `transform: scale(.9)` shrank the picture and never the layout box, so the
   badge painted 2.32px and 4.84px smaller than the 23.22 × 48.36 it occupied — the gap before it
   was not the gap every other row has, and its box made its row 2.22px taller than the nine
   without one. A stated height instead, inside the row's own line. */
.nav-premium {
    flex: none; margin-inline-start: auto;
    display: inline-flex; align-items: center; block-size: 18px;
    padding-block: 0; line-height: 1;
}
/* A role is a label, not a status and not a link: neutral ink on a quiet tint (DS-03 — the
   reseller secondary as text vanished on a light brand). */
.chip-role { font-size: var(--t-xs); font-weight: 600; padding: var(--s1) var(--s2); border-radius: var(--r); background: var(--neutral-bg); color: var(--ink-2); }
/* --ink-2 is derived against the PAGE GROUND, and this pill is a 12% brand tint that is darker
   than the ground for a saturated hue — 4.46:1 under #6D28D9, under the floor for 11px/600. --ink
   is the tier already gated for body copy: 5.83:1 there and 17.22:1 under a pale brand. */
.chip-role.admin { background: var(--kq-action-soft); color: var(--ink); }
/* ==========================================================================
   Forms
   ========================================================================== */
.form-row { margin-bottom: var(--s5); }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4) var(--s4); }
.grid-method-url { display: grid; grid-template-columns: 130px 1fr; gap: var(--s4) var(--s4); align-items: start; }
label.lbl { display: block; font-weight: 600; font-size: var(--t-md); margin-bottom: var(--s2); color: var(--n-8); }
label.lbl .req { color: var(--danger-ink); }
.control, input.control, select.control {
    width: 100%; padding: 8px 11px; min-block-size: 38px; font: inherit; color: var(--ink);
    background: var(--control-bg); border: 1px solid var(--control-edge); border-radius: var(--r);
    transition: border-color var(--dur-fast) var(--e-standard), box-shadow var(--dur-fast) var(--e-standard);
}
/* The mouse focus is the action colour's edge plus its soft tint — the old --kq-blue was the brand
   teal, the one accent a reseller never meant as "you are typing here". */
.control:focus { outline: none; border-color: var(--kq-btn-bg); box-shadow: 0 0 0 3px var(--kq-action-soft); }
/* `.control:focus` is (0,2,0) and the global `:focus-visible` is (0,1,0), so that
   `outline: none` was winning on KEYBOARD focus too — leaving every input in the app,
   the login form included, with only --ring, which composites to #ccecee over white:
   1.23:1. Same specificity, placed after, so the keyboard gets a ring and a mouse
   click still does not. */
.control:focus-visible {
    outline: 2px solid var(--ink); outline-offset: 1px;
    box-shadow: var(--focus-halo), var(--ring);
}
/* 2.65:1 before — a placeholder is text and has to be readable, not a hint of one */
.control::placeholder { color: var(--n-7); }
/* A phone, an e-mail or a key is typed left to right but still starts at the column edge the
   rest of an RTL form starts at. Not a textarea: a JSON body, a header list or a column of numbers
   is several lines, and aligning each line to the right on its own destroys the indentation. */
.control[dir="ltr"]:not(textarea) { text-align: right; }
.hint { font-size: var(--t-sm); color: var(--muted); margin-top: var(--s1); }
/* Country select with flag */
.cselect { position: relative; }
.cselect__btn { display: flex; align-items: center; gap: var(--s2); width: 100%; text-align: right; cursor: pointer; }
.cselect__btn .cselect__name { flex: 1; }
.cselect__flag { display: inline-flex; flex: none; }
.cselect__chev { flex: none; color: var(--muted); display: inline-flex; }
.cselect__chev svg { width: 16px; height: 16px; }
.cselect.open .cselect__chev { transform: rotate(180deg); }
.cselect__list { position: absolute; top: calc(100% + 4px); inset-inline: 0; z-index: 30; margin: 0; padding: var(--s1);
    /* A FLOAT — a pop-over opened over the form behind it — and CONTENT, so it keeps the light
       float rather than the frame's glass; see .usermenu__panel. */
    list-style: none; background: var(--float-bg-strong);
    -webkit-backdrop-filter: var(--float-blur); backdrop-filter: var(--float-blur); border: 1px solid var(--surface-edge); border-radius: var(--r);
    box-shadow: var(--float-shine), var(--shadow-pop); max-height: 280px; overflow: auto; }
/* TWO LINES PER OPTION. The flag and the number on the first; a consequence note such as
   «יועבר מ: …» wraps onto its own line under the number instead of being pushed off the end
   of a nowrap row, where a long agent name used to disappear. */
.cselect__opt {
    display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center;
    column-gap: var(--s2); row-gap: 2px;
    padding: var(--s2) var(--s3); border-radius: var(--r); cursor: pointer;
}
.cselect__opt > :not(.cselect__flag) { min-width: 0; overflow-wrap: anywhere; }
@media (hover: hover) { .cselect__opt:hover { background: var(--line-2); } }
/* the keyboard's position in the list, and the value that is chosen — two different states */
/* THE KEYBOARD'S POSITION IS AN INDICATOR, NOT A TINT: --line-2 paints 1.23:1 on the float list
   and it is the only thing marking the option the arrow keys are on. The ring is the 2px
   --kq-btn-bg the sheet already uses for a chosen thing, 6.00:1 on the list; the fill stays, one
   step lighter, so the row still reads as picked out. The [aria-selected="true"] rule on the next
   line is equal specificity and later, so a row that is both keeps the chosen fill and the ring. */
.cselect__opt.is-active { background: var(--row-hover); box-shadow: inset 0 0 0 2px var(--kq-btn-bg); }
.cselect__opt[aria-selected="true"] { background: var(--kq-action-soft); font-weight: 600; }
/* ERROR TEXT is the danger ink at 600 — the old --danger fill was 4.1:1 at 12px regular. The
   empty validation span Razor writes for a valid field takes no space. */
.field-error {
    display: flex; align-items: flex-start; gap: var(--s1);
    color: var(--danger-ink); font-size: var(--t-sm); font-weight: 600; margin-top: var(--s1);
}
.field-error:empty, .field-error.validation-summary-valid { display: none; }
.field-error > svg { flex: none; width: 16px; height: 16px; margin-top: 2px; }
.input-pw { position: relative; }
/* Reserve room for the eye toggle (sits at the physical-left in this RTL app) so
   the password text — RTL or LTR (dir="ltr") — never runs under it. */
.input-pw .control { padding-inline-end: var(--s7); }
.input-pw .toggle {
    position: absolute; inset-inline-end: 6px; top: 50%; transform: translateY(-50%);
    background: none; border: 0; cursor: pointer; color: var(--muted); padding: var(--s1); border-radius: var(--r);
}
@media (hover: hover) { .input-pw .toggle:hover { background: var(--bg); color: var(--ink); } }
/* The in-house icon set and its turquoise accent were reverted 2026-08-03 (owner: "תחזיר
   את האימוג'ים הרגילים"). The icons are plain single-colour strokes again, so the accent
   variable and the per-surface flips that went with it are gone rather than left inert. */

.checkbox { display: inline-flex; align-items: center; gap: var(--s2); cursor: pointer; font-size: var(--t-md); }
/* RADIOS ONLY. This used to read `.checkbox input`, which also sized the checkboxes and then
   fought the switch rules below on equal specificity — whichever came last in the file won. */
.checkbox input[type="radio"] { width: 18px; height: 18px; accent-color: var(--kq-btn-bg); }
/* ── Toggles ─────────────────────────────────────────────────────────────────────────
   Every checkbox on the platform is drawn as a switch (owner 2026-08-01: "שכל הצ'קבוקסים
   יהפכו להיות מתגים"). Applied to the ELEMENT, not to an opt-in class, so a checkbox added
   anywhere later is a switch too without anyone having to remember.
   type="checkbox" ONLY: the same .checkbox wrapper also holds RADIO inputs — the transfer
   insistence levels, the address mode — and an exclusive choice drawn as three switches reads
   as three independent settings. .ci-advtoggle is excluded because it is not a control at all;
   it is a hidden CSS mechanism behind the advanced-filter button. */
input[type="checkbox"]:not(.ci-advtoggle) {
    appearance: none; -webkit-appearance: none;
    position: relative; flex: 0 0 auto; display: inline-block;
    width: 40px; height: 22px; border-radius: var(--r-pill); margin: 0;
    /* THE OFF TRACK GOES ONE STEP LIGHTER, NOT DARKER (2026-09-19). The pill's OUTLINE is what
       identifies the control — the track itself is 1.2:1 on a white card whatever step it takes —
       and against --n-4 that outline measured 2.71:1. On --n-3 it is 3.04:1 against the track and
       3.75:1 against the card, so both sides of the line clear WCAG 1.4.11 for the first time. */
    background: var(--n-3); border: 1px solid var(--control-edge);
    cursor: pointer; vertical-align: middle;
    /* NO TRANSITION (SHELL-28). The control the reader pressed changes state at once; motion is
       kept for content that ARRIVES, never for the thing under the finger. */
}
/* the knob. inset-inline-start, not left: in RTL the switch must start on the right and
   travel left, and a hard-coded `left` would run the wrong way on every screen we have.
   Placed, not moved: with no animation there is no per-frame layout to avoid, so the knob sits
   at its two positions directly — 38px padding box − 16px knob − 2px inset = 20px — with no
   transform for a stylesheet or a forced-colours mode to fight. */
input[type="checkbox"]:not(.ci-advtoggle)::after {
    content: ""; position: absolute; top: 2px; inset-inline-start: 2px;
    width: 16px; height: 16px; border-radius: var(--r-circle); background: var(--n-0);
    box-shadow: var(--knob-shadow);
}
/* ON is the action colour: the brand teal gave the white knob 2.6:1 against its track */
input[type="checkbox"]:not(.ci-advtoggle):checked {
    background: var(--kq-btn-bg); border-color: var(--kq-btn-bg);
}
input[type="checkbox"]:not(.ci-advtoggle):checked::after { inset-inline-start: 20px; }
input[type="checkbox"]:not(.ci-advtoggle):disabled { opacity: .45; cursor: not-allowed; }
/* No focus rule here on purpose: a teal ring measures 2.75:1 on white, and this one was more
   specific than the global two-tone ring, so switches would have kept the failing colour. */
/* a switch is wider than the 18px box it replaced, so a label that hung its text off the old
   width needs the flex row to keep the two apart rather than let the text ride the track */
.checkbox > input[type="checkbox"] { margin-inline-end: 0; }
/* ── THE ONE DOCUMENTED OPT-OUT FROM THE SWITCH RULE (owner 2026-08-06: "שבעמוד התחברות
   לממשק יהיה צ'קבוקס בזכור אותי ולא מתג") ────────────────────────────────────────────────
   Every checkbox on the platform is a switch, and that stays true — the rule above still
   targets the ELEMENT, so anything added later is a switch without anyone remembering. This is
   an explicit opt-out, the same shape as .ci-advtoggle, not a hole in the rule.
   The reason it is right here: a switch says "this setting is now on", something that takes
   effect the moment you flip it. «זכור אותי» does nothing until the form is submitted — it is a
   choice ABOUT the submission, like the fields above it, and a switch on a login form promises an
   immediacy the control does not have.
   appearance:auto and not a hand-drawn box: the native checkbox is the one control every user
   already recognises, and accent-color tints it without taking over its rendering. */
input[type="checkbox"].kq-plain {
    appearance: auto; -webkit-appearance: auto;
    width: 18px; height: 18px; border-radius: 0;
    background: none; border: 0;
    accent-color: var(--kq-btn-bg);
    transition: none; cursor: pointer;
}
/* the knob belongs to the track this control no longer has */
input[type="checkbox"].kq-plain::after { content: none; }
.form-row.narrow { max-width: 260px; }
/* Grouped settings sections + capability rows (voice-engine settings screen) */
.section-head { display: flex; align-items: flex-start; gap: var(--s3); margin-bottom: var(--s4); }
.section-head .t { font-size: var(--t-base); font-weight: 700; }
.section-head .d { font-size: var(--t-md); color: var(--muted); margin-top: var(--s1); }
.section-sep { height: 1px; background: var(--line); margin: var(--s1) 0 var(--s4); }
.feature { padding: var(--s4) 0; border-bottom: 1px solid var(--line-2); }
.feature:first-of-type { padding-top: var(--s1); }
.feature:last-of-type { border-bottom: 0; padding-bottom: var(--s1); }
.feature > .checkbox { font-size: var(--t-base); font-weight: 600; }
.feature .d { font-size: var(--t-md); color: var(--muted); margin: var(--s1) 0 0; padding-inline-start: var(--s5); }
.feature .form-row { margin: var(--s3) 0 0; padding-inline-start: var(--s5); }
/* ==========================================================================
   Page header + misc
   ========================================================================== */
/* THE PAGE HEADER (approved kit, 2026-09-17): the page's only h1, an optional back link above it
   and one status sentence under it at the start; the page's own actions at the end. No rule under
   it any more — the panels below are glass and carry their own edges, and a hairline on top of
   them was a second separator competing with the first. */
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s4);
             margin-bottom: var(--s5); flex-wrap: wrap; }
.page-head > div { min-width: 0; }
/* No tracking, even the -0.005em the dark-frame sheet asked for: Hebrew has no optical gaps to
   close and tightening shears the letterforms (the h1 rule at the head of this file says the same,
   and mobile-test.sh enforces it). The figure tiles keep theirs — that rule sets tabular figures
   and is typesetting numbers. */
.page-head .t { font-size: var(--t-xl); font-weight: 700; line-height: 1.25; margin: 0; min-width: 0; overflow-wrap: anywhere; }
.page-head .d { color: var(--muted); font-size: var(--t-md); margin: var(--s1) 0 0; }
.divider { height: 1px; background: var(--line); margin: var(--s5) 0; }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: var(--s2) var(--s4); font-size: var(--t-base); }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; font-weight: 500; }
.tag-copy { font-variant-numeric: tabular-nums slashed-zero; font-size: var(--t-sm); background: var(--bg); padding: 2px var(--s2); border-radius: var(--r); border: 1px solid var(--line); }
/* ==========================================================================
   Auth (login / change password)
   ========================================================================== */
/* THE FRAME WITH A SHEET OF PAPER ON IT. The card is the same on all five auth screens: it is the
   first thing a customer sees and the only place with no rail to say whose product this is, so the
   page says it with the frame itself — and the card stays white, because the form is the work.
   SIGN-IN ALSO GETS THE SPLIT HERO BACK (owner 2026-09-20) — see .auth--hero below. What does NOT
   come back is the old markup: the hero used to fall back to the KIVOICE image, which put the
   platform's logo on a reseller's sign-in page. Every mark here is _BrandMark's now. */
body:has(> #main.auth), body:has(.auth) { background: var(--frame-bg); }
/* THE ONE PLACE A WASH SURVIVES, and the reason is the reason it died everywhere else: the wash
   went because muted ink sat on it and its contrast changed pixel by pixel. Nothing sits on this
   one — the card is opaque and the field around it is empty — so a single soft radial in the
   reseller's own primary gives the page depth and puts the brand on the first screen a customer
   ever sees, at no cost anyone can measure. One radial, not three. */
.auth {
    min-height: 100vh; display: grid; place-items: center;
    padding: var(--s6) var(--s4);
    background-color: var(--frame-bg);
    background-image: radial-gradient(1000px 560px at 50% -8%, rgba(var(--kq-cyan-rgb), .20), transparent 62%);
}
.auth__card {
    width: min(400px, 100%); min-width: 0;
    display: flex; flex-direction: column; gap: var(--s5);
    padding: var(--s6);
    background: var(--surface-material);
    border: 1px solid rgba(255, 255, 255, .10); border-radius: 14px;
    box-shadow: 0 30px 70px -28px rgba(0, 0, 0, .7);
}
.auth__card h1 { font-size: var(--t-xl); font-weight: 700; line-height: 1.25; margin: 0; }
.auth__card form { margin: 0; }
.auth__mark { display: flex; align-items: center; min-width: 0; }
.auth__mark .brandmark__logo { max-width: 200px; max-height: 56px; }
/* A heading with its one sentence under it, on the sign-in card. The card is a flex column with a
   var(--s5) gap and this file has no `p` reset, so a restored <p> would otherwise carry the UA's
   1em on top of that gap; the negative start pulls the pair back to the var(--s4) the pre-redesign
   screen measured. The ink is --ink-2, which is what .auth__intro p already uses for exactly this
   sentence on the other four auth screens. */
.auth__card .lead { margin: calc(var(--s1) * -1) 0 0; color: var(--ink-2); font-size: var(--t-base); }

/* ── THE SIGN-IN SPLIT (restored 2026-09-20) ────────────────────────────────────────────────────
   The owner asked for the sign-in screen's APPEARANCE back, colours aside. COMPOSITION is the
   pre-redesign screen verbatim — the 1.05fr/.95fr grid, the hero mark at min(80%, 460px), the card
   centred in its own half, the collapse at 980px. COLOUR is today's and only today's.
   ONE FLAG, ONE SCREEN: _AuthLayout adds .auth--hero only when a page sets ViewData["AuthHero"],
   which only Login.cshtml does. Flipping it on the reset, the invitation, the forced change or
   access denied gives them the hero too — that they stay on the single card is a decision.
   THE SEAM IS THE COMPOSITION, not a colour (amended 2026-09-20, second pass). The first pass
   restored every number on this screen and still did not look like it, because it painted both
   halves the frame: the seam measured ≤1 unit where the pre-redesign screen stepped 218, and a
   split nobody can see is not a split — it reads as a card floating on a dark page with a mark off
   to one side. That earlier reading («the seam is colour, therefore out of scope») was wrong. The
   split is restored out of the two materials this sheet ALREADY has, so not one colour is invented:
   the form half takes the product's light page ground and the brand half takes the frame.
   WHAT THE OLD RULE HAD AND THIS ONE STILL DOES NOT: var(--grad-sidebar) and --sb-ink/--sb-ink-2.
   Those ink tokens are dead in this sheet and the mid-grey gradient is the material the frame
   decision replaced, so the dark half is flat --frame-bg. Its old drop-shadow stays gone too:
   measured against a control column it peaked at 4-5 of 255 on that gradient, and on the frame it
   is nothing at all. */
.auth--hero { grid-template-columns: 1.05fr .95fr; place-items: stretch; padding: 0; }
.auth__brand {
    /* THE HERO'S OWN GROUND. It was standing down because a radial clipped to this box would have
       drawn its own rectangle on a page whose two halves were the same colour. That reason expired
       the moment the seam came back: the box has a visible edge again, so the rectangle IS the
       panel. The frame, plus the two soft radials of the pre-redesign rule over it, in the brand
       accent, at its two positions and its two alphas. Painted on the element rather than through
       ::after because nothing paints between an element's background and its content — same
       picture, and it is what makes .auth__brand > * unnecessary. The page's own radial cannot
       bleed into the light half: .auth__panel is opaque over it.
       Ink is the frame's second tier, because this panel IS the frame — the same material as the
       rail, which is why the mark it carries is the rail's. */
    background-color: var(--frame-bg);
    background-image:
        radial-gradient(620px 320px at 12% 12%, rgba(var(--kq-cyan-rgb), .22), transparent 60%),
        radial-gradient(520px 320px at 90% 88%, rgba(var(--kq-cyan-rgb), .12), transparent 55%);
    color: var(--frame-ink-2); min-width: 0;
    display: flex; align-items: center; justify-content: center;
    text-align: center; padding: var(--s7);
}
/* A RESELLER'S OWN FILE ON THE DARK HERO, AND WHY THERE IS NO PLATE UNDER IT. A light plate was
   tried here on 2026-09-20 and measured BOTH ways before it shipped: it takes a dark-ink upload
   from 1.14:1 to 14.83:1, and a LIGHT-ink upload from 15.70:1 to 1.08:1. CSS cannot see a file's
   ink, so a plate keyed on «this brand uploaded something» always breaks one of the two cases.
   It was removed, and the case it would have protected is the rarer one here: the rail has been
   dark since long before this design — a dark-ink file measures ~1.14:1 there too — so a reseller
   whose logo works anywhere in this product already uploaded light ink, and the hero asks
   _BrandMark for place="rail" precisely so it gets the same file the rail does. A logo that
   cannot read on the frame is a branding-screen problem, not a hero problem. */
.auth__hero { display: flex; flex-direction: column; align-items: center; min-width: 0; }
/* The 200x56 cap belongs to the CARD's mark. The hero is the one place the mark is the picture
   rather than a label, so every cap on it — .brandmark__logo's and .sidebar__logo's — is released
   here and the old width rule takes over. */
.auth__hero img, .auth__hero .brandmark__logo {
    width: min(80%, 460px); max-width: none; max-height: none; height: auto; margin: 0;
}
/* A reseller with no logo of its own gets its wordmark here — the whole reason the old hero had to
   go was that it showed KIVOICE's image to exactly this brand. Hero-sized and stacked: the rail's
   28px square and 20px name would be a speck in a 756px panel, and a long name on one nowrap line
   would be ellipsised down to nothing. --frame-ink because this is the frame.
   .auth--hero IS LOAD-BEARING IN THESE SELECTORS, not decoration: the brand kit's own
   .kq-wordmark--rail rules are two classes and sit 1,700 lines further down, so a two-class
   selector here loses to them on source order and the mark comes out rail-sized. Measured. */
.auth--hero .auth__hero .kq-wordmark { flex-direction: column; gap: var(--s4); max-width: 100%; }
.auth--hero .auth__hero .kq-wordmark__sq { width: 96px; height: 96px; border-radius: 20px; font-size: 52px; }
.auth--hero .auth__hero .kq-wordmark__name {
    font-size: 34px; color: var(--frame-ink);
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}
/* ABOVE 980px THE HERO IS THE BRAND MARK, so the card's own mark stands down — one mark at every
   width, which is what the old .auth__mobilelogo pair did with two images. The media query below
   hands it back when the hero goes. */
.auth--hero .auth__mark { display: none; }
/* The panel is a box only under the hero, where it keeps the card off the split's edge and lets
   .auth__brand bleed to the page edge (the hero's own padding is 0 for that reason). Everywhere
   else it is not a box at all, so the other four screens keep .auth's centring and padding exactly
   as they are.
   IT IS ALSO THE LIGHT HALF OF THE SPLIT. The pre-redesign panel was var(--bg), and that token
   still names the same role — the light ramp's --n-1, the product's page ground — so this is the
   old rule in today's palette and not a colour decision of its own. Opaque, which is what stops
   .auth's page radial reaching this side of the seam. */
.auth__panel { display: contents; }
.auth--hero .auth__panel {
    display: flex; align-items: center; justify-content: center;
    padding: var(--s6); min-width: 0;
    background: var(--bg);
}
/* NO CARD ON THIS ONE SCREEN. The pre-redesign form sat directly on the light panel: .auth__card
   was `width: 100%; max-width: 400px` and nothing else — no padding, no fill, no edge, no radius,
   no shadow. With the seam back there is nothing left for a card to float on, and the chrome was
   costing the form its width as well: 26px of padding plus a 1px edge made every field 346px where
   the old ones measured 400. THE OTHER FOUR AUTH SCREENS KEEP THEIR PAPER CARD — they have no
   split and no hero, so the card is the only thing holding them together, and this rule cannot
   reach them. The flex column and its gap stay: they are what replaced the old children's margins,
   and every restored measurement on this screen was taken with them. */
.auth--hero .auth__card {
    width: 100%; max-width: 400px;
    padding: 0; background: none; border: 0; border-radius: 0; box-shadow: none;
}
/* THE ONE LTR FIELD THAT DOES NOT START AT THE RTL COLUMN EDGE. The shared rule above
   (.control[dir="ltr"]) right-aligns an LTR value so it lines up with the Hebrew labels, and it
   stays that way everywhere — including the password on this very form. The sign-in E-MAIL is
   restored to the screen's pre-redesign appearance (owner 2026-09-20), where «you@company.com»
   sat at the field's own start: measured, the caret was at x=153 before the redesign and at x=529
   after it, and that is the difference on this screen a person points at. */
.auth--hero .auth__card input[type="email"].control { text-align: start; }
/* the secondary route under the form — «שכחתי סיסמה» */
.auth__alt { margin: calc(var(--s2) * -1) 0 0; text-align: center; font-size: var(--t-md); font-weight: 600; }
.auth__foot {
    margin: 0 calc(var(--s6) * -1) calc(var(--s4) * -1); padding: var(--s4) var(--s6) 0;
    border-top: 1px solid var(--line); text-align: center; font-size: var(--t-sm);
}
.auth__foot a { color: var(--muted); }
/* ==========================================================================
   Responsive
   ========================================================================== */
.menu-toggle { display: none; }
@media (max-width: 980px) {
    /* THE COLLAPSE, at the width the pre-redesign screen used. .auth--hero is named beside .auth
       on purpose: both are one class, so with only `.auth` here the wider rule above would win on
       source order alone and the split would survive onto the phone. */
    .auth, .auth--hero { grid-template-columns: 1fr; }
    .auth--hero .auth__brand { display: none; }
    /* the hero is gone, so the card's own mark comes back — centred, where the old phone logo was */
    .auth--hero .auth__mark { display: flex; justify-content: center; }
    .grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
    .grid.cols-3 { grid-template-columns: 1fr; }
    .grid.cols-2 { grid-template-columns: 1fr; }
    .form-grid { grid-template-columns: 1fr; }
    .grid-method-url { grid-template-columns: 1fr; }
}
@media (max-width: 820px) {
    /* THE PHONE RAIL DROPS ITS BLUR AND KEEPS ITS LIFT. It slides OVER the page here rather than
       sitting beside it — but the frame is opaque at every width, so a blur would be rastering a
       240 x 844 layer whose result is hidden by the surface on top of it: 68.4% of a phone
       viewport, measured, for nothing. The scrim behind it is what says the page is still there,
       and the shadow is what says the rail is in front of it. */
    .sidebar { transform: translateX(100%); transition: transform var(--dur-base) var(--e-out);
               box-shadow: var(--shadow-lg);
               -webkit-backdrop-filter: none; backdrop-filter: none; }
    .sidebar.open { transform: translateX(0); }
    .main { margin-inline-start: 0; }
    .menu-toggle { display: inline-flex; }
    .content { padding: var(--s4) var(--s4) var(--s6); }
    .topbar { padding: var(--s3) var(--s4); }
}
/* scrim for mobile sidebar — a fixed dark veil, never a brand tint (DS-10) */
.scrim { display: none; position: fixed; inset: 0; background: var(--scrim); z-index: 35; }
.scrim.show { display: block; }
/* ==========================================================================
   Dashboard — statistics, charts, breakdowns
   ========================================================================== */
.delta { display: inline-flex; align-items: center; gap: var(--s1); margin-top: var(--s2); font-size: var(--t-xs); font-weight: 700; padding: var(--s1) var(--s2); border-radius: var(--r-pill); }
.delta.up   { background: var(--ok-bg); color: var(--ok-ink); }
.delta.down { background: var(--danger-bg); color: var(--danger-ink); }
.dash-two { display: grid; grid-template-columns: 3fr 2fr; gap: var(--s4); margin-top: var(--s4); align-items: stretch; }
.dash-two.alt { grid-template-columns: 2fr 3fr; }
.chart-svg { width: 100%; height: auto; display: block; }
.chart-svg .chart-xlabel { font-size: var(--t-xs); fill: var(--muted); font-family: inherit; }
.chart-svg .chart-vlabel { font-size: var(--t-xs); font-weight: 700; fill: var(--ink-2); font-family: inherit; font-variant-numeric: tabular-nums slashed-zero; }
.chart-svg rect { transition: opacity var(--dur-fast) var(--e-standard); }
@media (hover: hover) { .chart-svg rect:hover { opacity: 1; } }
@media (max-width: 900px) { .dash-two, .dash-two.alt { grid-template-columns: 1fr; } }
/* DID quick-assign on the bot list — a flag-inside country-select; picking saves */
/* While a picker is open, the table's scroll container must not clip its list. */
.table-wrap:has([data-cselect].open) { overflow: visible; }
.did-assign .cselect { min-width: 195px; max-width: 250px; }
.did-assign .cselect__btn { padding: var(--s2) var(--s3); font-size: var(--t-md); }
.did-assign .cselect__list { max-height: 220px; overflow-y: auto; }
.cselect__opt[data-disabled] { opacity: .45; cursor: not-allowed; }
/* ── שדות חובה (חוק 2026-07-24): כוכבית על lbl req בלבד; שדה רשות ללא סימון ── */
.lbl.req::after { content: " *"; color: var(--danger-ink); font-weight: 700; }
/* ── סיכומי שיחות — תצוגת כרטיסיות (2026-07-24, סבב 2: היררכיה ורווחים לפי הבעלים) ── */
.ci-grid { display: grid; grid-template-columns: 1fr; gap: var(--s3); }
/* Paper, like every other panel (DS-13). The HOVER is where this one changed twice: it was a teal
   glow, then a shadow swap, and a shadow transition re-rasterises a blur on every frame. It now
   drops to the seat and darkens the EDGE — colour is the half that survives prefers-reduced-motion
   intact, and with a real edge there is finally an edge to darken. */
.ci-card { background: var(--surface-material); border: 1px solid var(--surface-edge); border-radius: var(--r-card);
           box-shadow: var(--surface-lift);
           padding: var(--s4) var(--s5); display: flex; flex-direction: column; min-width: 0;
           transition: box-shadow var(--dur-fast) var(--e-standard), border-color var(--dur-fast) var(--e-standard); }
@media (hover: hover) { .ci-card:hover { box-shadow: var(--surface-seat); border-color: var(--line-strong); } }
/* העיקר בימין: מתקשר + בוט; המשני בשמאל: כיוון + תאריך-שעה */
.ci-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3); }
.ci-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ci-caller { font-weight: 700; font-size: var(--t-lg); overflow-wrap: anywhere; }
.ci-bot { color: var(--muted); font-size: var(--t-sm); }
.ci-meta { display: flex; flex-direction: column; align-items: flex-end; gap: var(--s1); }
.ci-when { color: var(--muted); font-size: var(--t-xs); font-variant-numeric: tabular-nums slashed-zero; white-space: nowrap; }
.ci-summary { line-height: 1.65; font-size: var(--t-md); margin: var(--s3) 0 0;
              padding-top: var(--s3); border-top: 1px solid var(--line); }
/* כל שדה בשורה נפרדת */
.ci-fields { display: flex; flex-direction: column; gap: var(--s2); font-size: var(--t-md);
             background: var(--bg); border-radius: var(--r); padding: var(--s3) var(--s3); margin-top: var(--s3); }
.ci-field { display: flex; gap: var(--s2); align-items: baseline; }
/* The field name is told apart by its weight and its colon, not by a colour: the brand teal was
   2.75:1 here (DS-V01). */
.ci-k { color: var(--ink-2); font-weight: 600; white-space: nowrap; }
.ci-k::after { content: ":"; }
.ci-v { overflow-wrap: anywhere; line-height: 1.5; }
.ci-foot { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s3); }
/* ── סיכומי שיחות: סרגל סינון + פאנל מתקדם מתקפל (CSS בלבד, בלי JS) ── */
/* ═══ THE FILTER BAR, AS A TOOLBAR (owner 2026-08-03: "שיהיה הרבה יותר יפה ומקצועי") ═══
   It used to be loose controls floating on the page background between the heading and the
   table, with the advanced panel opening as a SECOND bordered box below it — so turning on a
   filter grew a second object on the page instead of extending the first one.
   Now it is one surface. The panel opens inside it, under a hairline, and the applied filters
   are listed back as chips so you can see what the list is filtered by WITHOUT opening the
   panel. That was the real gap: with the panel shut, a date range or a sentiment filter was
   invisible, and a short result set looked like the whole journal. */
/* It sits directly above the results card and used to be a second sheet of glass on top of the
   first. It stays PAPER — its fields are the sunk surface, and a sunk bar holding sunk fields is
   one flat grey rectangle — but it drops to the seat, so the results card below it is the heavier
   of the two, which is the order they should be read in. */
.ci-filterbar {
    margin-bottom: var(--s3); padding: var(--s3) var(--s3);
    background: var(--surface-material); border: 1px solid var(--line); border-radius: var(--r-card);
    box-shadow: none;
}
.ci-filterrow { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
/* Toolbar controls stand down to the height of a small button. A 44px field is right in a form,
   where it is the thing you came to fill in; in a strip of buttons it is just the loudest. */
.ci-filterrow .control { padding: var(--s2) var(--s3); min-block-size: 34px; font-size: var(--t-md); border-radius: var(--r); }
.ci-filterrow .search-box .control { padding-inline-start: calc(13px + 17px + var(--s2)); }
.ci-filterrow .search-icon svg { width: 15px; height: 15px; }
/* The search sits at the far edge of the filter row, the same edge on every tab (owner
   2026-08-03: "באותה שורה של הסינון בצד שמאל"). It is logical, not left: in RTL the auto
   margin pushes it to the left end, and the same rule would send it right in an LTR tenant.
   When the row is too narrow to hold both, the auto margin is dropped rather than left to
   strand the field mid-line — it takes the next line whole. */
.ci-filterrow .search-box { margin-inline-start: auto; flex: 0 1 320px; min-width: 220px; }
@media (max-width: 760px) {
    .ci-filterrow .search-box { margin-inline-start: 0; flex: 1 1 100%; }
}
.ci-advtoggle { position: absolute; opacity: 0; pointer-events: none; }
.ci-advtoggle:checked + label { background: var(--kq-btn-fill); color: var(--kq-btn-on); border-color: var(--kq-btn-bg); }
.ci-advpanel { display: none; }
.ci-advtoggle:checked ~ .ci-advpanel,
.ci-filterbar:has(.ci-advtoggle:checked) .ci-advpanel {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 220px)); gap: var(--s3);
    margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line-2);
}
.ci-advfield .lbl { font-size: var(--t-sm); margin-bottom: var(--s1); display: block; }
/* Applied filters, said back. Each chip drops its own filter and keeps the rest — which is
   what makes the row usable: narrowing down is easy on any interface, and backing out one step
   is the part people cannot do without re-picking everything from scratch. */
.ci-chips { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
            margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line); }
.ci-chips__lbl { font-size: var(--t-xs); color: var(--muted); font-weight: 600; }
/* A CHIP IS A CONTROL — it is the link that takes its own filter off — and its fill is 1.06:1
   against the card, so the border is the only thing that says it is there. A 24% tint of the
   action colour measured 1.40:1 against that fill; the solid token measures 5.24:1. Heavier than
   a hairline wants to be, and correct: at 1.4 nobody could see where to click. */
.ci-chip {
    display: inline-flex; align-items: center; gap: var(--s2); min-width: 0; max-width: 100%;
    padding: 2px var(--s1) 2px var(--s3); border-radius: var(--r-pill);
    background: var(--kq-action-soft); border: 1px solid var(--kq-btn-bg);
    font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink); text-decoration: none;
    transition: border-color var(--dur-fast) var(--e-standard),
                background-color var(--dur-fast) var(--e-standard);
}
.ci-chip b { font-weight: 700; color: inherit; min-width: 0; overflow-wrap: anywhere; }
/* the × is drawn, not typed: a font glyph would sit off-centre and change size with the face */
.ci-chip svg { width: 13px; height: 13px; color: currentColor; flex: none; }
@media (hover: hover) {
    .ci-chip:hover { background: var(--danger-bg); border-color: var(--danger-edge); color: var(--danger-ink); }
}
/* ═══ THE CALL JOURNAL (owner 2026-08-03: "הרבה יותר יפה ומובן") ═══════════════════════
   What was wrong was not the styling, it was that the row spent its weight on the wrong things.
   Two of the seven columns — direction and status — held the SAME value on essentially every
   row, each drawn as a filled pill, so the two loudest marks in the table were the two carrying
   the least information. Meanwhile the timestamp, which is what the journal is ordered by and
   what a reader scans for, was set in the quietest style on the row.
   So: the exceptions get the colour, the constants get an icon, and the time gets the weight. */
/* ═══ «קו» — THE STYLISTIC LAYER, ON THIS ONE SCREEN (2026-08-04) ═════════════════════
   A telephone line is a קו; a hairline rule is a קו. This screen is a board of lines that
   were answered or missed, so it is built from ONE primitive — the rule — and everything
   else is stripped until the rule is all that holds the page together. No card, no box, no
   shadow: the journal sits directly on the sheet and structure comes from alignment.

   Onto that silence, one inversion: THE FIGURE IS THE ONLY INK-BLACK THING HERE. Times,
   phone numbers and durations are set in --ink; every word — column headers, the bot's name,
   the "אל" label, the status — recedes to --n-8. The Hebrew becomes annotation and the data
   comes forward, which is the hierarchy an operations console needs and the exact inversion
   of the bold-label/grey-value pattern every generated dashboard ships.

   THE STEP IS THE WHOLE POINT, AND IT WAS NEARLY TOO SMALL. The proposal originally put the
   chrome at --n-9 #38414C: against --ink #14191F that is 1.74:1, weaker than the 3.4:1 of
   the cliché it claims to invert, i.e. imperceptible — the exact failure the owner has
   rejected twice. --n-8 #545D69 gives 2.70:1, and weight carries the rest (the figures are
   Assistant like everything else — see the note under the @font-face rules).

   Scoped to the journal table on purpose. It goes no further until it has been looked at. */
.calltbl { width: 100%; border-collapse: collapse; }
.calltbl th {
    /* no uppercase, no tracking: Hebrew is caseless, so text-transform is a no-op that
       leaves only the letter-spacing behind — and Hebrew must never be tracked. */
    text-align: start; font-size: var(--t-sm);
    color: var(--n-8); font-weight: 500; padding: var(--s2) var(--s4) var(--s3);
    border-bottom: 1px solid var(--n-5); white-space: nowrap;
}
.calltbl th a { color: inherit; text-decoration: none; }
@media (hover: hover) { .calltbl th a:hover { color: var(--ink-2); } }
/* 8px, not the 14px a form-like table would use: two of the six cells are two lines tall, so the
   padding is added to a 34px cell rather than a 20px one and the row grows twice as fast as the
   number suggests. */
.calltbl td { padding: var(--s3) var(--s4); border-bottom: 1px solid var(--n-4); vertical-align: middle;
              color: var(--n-8); }
.calltbl tr:last-child > td { border-bottom: 0; }
/* WHO — kept for one reason only: mobile-test.sh's GAP5-03 check asserts that a rule whose
   selector is exactly `.log-who` resets the <dl> margin a layout must not carry. No view
   renders the class any more; the rest of the set went with the markup. */
.log-who { display: grid; grid-template-columns: max-content minmax(0, 1fr);
           gap: 0 var(--s2); line-height: 1.25; margin: 0; }
/* ── לוח פלטפורמה: קוביות קיבולת פר-טננט (2026-07-25) ── */
/* min(340px, 100%) and not a bare 340px: a fixed track floor cannot shrink, so on a 360px phone
   (332px of content) the grid came out wider than the screen and the PAGE scrolled sideways. */
.tn-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: var(--s4); }
/* Paper like every panel. The side edge is coloured ONLY when the reader has something to do
   (RES-12): healthy stays neutral — a brand-teal edge on every healthy customer read as a status
   nobody had set, and trained the eye to skip the edge that matters. */
.tn-card { background: var(--surface-material); border: 1px solid var(--surface-edge); border-radius: var(--r-card);
           box-shadow: var(--surface-lift);
           padding: var(--s4) var(--s4); display: flex; flex-direction: column; gap: var(--s3); min-width: 0;
           border-inline-start-width: 4px; }
.tn-card.sev-red { border-inline-start-color: var(--danger); }
.tn-card.sev-yellow { border-inline-start-color: var(--warn); }
.tn-card.sev-ok { border-inline-start-color: var(--neutral-edge); }
.tn-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s2); }
.tn-name { font-weight: 700; font-size: var(--t-lg); color: var(--ink); text-decoration: none; min-width: 0; overflow-wrap: anywhere; }
@media (hover: hover) { .tn-name:hover { color: var(--kq-link-hover); } }
.tn-chips { display: flex; gap: var(--s1); flex-wrap: wrap; justify-content: flex-end; }
.tn-metric { display: flex; flex-direction: column; gap: var(--s1); }
.tn-metric-row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s2); }
.tn-lbl { color: var(--muted); font-size: var(--t-sm); font-weight: 600; }
.tn-val { font-size: var(--t-md); font-variant-numeric: tabular-nums slashed-zero; }
.tn-bar { height: 6px; background: var(--line); border-radius: var(--r); overflow: hidden; }
.tn-bar span { display: block; height: 100%; border-radius: var(--r); }
/* bars are fills, and a healthy fill is neutral for the same reason as the card edge — but it
   still has to be a fill you can see: --n-6 measured 2.35:1 on the track and the bar read as an
   empty strip on every healthy customer. --n-7 is the ramp's own faint tier and 4.14:1 there. */
.tn-bar .b-ok { background: var(--n-7); }
.tn-bar .b-warn, .tn-bar .b-yellow { background: var(--warn-ink); }
.tn-bar .b-danger, .tn-bar .b-red { background: var(--danger-ink); }
.tn-reco { font-size: var(--t-sm); line-height: 1.45; color: var(--muted); }
.tn-reco.r-yellow { color: var(--warn-ink); font-weight: 600; }
.tn-reco.r-red { color: var(--danger-ink); font-weight: 600; }
/* minmax(0, 1fr), not 1fr: a bare 1fr track still has a min-content floor, so one unbroken
   customer name widened the column past the screen (GAP3-07) */
@media (max-width: 640px) { .tn-grid { grid-template-columns: minmax(0, 1fr); } }
/* ── מיתוג מפיץ: בורר צבעים ותצוגת לוגו (2026-07-26) ──
   בורר צבעים בלי JS: רדיו מוסתר + label כדוגמית. ה-CSP חוסם סקריפט בשורה,
   ולכן הבחירה כולה מתבצעת ב-CSS דרך :checked + סלקטור אחים. */
.sw-row { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; }
.sw-row input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; }
/* A swatch is chosen, not moved: the selected one is marked by its edge alone, with no lift and
   no transition (SHELL-28, MOB-24). The keyboard ring for the hidden radio is in the focus block.
   THE EDGE IS THE WHOLE CONTROL HERE, twice over: it delimits a 30px square whose fill is the
   reseller's own colour — which may be paler than the card, and on «ללא צבע מותאם» is white —
   and it is also the only thing that says which swatch is chosen. On --line-2 it measured
   1.35:1, so the picker was a row of colours with no boxes.
   MEASURE IT AGAINST WHAT IT TOUCHES, not against the fill: the 2px frame has 2px of white
   inner ring inside it, so the swatch's colour is not adjacent to the frame at all and a ratio
   against that colour answers a question WCAG never asked (it reports a pale swatch as failing
   and a dark one as fine, neither of which is about the boundary). Scanned off the painted
   pixels — card | frame | ring — --control-edge is 3.99:1 on BOTH sides under all four brand
   hues at 1440 and 390. The chosen state stays --ink, 4.42:1 clear of the resting frame, so
   «chosen» still reads as loudly as it did. */
.sw {
    width: 30px; height: 30px; border-radius: var(--r); cursor: pointer; display: block;
    border: 2px solid var(--control-edge); box-shadow: inset 0 0 0 2px var(--n-0);
}
.sw-row input[type="radio"]:checked + .sw { border-color: var(--ink); }
/* דוגמית "ללא צבע מותאם" — משבצת שקופה עם קו אלכסוני */
.sw.sw-none {
    background: linear-gradient(135deg, var(--n-0) 45%, var(--danger) 45%, var(--danger) 55%, var(--n-0) 55%);
}
/* ==========================================================================
   Keyboard focus — LAST IN THE FILE ON PURPOSE
   ========================================================================== */
/* This block sat near the top and lost every fight it mattered in: `:focus-visible` and `.btn`
   have the same specificity, so the later rule won and the white halo never appeared on the one
   control most worth focusing. Specificity hacks would have hidden the problem; source order
   states it. */
/* Focus ring. Only `.control` had any focus styling in the whole stylesheet; every button,
   link, nav item, tab and row action showed nothing at all, so someone navigating by keyboard
   had no way to know where they were — they were tabbing blind through a screen that can delete
   a bot. `:focus-visible` and not `:focus`, so a mouse click does not leave a ring behind.
   The outline follows the element's own border-radius, so it needs no per-component rules. */
/* TWO-TONE, because one colour cannot clear 3:1 on every surface this app has. Measured:
   a teal ring is 2.75:1 on a card, 2.37:1 on the page background and 1.00:1 on a primary
   button — invisible on the very control most worth focusing. A dark outline with a white
   halo between it and the element always has one half that contrasts: 14.8:1 on a card,
   14.8:1 on the navy rail (via the halo), 5.4:1 on a teal button, 4.8:1 on danger red.
   `outline` and not box-shadow alone, because forced-colors mode discards box-shadow. */
:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
    box-shadow: var(--focus-halo);
}
/* THE RAIL (DS-V02). The dark outline is invisible on the navy rail, and the active item's own
   inset shadow is more specific than the halo above — so the one item most likely to hold focus
   showed nothing. The outline takes the rail's computed ink (BrandTheme already flips it for a
   light rail) and the active item gets its halo back. */
.nav__link:focus-visible { outline-color: var(--sb-ink); }
/* THE SELECTED ITEM'S HALO IS THE FRAME'S TOO. This is (0,3,0) and beats the frame's ring below
   at (0,2,0), so it kept handing the one item most likely to hold focus the PAPER halo — 2px of
   white, which on a white-outlined ring on a near-black rail is no second tone at all. Same
   token as the frame's own ring, so the two cannot drift. */
.nav__link.active:focus-visible { box-shadow: var(--frame-focus-halo); }
/* FOCUS ON THE FRAME. The product's two-tone ring is an ink outline over a white halo, and both
   halves disappear on a dark surface; inside the frame it inverts to a white outline over a
   frame-coloured halo. AFTER .nav__link:focus-visible, which is the same (0,2,0) — placed before
   it, the rail's outline would take --sb-ink, which BrandTheme flips to a DARK ink for a reseller
   with a light secondary. `.nav__link.active:focus-visible` above is (0,3,0) and keeps its WHITE
   halo on the selected item at any position; that is what it did before this merge too. */
:is(.sidebar, .topbar, .kq-savebar, .usermenu__panel) :focus-visible {
    outline: 2px solid #fff; outline-offset: 2px;
    box-shadow: var(--frame-focus-halo);
}
.ci-advtoggle:focus-visible + label,
input.kq-modal-t:focus-visible + label.btn {
    outline: 2px solid var(--ink); outline-offset: 2px; box-shadow: var(--focus-halo);
}
/* on its own: a browser without :has() would otherwise drop the whole list above */
body:has(#dlg-new:focus-visible) label[for="dlg-new"] {
    outline: 2px solid var(--ink); outline-offset: 2px; box-shadow: var(--focus-halo);
}
/* colour swatches: the same ring, outside the swatch's own white inner edge */
.sw-row input[type="radio"]:focus-visible + .sw {
    outline: 2px solid var(--ink); outline-offset: 2px; box-shadow: inset 0 0 0 2px var(--n-0), var(--focus-halo);
}
/* ==========================================================================
   Windows High Contrast / forced-colors
   ========================================================================== */
/* There was not one forced-colors rule in this file. The mode throws box-shadow away and
   keeps outline — so the ring above survives, but anything drawn only with a shadow does
   not — and it replaces `background`, which renders every progress bar empty. */
@media (forced-colors: active) {
    :focus-visible,
    .control:focus-visible { outline: 3px solid Highlight; outline-offset: 2px; box-shadow: none; }
    .card, .control, .badge, .chip, .btn { border: 1px solid CanvasText; }
    /* A SWITCH IS A BACKGROUND AND A KNOB, and forced colours flattens both to Canvas — on and off
       became the same empty pill (A11Y-16). System colours keep the two states apart. */
    input[type="checkbox"]:not(.ci-advtoggle) { forced-color-adjust: none; background: Canvas; border-color: CanvasText; }
    input[type="checkbox"]:not(.ci-advtoggle)::after { forced-color-adjust: none; background: CanvasText; box-shadow: none; }
    input[type="checkbox"]:not(.ci-advtoggle):checked { background: Highlight; border-color: Highlight; }
    input[type="checkbox"]:not(.ci-advtoggle):checked::after { background: HighlightText; }
    input[type="checkbox"]:not(.ci-advtoggle):disabled { opacity: 1; border-color: GrayText; }
    input[type="checkbox"]:not(.ci-advtoggle):disabled::after { background: GrayText; }
    /* the plain native checkbox — «זכור אותי», the day chips — is drawn by the system in its own
       colours; opting it out above would hand it back the brand tint */
    input[type="checkbox"].kq-plain { forced-color-adjust: auto; }
    /* a bar's length is its whole message */
    .tn-bar span, .fo-bar, .kq-progress span { forced-color-adjust: none; background: Highlight; }
    /* panels that are told apart only by a tint or a shadow get a real edge */
    .kq-notice, .kq-strip, .kq-errsum, .kq-dialog__box, .kq-drawer, .kq-savebar,
    .frame-danger, .danger-zone, .form-frame, .is-consequential, .kq-clearable,
    .kq-fig, .ci-card, .tn-card, .sh-strip, .fo-stat, .tt-result, .auth__card, .imp-banner { border-color: CanvasText; }
    .kq-steps__mark { forced-color-adjust: none; border-color: CanvasText; background: Canvas; color: CanvasText; }
    /* THE MATERIAL DOES NOT APPLY HERE, and saying so is the whole point of this block: in a
       forced-colours mode the system paints every surface and every edge itself. A blur only
       smears what the OS has decided must stay legible, a shadow is discarded before it paints,
       and the ground's wash is a tint the reader asked not to be given. The band behind the
       column names goes back to Canvas — the system's own header treatment is the border. */
    .topbar, .kq-savebar, .kq-drawer, .usermenu__panel, .cselect__list, .sidebar {
        -webkit-backdrop-filter: none; backdrop-filter: none;
    }
    .card, .kq-fig, .stat, .fo-stat, .ci-card, .ci-filterbar, .tn-card, .botcard, .sh-strip,
    .tt-result, .auth__card, .kq-dialog__box, .kq-modal-card, .topbar, .kq-savebar, .kq-drawer,
    .usermenu__panel, .cselect__list, .kq-rail__inner { box-shadow: none; border-color: CanvasText; }
    /* the phone table card is a surface too — the seat it now carries is a shadow like any other,
       and below 820px this row IS the card. Only the shadow: its border is the table's own rule,
       which the system already paints. */
    table.tbl.is-cardable > tbody > tr { box-shadow: none; }
    body { background-image: none; }
    /* ── THE FRAME IN FORCED COLOURS ────────────────────────────────────────────────────────
       The system paints every surface and every edge here, so the frame is not a colour any more
       — it is a Canvas with a CanvasText border — and the one thing this block must do is make
       sure the dark band behind the column names and the coloured chips do not survive as painted
       pixels the OS did not choose. forced-color-adjust is never used to keep a colour: only to
       release one. */
    .sidebar, .topbar, .kq-savebar, .usermenu__panel, .auth, body:has(.auth) {
        background: Canvas; color: CanvasText; forced-color-adjust: auto;
    }
    .sidebar { border-inline-end: 1px solid CanvasText; }
    .topbar { border-bottom: 1px solid CanvasText; }
    .kq-savebar { border-top: 1px solid CanvasText; }
    /* THE DIRTY DOT IS ONLY A COLOUR, AND THIS MODE TAKES COLOUR AWAY. Measured through CDP with
       forced-colors emulated: the system forces the dot's background to Canvas whichever of its
       two rules won the cascade, so a 9x9 window on it came back 0 pixels unlike the bar — a hole,
       in both states, with nothing to see. With this rule it is 32 and 48. It cannot be
       restated as a background from here either: its own rules are LATER in this file at the same
       weight, and a `background` shorthand there resets a background-image too. So the dot is
       given a SHAPE, which is the one thing forced colours does not flatten — an edge in the
       reader's own ink, filling as the form goes dirty. Nothing is kept that the reader did not
       choose; border-color is forced to CanvasText here whatever this rule says.
       The WORDS beside it stay the source of truth: the state line is role="status" and
       kq-dirty.js swaps «אין שינויים שלא נשמרו» for «יש שינויים שלא נשמרו».
       box-sizing comes with the edge, because the sheet's reset is `*` and `*` does not match a
       pseudo-element: on content-box a border is ADDED to the 7px rather than taken out of it, and
       the state line measured 141.8px and 143.9px against the 139.8px and 137.9px it has in every
       other mode — a bar re-laid out rather than a dot made visible. With it the two widths are
       the same to the pixel in both modes. */
    .kq-savebar__state::before,
    .kq-savebar [data-dirty-state]::before { box-sizing: border-box; border: 1px solid CanvasText; }
    html[data-dirty] .kq-savebar__state::before,
    html[data-dirty] .kq-savebar [data-dirty-state]::before { border-width: 3px; }
    /* EVERY LITERAL LIGHT INK THE FRAME SETS HAS TO BE RELEASED, not most of them: #fff on Canvas
       is nothing at all, and the first render of this branch lost the rail's wordmark and the
       account name to exactly that. */
    .nav__link, .nav__link.active, .topbar__title, .topbar__start > h1, .kq-ctx, .kq-ctx svg,
    .kq-ctx strong, .kq-ctx--warn, .usermenu__btn, .usermenu__btn .who .n, .usermenu__btn .who .r,
    .usermenu__btn > svg, .usermenu__panel a, .usermenu__panel button, .usermenu__panel a svg,
    .usermenu__panel button svg, .kq-savebar, .kq-savebar__state,
    a.sidebar__brand, .sidebar .kq-wordmark__name, .sidebar .brandmark__name,
    .kq-search .control, .kq-search .control::placeholder { color: CanvasText; }
    .nav__link.active svg { color: HighlightText; }
    .nav__link.active { background: Highlight; color: HighlightText; }
    .nav__link.active::before { background: HighlightText; }
    .kq-ctx, .usermenu__btn, .kq-search .control, .topbar .btn-icon {
        background: Canvas; border-color: CanvasText; color: CanvasText;
    }
    table.tbl > thead > tr > th { background: Canvas; color: CanvasText; border-bottom: 1px solid CanvasText; }
    .kq-fig__label svg { background: Canvas; color: CanvasText; border: 1px solid CanvasText; }
    .kq-bar__fill, .dash-hours .hr { forced-color-adjust: none; background: CanvasText; }
    .kq-bar.is-today .kq-bar__fill { background: Highlight; }
    .kq-bars__tip { background: Canvas; color: CanvasText; border: 1px solid CanvasText; }
    .kq-notice, .kq-strip, .kq-errsum { border-inline-start-width: 1px; }
    /* after the `.control:focus-visible` shorthand above, which would otherwise reset the colour */
    :is(.sidebar, .topbar, .kq-savebar, .usermenu__panel) :focus-visible { outline-color: CanvasText; box-shadow: none; }
}
/* ==========================================================================
   Hover — behind a capability query on purpose
   ========================================================================== */
/* An ungated :hover STICKS after a tap on a touch device: a rail item tapped to navigate
   stays highlighted on the next page, so the rail shows two items looking active, and two
   tapped table rows both look selected. :active and :focus-visible stay outside the query —
   a touch user needs both. */
@media (hover: hover) {
    .btn-primary:hover { background: var(--kq-btn-bg-hover); border-color: var(--kq-btn-bg-hover); color: var(--kq-btn-on); }
    /* DARKER on hover, not lighter: with the resting edge at --control-edge (--n-6), --n-5 was a
       hover that made the button fainter than it was at rest. */
    .btn-ghost:hover { border-color: var(--n-7); background: var(--n-2); color: var(--ink); }
    /* each variant restates its ink: a link-shaped button would otherwise take a:hover's colour */
    .btn-success:hover { background: var(--ok-bg); color: var(--ok-ink); }
    .btn-danger:hover { background: var(--danger-bg); color: var(--danger-ink); }
    .btn-danger-solid:hover { background: var(--danger-strong); border-color: var(--danger-strong); color: var(--n-0); }
    .btn-warn:hover { background: var(--warn-bg); color: var(--warn-ink); }
    .btn-icon:hover { background: var(--n-2); border-color: var(--n-7); color: var(--ink); }
    /* ── the frame's own hovers. A white veil, never a second colour. ────────────────────── */
    .nav__link:hover { background: var(--frame-hover); color: var(--frame-ink); }
    .nav__link:hover svg { opacity: 1; }
    .topbar .btn-icon:hover { background: rgba(0, 0, 0, .34); border-color: rgba(255, 255, 255, .62); color: var(--frame-ink); }
    .kq-savebar .btn-ghost:hover { background: rgba(0, 0, 0, .34); border-color: rgba(255, 255, 255, .62); color: var(--frame-ink); }
    .usermenu__panel a:hover, .usermenu__panel button:hover { background: var(--frame-hover); color: var(--frame-ink); }
    /* the column names on the dark band */
    table.tbl > thead > tr > th a:hover { color: var(--frame-ink); }
    table.tbl > thead > tr > th a:hover svg { opacity: 1; }
    .tab:hover { color: var(--ink-2); }
    table.tbl tbody tr:hover { background: var(--row-hover); }
    .btn-primary:hover::after { opacity: 1; }
}
/* ==========================================================================
   Busy state + screen-reader utilities
   ========================================================================== */
/* Scoped to .btn, not to [aria-busy] globally: aria-busy is a legitimate attribute on
   tables and live regions, and dimming their children would be wrong. */
/* The label goes transparent and the spinner takes its place (FORM-22). `color: transparent`
   would erase the spinner too, because its ring is drawn in currentColor; the text-fill property
   hides only the glyphs. */
.btn[aria-busy="true"] { position: relative; pointer-events: none; -webkit-text-fill-color: transparent; }
.btn[aria-busy="true"] > * { opacity: .25; transition: opacity var(--dur-fast) linear; }
.btn[aria-busy="true"]::after {
    content: ""; position: absolute; inset: 0; margin: auto;   /* centred with no transform,
                                                                 so it stays RTL-neutral */
    inline-size: 16px; block-size: 16px; border-radius: var(--r-circle);
    border: 2px solid currentColor; border-block-start-color: transparent;
    opacity: 0; box-shadow: none;
    /* The 300ms delay needs no JS timer: below that threshold the spinner never becomes
       visible, so a fast save shows nothing instead of a two-frame strobe that reads as a
       glitch. Once it does appear it stays for the rest of the round trip. */
    animation: kq-spin 600ms linear 300ms infinite, kq-reveal 1ms linear 300ms forwards;
}
@keyframes kq-spin { to { rotate: 360deg; } }
@keyframes kq-reveal { to { opacity: 1; } }
/* A default submit button that is never seen but is always FIRST in tree order — see the
   note on the bot-create form. Not display:none: a display:none default button is skipped
   by some engines, which puts the bug straight back. */
.kq-default-submit {
    position: absolute; inline-size: 1px; block-size: 1px; padding: 0; border: 0;
    overflow: hidden; clip-path: inset(50%); pointer-events: none;
}
.sr-only {
    position: absolute; inline-size: 1px; block-size: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* ==========================================================================
   Voice-bot cards
   ========================================================================== */
/* The bots list is cards, not a table (owner 2026-08-01). A table is the right shape when rows
   are compared column by column; a tenant has a handful of bots and reads each one as an object
   — its number, its voice, whether it is answering — so each bot gets a card.
   auto-fill and not auto-fit: with two bots, auto-fit would stretch them to half the screen
   each; auto-fill keeps the card its own size and lets the grid stay left-over. */
/* min(336px, 100%) and not a bare 336px: a fixed track floor cannot shrink, so on a 360px phone
   (332px of content) the card was wider than the screen and the PAGE scrolled sideways. The
   desktop intent is unchanged — wherever there is room for 336px, the track is still 336px. */
.botgrid {
    display: grid; gap: var(--s4);
    grid-template-columns: repeat(auto-fill, minmax(min(336px, 100%), 1fr));
    align-items: start;
}
/* Same treatment as a dashboard block (owner 2026-08-03: "תעצב לי באותו סגנון"): one hairline,
   the small radius, and the SEAT rather than the lift. The comment here has always said "no
   shadow" — a shadow lifts a card off the page and makes a grid of them read as a pile of objects
   — while the rule carried the full 36px halo, because on 68% glass the hairline was 1.05:1
   against the card's own face and the shadow was the only thing finding the card's edge. With a
   real edge at 1.65:1 the hairline does the drawing and the shadow only has to seat it. */
.botcard {
    display: flex; flex-direction: column; gap: var(--s3);
    padding: var(--s4) var(--s4);
    background: var(--surface-material); border: 1px solid var(--surface-edge); border-radius: var(--radius-sm);
    /* NOT hidden: the number picker opens an absolutely-positioned list that would be clipped
       by the card it sits in. */
    overflow: visible;
    transition: border-color var(--dur-fast) var(--e-standard);
    box-shadow: var(--surface-seat);
}
@media (hover: hover) { .botcard:hover { border-color: var(--line-strong); } }
/* THE CARD, RECOMPOSED (owner 2026-08-03: "משהו בעיצוב לא מסתדר לי").
   The head now holds the name and nothing else. One loud element per card. */
.botcard.is-off { background: color-mix(in srgb, var(--ink) 4%, var(--card)); }
.botcard.is-off .botcard__name { color: var(--muted); }
/* THE ONLY PLACE THE STATE IS SAID NOW — so it says it in full and in colour, and it is the
   one thing on the card carrying a hue. --ok-ink and --warn-ink are the ink weights of the green
   and the amber: the fills they are derived from reach only 3.30:1 and 3.19:1 as text on the
   card, and a caption has to clear 4.5:1. Both of these clear 5:1. */
/* the rule under the name divides the card the way every panel head is divided */
.botcard__head { display: flex; align-items: center; gap: var(--s3);
                 padding-block-end: var(--s3); border-block-end: 1px solid var(--line-2); }
.botcard__name { font-weight: 650; font-size: var(--t-base); line-height: 1.3; min-width: 0;
                 overflow-wrap: anywhere; }
.botcard__did { min-width: 0; }
.botcard__did .cselect { min-width: 0; max-width: none; }
/* OUTBOUND-ONLY AGENT, on the card (2026-08-26). The number rides the switch with the same
   pure-CSS :has the bot form uses for the transfer number — the CSP is script-src 'self', so a
   toggle written in JS on this page would be silently dead. Scoped to the form the switch and
   the field share, never to a card id, so a future rename cannot switch the rule off. */
.fwd-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s2); }
.fwd-row .checkbox { margin: 0; white-space: nowrap; }
.fwd-num { display: none; align-items: center; gap: var(--s1); }
.fwd-row:has(input[name="outboundOnly"]:checked) .fwd-num { display: flex; }
.fwd-num .control { max-width: 170px; }
.botcard__meta {
    display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3);
    font-size: var(--t-sm); color: var(--muted);
}
/* GRID, not flex, and that is the whole fix for "כל הכפתורים באותו גודל". With flex the five
   actions came out 67/67/57/57/57 even at `flex: 1 1 0` on every child, because two of them are
   the button itself and three are a <form> wrapping one — two different content-sizing paths,
   and content still leaks into the result. `grid-auto-columns: 1fr` sizes the tracks from the
   container alone, so the count can be three, four or five and every column is identical. */
.botcard__actions {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    gap: var(--s1); align-items: stretch;
    padding-block-start: var(--s3); margin-block-start: auto;
    border-block-start: 1px solid var(--line-2);
}
/* Each action is a form or a link, so the FLEX CHILD is the wrapper, not the button — sizing
   the button alone would have done nothing. */
/* a form is a grid item like any other; its button fills it */
.botcard__actions > form { display: flex; min-width: 0; }
.botcard__actions > .botbtn { min-width: 0; }
/* Icon over a caption (owner 2026-08-01: "תוסיף מלל בקטן מתחת לכל אימוגי... ותעשה לי אותם יותר
   גדולים ומובנים"). An icon-only row was compact but it asked the user to remember five
   pictograms; a two-line button is bigger, says what it does, and still fits five across
   because the captions are one word each. The full sentence stays on the title and the
   aria-label, which also name the bot. */
.botcard__actions .botbtn {
    width: 100%; height: auto;
    flex-direction: column; gap: var(--s1);
    padding: var(--s2) var(--s1);
    font-size: var(--t-xs); font-weight: 600; line-height: 1.2;
    color: var(--ink-2);
    background: #fff; border: 1px solid var(--control-edge);
    text-align: center;
}
.botcard__actions .botbtn svg { width: 20px; height: 20px; }
/* one line, clipped rather than wrapped: a caption that wraps makes its button taller than
   the four beside it, and equal size was the whole point */
.botcard__actions .botbtn > span { display: block; width: 100%; white-space: nowrap;
                                   overflow: hidden; text-overflow: ellipsis; }
/* NO SHADOW ON A CARD ACTION, from either source (owner 2026-08-03: "תסיר את הצל הירוק
   מהכפתור הפעלה"). `.btn-success` carries a resting green glow on the box itself and the
   hover shadow lives on ::after; the card overrode the background and the border but not
   either shadow, so the activate button still floated in a green haze while the four beside
   it sat flat. Both are switched off here. */
.botcard__actions .botbtn,
.botcard__actions .botbtn::after { box-shadow: none; }
/* ==========================================================================
   Dashboard — minimalist, but with structure
   ========================================================================== */
/* Rebuilt 2026-08-03: five card-framed panels, two charts, a per-bot bar list and six metrics
   became three numbers, one bar, one chart and one list.
   Then the frames came off entirely and it read as unsorted (owner: "זה נראה לא מסודר") — with
   nothing but space between them, four sections at four different widths have no edges for the
   eye to line up on. Structure is back, but as ONE device used everywhere: a hairline. No
   shadows, no fills, no second border weight. A rule under the header, rules between the
   figures, a rule around each block. That is the whole system. */
.dash-month { display: inline-flex; align-items: center; gap: var(--s1); font-size: var(--t-md);
              color: var(--muted); }
.dash-month > span:not(.dash-mbtn) { min-width: 8.5ch; text-align: center; }
.dash-mbtn { display: inline-flex; align-items: center; justify-content: center;
             width: 30px; height: 30px; border-radius: var(--r); color: var(--muted); }
.dash-mbtn svg { width: 17px; height: 17px; }
.dash-mbtn.is-off { opacity: .3; pointer-events: none; }
.dash-clear {
    display: flex; align-items: center; gap: var(--s2);
    color: var(--muted); font-size: var(--t-sm); margin: 0;
}
/* ═══ DASHBOARD, REBUILT (owner 2026-08-04: "הרבה יותר מרשים והרבה יותר מובן") ═════════
   Three blocks that were computed on every load and never rendered — the answered/missed
   split, the hour-of-day profile and the per-bot share — now have somewhere to land. */

/* WHEN DOES THE PHONE RING — 24 columns, one per hour. The shape is the message, so the
   bars carry no labels; the axis under them is enough to place a peak. */
.dash-hours { display: grid; grid-template-columns: repeat(24, 1fr); gap: 2px;
              align-items: end; height: 108px; }
.dash-hours .hr { display: block; height: var(--h); min-height: 2px;
                  background: var(--dv-1); border-radius: 2px 2px 0 0; }
.dash-hours .hr.is-zero { background: var(--n-4); }
.dash-hours__ax { display: flex; justify-content: space-between; margin-top: var(--s1);
                  font-size: var(--t-xs); color: var(--n-8);
                  font-variant-numeric: tabular-nums; }
/* ═══ CONTACT TIMELINE — one number's whole history (2026-08-05) ══════════════════════ */
.ct-item { padding: var(--s3) 0; border-bottom: 1px solid var(--n-4); }
.ct-item:last-child { border-bottom: 0; }
.ct-head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.ct-sum { margin: var(--s2) 0 0; color: var(--n-8); line-height: 1.6; max-width: 80ch; }
/* The edit screen's read-only block: what this screen will not let you change, shown as facts
   so its absence reads as a decision rather than an oversight. */
.cm-facts { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); margin-top: var(--s2); }
/* One row per range: day, from, to, remove. `auto` on the last column keeps the button hard
   against the row instead of stretched across whatever space is left. Each row now carries its
   own labels, so there is no header row whose columns this one has to line up with. */
.cm-win {
    display: grid; grid-template-columns: minmax(90px, 1.1fr) minmax(96px, 1fr) minmax(96px, 1fr) auto;
    gap: var(--s2); align-items: center; margin-bottom: var(--s2);
}
/* A stacked row needs its own spacing to stay readable as one unit. */
@media (max-width: 560px) {
    .cm-win { grid-template-columns: 1fr 1fr; gap: var(--s2); padding-bottom: var(--s2);
              border-bottom: 1px solid var(--n-4); }
}
/* ── CSS-ONLY MODAL ──────────────────────────────────────────────────────────────────────
   Driven by a hidden checkbox and a <label for>, because the CSP is script-src 'self' and an
   inline handler is silently dropped — a JS dialog here would look right in source and never
   open. The label IS the button, so it stays keyboard-reachable via the input.
   The backdrop is also a label, so clicking outside closes it without a line of script. */
.kq-modal-t { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.kq-modal-t:not(:checked) ~ .kq-modal { display: none; }
.kq-modal {
    position: fixed; inset: 0; z-index: 90; display: grid; place-items: center;
    padding: var(--s5); background: var(--scrim);
}
.kq-modal-bg { position: absolute; inset: 0; cursor: default; }
/* The product's OTHER modal box, and it takes the same edge as .kq-dialog__box: two boxes on the
   same scrim drawn with two different hairlines read as two components, and they are one. --line
   measured 1.24:1 against its own white face; --surface-edge is the 1.65:1 every card now has. */
.kq-modal-card {
    position: relative; width: min(1040px, 100%); max-height: 82vh; overflow: auto;
    background: var(--surface-material); border: 1px solid var(--surface-edge); border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg); padding: var(--s5);
}
.kq-modal-x {
    position: sticky; top: 0; float: inline-start; cursor: pointer;
    background: var(--card); border-radius: var(--r-pill); padding: 2px 8px; line-height: 1.6;
}
.kq-modal-t:focus-visible ~ .kq-modal .kq-modal-card { outline: 2px solid var(--ink); outline-offset: 2px; }
/* ==========================================================================
   THE PHONE PASS (owner 2026-08-06: "שיתאים לטלפונים הניידים באופן מושלם")
   ==========================================================================
   ONE APPENDED BLOCK, on purpose. There is no git in this project, so a mobile pass spread
   through 1689 lines of stylesheet could not be undone; everything below can be deleted in one
   cut and the desktop product is exactly what it was.

   MEASURED, NOT IMAGINED. Every rule here answers something observed at a real 390px viewport
   (a same-origin srcdoc frame against the live app, which is the only way this environment
   renders narrow — window resizing reports success and silently does nothing). The numbers in
   the comments are what the browser reported, not estimates.

   The starting point was better than expected: of thirteen tenant screens measured, eleven had
   ZERO horizontal overflow and the off-canvas drawer already worked. So this is a repair list,
   not a rebuild. */

/* ── 1. THE SHRINK FIX — the single defect behind every page that scrolled sideways ───────
   A grid item's automatic minimum size is its MIN-CONTENT, not zero. So one wide child — the
   31-day chart at min-width:403px, a <pre> code block at 591px — sets a floor the track cannot
   go below, and that floor propagates up through .content to <html>.
   The overflow:auto already on those children does not help: it stops THEM from spilling, but
   their intrinsic width is still what the parent grid measures.
   Measured: /Admin rendered 447px wide inside a 375px viewport (+72), /Admin/ApiKeys/Docs +230.
   min-width:0 releases the floor, and the scroll container then does the job it was added for.
   A correctness fix at every width, which is why it is not inside a media query. */
.dash-grid2 > *,
.dash-two > *,
.dash-block { min-width: 0; }
/* The other half of the same defect — a grid TRACK floor wider than a small phone — is fixed at
   the two declarations themselves (.botgrid, .tn-grid) rather than overridden from down here.
   An override would leave the original 336px sitting in the file looking authoritative, and the
   next person to read that line would believe it. mobile-test.sh scans for the pattern, so a new
   one cannot be introduced without the guard saying so. */

/* ── 2. VIEWPORT UNITS ────────────────────────────────────────────────────────────────────
   100vh on a phone is the viewport WITHOUT the browser's own chrome, so a full-height shell is
   taller than what can actually be seen and its bottom sits under the URL bar. dvh tracks the
   visible height. Declared twice: the vh line is the fallback, and a browser that understands
   dvh simply takes the second. */
.app { min-height: 100vh; min-height: 100dvh; }
.auth { min-height: 100vh; min-height: 100dvh; }
/* ── 3. TOUCH SCROLLING FOR EVERY HORIZONTAL SCROLLER ─────────────────────────────────────
   A table that scrolls sideways is the honest answer for six columns of call data on a phone —
   but only if the gesture feels native and does not drag the PAGE along once the table reaches
   its end. Overscroll chaining is what makes a nested scroller feel broken. */
.table-wrap {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
}
/* ══ PHONES AND SMALL TABLETS ═════════════════════════════════════════════════════════════
   820px is the breakpoint the drawer already uses. Reusing it rather than inventing a second
   one keeps "mobile" a single idea in this file. */
@media (max-width: 820px) {

    /* ── 4. iOS SAFARI ZOOMS THE WHOLE PAGE when a focused field is under 16px ────────────
       Not a preference — it is unconditional in Safari, and it leaves the user zoomed in with
       the layout wider than the screen after the keyboard closes. Measured on the call journal:
       the agent filter and the search box both computed to 13.1px, so tapping either threw the
       page out of alignment before a single character was typed.
       The toolbar rules that made them 13.1px are (0,2,0), so they are restated here rather
       than relying on a bare element selector that would lose to them. */
    input:not([type="checkbox"]):not([type="radio"]),
    select, textarea,
    .control, input.control, select.control,
    .ci-filterrow .control,
    .search-box .control { font-size: 16px; }
    /* ── 5. TAP TARGETS ──────────────────────────────────────────────────────────────────
       Measured before this: .btn 35px, .btn-icon 34px, a sort-header link 15px. A finger is not
       a mouse pointer, and the sort links in particular were unhittable without zooming. Height
       only — the padding tokens stay, so nothing about the desktop rhythm leaks in here. */
    .btn { min-height: 40px; }
    .btn-sm { min-height: 36px; }
    /* 40px is --kq-bar-ctl-h's own value, and shell-geometry-test pins the two together: the
       hamburger, the folded search and the user pill must be one size beside each other, which
       they were not — the coarse-pointer floor below raised this button to 44px and left the
       search at 40. Written as a literal because mobile-test pins this declaration verbatim. */
    .btn-icon { width: 40px; height: 40px; }
    /* the header link is inline, so it has no height of its own to grow — padding gives it one */
    table.tbl th a, .calltbl th a { display: inline-block; padding-block: var(--s2); }
    /* a row of actions that cannot fit stacks instead of squeezing its buttons to nothing */
    .row-actions { flex-wrap: wrap; justify-content: flex-start; }
    /* ── 6. THE TOP BAR MUST NEVER BE THE THING THAT BREAKS ──────────────────────────────
       It fits today, but only because the current user's name is short. Nothing was stopping a
       long one from pushing the page title out of the bar: a flex item's default min-width is
       auto, so the name refuses to shrink and something else has to give way. */
    .topbar { padding-inline: var(--s4); gap: var(--s3); }
    /* The title slot, the wrapping guard and the pill's cap are stated once in the component
       layer now — they were never phone problems. What was left here named the start group by
       POSITION (.topbar > div:first-child) and sized the h1 differently from the paragraph in
       the same slot; both are gone. The pill's cap is narrower here because the bar also holds
       the drawer toggle and, for a platform owner, the folded search. */
    .usermenu__btn { max-inline-size: 46vw; }
    /* ── 7. DIALOGS ─────────────────────────────────────────────────────────────────────
       s5 of backdrop padding on a 390px screen spends 40px of the width on nothing. */
    .kq-modal { padding: var(--s3); }
    .kq-modal-card { padding: var(--s4); max-height: 88vh; max-height: 88dvh; }
    /* ── 8. COMPONENTS THAT ASSUME A WIDE COLUMN ────────────────────────────────────────*/
    /* a tool's description was one clipped line at max-width:520px — on a phone that ellipsis
       landed after about three words, so the list said nothing about any of the tools */
    .tool-item { flex-wrap: wrap; }
    .tool-item .tool-desc { white-space: normal; max-width: none; }
    .tool-item .tool-lead { min-width: 0; }
    .form-row.narrow { max-width: none; }
    /* ── 10. THE CONVERSATION BUILDER'S ROWS ─────────────────────────────────────────────
       Found by an adversarial audit, NOT by looking — and it could not have been found by
       looking, because these rows live in the ההנחיות panel, which is display:none until its
       radio is checked. A hidden element has no layout, so every width measurement of the
       editor reported it clean.
       The audit called it page-level overflow. It is not — measured at 390px the row fits in
       305px and the page does not scroll. What actually happens is worse to use and easier to
       miss: the three tracks resolve to 59px / 76px / 76px, so each field shows about four
       characters of a Hebrew sentence. The rule row is 95px / 123px.
       ONE COLUMN, and !important is the correct tool exactly once here: the template is an
       inline style attribute on four elements (both server-rendered rows and both prototypes,
       which voicebot-form.js clones with cloneNode(true) — so the inline copy is carried into
       every row the + button adds). An author !important beats a normal inline declaration,
       which is why this fixes the cloned rows too without touching the markup or the script.
       NO min-width:0 here: .control is width:100%, which is a definite size suggestion, so the
       automatic minimum is already suppressed — that is why the fields shrank to 59px instead
       of forcing the page wide. Adding it would be cargo cult.
       justify-self on the delete button because a grid item stretches by default, and a
       full-width «הסרה» directly under the last field is an easy mis-tap on a destructive
       control. */
    .kq-step, .kq-rule { grid-template-columns: 1fr !important; }
    .kq-step .kq-row-del, .kq-rule .kq-row-del { justify-self: start; }
    /* an email or an API path has no space to break at, so one long value sets a column floor
       nothing else can shrink past. Mobile only — desktop columns are wide enough that breaking
       mid-word would just look careless. */
    table.tbl td { overflow-wrap: anywhere; }
}
/* ══ PHONES PROPER ════════════════════════════════════════════════════════════════════════ */
/* ══ PHONES PROPER, FOR THE TOP BAR ═══════════════════════════════════════════════════════
   MEASURED, not chosen: at 390px the bar has 362px of room and the toggle, the title's floor,
   a chip and the user pill add up to more than that. Whatever is left over goes to the chip,
   so below this width the chip renders as a 22px stub — an icon and nothing it was there to
   say — which is worse than not rendering at all. 561px is where «בחשבון: קליניקת אור» still
   fits beside a five-character title, so that is where it stops.
   What survives here is what a person cannot get anywhere else on the screen: whose account
   they are inside if they are inside someone's, and the name of the screen they are on. Their
   OWN name is the one thing in the bar they already know, so the pill keeps its avatar and
   hands the two lines to the screen reader instead of to the layout. */
@media (max-width: 560px) {
    .kq-ctx:not(.kq-ctx--warn) { display: none; }
    .usermenu__btn { max-inline-size: none; }
    .usermenu__btn .who {
        position: absolute; inline-size: 1px; block-size: 1px; padding: 0; margin: -1px;
        overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
    }
}
@media (max-width: 560px) {
    /* a key/value list has no room for two columns; the label goes above its value */
    .kv { grid-template-columns: 1fr; gap: 0; }
    .kv dt { margin-top: var(--s3); }
    .kv dt:first-of-type { margin-top: 0; }
    /* five controls across a 362px row is 60px each — the tool parameter editor stops being
       usable long before that. One field per line. */
    .param-row { grid-template-columns: 1fr; }
    /* --t-lg is the card-title size now, so the h1 takes a step of its own rather than becoming
       a card title. */
    .page-head .t { font-size: 1.1875rem; }
}
/* ══════════════════════════════════════════════════════════════════════════════════════════
   TABLES BECOME CARDS (owner 2026-08-06: "טבלאות לא מוצגות בכלל טוב בטלפון... תשנה את זה
   למבנה של כרטיסיות")
   ══════════════════════════════════════════════════════════════════════════════════════════
   Horizontal scroll was the honest first answer and it is not good enough. A six-column journal
   in a 347px scroller makes you drag sideways for the one value you came for, and by the time
   you get there the cell that identified the row — the time, the name — has scrolled off the
   other edge. You cannot read across a row you cannot see the start of.

   So below 820px a row stops being a row and becomes a card: every value on its own line, next
   to its column name, with nothing to scroll. Above 820px not one of these rules applies and the
   tables are exactly what they were.

   .is-cardable IS ADDED BY kinetiq.js, and that is deliberate — see the note there. The label
   beside each value is `attr(data-label)`, and that attribute only exists once the script has
   copied it out of the table's own <thead>. Keying the layout on a class the script adds means
   a scripting failure leaves a scrolling TABLE rather than a stack of anonymous values. */
@media (max-width: 820px) {

    /* the wrapper has nothing left to scroll, and while it is still a scroll container it clips
       the number picker's dropdown */
    .table-wrap:has(table.tbl.is-cardable) { overflow-x: visible; }
    table.tbl.is-cardable,
    table.tbl.is-cardable > tbody,
    table.tbl.is-cardable > tbody > tr,
    table.tbl.is-cardable > tbody > tr > td { display: block; width: auto; }
    /* the header's job has moved into the cells */
    table.tbl.is-cardable > thead { display: none; }
    /* PAPER, in the material's own vocabulary. This IS the product's phone table card — every
       cardable table renders it, from the users list to the campaign recipients — and it was
       left on the previous one: --card (the legacy alias of the same white), --line for its
       OUTER edge, and no lift. Measured at 390 on the platform search: that edge is 1.24:1
       against the card's own face and 1.09:1 against the ground behind it, i.e. visible from
       neither side, while the call journal's own row-card — the one instance the material pass
       touched — sits beside it at 1.65:1 with the seat. One surface, one edge, one lift.
       --line stays what it is: the rule BETWEEN two values inside the card (the td below). */
    table.tbl.is-cardable > tbody > tr {
        border: 1px solid var(--surface-edge); border-radius: var(--radius-sm);
        background: var(--surface-material); padding: var(--s3) var(--s4);
        margin-bottom: var(--s3);
        box-shadow: var(--surface-seat);
    }
    /* the base rule strips the last row's border to close a ruled list; as a card it needs its
       own edge like every other card */
    table.tbl.is-cardable > tbody > tr:last-child { border-bottom: 1px solid var(--surface-edge); }
    table.tbl.is-cardable > tbody > tr > td {
        display: grid; grid-template-columns: minmax(84px, 34%) minmax(0, 1fr);
        gap: var(--s3); align-items: baseline;
        /* justify-items, and it is not cosmetic: a grid item STRETCHES by default, so every
           badge and every switch became a full-width bar with its text stranded at one end —
           a «פעיל» pill rendered as a 200px green slab. start makes each value take its own
           width again, which is what made them read as chips in the table. */
        justify-items: start;
        padding: var(--s2) 0; border: 0; border-bottom: 1px solid var(--line-2);
    }
    table.tbl.is-cardable > tbody > tr > td:last-child { border-bottom: 0; padding-bottom: 0; }
    table.tbl.is-cardable > tbody > tr > td:first-child { padding-top: 0; }
    table.tbl.is-cardable > tbody > tr > td::before {
        content: attr(data-label);
        color: var(--muted); font-size: var(--t-sm); font-weight: 600;
        overflow-wrap: anywhere;
    }
    /* An actions column has an empty header, so it gets no label row and takes the full width —
       the buttons are the only thing in the product that says what it does by itself. */
    table.tbl.is-cardable > tbody > tr > td[data-label=""] { grid-template-columns: minmax(0, 1fr); }
    table.tbl.is-cardable > tbody > tr > td[data-label=""]::before { content: none; }
    /* .row-actions is a class ON the td, so the card rule above turned the actions cell into a
       one-column grid and stacked עריכה above חסום, each on its own line. It has to go back to
       being the flex row it already is everywhere else — restated at a specificity that beats
       the card rule rather than by weakening the card rule for every other cell. */
    table.tbl.is-cardable > tbody > tr > td.row-actions {
        display: flex; flex-wrap: wrap; justify-content: flex-start;
        align-items: center; gap: var(--s2);
    }
    /* A colspan panel — the API-key editor, a campaign expander — is not a data row. The script
       marks it rather than labelling its contents with whatever column happened to be first. */
    table.tbl.is-cardable > tbody > tr.is-wide-row { border: 0; padding: 0; margin: 0; background: none; box-shadow: none; }
    table.tbl.is-cardable > tbody > tr.is-wide-row > td { display: block; padding: 0; border: 0; }
    table.tbl.is-cardable > tbody > tr.is-wide-row > td::before { content: none; }
    /* AN EMPTY CELL IS NOT A LINE (GAP4-03). kinetiq.js marks a cell whose text is empty or «—»,
       and on a card it takes no row: a column name next to nothing cost ~40px per record. */
    table.tbl.is-cardable td[data-empty] { display: none; }
    /* ONE SURFACE, NOT A CARD INSIDE A CARD (MOB-26). When the table IS the whole card, the rows
       are the cards and the wrapper stands down. Only then: a card with a head or a form under
       its table keeps its frame, or the head would float loose on the page.
       THE SECOND SHAPE was missed for as long as both layers were 68% glass — two translucent
       sheets over the same ground look like one — and is the first thing you see once both are
       opaque. This rule only ever matched a card holding `.card__body.tight` as its ONLY child,
       and the call journal's card and the platform search's two section cards hold the
       .table-wrap directly, so on a phone every row there was a card inside a card. */
    .card:has(> .card__body.tight:only-child > .table-wrap > table.tbl.is-cardable),
    .card:has(> .table-wrap > table.tbl.is-cardable) {
        background: none; border-color: transparent; box-shadow: none;
    }
    /* the pager was ruled off from the table above it; with the card gone there is nothing to rule
       it off from, and the line hung in the page under the last row-card */
    .card:has(> .table-wrap > table.tbl.is-cardable) > .kq-pager { border-top: 0; }
    .card:has(> .card__head) > .card__body.tight:has(> .table-wrap > table.tbl.is-cardable) { padding: var(--s3); }
}
/* ── Server health ───────────────────────────────────────────────────────────
   Reuses the tenant-capacity card idiom (.tn-*) on purpose: the dashboard now
   shows two kinds of capacity — a customer's and the machine's — and they read
   as one language rather than two dialects. Appended after the mobile block, so
   each rule here that needs a narrow-screen form carries its own query below. */
.sh-strip { display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
            background: var(--surface-material); border: 1px solid var(--surface-edge);
            box-shadow: var(--surface-lift);
            border-inline-start-width: 4px; border-radius: var(--r-card);
            padding: var(--s3) var(--s4); margin-bottom: var(--s4);
            text-decoration: none; color: inherit; }
.sh-strip.sev-red { border-inline-start-color: var(--danger); }
.sh-strip.sev-yellow { border-inline-start-color: var(--warn); }
/* healthy is neutral, like the customer cards — the edge is coloured only when there is a job */
.sh-strip.sev-ok { border-inline-start-color: var(--neutral-edge); }
@media (hover: hover) { .sh-strip:hover { border-color: var(--line-strong); color: inherit; } }
/* no focus rule of its own: the teal ring it had measured 2.75:1 — the global two-tone ring applies */
.sh-strip-main { display: flex; align-items: center; gap: var(--s3); min-width: 0; }
.sh-strip-icon { color: var(--muted); display: inline-flex; }
.sh-strip-head { font-weight: 700; font-size: var(--t-md); color: var(--ink); }
.sh-strip-job { font-size: var(--t-sm); line-height: 1.45; color: var(--muted); }
.sh-strip-job.r-yellow { color: var(--warn-ink); font-weight: 600; }
.sh-strip-job.r-red { color: var(--danger-ink); font-weight: 600; }
.sh-strip-nums { display: flex; gap: var(--s5); flex-shrink: 0; }
.sh-strip-nums > div { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.sh-stamp { display: inline-flex; align-items: center; gap: var(--s1);
            color: var(--muted); font-size: var(--t-sm); }
.sh-verdict { margin-bottom: var(--s4); }
.sh-verdict-txt { display: flex; flex-direction: column; gap: var(--s1); min-width: 0; }
.sh-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
           gap: var(--s4); margin-bottom: var(--s4); }
.sh-block { margin-bottom: var(--s4); }
.sh-note { color: var(--muted); font-size: var(--t-sm); line-height: 1.6;
           margin: 0 0 var(--s3); max-width: 68ch; }
.sh-psi { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: var(--s3); }
.sh-psi > div { display: flex; flex-direction: column; gap: 2px; }
.sh-cap-bar { margin: var(--s3) 0; }
.sh-tbl th { font-weight: 600; color: var(--muted); white-space: nowrap; }
/* THE SEPARATOR BELONGS TO THE ROW. These cards are `<tr><th>label</th><td>value</td></tr>`, and
   with border-collapse the CELL's border outranks the ROW's where they meet: every rule was
   --n-5 under the label and --line-2 under the value, 1.37:1 apart across one 1px line. Dropping
   the cell's border leaves `table.tbl tbody tr` to draw it, which :977-984 already says it
   should. (0,1,2), tied with `table.tbl th` and later, so it wins; the thead's th is untouched. */
.sh-tbl tbody th { border-bottom: 0; }
/* A table is the one thing on this page that can be wider than the viewport. */
.sh-scroll { overflow-x: auto; }
/* The sparkline is decoration for the number above it, not a chart to read values
   off, so it carries no axes and no labels — just the shape of the last 24 hours. */
.sh-spark { width: 100%; height: 34px; display: block; overflow: visible; }
.sh-spark-tall { height: 52px; }
/* A DATA LINE TAKES THE DATA RAMP. This is the same rule the reseller dashboard's thirty-day line
   carries (.dash-spark polyline) and it is here for CONSISTENCY, not because it fails:
   /Platform/ServerHealth is Kinetiq-only, no brand block ever reaches it, so --kq-azure
   resolved to the sheet's own #0E8F96
   and measures 3.90:1 on the card — past the 3:1 a graphical object is held to. What was wrong was
   the CHOICE: --kq-azure is decoration («the exact brand colour, never moved», Web/BrandTheme.cs)
   and carries no floor at all — which is what washed the identical line out on the reseller
   dashboard, where the measurement is written down beside .dash-spark polyline. Leaving one of the
   two on a token with no floor is how the next screen to take a brand block inherits the defect.
   --dv-1 is a literal no brand block reaches, and it is 4.95:1 on the card here. */
.sh-spark polyline { fill: none; stroke: var(--dv-1); stroke-width: 2;
                     vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-linecap: round; }
@media (max-width: 760px) {
    .sh-strip { flex-direction: column; align-items: stretch; gap: var(--s3); }
    .sh-strip-nums { gap: var(--s4); justify-content: space-between; }
    .sh-strip-nums > div { align-items: flex-start; }
}
/* A failed trunk is the one figure on the strip that means "no calls can arrive at all",
   so it is the only one allowed to carry colour of its own. */
.sh-strip-nums .sh-bad { color: var(--danger-ink); font-weight: 700; }
.sh-expired { color: var(--danger-ink); font-weight: 600; }
/* ══ PLATFORM DETAIL SCREENS ════════════════════════════════════════════════════════════
   The reseller and customer pages were carrying ~46 inline style attributes, two duplicated
   <style> blocks with literal colours, and four class names with no rule behind them
   (.setting-row, .feature-row, .in, .check). Everything below is the layer they were
   imitating. Nothing here introduces a colour: the only two values are --danger-ink, which
   is byte-identical to the #b3261e they hardcoded, and a color-mix derived from --danger.
   That matters because a page <style> block is emitted AFTER @brand.StyleBlock(), so a
   literal there is unreachable by a reseller's white label by construction. */

/* A grid item's automatic minimum is its MIN-CONTENT. A card holding a six-column table
   therefore sets its own track and pushes the page sideways, and overflow-x on the
   .table-wrap inside cannot release it — the ancestor still measures the table's intrinsic
   width. The mobile pass gave this valve to .dash-grid2/.dash-two/.dash-block only; the
   generic .grid, which is what these pages are built on, never got it. */
.grid > * { min-width: 0; }
/* Collapses on its OWN width instead of the viewport's. A cols-2 nested inside a card is
   governed by the page-level 980px query, so it stayed two-up while its container was
   200px wide, and collapsed while the container was 840px. */
.grid.cols-fit { grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.grid > .span-2 { grid-column: 1 / -1; }
.grid.top { align-items: start; }
/* A settings row: title and state badge at the start, its own control at the end. Written
   out inline six times on the customer screen; two of the copies carried a class name that
   no stylesheet defines, and one of those had no inline fallback either, so it rendered
   with no layout at all. .feature already owns the vertical rhythm and the first/last-of-type
   edges — .split only adds the two columns, so those edges keep working. */
.feature.split { display: flex; align-items: flex-start; justify-content: space-between;
                 gap: var(--s3); flex-wrap: wrap; }
.feature.split > :first-child { min-width: 0; flex: 1 1 22rem; }
.feature.split .d { padding-inline-start: 0; max-width: 52ch; }
.feature.split form { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.feature .t { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
              font-size: var(--t-base); font-weight: 700; }
/* A form beneath its own table in one card. The table's body is .tight, i.e. padding:0, so
   a form put in there sits flush against the card's edge. */
.card__body + .card__body { border-block-start: 1px solid var(--line-2); }
/* .tbl strips the last row's rule, so a totals row had no separator from the data above it
   and wore its weight inline. */
table.tbl tfoot td { border-block-start: 1px solid var(--n-5); font-weight: 600; }
/* One width for a quota box, in ch so it follows the 16px bump on phones — a fixed pixel
   width does not, and these were 130px inline three times and 150px once. */
.control.num { max-width: 14ch; }
/* Irreversible actions. .dz-title is gone: it was re-implementing .card__head.
   A DANGER FRAME (DS-14): the danger edge on the danger wash. The old 30% tint of the red was
   indistinguishable from the ordinary card edge, so the one card that deletes a customer looked
   like every card above it. */
.danger-zone, .frame-danger { border: 1px solid var(--danger-edge); background: var(--danger-wash); border-radius: var(--r-card); }
.danger-zone { max-width: 820px; margin-block-start: var(--s4); }
.frame-danger { padding: var(--s4); }
.danger-zone .card__head h2 { color: var(--danger-ink); }
.danger-zone .control { max-width: 340px; }
.dz-text { margin: 0 0 var(--s3); color: var(--muted); font-size: var(--t-base); }
.dz-list { margin: 0 0 var(--s4); padding-inline-start: var(--s5);
           color: var(--ink-2); font-size: var(--t-base); line-height: 1.7; }
.dz-list .tag-copy { overflow-wrap: anywhere; }
/* Written inline on these pages 6 and 42 times respectively across the product. */
.btn-row { display: flex; gap: var(--s2); flex-wrap: wrap; }
.form-row.flush { margin-block-end: 0; }
.card__head { flex-wrap: wrap; }
.kv dd { overflow-wrap: anywhere; min-width: 0; }
/* The modal toggle is a checkbox, and the global switch rule is (0,2,1) against this one's
   (0,1,0) — so it was being given a 40x22 track. Invisible, but still a 40px flex item
   shoving its own button off the end of the row. Element-qualified to match that
   specificity and win on order, exactly as .kq-plain does. */
input[type="checkbox"].kq-modal-t { appearance: none; -webkit-appearance: none;
    position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none;
    border: 0; background: none; }
@media (max-width: 1024px) {
    /* A destructive action 4px from a routine one, both 36px tall. Every other touch rule
       is keyed to 820px, which leaves an 834px tablet with the rail AND the small targets. */
    .row-actions { flex-wrap: wrap; gap: var(--s2); }
    .row-actions .btn { min-height: 44px; padding-inline: var(--s4); }
}
@media (max-width: 820px) {
    /* tBodies never returns a tfoot, and every card rule is scoped `> tbody >` — so once a
       table became cards the totals row stayed a table-row underneath the stack, with its
       colspans resolved against nothing. */
    table.tbl.is-cardable > tfoot,
    table.tbl.is-cardable > tfoot > tr,
    table.tbl.is-cardable > tfoot > tr > td { display: block; width: auto; }
    table.tbl.is-cardable > tfoot > tr { display: flex; justify-content: space-between;
        gap: var(--s3); padding: var(--s3) var(--s4); border-block-start: 1px solid var(--line); }
    table.tbl.is-cardable > tfoot > tr > td { padding: 0; border: 0; }
    table.tbl.is-cardable > tfoot > tr > td:empty { display: none; }
}
/* A seven-character #RRGGBB box. Was 280px wide because the explanatory sentence
   was living in its placeholder. */
.control.num-hex { max-width: 12ch; margin-block-start: var(--s2); }
/* Figures — phone numbers, keys, times, identifiers. NOT a typeface switch: Assistant is the only
   face in the product, digits included (owner 2026-08-05; the note under the @font-face rules).
   It ships no tabular figures, so tabular-nums documents the intent and starts working the day a
   face that has them is chosen. .fig is the name new markup uses; .mono is its older alias and
   keeps its slight tracking, which only ever applies to Latin digits and letters. */
.fig, .mono { font-variant-numeric: tabular-nums slashed-zero; }
.mono { letter-spacing: .02em; }
/* ── Greeting failover, on the server-health page ──────────────────────────────────────────
   A row of figures over a per-day bar strip. The strip is the point: a steady trickle is the
   normal state of this gate, so the eye needs the SHAPE of the days to tell a baseline from a
   bad window, and a single total would hide exactly that. Bars carry a minimum height so a day
   with one event still draws — a day that is absent and a day that is quiet must not look the
   same. Tokens without fallbacks: every one is defined on :root, so a fallback never ran and only
   hid a hard-coded colour where no brand could reach it. */
.fo-stats { display: flex; flex-wrap: wrap; gap: var(--s3); margin-bottom: var(--s4); }
/* a row of them, so the seat and not the lift — see .stat */
.fo-stat { display: flex; flex-direction: column; gap: 2px; min-width: 96px;
           padding: var(--s2) var(--s3); border: 1px solid var(--surface-edge);
           border-radius: var(--r-card); background: var(--surface-material);
           box-shadow: var(--surface-seat); }
.fo-stat .n { font-size: 1.3rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.fo-stat .l { font-size: var(--t-sm); color: var(--muted); }
.fo-stat.bad .n { color: var(--danger-ink); }
.fo-days { display: flex; align-items: flex-end; gap: 3px; height: 88px;
           margin-bottom: var(--s4); overflow-x: auto; padding-bottom: 18px; }
.fo-day { position: relative; flex: 1 0 24px; height: 100%;
          display: flex; align-items: flex-end; justify-content: center; }
.fo-bar { width: 100%; min-height: 3px; border-radius: 3px 3px 0 0;
          background: var(--kq-azure); }
.fo-dl { position: absolute; bottom: -17px; font-size: .66rem; white-space: nowrap;
         color: var(--muted); font-variant-numeric: tabular-nums; }
.fo-table td { font-variant-numeric: tabular-nums; }
/* ── Tool test box, on the agent's tool screen ─────────────────────────────────────────────
   The panes hold a URL, a JSON body and a vendor's error object — all of it LTR, all of it
   meaningful character by character, so it is monospaced, wraps rather than truncates, and
   never scrolls the page sideways. The request is shown beside the response deliberately: the
   fault this box was built for was a body whose placeholder was missing, which is invisible in
   a response and obvious in a request. */
.tt-args { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
           gap: var(--s3); margin-bottom: var(--s3); }
.tt-result { margin-top: var(--s4); padding: var(--s3); border: 1px solid var(--surface-edge);
             border-radius: var(--r-card); background: var(--surface-material);
             box-shadow: var(--surface-lift); }
.tt-head { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); }
.tt-label { font-weight: 600; font-size: var(--t-sm); margin: var(--s3) 0 var(--s1); }
.tt-pre { direction: ltr; text-align: left; white-space: pre-wrap; overflow-wrap: anywhere;
          font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
          font-size: .78rem; line-height: 1.55; padding: var(--s2) var(--s3);
          border: 1px solid var(--line); border-radius: var(--radius-sm);
          background: var(--bg); margin-bottom: var(--s2); max-height: 320px; overflow-y: auto; }
/* ══════════════════════════════════════════════════════════════════════════════════════════
   THE COMPONENT LAYER (redesign 2026-09-17)
   ══════════════════════════════════════════════════════════════════════════════════════════
   One recipe per component, taken from the approved kit, and named the way the helpers and tag
   helpers write the markup. Screens compose these. A screen that needs something more writes it
   in its own section at the end of this file — it does not restyle a component in place, which
   is how five "card title" sizes got into one product. Tokens only; logical properties only. */

/* ── Arrivals ───────────────────────────────────────────────────────────────────────────────
   Motion belongs to content that ARRIVES — a menu opening, a notice appearing, a dialog, a list
   that was just replaced — never to the control under the finger (SHELL-28). 120ms of opacity
   and 4px of travel in, nothing on the way out. @starting-style is what gives an element that
   was display:none a frame ago somewhere to start from; a browser without it simply shows it. */
.usermenu.open .usermenu__panel,
.cselect__list:not([hidden]),
.kq-notice {
    transition: opacity var(--dur-fast) var(--e-out), translate var(--dur-fast) var(--e-out);
}
.scrim.show { transition: opacity var(--dur-fast) var(--e-out); }
dialog.kq-dialog[open] .kq-dialog__box { transition: opacity var(--dur-fast) var(--e-out), scale var(--dur-fast) var(--e-out); }
[data-list-region] { transition: opacity var(--dur-fast) linear; }
@starting-style {
    .usermenu.open .usermenu__panel,
    .cselect__list:not([hidden]),
    .kq-notice { opacity: 0; translate: 0 -4px; }
    .scrim.show { opacity: 0; }
    dialog.kq-dialog[open] .kq-dialog__box { opacity: 0; scale: .98; }
    [data-list-region] { opacity: 0; }
}
/* Travel is the vestibular trigger, opacity is not: the fades survive, the movement does not.
   Replaced list results do not even fade — a reader who asked for less motion gets the rows. */
@media (prefers-reduced-motion: reduce) {
    .usermenu.open .usermenu__panel,
    .cselect__list:not([hidden]),
    .kq-notice { translate: none !important; }
    dialog.kq-dialog[open] .kq-dialog__box { scale: none !important; }
    [data-list-region] { transition: none; }
}
/* ── Notices (the four levels of a message about what just happened) ───────────────────────
   Dismissible, never auto-hiding: a slow reader or a screen-reader user would miss a notice that
   leaves by itself. The message is announced by kq-core.js through the live regions, so the
   notice itself carries no role. */
.kq-notice {
    display: flex; align-items: flex-start; gap: var(--s3);
    padding: var(--s3) var(--s4); margin-bottom: var(--s3);
    /* A NOTICE GETS A SPINE. The tinted box alone reads as a block of colour; a 3px rule along
       the side the reader starts from gives the level a shape as well as a hue, which is the half
       that survives a monochrome print and a colour-blind reader. The longhand comes AFTER the
       shorthand, which would otherwise reset it. */
    border: 1px solid transparent; border-inline-start-width: 3px; border-radius: var(--r);
    font-size: var(--t-md); line-height: 1.6;
}
.kq-notice[hidden] { display: none; }
.kq-notice > svg { flex: none; width: 18px; height: 18px; margin-top: 2px; }
.kq-notice__text { flex: 1 1 auto; min-width: 0; margin: 0; overflow-wrap: anywhere; }
.kq-notice__text strong, .kq-notice__text b { font-weight: 700; }
.kq-notice__x {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; margin-block: -3px; padding: 0;
    border: 0; border-radius: var(--r); background: transparent; color: inherit; cursor: pointer;
}
.kq-notice__x svg { width: 16px; height: 16px; }
@media (hover: hover) { .kq-notice__x:hover { background: color-mix(in srgb, currentColor 10%, transparent); } }
.kq-notice--ok { color: var(--ok-ink); background: var(--ok-bg); border-color: var(--ok-edge); border-inline-start-color: var(--ok-ink); }
.kq-notice--info { color: var(--info-ink); background: var(--info-bg); border-color: var(--info-edge); border-inline-start-color: var(--info-ink); }
.kq-notice--warn { color: var(--warn-ink); background: var(--warn-bg); border-color: var(--warn-edge); border-inline-start-color: var(--warn-ink); }
.kq-notice--danger { color: var(--danger-ink); background: var(--danger-bg); border-color: var(--danger-edge); border-inline-start-color: var(--danger-ink); }
/* The service strip: a state, not a message — it stays for as long as the state does, so it has
   no close button and sits above the page header on every screen it concerns. */
.kq-strip {
    display: flex; align-items: flex-start; gap: var(--s3);
    padding: var(--s4); margin-bottom: var(--s4);
    border: 1px solid var(--line); border-inline-start-width: 3px; border-radius: var(--r-card);
    font-size: var(--t-base); line-height: 1.6; color: var(--ink);
}
.kq-strip > svg { flex: none; width: 18px; height: 18px; margin-top: 3px; }
.kq-strip p { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.kq-strip a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.kq-strip.is-danger { border-color: var(--danger-edge); border-inline-start-color: var(--danger-ink); background: var(--danger-wash); }
.kq-strip.is-danger > svg,
.kq-strip.is-danger strong,
.kq-strip.is-danger b { color: var(--danger-ink); font-weight: 700; }
/* The error summary at the top of a form after a failed save. It receives focus, so it needs no
   close button and no alert role; each line is a link to its field. */
.kq-errsum {
    display: block; padding: var(--s3) var(--s4); margin-bottom: var(--s4);
    border: 1px solid var(--danger-edge); border-inline-start-width: 3px;
    border-inline-start-color: var(--danger-ink); border-radius: var(--r);
    background: var(--danger-bg); color: var(--danger-ink); font-size: var(--t-md);
}
.kq-errsum:has(> svg) { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--s3); }
.kq-errsum:has(> svg) > :not(svg) { grid-column: 2; }
.kq-errsum > svg { width: 18px; height: 18px; margin-top: 3px; }
.kq-errsum h2 { margin: 0; font-size: var(--t-base); font-weight: 700; line-height: 1.6; color: inherit; }
.kq-errsum ul { list-style: none; margin: var(--s1) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--s1); }
.kq-errsum a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
/* The two empty states: «עוד אין …» and «אין התאמה לסינון». One quiet icon, one sentence, and the
   clear-filter link only for the second — never a primary button, the header already has it. */
.kq-empty {
    display: flex; flex-direction: column; align-items: center; gap: var(--s2);
    padding: var(--s6) var(--s4); text-align: center; color: var(--ink-2);
}
.kq-empty__icon { display: inline-flex; color: var(--n-7); }
.kq-empty__icon svg, .kq-empty > svg { width: 26px; height: 26px; color: var(--n-6); }
.kq-empty p { margin: 0; font-size: var(--t-base); font-weight: 600; }
.kq-empty a { font-size: var(--t-md); font-weight: 600; }
.eyebrow { margin: 0 0 var(--s2); font-size: var(--t-sm); font-weight: 600; color: var(--muted); }
/* ── Forms ──────────────────────────────────────────────────────────────────────────────────── */
/* A group of radios is a fieldset with a legend (AGE-22), and a fieldset must not draw the
   browser's groove box around a choice that is one line of a form. */
fieldset { border: 0; padding: 0; margin: 0; min-inline-size: 0; }
/* :where() — no specificity. <kq-choice-group class="form-row"> renders `form-row kq-group`, and a
   class-weight reset this late in the file would take the row's spacing away. */
:where(fieldset.choice-group, .kq-group, .kq-fieldset) { border: 0; padding: 0; margin: 0; min-inline-size: 0; }
legend.lbl {
    display: block; float: none; padding: 0; margin: 0 0 var(--s2);
    font-weight: 600; font-size: var(--t-md); color: var(--n-8);
}
.field { min-width: 0; }
/* the character counter under a long field — visible from 80%, in the danger ink once over */
.field-count {
    display: flex; justify-content: flex-end; margin: var(--s1) 0 0;
    font-size: var(--t-sm); color: var(--muted); font-variant-numeric: tabular-nums;
}
.field-count[hidden], .field-count:empty { display: none; }
.field-count.is-over { color: var(--danger-ink); font-weight: 600; }
.field-errors { list-style: none; margin: var(--s1) 0 0; padding: 0; color: var(--danger-ink); font-size: var(--t-sm); font-weight: 600; }
.field-errors li + li { margin-top: 2px; }
/* several errors under one field sit beside the alert icon, level with it */
.field-error > .field-errors { margin: 0; min-width: 0; }
/* A FIELD IN ERROR (FORM-18, AUTH-10) is told apart by its edge in the danger ink, not by the
   message alone: the message can sit under a long textarea, out of view. The three selectors are
   the server's state, the tag helper's state and the browser's own check after a submit. */
.control[aria-invalid="true"],
.control.input-validation-error,
form.kq-submitted .control:invalid { border-color: var(--danger-ink); border-width: 1.5px; }
/* on its own: a browser that does not know :user-invalid would discard the whole list above */
.control:user-invalid { border-color: var(--danger-ink); border-width: 1.5px; }
/* Choice cards: a radio with a title and a one-line description, the whole card clickable. The
   caution variant marks the choice whose consequence the reader must notice. */
.kq-choice { display: grid; gap: var(--s2); }
.kq-choice__opt {
    display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--s3); align-items: start;
    padding: var(--s3) var(--s4); border: 1px solid var(--control-edge); border-radius: var(--r);
    background: var(--control-bg); color: var(--ink); font-weight: 600; cursor: pointer;
}
.kq-choice__opt > input[type="radio"] { width: 18px; height: 18px; margin: 3px 0 0; accent-color: var(--kq-btn-bg); }
.kq-choice__opt > :not(input) { grid-column: 2; min-width: 0; }
.kq-choice__opt:has(input:checked) { border-color: var(--kq-btn-bg); box-shadow: inset 0 0 0 1px var(--kq-btn-bg); }
.kq-choice__opt.is-caution:has(input:checked) { border-color: var(--warn-edge); background: var(--warn-bg); box-shadow: none; }
.kq-choice__d { display: block; margin: 2px 0 0; font-size: var(--t-sm); font-weight: 400; color: var(--muted); }
/* A section that exists only while its switch is on. The switch is the reveal's own — a switch
   nested inside the body must not collapse the body it lives in. */
.kq-reveal__body { margin-top: var(--s3); }
.kq-reveal:has(> input[role="switch"]:not(:checked)) > .kq-reveal__body { display: none; }
.kq-reveal:has(> :not(.kq-reveal__body) input[role="switch"]:not(:checked)) > .kq-reveal__body { display: none; }
/* A select and its button on one line (number assignment rows); stacked on a phone, where the
   two side by side left the list 90px wide (MOB-22). */
.row-form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin: 0; }
.row-form .control { width: auto; flex: 1 1 10rem; max-width: 14rem; min-width: 0; }
.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); margin-top: var(--s5); }
/* a value shown where a field would be, because this screen does not let it change */
.kv-static { margin: 0; padding-block: var(--s2); color: var(--ink); font-weight: 500; overflow-wrap: anywhere; }
/* FRAMES. A consequential row wears its danger frame only while its switch is OFF — the state
   whose consequence the reader must see — and keeps a transparent edge the rest of the time so
   flipping the switch never moves the layout. */
.is-consequential, .kq-clearable { border: 1px solid transparent; border-radius: var(--r-card); padding: var(--s3) var(--s4); }
.form-frame { border: 1px solid var(--line); border-radius: var(--r-card); padding: var(--s4); }
.is-consequential:has(input[type="checkbox"]:not(:checked)) { border-color: var(--danger-edge); background: var(--danger-wash); }
.when-off { display: none; margin: var(--s2) 0 0; font-size: var(--t-sm); font-weight: 600; color: var(--danger-ink); }
.is-consequential:has(input[type="checkbox"]:not(:checked)) .when-off { display: block; }
/* a stored secret with a «clear it» switch: while the switch is on, the frame says what saving will do */
.kq-clear { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s2); font-size: var(--t-md); }
.kq-clearable:has(.kq-clear input:checked) { border-color: var(--danger-edge); background: var(--danger-wash); }
.kq-clearable:has(.kq-clear input:checked) input[type="password"] { opacity: .55; }
/* Password rules: each requirement ticks as it is met. No transition — the change is the news. */
.pw-rules {
    list-style: none; margin: var(--s2) 0 0; padding: 0;
    display: grid; grid-template-columns: repeat(auto-fill, minmax(min(12rem, 100%), 1fr)); gap: 2px var(--s4);
    font-size: var(--t-sm); color: var(--muted);
}
.pw-rules li { display: flex; align-items: center; gap: var(--s2); }
.pw-rules__mark { flex: none; width: 8px; height: 8px; border-radius: var(--r-circle); border: 1.5px solid currentColor; }
.pw-rules li[data-met="true"] { color: var(--ok-ink); }
.pw-rules li[data-met="true"] .pw-rules__mark { background: currentColor; }
/* ── The save bar ───────────────────────────────────────────────────────────────────────────
   Sticky at the bottom of a long form. The page reserves its height as scroll padding, so a field
   focused near the bottom lands above the bar instead of under it (A11Y-19). */
html:has(.kq-savebar) { scroll-padding-block-end: calc(var(--kq-savebar-h) + 12px); }
.kq-savebar {
    position: sticky; bottom: 0; z-index: 20;
    display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--s4);
    min-height: var(--kq-savebar-h); margin-top: var(--s5);
    padding: var(--s3) var(--s5); padding-bottom: max(var(--s3), env(safe-area-inset-bottom));
    /* THE BAR THAT COMMITS A FORM IS PART OF THE TOOL, so it is part of the frame: a dark strip
       closing the bottom of the work area, with the save button the one lit thing on it. No
       --float-shine here — a white inset hairline on a dark bar is the light material showing. */
    background: var(--frame-float); color: var(--frame-ink-2);
    -webkit-backdrop-filter: var(--float-blur); backdrop-filter: var(--float-blur);
    border-top: 1px solid rgba(255, 255, 255, .12); box-shadow: var(--shadow-savebar);
    border-radius: var(--r-card) var(--r-card) 0 0;
}
.kq-savebar__state,
.kq-savebar [data-dirty-state] {
    margin: 0; min-width: 0; display: flex; align-items: center; gap: var(--s2);
    font-size: var(--t-md); font-weight: 600; color: var(--frame-ink-2);
}
/* The dot is not --ok-ink / --warn-ink, because it sits on the DARK bar: the status inks are
   measured on paper and neither of them reads there. It is the frame's own pair (:root), which is
   where a colour belongs — as a literal here it was two of ds-gate's six DS08 findings. */
.kq-savebar__state::before,
.kq-savebar [data-dirty-state]::before {
    content: ""; flex: none; width: 7px; height: 7px; border-radius: var(--r-circle); background: var(--frame-dot-ok);
}
html[data-dirty] .kq-savebar__state::before,
html[data-dirty] .kq-savebar [data-dirty-state]::before { background: var(--frame-dot-warn); }
.kq-savebar__act { display: flex; align-items: center; gap: var(--s3); flex: none; margin-inline-start: auto; }
/* the bar's secondary button is a frame control, not a ghost button on paper */
.kq-savebar .btn-ghost {
    background: var(--frame-ctl-bg); border-color: var(--frame-ctl-edge); color: var(--frame-ink);
}
/* AND SO IS THE PRIMARY. Measured on painted pixels: the gradient face is 2.58:1 at the top and
   2.12:1 at the foot against the bar, and its own --kq-btn-bg edge 2.34:1 — all under the 3:1
   every operable boundary in this sheet is held to, where on paper the same button was 5.45:1.
   The FILL does not move (white on it is 5.4:1 and a lighter face spends exactly that); the edge
   carries it, and it is the same white the button beside it takes. Painted, it is 5.51:1. */
.kq-savebar .btn-primary { border-color: var(--frame-ctl-edge); }
/* ── The consequence dialog ─────────────────────────────────────────────────────────────────
   A real <dialog> opened with showModal(): focus trap, Escape and an inert page come from the
   browser. The layer is transparent and scrollable; the box is opaque white on a dark scrim that
   no brand can lighten, so the words stay the only thing to read. */
dialog.kq-dialog {
    position: fixed; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none;
    margin: 0; padding: var(--s5); border: 0; background: transparent; color: var(--ink);
    overflow-y: auto; overscroll-behavior: contain;
}
dialog.kq-dialog[open] { display: grid; }
dialog.kq-dialog::backdrop { background: var(--scrim); }
/* The dialog was already opaque white on a dark scrim — the one surface in the product that was
   right before the material split — so only its edge joins the new hierarchy, and a neutral dialog
   is framed like a card rather than found by its shadow. */
.kq-dialog__box {
    margin: auto; width: min(520px, 100%); min-width: 0;
    background: var(--n-0); color: var(--ink);
    border: 1px solid var(--surface-edge); border-radius: var(--r-card);
    box-shadow: var(--shadow-dialog); overflow: hidden;
}
.kq-dialog.is-danger .kq-dialog__box { border-color: var(--danger-edge); }
.kq-dialog__head { display: flex; align-items: center; gap: var(--s4); padding: var(--s5) var(--s5) 0; }
.kq-dialog__icon {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; border-radius: var(--r-circle);
    background: var(--neutral-bg); color: var(--neutral-ink);
}
.kq-dialog__icon svg { width: 20px; height: 20px; }
.kq-dialog.is-danger .kq-dialog__icon { background: var(--danger-bg); color: var(--danger-ink); }
.kq-dialog__title { margin: 0; min-width: 0; font-size: var(--t-lg); font-weight: 700; line-height: 1.4; overflow-wrap: anywhere; }
/* the lead sits under the title, past the icon's column: the head's own padding, the icon's
   38px (.kq-dialog__icon above — it was 40px when this line was written) and the head's gap */
.kq-dialog__lead {
    margin: 0; padding: var(--s1) var(--s5) 0;
    padding-inline-start: calc(var(--s5) + 38px + var(--s4));
    font-size: var(--t-base); color: var(--ink-2); overflow-wrap: anywhere;
}
.kq-dialog__list {
    list-style: none; margin: var(--s4) var(--s5) 0; padding: 0 var(--s4);
    border: 1px solid var(--line); border-radius: var(--r);
}
.kq-dialog__item { display: flex; align-items: flex-start; gap: var(--s3); padding: var(--s3) 0; font-size: var(--t-base); color: var(--ink); }
.kq-dialog__item + .kq-dialog__item { border-top: 1px solid var(--line); }
.kq-dialog__item svg { flex: none; width: 18px; height: 18px; margin-top: 3px; color: var(--danger-ink); }
.kq-dialog__item strong { font-weight: 700; font-variant-numeric: tabular-nums; }
.kq-dialog__item.is-kept { color: var(--muted); }
.kq-dialog__item.is-kept svg { color: var(--ok-ink); }
.kq-dialog__type { display: flex; flex-direction: column; gap: var(--s1); margin: var(--s5) var(--s5) 0; }
.kq-dialog__type .lbl { margin: 0; }
.kq-dialog__foot {
    display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: var(--s3);
    margin-top: var(--s5); padding: var(--s4) var(--s5); border-top: 1px solid var(--line);
    background: var(--n-1);
}
.kq-dialog__reason {
    margin: 0; margin-inline-end: auto; min-width: 0; display: flex; align-items: center; gap: var(--s2);
    font-size: var(--t-sm); color: var(--muted);
}
.kq-dialog__list[hidden], .kq-dialog__type[hidden], .kq-dialog__reason[hidden] { display: none; }
.kq-dialog__foot .btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }
/* ── The shell ──────────────────────────────────────────────────────────────────────────────── */
/* The skip link is the first thing a keyboard reaches and invisible until it does. */
.kq-skip {
    position: fixed; inset-inline-start: var(--s3); top: var(--s3); z-index: 100;
    padding: var(--s2) var(--s4); border-radius: var(--r);
    background: var(--n-0); color: var(--ink); font-weight: 600; box-shadow: var(--shadow-lg);
}
.kq-skip:not(:focus) {
    position: absolute; inline-size: 1px; block-size: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; box-shadow: none;
}
/* Whose account this is — in the top bar whenever there IS one, because on a phone the rail that
   used to say it is closed. A platform owner outside every customer account has none, and the
   layout renders nothing there rather than falling back to the product's own name. The warning
   variant is the impersonation state. Both are bar controls, so both take the bar's control
   height: three nested pill sizes on one row read as carelessness, not as hierarchy. */
.kq-ctx {
    display: inline-flex; align-items: center; gap: var(--s2);
    min-inline-size: 0; max-inline-size: 18rem; block-size: var(--kq-bar-ctl-h);
    padding-block: 0; padding-inline: var(--s3); border-radius: var(--r-pill);
    /* NOT A CONTROL. The account chip is a static indicator — nothing happens when it is pressed —
       so it takes a quieter edge than the pill and the search beside it, the same way --line and
       --control-edge mean two different things on the light half of the product. Its INK is what
       has to clear 4.5:1, and it does. */
    background: var(--frame-ctl-bg); border: 1px solid rgba(255, 255, 255, .26);
    font-size: var(--t-md); color: var(--frame-ink-2); white-space: nowrap;
}
.kq-ctx svg { flex: none; width: 16px; height: 16px; color: var(--frame-ink-dim); }
.kq-ctx strong,
.kq-ctx__txt { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; color: var(--frame-ink); font-weight: 700; }
.kq-ctx__txt { color: inherit; font-weight: inherit; }
/* The impersonation chip keeps its warm identity — it is the one thing on the bar that must not
   look like the bar — but it is drawn for a dark ground: an amber ink on a dark amber wash rather
   than a pale block punched into the frame. */
.kq-ctx--warn {
    background: var(--frame-warn-bg); border-color: var(--frame-warn-edge); color: var(--frame-warn-ink); font-weight: 600;
    max-inline-size: 22rem; overflow: hidden; text-overflow: ellipsis;
}
/* The page title in the top bar is for phones only: on a wide screen the page header below
   already says it, and saying it twice is a second heading to read past. */
.topbar__title { display: none; }
/* The page header's parts (<kq-page-header>). */
.page-head__main { display: flex; flex-direction: column; min-width: 0; }
.page-head__back,
.back-link {
    display: inline-flex; align-items: center; gap: var(--s1); align-self: flex-start;
    margin-bottom: var(--s1); font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink);
}
.page-head__back svg, .back-link svg { flex: none; width: 18px; height: 18px; }
.page-head__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }
.page-head__actions:empty { display: none; }
/* A page with no layout of its own — the error page. Centred, one icon, one heading, the ways on. */
.kq-status-page {
    min-height: 100vh; min-height: 100dvh; max-width: 520px; margin: 0 auto;
    padding: var(--s7) var(--s4);
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s4);
    text-align: center;
}
.kq-status-page > svg { width: 40px; height: 40px; color: var(--n-7); }
.kq-status-page h1 { margin: 0; font-size: var(--t-xl); font-weight: 700; line-height: 1.25; }
.kq-status-page p { margin: 0; color: var(--ink-2); }
.kq-status-page .form-actions { margin-top: 0; justify-content: center; }
.kq-status-page .field { width: min(360px, 100%); text-align: start; }
/* The notices region sits in the flow of the sign-in card; with nothing in it, it must not take a
   gap of its own, and the last notice's own margin would double the gap under it. */
#kq-notices:empty { display: none; }
.auth__card .kq-notice:last-child { margin-bottom: 0; }
/* The platform search in the top bar. The magnifier is a mask on the form, so it takes the text
   colour and needs no image request or icon markup. On a phone it folds to a 40px icon button and
   opens across the bar while it has focus. */
/* A REAL WIDTH, NOT A FLEX BASIS (SHELL-30). `flex: 0 1 320px` told the bar what the field wanted
   but not what it was worth: the end group is `flex: none`, so it sized itself from the input's
   own 198.86px intrinsic width and then laid out children asking for 320 + 10 + 149 = 479px. The
   140px deficit came out of both — the field shrank to 198.86px, which left 5.22px of margin over
   the placeholder and none at all in the fallback face or at the 16px a touchscreen forces, and
   the user pill shrank by the 19px that made the name wrap. One declared size fixes both. */
.kq-search {
    --kq-search-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E");
    position: relative; flex: 0 0 auto; margin: 0;
    inline-size: clamp(240px, 24vw, 320px); min-inline-size: 240px; max-inline-size: 320px;
}
.kq-search::before {
    content: ""; position: absolute; inset-inline-start: 11px; top: 50%; width: 16px; height: 16px; margin-top: -8px;
    background: var(--frame-ink-dim); pointer-events: none;
    -webkit-mask: var(--kq-search-glyph) center / contain no-repeat;
    mask: var(--kq-search-glyph) center / contain no-repeat;
}
.kq-search .control {
    inline-size: 100%; block-size: var(--kq-bar-ctl-h);
    padding-block: 0; padding-inline: calc(11px + 16px + var(--s2)) var(--s3);
    font-size: var(--t-md); border-radius: var(--r-pill);
    background: var(--frame-ctl-bg); border-color: var(--frame-ctl-edge);
}
.kq-search .control:focus { border-color: #fff; box-shadow: 0 0 0 3px rgba(255, 255, 255, .18); }
/* ≤820 the search FOLDS to an icon button and the block below paints its text and placeholder
   transparent to do it. Colouring them unconditionally would put «מספר, שם לקוח או מזהה שיחה»
   back inside a 40px circle, so the ink is stated only where the field is a field. */
@media (min-width: 821px) {
    .kq-search .control { color: var(--frame-ink); }
    .kq-search .control::placeholder { color: var(--frame-ink-dim); }
}
/* the late-callback count on a rail item — a job waiting, so it is a pill after the label */
.nav__count {
    flex: none; margin-inline-start: auto; min-width: 20px; padding: 0 var(--s1);
    border-radius: var(--r-pill); background: var(--danger-bg); color: var(--danger-ink); border: 0;
    font-size: var(--t-xs); font-weight: 700; line-height: 1.6; text-align: center; font-variant-numeric: tabular-nums;
}
/* The invitation link card is rendered once by the layout, above the page it belongs to. */
.kq-invite { margin-bottom: var(--s5); }
/* ── Lists ──────────────────────────────────────────────────────────────────────────────────── */
.kq-pager {
    display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--s3);
    /* It sits on the card's own foot, which needs a line above it now: the table under the dark
       band has no bottom rule of its own. */
    padding: var(--s3) var(--s5); border-top: 1px solid var(--line); background: var(--n-1);
    font-size: var(--t-md);
}
.kq-pager > p { margin: 0; color: var(--muted); white-space: nowrap; }
.kq-pager > div,
.kq-pager ul, .kq-pager ol { list-style: none; margin: 0; padding: 0; display: flex; align-items: center; flex-wrap: wrap; gap: var(--s1); }
.kq-pager a:not(.btn) {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--s1);
    min-width: 32px; padding: var(--s2); border: 1px solid transparent; border-radius: var(--r);
    font-weight: 600; color: var(--ink-2); text-decoration: none; font-variant-numeric: tabular-nums;
}
.kq-pager a svg { width: 16px; height: 16px; }
.kq-pager a[aria-current="page"] { background: var(--kq-btn-fill); border-color: var(--kq-btn-bg); color: var(--kq-btn-on); }
@media (hover: hover) {
    .kq-pager a:not(.btn):not([aria-current="page"]):hover { background: var(--bg); border-color: var(--control-edge); color: var(--ink); }
}
.kq-pager__jump { display: inline-flex; align-items: center; gap: var(--s2); margin: 0; margin-inline-start: var(--s3); }
.kq-pager__jump label { white-space: nowrap; color: var(--ink-2); }
.kq-pager__jump .control { width: 4.5rem; padding: var(--s2); font-variant-numeric: tabular-nums; }
.list-status { margin: 0 0 var(--s3); font-size: var(--t-md); color: var(--muted); }
/* the sort control a phone needs because its table has become cards with no headers to click */
.list-order { display: none; }
/* a list being replaced stays readable but visibly not final */
[data-list-region][aria-busy="true"] { opacity: .6; }
/* ── Data ───────────────────────────────────────────────────────────────────────────────────── */
/* Figure tiles: label, the number, one comparison line. The whole tile is a link to its view. */
.kq-figs { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s4); margin-bottom: var(--s5); }
.kq-fig {
    display: flex; flex-direction: column; min-width: 0; gap: var(--s2);
    padding: var(--s4) var(--s5); color: var(--ink); text-decoration: none;
    /* four of these in a row above the dashboard's cards: the seat, not the lift — see .stat */
    background: var(--surface-material);
    border: 1px solid var(--surface-edge); border-radius: var(--r-card);
    box-shadow: var(--surface-seat);
    /* border-colour only: the hover no longer swaps the shadow, so a box-shadow transition here
       would animate a value that never changes */
    transition: border-color var(--dur-fast) var(--e-standard);
}
.kq-fig__label { display: flex; align-items: center; justify-content: space-between; gap: var(--s2); font-size: var(--t-md); color: var(--muted); }
.kq-fig__label svg { flex: none; width: 18px; height: 18px; }
/* THE CHIP LEADS THE ROW. The tile's icon used to sit at the far end of the label line, where
   space-between strands it opposite the words it belongs to. Reversed, it becomes the first thing
   in reading order — chip, then label, then the number under both — and four tiles read as one
   row of four objects instead of four boxes with a mark in the corner.
   The hue is the tile's POSITION, never its meaning: first tile teal, second indigo, and so on,
   so five tiles read as five things at a glance while every tile still carries its own word, its
   own number and its own link.
   These carry the TILE class as well, at (0,2,0) and (0,2,1): `.dash .kq-fig__label svg` in the
   dashboard section is the same (0,2,1), and the two are told apart by source order alone. That
   copy was deleted when this was merged in — see the note there. */
.kq-fig .kq-fig__label {
    flex-direction: row-reverse; justify-content: flex-end; align-items: center; gap: var(--s3);
    font-size: var(--t-md); font-weight: 600; color: var(--n-8); min-block-size: 32px;
}
.kq-fig .kq-fig__label > span { min-inline-size: 0; overflow-wrap: anywhere; }
.kq-fig .kq-fig__label svg {
    inline-size: 32px; block-size: 32px; padding: 7px; box-sizing: border-box; flex: none;
    border-radius: 9px; background: var(--dv-1-soft); color: var(--dv-1);
}
.kq-figs > .kq-fig:nth-child(2) .kq-fig__label svg { background: var(--dv-2-soft); color: var(--dv-2); }
.kq-figs > .kq-fig:nth-child(3) .kq-fig__label svg { background: var(--dv-3-soft); color: var(--dv-3); }
.kq-figs > .kq-fig:nth-child(4) .kq-fig__label svg { background: var(--dv-4-soft); color: var(--dv-4); }
.kq-figs > .kq-fig:nth-child(5) .kq-fig__label svg { background: var(--dv-5-soft); color: var(--dv-5); }
.kq-fig__value { font-size: var(--t-2xl); font-weight: 700; line-height: 1.05; letter-spacing: -0.015em; font-variant-numeric: tabular-nums slashed-zero; overflow-wrap: anywhere; }
.kq-fig__sub { display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--s2); font-size: var(--t-sm); color: var(--muted); }
.kq-fig .kq-fig__sub { font-size: var(--t-sm); color: var(--muted); margin-top: 2px; }
/* THE HOVER MOVES A COLOUR, NOT A SHADOW. It used to swap in a 20px halo, which re-rasterises a
   blur on every frame of the transition; and with a real edge there is finally an edge to darken.
   Colour is also the half that survives prefers-reduced-motion intact — the global guard keeps
   border-color in its transition-property whitelist and drops travel. */
@media (hover: hover) { a.kq-fig:hover { color: var(--ink); border-color: var(--control-edge); box-shadow: var(--surface-lift); } }
@media (max-width: 720px) {
    .kq-figs { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3); }
    .kq-fig { padding: var(--s3) var(--s4); }
    .kq-fig__value { font-size: var(--t-xl); }
}
/* A status dot on its own, beside a sentence — the tone is its colour, the sentence its meaning */
.kq-dot.is-ok { background: var(--ok-ink); }
.kq-dot.is-warn { background: var(--warn-ink); }
.kq-dot.is-danger { background: var(--danger-ink); }
.kq-dot.is-info { background: var(--info-ink); }
.kq-dot.is-neutral { background: var(--neutral-ink); }
/* «דורש טיפול»: dot, the job and its detail, the action named by what it does. The detail is
   never hidden — on a phone the action drops under it instead (A11Y-18). */
.kq-attn { list-style: none; margin: 0; padding: 0; }
.kq-attn > li {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto; column-gap: var(--s3); align-items: center;
    padding: var(--s3) 0; border-top: 1px solid var(--line);
}
.kq-attn > li:first-child { border-top: 0; }
.kq-attn .kq-dot { align-self: start; margin-top: 9px; }
.kq-attn__t { display: block; font-size: var(--t-base); font-weight: 600; line-height: 1.5; color: var(--ink); overflow-wrap: anywhere; }
.kq-attn__d { display: block; font-size: var(--t-sm); line-height: 1.5; color: var(--muted); overflow-wrap: anywhere; }
@media (max-width: 560px) {
    .kq-attn > li { grid-template-columns: auto minmax(0, 1fr); row-gap: var(--s2); }
    .kq-attn > li > :nth-child(3) { grid-column: 2; justify-self: start; }
}
/* The activation path: done, current, still to do — told apart by fill, ring weight and a hidden
   word, never by colour alone. */
.kq-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.kq-steps > li {
    position: relative; display: grid; grid-template-columns: 22px minmax(0, 1fr); column-gap: var(--s4);
    padding-bottom: var(--s4);
}
.kq-steps > li:last-child { padding-bottom: 0; }
.kq-steps > li:not(:last-child)::before {
    content: ""; position: absolute; inset-inline-start: 10px; top: 26px; bottom: var(--s1);
    width: 2px; border-radius: 2px; background: var(--line);
}
.kq-steps > li.is-done:not(:last-child)::before { background: var(--kq-btn-bg); }
.kq-steps__mark,
.kq-steps > li > [aria-hidden="true"]:first-child {
    width: 22px; height: 22px; border-radius: var(--r-circle);
    display: flex; align-items: center; justify-content: center;
    background: var(--n-0); border: 1px solid var(--line-strong); color: var(--muted);
    font-size: var(--t-sm); font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums;
}
.kq-steps__mark svg, .kq-steps > li > [aria-hidden="true"]:first-child svg { width: 14px; height: 14px; }
.kq-steps > li.is-done .kq-steps__mark,
.kq-steps > li.is-done > [aria-hidden="true"]:first-child { background: var(--kq-btn-bg); border-color: var(--kq-btn-bg); color: var(--kq-btn-on); }
.kq-steps > li[aria-current="step"] .kq-steps__mark,
.kq-steps > li[aria-current="step"] > [aria-hidden="true"]:first-child { border: 2px solid var(--kq-btn-bg); color: var(--kq-link-ink); font-weight: 700; }
.kq-steps > li > :not(:first-child) { grid-column: 2; min-width: 0; }
/* Each part answers to two names: the short one and the one Pages/Shared/_StepTrack.cshtml writes
   (.kq-steptrack__head/__count/__bar/__seg, .kq-steps__body/__title/__detail). */
.kq-steps__body { min-width: 0; }
.kq-steps__t, .kq-steps__title { margin: 0; font-size: var(--t-base); font-weight: 600; line-height: 1.6; color: var(--ink); }
.kq-steps__d, .kq-steps__detail { margin: 0; font-size: var(--t-sm); color: var(--muted); overflow-wrap: anywhere; }
.kq-steps__act { margin-top: var(--s2); }
/* «3 מתוך 6 הושלמו» and the six segments beside it — the whole path at a glance */
.kq-steps__head, .kq-steptrack__head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2) var(--s4); margin: 0 0 var(--s4); font-size: var(--t-md); color: var(--ink-2); }
.kq-steptrack__count { margin: 0; font-weight: 600; }
.kq-steps__bar, .kq-steptrack__bar { display: flex; gap: var(--s1); flex: 1 1 8rem; min-width: 0; }
.kq-steps__seg, .kq-steptrack__seg { flex: 1 1 0; height: 6px; border-radius: var(--r-pill); background: var(--n-3); }
.kq-steps__seg.is-done, .kq-steptrack__seg.is-done { background: var(--kq-btn-bg); }
/* Quick links to the places a reader goes next. */
.kq-shortcuts { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s2); }
.kq-shortcuts a {
    display: inline-flex; align-items: center; gap: var(--s2);
    padding: var(--s2) var(--s4); border: 1px solid var(--control-edge); border-radius: var(--r);
    background: var(--ghost-bg); color: var(--ink-2); font-size: var(--t-md); font-weight: 600; text-decoration: none;
}
.kq-shortcuts a svg { width: 18px; height: 18px; }
@media (hover: hover) { .kq-shortcuts a:hover { background: var(--bg); border-color: var(--n-7); color: var(--ink); } }
/* A progress bar. The fill is the action colour; a bar close to its limit takes the status fill. */
.kq-progress { height: 7px; border-radius: var(--r-pill); background: var(--n-3); overflow: hidden; min-width: 0; }
.kq-progress > span { display: block; height: 100%; border-radius: inherit; background: var(--kq-btn-bg); }
.kq-progress.is-warn > span { background: var(--warn-ink); }
.kq-progress.is-danger > span { background: var(--danger-ink); }
/* live data that has stopped updating: still there, visibly not current */
.is-stale { opacity: .55; }
/* A disclosure for the details most readers skip. */
.kq-more > summary {
    display: inline-flex; align-items: center; gap: var(--s2); cursor: pointer; list-style: none;
    font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink);
}
.kq-more > summary::-webkit-details-marker { display: none; }
.kq-more > summary::after {
    content: ""; flex: none; width: 7px; height: 7px; margin-top: -3px;
    border: solid currentColor; border-width: 0 1.5px 1.5px 0; rotate: 45deg;
}
.kq-more[open] > summary::after { margin-top: 3px; rotate: -135deg; }
/* The call outcome badge: never wraps, and its time lines up with the times in the column. */
.kq-outcome { display: inline-flex; align-items: center; gap: var(--s2); white-space: nowrap; }
.kq-outcome bdi { font-variant-numeric: tabular-nums; }
/* ── The drawer ─────────────────────────────────────────────────────────────────────────────
   A panel fixed to the inline end under the top bar (the browser test call); a bottom sheet on a
   phone, where the page pads by the sheet's height so nothing ends up underneath it. */
.kq-drawer {
    position: fixed; top: var(--kq-topbar-h); bottom: 0; inset-inline-end: 0; z-index: 60;
    width: min(460px, 100%); display: flex; flex-direction: column; overflow-y: auto;
    /* A FLOAT, and CONTENT: nothing reflows for it, so the page really is behind it — and people read a
       live transcript in here, so it takes the strong float and not the plain one. */
    background: var(--float-bg-strong);
    -webkit-backdrop-filter: var(--float-blur); backdrop-filter: var(--float-blur);
    border-inline-start: 1px solid var(--surface-edge);
    box-shadow: var(--float-shine), var(--shadow-drawer);
}
.kq-drawer[hidden] { display: none; }
/* Where it is a side panel it meets the top bar at a corner; as a bottom sheet (the ≤820px block
   below) it takes the sheet's own two corners instead. */
@media (min-width: 821px) { .kq-drawer { border-radius: var(--r-card) 0 0 0; } }
/* ── The brand kit ──────────────────────────────────────────────────────────────────────────
   A reseller with no logo gets a wordmark: its first letter on a square in its primary colour and
   its name beside it (DS-07, AUTH-06) — never the KIVOICE image. .brandmark is the same component
   under the name the mark partial's image uses. */
.kq-wordmark, .brandmark { display: inline-flex; align-items: center; gap: var(--s3); min-width: 0; max-width: 100%; line-height: 1; }
.kq-wordmark__sq, .brandmark__glyph {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: 8px;
    background: var(--kq-mark-bg); color: var(--kq-mark-on);
    font-size: 19px; font-weight: 800;
}
.kq-wordmark__name, .brandmark__name {
    min-width: 0; font-size: 22px; font-weight: 800; line-height: 1.15; color: var(--ink); overflow-wrap: anywhere;
}
.kq-wordmark--rail .kq-wordmark__sq, .sidebar .kq-wordmark__sq, .sidebar .brandmark__glyph {
    width: 28px; height: 28px; border-radius: var(--r); font-size: 15px;
}
.kq-wordmark--rail .kq-wordmark__name, .sidebar .kq-wordmark__name, .sidebar .brandmark__name {
    font-size: 20px; color: var(--frame-ink);
    overflow-wrap: normal; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brandmark__logo { display: block; max-width: 200px; max-height: 56px; height: auto; }
/* THE CAP STAYS IN THE RAIL. `max-height: none` removed the only thing holding the first row of
   the menu still: a square reseller logo rendered 150 × 150 and started the nav 118.72px lower.
   150px of width, 40px of height, and the ratio is kept by `inline-size: auto`. */
.sidebar .brandmark__logo { inline-size: auto; max-inline-size: 150px; max-block-size: 40px; }

/* ── THE PRODUCT MARK BESIDE A RAIL SECTION HEADING ──────────────────────────────────────────────
   Owner, 2026-09-23: «הלוגו של KIVOICE בקטן צמוד שמאל ליד "שיחות" בתפריט». Written by
   Pages/Shared/_ProductMark.cshtml, inside the .nav__section it decorates, AFTER the text — the
   rail is RTL, so it lands to the text's left, which is what «צמוד שמאל» asks for.

   TWO SIZES, AND THE SECOND ONE IS WHY THIS RULE STILL EXISTS. It began at 12px beside the words
   «טלפון» and «צ׳אט»: the heading is --t-xs = 11px at weight 600, its capitals stand about 8px,
   and a wordmark whose letters fill its file reads at roughly 9px there — a hair above the label,
   which is what makes it a mark and not another word. Then the owner removed the words
   («תסיר את ה"טלפון" וה"צ'אט" … שיהיה רק את הלוגו … בלי כיתוב נוסף», 2026-09-23) and the mark
   stopped being next to a label and became the label. A thing that names a section is sized for
   that, so .nav__section--mark takes it to 16px — the size the earlier ladder rejected precisely
   because it «reads as a second heading», which is now the job.
   THE 12px BASE IS NOT DEAD. The one-product layout still sets a mark beside «ניהול החשבון», text
   and all, and that is the heading the base rule is measured against.

   NOT A FLEX CONTAINER. Making .nav__section a flex row would have been tidier here and would
   have changed the box of every section heading in the product — the platform rail's, the
   reseller rail's — for one that gained a 14px image. The mark is an inline-block aligned to the
   text's middle instead, so a heading WITHOUT a mark is laid out by exactly the rule it was laid
   out by before this file was touched.

   THE GAP IS ON THE MARK'S INLINE-START, which in this RTL rail is its right side — the side the
   heading text is on. */
.nav__mark {
    display: inline-block; vertical-align: middle;
    block-size: 12px; inline-size: auto;
    margin-inline-start: var(--s2);
}
/* A HEADING THAT IS ONLY A MARK (owner, 2026-09-23: «שיהיה רק את הלוגו … בלי כיתוב נוסף»).
   The words «טלפון» and «צ׳אט» left the two product headings, so the mark is no longer beside a
   label — it IS the label, and it is sized for that: 16px rather than the 12px it took when it
   trailed 11px text. The padding is the section rule's own, unchanged, so the two product
   headings keep the vertical rhythm of «ניהול החשבון» above and below them; only the glyph inside
   grew. The margin the mark carries for the text it used to sit beside is cancelled here, or the
   logo would hang off-centre in a box with nothing to sit beside. */
.nav__section--mark { line-height: 0; }
.nav__section--mark .nav__mark { block-size: 16px; margin-inline-start: 0; }

/* THE PRODUCT HEADING IS A BUTTON THAT CLOSES ITS SECTION (owner, 2026-09-23: «חץ בצד שמאל של
   KIVOICE או KICHAT … שיסגור ויפתח באנימציה את התפריט של המוצרים האלה. שהחץ יהיה צמוד לצד שמאל
   של התפריט ולא יהיה צמוד ללוגואים»).
   THE WHOLE ROW IS THE CONTROL, not the glyph alone. The logo is the section's name, so putting
   both inside one button gives the control that name for free — the WAI accordion shape — and a
   pointer user a target the width of the rail instead of a 16px arrow. The heading DIV stays a
   div: it is one of four kinds of heading in this rail and the other three are not controls.
   THE ARROW IS AT THE FAR SIDE AND THE LOGO AT THE READING EDGE, which `space-between` does in
   whichever direction the document runs. The negative inline-end margin cancels the heading's own
   10px so the glyph lands on the rail's gutter rather than 10px inside it — «צמוד לצד שמאל של
   התפריט» — and the padding it is given back keeps the pointer target off the frame.
   ROTATED, NOT SWAPPED. One glyph turned a quarter turn says the same thing as two glyphs and
   cannot fall out of step with itself; anti-clockwise, so a closed section in an RTL rail points
   the way the text starts. */
.nav__toggle {
    display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
    inline-size: 100%;
    margin-inline-end: calc(var(--s3) * -1);
    padding: 0; padding-inline-end: var(--s2);
    border: 0; background: none; color: inherit; font: inherit; cursor: pointer;
}
.nav__chev {
    inline-size: 16px; block-size: 16px; flex: none;
    opacity: .6; transition: transform .22s ease, opacity .12s ease;
}
/* A pointer that can hover, and only one: a touch device keeps a :hover rule STUCK on the
   last thing it touched (mobile-test.sh, the house rule). */
@media (hover: hover) { .nav__toggle:hover .nav__chev { opacity: 1; } }
.nav__toggle[aria-expanded="false"] .nav__chev { transform: rotate(-90deg); }
.nav__toggle:focus-visible { outline-offset: 0; border-radius: var(--r); }

/* THE SECTION'S ITEMS, IN ONE BOX SO THEY CAN BE CLOSED AS ONE. The box is a flex column with the
   rail's own 2px gap, so a group of items is laid out exactly as the same items were when they
   were siblings of the headings — nothing about an open rail moved.
   max-height AND NOT height, because `auto` cannot be transitioned; kq-rail.js measures the
   content, writes the number, and clears it when the movement ends, so a section that grows later
   is never clipped by a stale value.
   OVERFLOW IS CLIPPED ONLY WHILE IT MATTERS — closed, or moving. An open section left with
   `overflow:hidden` would cut the focus ring of its own links, which are 2px outside their box.
   visibility:hidden ON A CLOSED SECTION takes its links out of the tab order and the
   accessibility tree together. Invisible but focusable is the worst of both: a keyboard reader
   tabs into a menu nobody can see and cannot tell where they are. */
.nav__group { display: flex; flex-direction: column; gap: 2px; transition: max-height .22s ease; }
.nav__group.is-animating { overflow: hidden; }
.nav__group.is-collapsed { max-height: 0; overflow: hidden; visibility: hidden; }
/* THE FIRST FRAME, decided before a single element of the rail exists. kq-rail.js runs in the
   head and puts these classes on <html> from what the reader last chose; without them the rail
   would paint open and snap shut a moment later, on every page. The class is exchanged for
   .is-collapsed once the DOM is ready, at the same computed height, so nothing animates on load. */
:root.kq-rail-off-voice .nav__group[data-kq-rail="voice"],
:root.kq-rail-off-chat .nav__group[data-kq-rail="chat"],
:root.kq-rail-off-pf-voice .nav__group[data-kq-rail="pf-voice"],
:root.kq-rail-off-pf-chat .nav__group[data-kq-rail="pf-chat"] {
    max-height: 0; overflow: hidden; visibility: hidden;
}
@media (prefers-reduced-motion: reduce) {
    .nav__group { transition: none; }
    .nav__chev { transition: none; }
}

/* ── the AI-usage chart's metric switch ───────────────────────────────────────────────────────
   FOUR SERIES ON THE PAGE, ONE VISIBLE. The radios are siblings of both the label strip and the
   series, which is what lets `:checked ~` reach them; they are the same mechanism the agent
   studio's panels use, for the same reason — the CSP is script-src 'self' and this has to switch
   with no script at all.

   THE RADIO IS NOT display:none. A radio that is removed from the layout is removed from the tab
   order with it, and the metric strip would become four words nobody can reach by keyboard. It is
   a 1px transparent box instead, and the focus ring is drawn on its LABEL, where the reader is
   looking.

   A hidden series is display:none on purpose rather than visibility or a clip: its sixty buttons
   have to leave the tab order and the accessibility tree together, or a keyboard lands in a chart
   that is not on the screen. */
.au-metric__r { position: absolute; width: 1px; height: 1px; margin: 0; padding: 0; opacity: 0; pointer-events: none; }
.au-metric { margin-bottom: var(--s4); }
.au-metric .tab { cursor: pointer; user-select: none; }
.au-series { display: none; }
#au-m-cost:checked ~ .au-series[data-metric="cost"],
#au-m-calls:checked ~ .au-series[data-metric="calls"],
#au-m-minutes:checked ~ .au-series[data-metric="minutes"],
#au-m-tokens:checked ~ .au-series[data-metric="tokens"] { display: block; }
#au-m-cost:checked ~ .au-metric label[for="au-m-cost"],
#au-m-calls:checked ~ .au-metric label[for="au-m-calls"],
#au-m-minutes:checked ~ .au-metric label[for="au-m-minutes"],
#au-m-tokens:checked ~ .au-metric label[for="au-m-tokens"] {
    color: var(--kq-link-ink); border-bottom-color: var(--kq-btn-bg);
}
#au-m-cost:focus-visible ~ .au-metric label[for="au-m-cost"],
#au-m-calls:focus-visible ~ .au-metric label[for="au-m-calls"],
#au-m-minutes:focus-visible ~ .au-metric label[for="au-m-minutes"],
#au-m-tokens:focus-visible ~ .au-metric label[for="au-m-tokens"] {
    outline: 2px solid var(--ink); outline-offset: 2px; box-shadow: var(--focus-halo);
}

/* NO FALLBACK RULE HERE ANY MORE. For a few hours .kq-wordmark--nav stood in for the KICHAT
   mark, before the owner supplied the artwork on 2026-09-23. Both products are an <img>
   through .nav__mark now, and the stand-in went with the gap it filled rather than being
   left as three selectors nothing reaches. */
/* The live brand preview on the reseller form: a 260×120 miniature of the shell whose colours are
   set as tokens on this element alone, so the page around it never changes while a colour is
   being tried. Its contents belong to the form's own section. */
.brand-preview {
    position: relative; display: grid; grid-template-columns: 88px minmax(0, 1fr);
    width: 260px; max-width: 100%; height: 120px; overflow: hidden;
    border: 1px solid var(--line); border-radius: var(--r-card); background: var(--ground);
}
/* ── Phones and small tablets, for the components above ───────────────────────────────────── */
@media (max-width: 820px) {
    .kq-savebar { padding-inline: var(--s4); border-radius: 0; }
    /* after the 720px block above, which sets a narrower tile padding of its own */
    .kq-figs { gap: var(--s3); }
    .kq-fig { padding: var(--s3) var(--s4) var(--s4); }
    .kq-fig__value { font-size: var(--t-xl); }
    dialog.kq-dialog { padding: var(--s3); }
    .kq-dialog__lead { padding-inline-start: var(--s5); }
    .row-form { flex-direction: column; align-items: stretch; }
    .row-form .control, .row-form .btn { width: 100%; max-width: none; }
    table.tbl.is-cardable td:has(> .row-form) { grid-template-columns: minmax(0, 1fr); }
    .list-order { display: revert; }
    /* the only shrinkable item in the start group, so it absorbed the whole squeeze: with an
       impersonation chip beside it the screen name was 0.00px wide at 390px — the topbar named
       nothing at all. A floor here, and the chip shrinks instead. */
    .topbar__title { display: block; flex: 1 1 auto; min-inline-size: 5rem; }
    /* The account chip SURVIVES the fold now. The rule above it says it is here because on a
       phone the rail that names the account is closed, and `display: none` said the opposite —
       so on the one width where it is the only thing naming the account, nothing did. It is
       rendered only when there IS an account, so it is no longer noise. When the operator is
       inside a customer the warning chip carries the account line instead and the rail banner
       carries the name in full, so the two do not both fight for a 390px bar. */
    .kq-ctx { max-inline-size: 38vw; }
    .topbar__start:has(.kq-ctx--warn) > .kq-ctx:not(.kq-ctx--warn) { display: none; }
    /* a fixed ceiling, not a fraction of the window: at 40vw the chip reflowed by 1px for every
       2.5px of resize and stood on three lines, 37.19px tall, inside a 40px content box */
    .kq-ctx--warn { flex: 0 1 auto; max-inline-size: 60vw; font-size: var(--t-xs); }
    .kq-ctx__more { display: none; }
    /* Folded to one square button, sized by the same token as the hamburger beside it. It used
       to be a 40px form holding a 42px input — 40px of lead padding on a border-box field floors
       its width at 42 — with the glyph 1px off centre of a box it overflowed. */
    .kq-search { flex: none; inline-size: var(--kq-bar-ctl-h); min-inline-size: 0; max-inline-size: none; }
    .kq-search .control {
        inline-size: 100%; block-size: var(--kq-bar-ctl-h); padding-inline: 0;
        font-size: 16px; border-radius: var(--r); color: transparent; cursor: pointer;
    }
    .kq-search .control::placeholder { color: transparent; }
    .kq-search::before { inset-inline-start: 50%; margin-inline-start: -8px; }
    .kq-search:focus-within {
        position: absolute; inset-inline: var(--s4); top: 50%; translate: 0 -50%;
        inline-size: auto; max-inline-size: none; z-index: 5;
    }
    /* opaque while open: it lies over the title and the user menu, which would show through */
    .kq-search:focus-within .control { inline-size: 100%; padding-inline: calc(11px + 16px + var(--s2)) var(--s3); color: var(--ink); background: var(--n-0); border-color: var(--control-edge); cursor: text; }
    .kq-search:focus-within .control::placeholder { color: var(--n-7); }
    .kq-search:focus-within::before { inset-inline-start: 11px; margin-inline-start: 0; }
    /* the drawer becomes a bottom sheet, and the page makes room for it */
    .kq-drawer {
        top: auto; inset-inline: 0; width: auto; max-height: 70vh; max-height: 70dvh;
        border-inline-start: 0; border-top: 1px solid var(--line);
        border-radius: var(--r-card) var(--r-card) 0 0;
        padding-bottom: env(safe-area-inset-bottom);
    }
    html:has(.kq-drawer:not([hidden])) .content { padding-block-end: calc(var(--wtc-h, 64px) + var(--s4)); }
}
/* ══ TOUCH ═══════════════════════════════════════════════════════════════════════════════════
   Keyed on the POINTER, not the width (MOB-11): a phone held sideways and a tablet are wider than
   820px and got the desktop's 13px fields — which iOS answers by zooming the whole page — and its
   34px targets. The width-keyed phone rules above stay; these add to them on any coarse pointer.
   Where a glyph must stay small, a transparent pseudo-element grows the area a finger can hit. */
@media (pointer: coarse) {
    :root { --tap: 44px; }
    /* THE BAR GROWS WITH ITS CONTROLS. The hamburger already took the 44px floor while the search
       button beside it stayed 40px — two neighbours, two sizes — and a 44px control in a 40px
       content box ate 2px of the bar's padding at each end. Raising both together keeps the bar
       exactly as tall as what is in it: 44 + 10 + 10 + 1. */
    :root { --kq-bar-ctl-h: var(--tap); --kq-topbar-h: 65px; }
    input:not([type=checkbox]):not([type=radio]), select, textarea, .control, .ci-filterrow .control, .search-box .control, .kq-search .control { font-size: 16px; }
    .btn, .btn-sm { min-height: var(--tap); }
    .btn-icon, .topbar .btn-icon { min-width: var(--tap); min-height: var(--tap); }
    /* `.ci-filterrow .control` is restated here for the same reason the 16px size is restated one
       line up: the toolbar rule that stands a filter-bar field down to 34px is (0,2,0) and would
       keep it there under a finger, where the identical field in the bar above is 44px. It reaches
       every screen with a filter bar — the call journal included — which is the same repair. */
    .control, input.control, select.control, .ci-filterrow .control { min-block-size: var(--tap); padding: 10px 12px; }
    .nav__link { min-block-size: var(--tap); }
    /* the list-view tab strip: 42.1px, the smallest thing on a screen whose every other control
       in this block is 44 — and it is the row that switches what the whole page shows */
    .tab { min-block-size: var(--tap); }
    .usermenu__panel a, .usermenu__panel button { min-block-size: var(--tap); }
    /* the table cell gives its lead back: on a tablet a row is a thing you tap, not a line you
       scan. This block is later than `table.tbl td` in the Tables section, which is the only
       other home that declares the cell's padding. */
    table.tbl td { padding: var(--s3) var(--s4); }
    table.tbl td .btn-sm { min-height: var(--tap); }
    .dash-mbtn { width: var(--tap); height: var(--tap); }
    .cselect__opt { min-height: var(--tap); }
    .sw { width: var(--tap); height: var(--tap); }
    .ci-chip { position: relative; }
    .ci-chip::after { content: ""; position: absolute; inset: -9px 0; }
    /* the disclosure that opens a carrier's own reason for a failed registration, and the one on
       server health: an 18.6px line of text, the one control on the number pool a finger cannot
       reliably hit. ::before, because ::after is already the chevron; -13px and not -12px because
       18.6 + 24 is still 42.6px. */
    .kq-more > summary { position: relative; }
    .kq-more > summary::before { content: ""; position: absolute; inset: -13px 0; }
    .input-pw .toggle::after { content: ""; position: absolute; inset: -9px; }
    /* THE SWITCH IS 40 × 22 AND IT IS EVERY CHECKBOX IN THE PRODUCT. `.sw` above is the reseller's
       colour swatch, not this. Where the switch is wrapped in its own label the LABEL is the hit
       area, so it takes the floor and the row grows honestly instead of overhanging the field
       above it — with the pseudo alone, a point 9px above the switch resolved to the switch and
       ate the bottom of the «תיאור» field stacked over it. Where the switch stands alone (the tool
       editor's «הכלי פעיל») the pseudo grows it, the way .ci-chip and the password eye already do;
       the track is untouched. */
    label.checkbox { min-block-size: var(--tap); }
    input[type="checkbox"]:not(.ci-advtoggle)::before { content: ""; position: absolute; inset: -11px -2px; }
    .kq-modal-x::after { content: ""; position: absolute; inset: -11px -6px; }
    .kq-notice__x { width: 40px; height: 40px; }
    /* the studio's chapters. Below 861px the rail is a horizontal strip of chips and its padding
       alone leaves them 31px, while every control beside them in this block is 44. */
    .kq-rail__item { min-block-size: var(--tap); }
}
/* ══════════════════════════════════════════════════════════════════════════════════════════
   SCREEN SECTIONS — filled from the staging files after each wave (contract §8). Each screen's
   own rules live between its pair of markers and nowhere else; they come last, so a screen can
   refine a component above by source order without raising specificity.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
/* ==== SECTION shell ==== */
/* OBSOLETE, rewritten 2026-09-20: .auth__brand, .auth__hero, .auth__hero img and .auth__panel are
   rendered by _AuthLayout again (sign-in only) and were struck from this list. The old
   .auth__brand::after radials came back with them, as background-image ON .auth__brand — the same
   two positions and alphas without the pseudo-element, which is why .auth__brand > * stayed
   struck. Still obsolete: .auth__brand > *, .auth__hero-name, .auth__hero-sub, .auth__mobilelogo,
   .auth__mobilelogo img and .auth__mobilelogo .name — see the fuller note in SECTION auth-pages.
   .topbar .crumb was already unused. */

/* SHELL (UI-SHELL, wave 1). Only what the new shell markup needs and the component layer does not
   define: the two topbar groups that replaced the inline flex style, the notice stack, the
   invitation card's spacing and the rail brand link. */

/* Topbar: start group = menu toggle, page title or h1, account chips; end group = search, user menu.
   ONE OWNER (SHELL-30): the component layer used to declare the same two selectors as well, with
   `.topbar__end { flex: 0 1 auto }`, and lost to this copy 2193 lines later — so the file said two
   opposite things about the group that turned out to be the cause of both of the owner's
   complaints. The size rules for the title and the chips live with their components now; what is
   left here is the two groups, which exist only in this shell's markup. */
.topbar__start,
.topbar__end {
    display: flex;
    align-items: center;
    gap: var(--s3);
    min-inline-size: 0;
}
.topbar__start { flex: 1 1 auto; }
/* The end group is as wide as its children declare. That was true before too — but the search
   expressed itself as a flex BASIS, which intrinsic sizing does not read, so the group sized
   itself from the input's own 198.86px and then squeezed both children by 140px. The search
   states a width now, so this stays honest. */
.topbar__end { flex: 0 0 auto; justify-content: flex-end; }

/* The chips give way before the title does; each one clips its own long value. */
.topbar__start > .kq-ctx { flex: 0 1 auto; }

/* The notice stack under the service strip. Empty = takes no room. */
#kq-notices {
    display: grid;
    gap: var(--s2);
    min-width: 0;
}
#kq-notices:has(> *) { margin-block-end: var(--s4); }
.kq-strip { margin-block-end: var(--s4); }

/* The one-time invitation link card, rendered by the layout above every page body. */
.kq-invite { margin-block-end: var(--s4); }
.kq-invite .kq-notice { margin-block-end: var(--s3); }
.kq-invite .card__head h2 { min-width: 0; overflow-wrap: anywhere; }

/* The rail brand is a link home now; it keeps the rail's ink, never the link colour. */
/* The frame's ink is a literal, never --sb-ink: a reseller with a light secondary gets a DARK
   --sb-ink from BrandTheme, which is invisible on a frame that is always dark. */
a.sidebar__brand { color: var(--frame-ink); text-decoration: none; }

/* The sign-in card's footer and the error page's support code row. */
.auth__foot { text-align: center; }
.kq-status-page .field { margin-block-start: var(--s4); }
/* ==== END SECTION shell ==== */
/* ==== SECTION agents-list ==== */
/* OBSOLETE: .botdial, .botdial svg, .botdial[disabled], span.botdial, button.botdial:active, .botdial:hover,
   .botcard__toggle, @media (max-width: 820px) .botdial, .botcard__head .badge, .botcard__meta > span, .botcard__meta svg,
   .cselect__opt .opt-note, .botcard__actions .btn-danger.botbtn, .botcard__actions .btn-success.botbtn, .botcard__state .dot
   — the agents list no longer renders the dial circle, the «יועבר מ:» note or the old state dot. The `.botdial {` body stays
   in kinetiq.css until UI-FINAL removes the set. */

/* AGENTS LIST (AGT-LIST, wave 2) — /Admin/VoiceBots, Agents.dc.html.
   One card per agent: a head with the name, a body that says what dialling its number does, and an
   action row. The card is a glass panel with full-width rules between those three parts, so the
   padding moves from the card onto its parts. */

.botgrid { align-items: stretch; }

.botcard {
    padding: 0;
    gap: 0;
    border-radius: var(--r-card);
    /* #bot-{id}: every card action returns here, and the name below the topbar must not hide under it */
    scroll-margin-top: calc(var(--kq-topbar-h) + var(--s4));
}
/* A paused agent is still the same object: its state line says it is paused, the card does not
   fade. This restates the card's OWN paper so the 4%-ink tint the global .botcard.is-off rule
   applies never reaches it — it is not a duplicate of the base rule, it is the thing that beats
   that tint. */
.botcard.is-off { background: var(--surface-material); }
.botcard.is-off .botcard__name { color: var(--ink); }

/* The card that was just acted on. A static mark, never an animation: the reader pressed something
   inside this card, and motion belongs to content that arrives. */
.botcard:target { outline: 2px solid var(--kq-btn-bg); outline-offset: 2px; }
.botcard h2:focus-visible { outline-offset: 2px; scroll-margin-top: calc(var(--kq-topbar-h) + var(--s4)); }

/* ── Head ─────────────────────────────────────────────────────────────────────────────────── */
.botcard__head {
    align-items: flex-start;
    gap: var(--s2);
    padding: var(--s4) var(--s5);
    border-block-end: 1px solid var(--line);
}
.botcard__head > svg { flex: none; width: 18px; height: 18px; margin-block-start: 3px; color: var(--ink-2); }
/* The whole name, wrapped — never clamped: two agents that differ only at the end of a long name
   must not look identical. */
.botcard__name {
    margin: 0;
    min-width: 0;
    font-size: var(--t-lg);
    font-weight: 700;
    line-height: 1.35;
    overflow-wrap: anywhere;
}

/* ── Body ─────────────────────────────────────────────────────────────────────────────────── */
.botcard__body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: var(--s4);
    min-width: 0;
    padding: var(--s5);
}
.botcard__meta {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    gap: var(--s2);
    font-size: var(--t-md);
    color: var(--muted);
}
.botcard__meta p { margin: 0; }

/* ── THE STATE OWNS A BAND OF THE CARD (owner 2026-09-25, option ב) ───────────────────────────
   It was one line among three, marked by a 7px dot, and the owner's complaint was the consequence:
   a paused agent and a live one read alike down a list. The words are untouched — they still come
   from VoiceBotState — and so is the tone it chooses. What changed is that the answer now has a
   tinted panel the width of the card.

   THE DOT STAYS. Colour is never the only signal here: the dot keeps its own shape beside the
   words, and the words themselves say the state, so nothing depends on separating two tints.

   THE TINT IS THE TONE'S OWN PAIR (--ok-bg/--ok-ink and the rest), which contrast-test.sh already
   holds to 4.5:1 — the panel does not invent a colour, it reuses the notice palette. */
.botcard__state,
.botcard__state.is-off {
    display: flex;
    align-items: flex-start;
    gap: var(--s2);
    padding: var(--s3) var(--s4);
    border-radius: var(--r);
    border: 1px solid var(--neutral-edge);
    background: var(--neutral-bg);
    font-size: var(--t-base);
    font-weight: 700;
    color: var(--neutral-ink);
}
.botcard__state.is-ok { background: var(--ok-bg); border-color: var(--ok-edge); color: var(--ok-ink); }
.botcard__state.is-warn { background: var(--warn-bg); border-color: var(--warn-edge); color: var(--warn-ink); }
.botcard__state.is-danger { background: var(--danger-bg); border-color: var(--danger-edge); color: var(--danger-ink); }
.botcard__state.is-info { background: var(--info-bg); border-color: var(--info-edge); color: var(--info-ink); }
.botcard__state .kq-dot { flex: none; margin-block-start: .55em; }

/* ── THE REST, AS LABELLED FIELDS ──────────────────────────────────────────────────────────────
   Three facts of different kinds — a voice, a count, a time — used to be one run-on line in which
   nothing said which was which. auto-fit rather than three columns because the chat card has ONE
   field and the voice card three: a fixed count would leave two empty tracks on every chat card. */
.botcard__facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
    gap: var(--s2) var(--s3);
    margin: 0;
}
.botcard__facts dt { font-size: var(--t-sm); color: var(--muted); }
.botcard__facts dd { margin: 2px 0 0; font-size: var(--t-base); font-weight: 600; color: var(--ink);
                     font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }

.botcard__did { display: flex; flex-direction: column; gap: var(--s3); }
.botcard__did p { margin: 0; }
.botcard__nodids { font-size: var(--t-base); color: var(--ink); }
.botcard__did .hint { margin: 0; }
.botcard__kv { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2); }
.botcard__kv .lbl { font-weight: 600; font-size: var(--t-md); color: var(--ink-2); }

/* The number picker, full width inside the card. */
.did-assign { display: flex; flex-direction: column; min-width: 0; }
.did-assign label.lbl { margin-block-end: var(--s1); }
.did-assign .cselect__btn { justify-content: space-between; padding: var(--s3); font-size: var(--t-base); }
/* Content-wide, so the number sits at the start of the row whatever its own direction; the chevron takes the end. */
.did-assign .cselect__btn .cselect__name { flex: 0 1 auto; min-width: 0; font-variant-numeric: tabular-nums; }
/* GAP3-08: nothing inside the list may push it sideways — a long agent name wraps instead. */
.did-assign .cselect__list { max-height: 280px; overflow-y: auto; overflow-x: hidden; }
/* Two lines per option: the number (and its label) first, then who holds it or «פנוי». */
.did-assign .cselect__opt {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 2px;
}
.cselect__line { min-width: 0; overflow-wrap: anywhere; }
.cselect__line .fig { font-variant-numeric: tabular-nums; }
.cselect__note {
    min-width: 0;
    font-size: var(--t-sm);
    font-weight: 400;
    line-height: 1.4;
    color: var(--muted);
    overflow-wrap: anywhere;
}
/* A number another LIVE agent answers on: picking it asks first, and the note says why in the warn ink. */
.did-assign .cselect__opt[data-consequence-title] .cselect__note { color: var(--warn-ink); font-weight: 600; }

/* The in-card confirmation of a number move (VLAB-01): a danger frame under the picker. */
.did-confirm {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    margin-block-start: var(--s3);
    padding: var(--s4);
    border: 1px solid var(--danger-edge);
    border-radius: var(--r-card);
    background: var(--danger-wash);
}
.did-confirm[hidden] { display: none; }
.did-confirm__icon { flex: none; display: inline-flex; color: var(--danger-ink); margin-block-start: 1px; }
.did-confirm__icon svg { width: 18px; height: 18px; }
.did-confirm__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--s3); }
.did-confirm__title,
.did-confirm__detail { margin: 0; font-size: var(--t-md); overflow-wrap: anywhere; }
.did-confirm__title { font-weight: 700; color: var(--ink); }
.did-confirm__detail { color: var(--ink-2); }
.did-confirm__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: var(--s2); }

/* Outbound-only row. The number field and its visible label ride the switch (kinetiq.css :has rule). */
.fwd-row { margin-block-start: 0; gap: var(--s2) var(--s3); }
.fwd-num { flex: 1 1 100%; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); }
.fwd-row .fwd-num label.lbl { margin: 0; }
.fwd-num .control { max-width: 190px; }
.fwd-num .field-error { flex: 1 1 100%; margin: 0; }
.fwd-num .field-error svg { flex: none; width: 16px; height: 16px; }

/* Why a control in the row cannot be used — visible, and pointed at by its aria-describedby. */
.botcard__why { display: flex; flex-direction: column; gap: var(--s1); margin-block-start: auto; }
/* every reason hidden: the block takes no room and no gap */
.botcard__why:not(:has(> :not([hidden]))) { display: none; }
.botcard__why .hint,
.botgrid__note {
    display: flex;
    align-items: flex-start;
    gap: var(--s2);
    margin: 0;
    font-size: var(--t-sm);
    color: var(--muted);
}
.botcard__why .hint svg,
.botgrid__note svg { flex: none; width: 16px; height: 16px; margin-block-start: 1px; }
#svc-test-note { margin-block-start: var(--s4); }
/* after the rule above, and more specific: a hidden reason stays hidden */
.botcard__why .hint[hidden] { display: none; }

/* ── Action row ───────────────────────────────────────────────────────────────────────────────
   Five equal slots in the Agents mockup order. A viewer who may only pause and activate gets the one
   control in the first slot, the same size it has for an operator. */
.botcard__actions {
    grid-auto-flow: row;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: var(--s2);
    margin-block-start: 0;
    padding: var(--s4) var(--s5);
    border-block-start: 1px solid var(--line);
}
/* THE FIVE ACTIONS ARE BUTTONS. Their fill is --ghost-bg on a white card (1.06:1), so the
   frame is the whole of the affordance, and on --line it measured 1.31:1 against that fill —
   five controls in a row that the eye has to infer from the captions. --control-edge is the
   boundary every operable thing in the product now draws (WCAG 1.4.11, 3:1). */
.botcard__actions .botbtn {
    gap: var(--s1);
    padding: var(--s2) var(--s1);
    font-size: var(--t-sm);
    color: var(--ink);
    background: var(--ghost-bg);
    border-color: var(--control-edge);
}
.botcard__actions .botbtn > span { color: var(--ink-2); }
.botcard__actions .botbtn svg { width: 18px; height: 18px; }
/* The name after each caption is for screen readers only; the caption rule above must not size it. */
.botcard__actions .botbtn > .sr-only { display: block; inline-size: 1px; }
/* The consequence is in the frame, not the fill (DS-14): pause in the warn ink, activate in the ok
   ink, delete in the danger edge. The caption stays the neutral ink. (0,3,0) — after the (0,2,0)
   card rule and the owner's 2026-08-03 neutral override of .btn-danger/.btn-success. */
.botcard__actions .botbtn.botbtn--warn { border-color: var(--warn-ink); }
.botcard__actions .botbtn.botbtn--ok { border-color: var(--ok-ink); }
.botcard__actions .botbtn.botbtn--danger { border-color: var(--danger-edge); }
@media (hover: hover) {
    /* --line-strong IS --n-6, which is now the RESTING edge: the hover had become a no-op. */
    .botcard__actions .botbtn:hover:not([aria-disabled="true"]) { background: var(--n-0); border-color: var(--n-7); }
    .botcard__actions .botbtn.botbtn--warn:hover:not([aria-disabled="true"]) { border-color: var(--warn-ink); }
    .botcard__actions .botbtn.botbtn--ok:hover:not([aria-disabled="true"]) { border-color: var(--ok-ink); }
    .botcard__actions .botbtn.botbtn--danger:hover:not([aria-disabled="true"]) { border-color: var(--danger-ink); background: var(--danger-wash); }
}

@media (max-width: 820px) {
    .botcard__head,
    .botcard__body { padding: var(--s4); }
    .botcard__actions { padding: var(--s3) var(--s4); gap: var(--s1); }
    .fwd-num .control { max-width: none; flex: 1 1 160px; }
}
/* ==== END SECTION agents-list ==== */
/* ==== SECTION studio ==== */
/* OBSOLETE (no longer in any view after AGT-STUDIO; listed for UI-FINAL, not deleted here):
   .bot-shell, .bnav, .bnav__inner, .bnav__i, .bnav__r, .bnav label (b, em, > span:last-child),
   .bmain, .savebar (the studio's own), .savebar__act, .savebar__back, .gobar, .tools-head,
   .etpanel { display:block }, @keyframes bslide / bfade, the custom properties --bs-appbar,
   --bs-pad, --bs-head, --bs-rail, --bs-gap (all were page <style> blocks in Edit/Create.cshtml),
   and in kinetiq.css the eight `#r-…:focus-visible ~ .bnav label[for="r-…"]` selectors of the
   hidden-input focus rule plus `.bnav label:hover` in the reduced-motion transform reset. */

/* ── THE AGENT STUDIO (AGT-STUDIO, wave 2) — /Admin/VoiceBots/Edit and /Admin/VoiceBots/Create ──
   One grid for both screens: the rail at the inline start, the open panel in the middle, and on a
   wide screen how the call opens at the inline end. Section switching is CSS only (the CSP is
   script-src 'self'): one radio per panel, rendered as siblings of .kq-rail and .studio__main, and a
   :checked rule per panel. EVERY RULE THAT NAMES PANELS NAMES ALL SEVEN — who, talk, know, extract,
   tools, lookup, behave. r-behave was once missing from the selection rules, which is how a chosen
   panel went unmarked (AGE-31). There were eight until 2026-09-18: «free» stopped being a panel when
   the instructions moved into «talk», where the mode chooser now sits beside the two bodies it
   switches between. */

.studio {
    position: relative;
    display: grid;
    /* THIS WIDTH NEVER PAINTS: ≥861px restates it at 236px below and ≤860px makes the studio one
       column, so the two rules under it are the only two that decide a rail. It is left as it is
       rather than quietly changed — but it is the number the 1360px rule had copied, which is how
       the same rail came to be 236px on one screen and 240px on the next. */
    grid-template-columns: 240px minmax(0, 1fr);
    gap: var(--s5);
    align-items: stretch;
}
@media (min-width: 1360px) {
    /* 236px, the width the rule below states for every studio wide enough to have a rail. It said
       240 — the bare column width above — so the same rail was 236px on «סוכן חדש», which renders
       no .studio__aside, and 240px on the agent it becomes. */
    .studio:has(> .studio__aside) { grid-template-columns: 236px minmax(0, 1fr) 300px; }
}
/* ≤860 the studio is one column and the rail is a horizontal strip of chapters, so this width is
   stated rather than left unconditional — a bare column width beats that rule and squeezes the
   form into 110px. It sits after the declaration above and before the 860px block at the foot of
   this section, which is the order the three depend on. */
@media (min-width: 861px) {
    .studio { grid-template-columns: 236px minmax(0, 1fr); }
}
.studio__main { min-width: 0; align-self: start; }

/* The radios. Out of the grid, invisible, and FIXED under the top bar: a focused element is scrolled
   into view, and a radio parked at the top of the grid pulled a reader who tabbed back into the rail
   from the bottom of a long panel all the way up the page. Fixed inside the viewport, focusing one
   never scrolls anything. */
.kq-rail__r {
    position: fixed;
    inset-block-start: calc(var(--kq-topbar-h) + var(--s5));
    inset-inline-start: 50%;
    width: 1px; height: 1px;
    margin: 0; padding: 0;
    opacity: 0;
    pointer-events: none;
}

/* ── the rail ─────────────────────────────────────────────────────────────────────────────────
   STICKY INSIDE ITS GRID CELL, not fixed (AGE-06). The fixed rail sat over the notices and the error
   list at the top of the content; a sticky one starts where its column starts — below them — and only
   then follows the scroll. */
.kq-rail { min-width: 0; }
.kq-rail__inner {
    position: sticky;
    top: calc(var(--kq-topbar-h) + var(--s5));
    max-height: calc(100dvh - var(--kq-topbar-h) - var(--s6));
    overflow-y: auto;
    /* one axis set to auto computes the other to auto too; labels wrap instead of scrolling sideways */
    overflow-x: hidden;
    display: flex; flex-direction: column; gap: 2px;
    padding: var(--s2);
    /* PAPER. The rail sits in its own grid column and .kq-rail__inner is sticky INSIDE it, so the
       page has never passed behind it — the blur here was a 244px-wide raster for a depth cue with
       nothing under it, and it is the second-largest blurred layer the studio used to paint. */
    background: var(--surface-material);
    border: 1px solid var(--surface-edge);
    border-radius: var(--r-card);
    box-shadow: var(--surface-lift);
}
.kq-rail__group {
    margin: 0;
    padding: var(--s2) var(--s3) var(--s1);
    font-size: var(--t-xs); font-weight: 600; line-height: 1.5;
    color: var(--n-7);
}
.kq-rail__list + .kq-rail__group {
    margin-top: var(--s1);
    padding-top: var(--s3);
    border-top: 1px solid var(--line);
}
.kq-rail__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
/* NO TRANSITION, on purpose (owner 2026-08-01): picking a section is a discrete choice, and the rail
   must already show the new selection by the time the eye gets back to it. */
.kq-rail__item {
    position: relative;
    display: flex; align-items: flex-start; gap: var(--s2);
    padding: var(--s2) var(--s3);
    border-radius: var(--r);
    color: var(--ink-2);
    cursor: pointer;
}
@media (hover: hover) {
    .kq-rail__item:hover { background: var(--n-1); }
}
.kq-rail__icon { flex: none; display: inline-flex; margin-top: 1px; color: var(--muted); }
.kq-rail__icon svg { width: 18px; height: 18px; }
.kq-rail__text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.kq-rail__title { font-size: var(--t-md); font-weight: 600; line-height: 1.4; overflow-wrap: anywhere; }
.kq-rail__sub { font-size: var(--t-sm); line-height: 1.4; color: var(--muted); overflow-wrap: anywhere; }
/* unsaved changes in this panel — kq-dirty.js puts .is-dirty on label[for=r-{panel}] */
.kq-rail__dirty {
    display: none; flex: none;
    width: 7px; height: 7px; margin-top: 7px;
    border-radius: var(--r-circle);
    background: var(--warn-ink);
}
.kq-rail__item.is-dirty .kq-rail__dirty { display: block; }
/* fields in error in this panel, rendered by the server from ModelState */
.kq-rail__err {
    flex: none;
    min-width: 18px; margin-top: 2px; padding: 0 var(--s1);
    border: 1px solid var(--danger-edge); border-radius: var(--r-pill);
    background: var(--danger-bg); color: var(--danger-ink);
    font-size: var(--t-xs); font-weight: 700; line-height: 16px; text-align: center;
    font-variant-numeric: tabular-nums;
}

/* the chosen panel — one selector per panel, since :has cannot reach a sibling's label.
   SIX LISTS NAME EVERY PANEL and a new one has to join all six: the selected tint, its edge, its
   icon, its subtitle, its focus ring and — the one that matters — the panel's own display. KICHAT's
   studio added «business» and «limits» on 2026-09-24 and they were missing from the last list
   first: the rail item selected, and the page showed nothing at all. */
#r-who:checked ~ .kq-rail label[for="r-who"],
#r-talk:checked ~ .kq-rail label[for="r-talk"],
#r-know:checked ~ .kq-rail label[for="r-know"],
#r-extract:checked ~ .kq-rail label[for="r-extract"],
#r-tools:checked ~ .kq-rail label[for="r-tools"],
#r-lookup:checked ~ .kq-rail label[for="r-lookup"],
#r-business:checked ~ .kq-rail label[for="r-business"],
#r-limits:checked ~ .kq-rail label[for="r-limits"],
#r-rules:checked ~ .kq-rail label[for="r-rules"],
#r-behave:checked ~ .kq-rail label[for="r-behave"] {
    background: var(--kq-action-soft);
    color: var(--kq-link-ink);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--kq-link-ink) 28%, transparent);
}
#r-who:checked ~ .kq-rail label[for="r-who"]::before,
#r-talk:checked ~ .kq-rail label[for="r-talk"]::before,
#r-know:checked ~ .kq-rail label[for="r-know"]::before,
#r-extract:checked ~ .kq-rail label[for="r-extract"]::before,
#r-tools:checked ~ .kq-rail label[for="r-tools"]::before,
#r-lookup:checked ~ .kq-rail label[for="r-lookup"]::before,
#r-business:checked ~ .kq-rail label[for="r-business"]::before,
#r-limits:checked ~ .kq-rail label[for="r-limits"]::before,
#r-rules:checked ~ .kq-rail label[for="r-rules"]::before,
#r-behave:checked ~ .kq-rail label[for="r-behave"]::before {
    content: "";
    position: absolute;
    inset-inline-start: 0; top: 18%; bottom: 18%;
    width: 3px;
    border-radius: var(--r);
    background: var(--kq-btn-bg);
}
#r-who:checked ~ .kq-rail label[for="r-who"] .kq-rail__icon,
#r-talk:checked ~ .kq-rail label[for="r-talk"] .kq-rail__icon,
#r-know:checked ~ .kq-rail label[for="r-know"] .kq-rail__icon,
#r-extract:checked ~ .kq-rail label[for="r-extract"] .kq-rail__icon,
#r-tools:checked ~ .kq-rail label[for="r-tools"] .kq-rail__icon,
#r-lookup:checked ~ .kq-rail label[for="r-lookup"] .kq-rail__icon,
#r-business:checked ~ .kq-rail label[for="r-business"] .kq-rail__icon,
#r-limits:checked ~ .kq-rail label[for="r-limits"] .kq-rail__icon,
#r-rules:checked ~ .kq-rail label[for="r-rules"] .kq-rail__icon,
#r-behave:checked ~ .kq-rail label[for="r-behave"] .kq-rail__icon { color: inherit; }
#r-who:checked ~ .kq-rail label[for="r-who"] .kq-rail__sub,
#r-talk:checked ~ .kq-rail label[for="r-talk"] .kq-rail__sub,
#r-know:checked ~ .kq-rail label[for="r-know"] .kq-rail__sub,
#r-extract:checked ~ .kq-rail label[for="r-extract"] .kq-rail__sub,
#r-tools:checked ~ .kq-rail label[for="r-tools"] .kq-rail__sub,
#r-lookup:checked ~ .kq-rail label[for="r-lookup"] .kq-rail__sub,
#r-business:checked ~ .kq-rail label[for="r-business"] .kq-rail__sub,
#r-limits:checked ~ .kq-rail label[for="r-limits"] .kq-rail__sub,
#r-rules:checked ~ .kq-rail label[for="r-rules"] .kq-rail__sub,
#r-behave:checked ~ .kq-rail label[for="r-behave"] .kq-rail__sub { color: var(--ink-2); }

/* KEYBOARD FOCUS IS DRAWN ON THE LABEL (DS-06, AGE-05): the radio that holds focus is invisible, so a
   ring on it is invisible too. Two-tone, so it reads on the glass and on the selected tint alike. */
#r-who:focus-visible ~ .kq-rail label[for="r-who"],
#r-talk:focus-visible ~ .kq-rail label[for="r-talk"],
#r-know:focus-visible ~ .kq-rail label[for="r-know"],
#r-extract:focus-visible ~ .kq-rail label[for="r-extract"],
#r-tools:focus-visible ~ .kq-rail label[for="r-tools"],
#r-lookup:focus-visible ~ .kq-rail label[for="r-lookup"],
#r-business:focus-visible ~ .kq-rail label[for="r-business"],
#r-limits:focus-visible ~ .kq-rail label[for="r-limits"],
#r-rules:focus-visible ~ .kq-rail label[for="r-rules"],
#r-behave:focus-visible ~ .kq-rail label[for="r-behave"] {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
    box-shadow: var(--focus-halo);
}

/* ── the panels ───────────────────────────────────────────────────────────────────────────────
   One visible at a time. Hidden panels keep their fields in the form, so they still post. */
.bpanel { display: none; }
#r-who:checked ~ .studio__main #p-who,
#r-talk:checked ~ .studio__main #p-talk,
#r-know:checked ~ .studio__main #p-know,
#r-extract:checked ~ .studio__main #p-extract,
#r-tools:checked ~ .studio__main #p-tools,
#r-lookup:checked ~ .studio__main #p-lookup,
#r-business:checked ~ .studio__main #p-business,
#r-limits:checked ~ .studio__main #p-limits,
#r-rules:checked ~ .studio__main #p-rules,
#r-behave:checked ~ .studio__main #p-behave {
    display: block;
    animation: kq-panel-in var(--dur-base) var(--e-out);
}
/* opacity only: the content arrives, it does not travel */
@keyframes kq-panel-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
    .bpanel { animation: none !important; }
}

.studio-panel__title {
    display: flex; align-items: center; gap: var(--s2);
    margin: 0 0 var(--s4);
    font-size: var(--t-lg); font-weight: 700; line-height: 1.6;
    color: var(--ink);
}
.studio-panel__title svg { flex: none; width: 18px; height: 18px; }
.studio-panel__head {
    display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
    gap: var(--s3);
    margin-bottom: var(--s4);
}
.studio-panel__head .studio-panel__title { margin: 0; }
.studio-note { margin: 0 0 var(--s4); font-size: var(--t-md); color: var(--ink-2); }

/* ── the save bar ─────────────────────────────────────────────────────────────────────────────
   Outside the bot form, last in the main column (VLAB-17), sticky to the bottom of the viewport. The
   page reserves its height as scroll padding, so a field focused low on the page lands above it. */
.kq-savebar { position: sticky; bottom: 0; }
html:has(.kq-savebar) { scroll-padding-block-end: calc(var(--kq-savebar-h) + 12px); }
/* Panels that are not the bot form: «שמירה» shows only while the bot form has unsaved changes. */
#r-tools:checked ~ .studio__main .kq-savebar__act,
#r-behave:checked ~ .studio__main .kq-savebar__act,
#r-lookup:checked ~ .studio__main .kq-savebar__act { display: none; }
html[data-dirty] #r-tools:checked ~ .studio__main .kq-savebar__act,
html[data-dirty] #r-behave:checked ~ .studio__main .kq-savebar__act,
html[data-dirty] #r-lookup:checked ~ .studio__main .kq-savebar__act { display: flex; }
/* On the create screen those two panels ARE a create button: the bar would be a second one.
   Both studios, by form id — #bot-form is KIVOICE's and #chat-form is KICHAT's. The selector has
   to name the FORM and not just the panel, because on the EDIT screens the same two panels sit
   outside the form and the bar belongs there. */
#r-tools:checked ~ .studio__main:has(#bot-form #p-tools) .kq-savebar,
#r-tools:checked ~ .studio__main:has(#chat-form #p-tools) .kq-savebar,
#r-lookup:checked ~ .studio__main:has(#bot-form #p-lookup) .kq-savebar,
#r-lookup:checked ~ .studio__main:has(#chat-form #p-lookup) .kq-savebar { display: none; }

/* ── the header: state, steps, test call ──────────────────────────────────────────────────────
   _StudioHeader is the child content of the page header, so it arrives in .page-head__actions.
   The state block goes under the title and the buttons stay at the end, both rows of one grid. */
.page-head:has(.studio-head) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    column-gap: var(--s4);
    row-gap: var(--s1);
}
.page-head:has(.studio-head) > .page-head__main { grid-column: 1; grid-row: 1; }
.page-head:has(.studio-head) > .page-head__actions,
.page-head .studio-head { display: contents; }
.studio-head__state { grid-column: 1; grid-row: 2; min-width: 0; display: flex; flex-direction: column; gap: var(--s1); }
.studio-head__actions {
    grid-column: 2; grid-row: 1 / span 2;
    align-self: end;
    display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: flex-end;
    gap: var(--s3);
}
.studio-head__action { display: flex; flex-direction: column; align-items: flex-end; margin: 0; }
.studio-head__status {
    margin: 0;
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
    font-size: var(--t-md); color: var(--muted);
}
.studio-head__reason { margin: var(--s1) 0 0; max-width: 18rem; font-size: var(--t-sm); color: var(--muted); }
/* the compact activation path: one row, done / current / to do told apart by fill and ring */
.kq-steps.studio-steps {
    flex-direction: row; flex-wrap: wrap; align-items: center;
    gap: var(--s1) var(--s3);
    font-size: var(--t-md); color: var(--ink-2);
}
.kq-steps.studio-steps > li { display: flex; align-items: center; gap: var(--s2); padding-bottom: 0; }
.kq-steps.studio-steps > li::before { content: none; }
.kq-steps.studio-steps > li + li::before {
    content: "·";
    position: static;
    width: auto;
    background: none;
    color: var(--n-6);
}
.kq-steps.studio-steps > li > [aria-hidden="true"]:first-child { width: 18px; height: 18px; font-size: var(--t-xs); }
.kq-steps.studio-steps > li > [aria-hidden="true"]:first-child svg { width: 12px; height: 12px; }
.studio-steps__t { font-weight: 600; }
.studio-steps__d { color: var(--muted); }

/* ── the opening preview (≥1360px) ──────────────────────────────────────────────────────────── */
.studio__aside { display: none; min-width: 0; }
@media (min-width: 1360px) {
    .studio__aside {
        display: block;
        align-self: start;
        position: sticky;
        top: calc(var(--kq-topbar-h) + var(--s5));
    }
}
.studio__aside .card__head h2 svg { width: 18px; height: 18px; }
.studio-open { margin: 0 0 var(--s5); display: flex; flex-direction: column; gap: var(--s2); }
.studio-open figcaption { font-size: var(--t-sm); font-weight: 600; color: var(--muted); }
.studio-open blockquote {
    margin: 0;
    padding: var(--s3) var(--s4);
    background: var(--n-0);
    border: 1px solid var(--line);
    border-radius: var(--r-card);
    border-start-start-radius: 2px;
    font-size: var(--t-base); line-height: 1.6; color: var(--ink);
    overflow-wrap: anywhere;
}
.studio-open__fact { margin: 0 0 var(--s1); font-size: var(--t-md); }

/* ── who answers: number, transfer, hold music ─────────────────────────────────────────────── */
/* «בלי מספר הסוכן יושהה» only while «ללא מספר» is chosen (the view renders it only for an agent that is on) */
.studio-did__note { display: none; margin: var(--s1) 0 0; font-size: var(--t-sm); font-weight: 600; color: var(--warn-ink); }
.studio-did:has(option[value='']:checked) .studio-did__note { display: block; }

/* The transfer details ride the transfer switch. SCOPED TO THE SWITCH'S OWN ROW, never to a panel id:
   a rule that named #bs-who switched itself off when the panels were renamed, and ticking «העברה
   לנציג אנושי» opened nothing (owner 2026-08-01). */
.tr-details { display: none; margin-top: var(--s3); }
.form-row:has(input[name="Input.TransferEnabled"]:checked) .tr-details { display: block; }
/* a browser without :has() shows the details always — untidy, never unreachable */
@supports not selector(:has(*)) {
    .tr-details { display: block; }
}
.studio-transfer__number { max-width: 280px; }

/* «אותו מוח»: the voice agent and the approver ride the switch, in the switch's own row — the same
   shape as the transfer details above, for the same reason. */
.sb-details { display: none; margin-top: var(--s3); }
.form-row:has(input[name="SameBrainOn"]:checked) .sb-details { display: block; }
@supports not selector(:has(*)) {
    .sb-details { display: block; }
}

.hm-block {
    margin-top: var(--s3);
    padding: var(--s3) var(--s4);
    border: 1px dashed var(--line);
    border-radius: var(--r-card);
}
.hm-current {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3);
    margin: 0 0 var(--s2);
    font-size: var(--t-md); color: var(--muted);
}
.hm-current p { margin: 0; }
.hm-current audio { height: 36px; max-width: min(100%, 420px); }
.hm-clear { margin-top: var(--s2); }
/* .field-error is display:flex, which would override the hidden attribute */
.studio .field-error[hidden] { display: none; }

/* ── what the agent says: the guided builder ──────────────────────────────────────────────── */
.studio-collect-add { display: flex; flex-wrap: wrap; gap: var(--s1); margin-bottom: var(--s1); }

.kq-adv__summary { cursor: pointer; padding: var(--s1) 0; font-weight: 600; }
.kq-adv__sub { display: block; margin-top: 2px; font-weight: 400; }

.studio-flowtpl { margin: var(--s2) 0; }
.studio-flowtpl .control { max-width: 340px; }
.studio-rows__list { display: flex; flex-direction: column; gap: var(--s2); margin-top: var(--s2); }
.studio-rows__add { margin-top: var(--s2); }
/* .btn is display:inline-flex, which would override the hidden attribute at the row cap */
.studio-rows__add[hidden] { display: none; }

/* The row grids and their one visible header row share the same tracks, so the headings sit over
   their fields. The last track is the remove button. */
.kq-step,
.kq-rowhead--steps {
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.4fr) minmax(0, 1.4fr) 6rem;
    gap: var(--s2);
    align-items: start;
}
.kq-rule,
.kq-rowhead--rules {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) 6rem;
    gap: var(--s2);
    align-items: start;
}
.kq-step .kq-row-del,
.kq-rule .kq-row-del { justify-self: end; }
.kq-rowhead {
    margin: var(--s3) 0 0;
    font-size: var(--t-sm); font-weight: 600; color: var(--muted);
}
.studio-rows:not(:has(.kq-step, .kq-rule)) .kq-rowhead { display: none; }
@media (max-width: 860px) {
    .kq-rowhead { display: none; }
}

.studio-compiled { margin: var(--s1) 0 var(--s4); }
.studio-compiled summary { cursor: pointer; font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink); }
.studio-compiled__meter { margin: var(--s2) 0; }
.studio-compiled__text {
    margin: 0;
    padding: var(--s2) var(--s3);
    max-height: 28rem; overflow: auto;
    white-space: pre-wrap; overflow-wrap: anywhere;
    background: var(--n-1);
    border: 1px solid var(--line);
    border-radius: var(--r);
    font-family: inherit; font-size: var(--t-md); line-height: 1.7;
}

/* ── tools and behaviour rules ──────────────────────────────────────────────────────────────── */
.tool-actions { display: flex; flex-shrink: 0; align-items: center; gap: var(--s1); }
.tool-actions form { margin: 0; }
.tool-state { text-decoration: none; }
/* a tool the editor just saved is the #tool-… target: a static outline, no animation */
.tool-item:target { outline: 2px solid var(--kq-btn-bg); outline-offset: 2px; }

/* one row per rule, the switch pinned to the start so nineteen of them scan as a column */
#ep-behave .bhv { display: grid; gap: 2px; }
#ep-behave .bhv-row {
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s2); align-items: start;
    padding: var(--s3) var(--s2);
    border-radius: var(--r);
}
#ep-behave .bhv-row + .bhv-row { border-top: 1px solid var(--line); border-radius: 0; }
@media (hover: hover) {
    #ep-behave .bhv-row:hover { background: var(--n-1); }
}
#ep-behave .bhv-row label.checkbox { margin: 0; }
#ep-behave .bhv-txt b { display: block; font-weight: 600; }
#ep-behave .bhv-txt span { display: block; font-size: var(--t-sm); color: var(--muted); }

/* ── caller identification (the panel moved verbatim into _LookupPanel.cshtml) ────────────────
   These rules came out of Edit.cshtml's <style> unchanged so the moved panel keeps working. The
   lookup package owns the panel from wave 3 and replaces them in its own section.
   WRAPPED IN @supports ON PURPOSE: without :has() the settings would be hidden with nothing to reveal
   them; this way the failure mode is everything visible. */
@supports selector(:has(*)) {
    #ep-lookup .lkcfg { display: none; }
    #ep-lookup:has(#lk-on:checked) .lkcfg { display: block; }
    #ep-lookup .lksrc-api, #ep-lookup .lksrc-sheet { display: none; }
    #ep-lookup:has(#lk-src-api:checked) .lksrc-api { display: block; }
    #ep-lookup:has(#lk-src-sheet:checked) .lksrc-sheet { display: block; }
    #ep-lookup .lksrc-api-i, #ep-lookup .lksrc-sheet-i { display: none; }
    #ep-lookup:has(#lk-src-api:checked) .lksrc-api-i { display: inline; }
    #ep-lookup:has(#lk-src-sheet:checked) .lksrc-sheet-i { display: inline; }
    #ep-lookup .lkmap { display: grid; gap: var(--s2); }
    #ep-lookup .lkmap-row {
        display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 14rem);
        gap: var(--s3); align-items: center;
    }
    #ep-lookup .lkmap-col { display: flex; gap: var(--s2); align-items: baseline; flex-wrap: wrap; min-width: 0; }
    #ep-lookup .lkmap-let { font-weight: 600; font-size: var(--t-sm); min-width: 1.6rem; color: var(--muted); }
    #ep-lookup .lkmap-name { font-weight: 600; }
    #ep-lookup .lkmap-eg { font-size: var(--t-sm); color: var(--muted); min-width: 0; }
    @media (max-width: 640px) { #ep-lookup .lkmap-row { grid-template-columns: minmax(0, 1fr); } }
}

/* ── THE RAIL LIES DOWN ON A PHONE (≤860px) ───────────────────────────────────────────────────
   AFTER the base rail rules, on purpose (AGE-33): the same selectors declared later win, and the old
   page block lost to base rules written below it — the chips kept their subtitles and 72px height.
   On its side the rail is one row of chips that scrolls sideways; the chosen chip is brought into
   view by voicebot-form.js. */
@media (max-width: 860px) {
    .studio { grid-template-columns: minmax(0, 1fr); }
    .kq-rail__inner {
        position: static;
        max-height: none;
        flex-direction: row; flex-wrap: nowrap;
        overflow-x: auto; overflow-y: hidden;
        overscroll-behavior-x: contain;
        gap: var(--s1);
        padding: var(--s1);
    }
    .kq-rail__group { display: none; }
    .kq-rail__list { flex-direction: row; flex: none; gap: var(--s1); }
    .kq-rail__item { flex: 0 0 auto; align-items: center; padding: var(--s2) var(--s3); }
    .kq-rail__title { white-space: nowrap; }
    .kq-rail__sub { display: none; }
    .kq-rail__dirty, .kq-rail__err { margin-top: 0; }
    /* a step or a rule stacks its fields; «הסרה» sits at the start, not stretched under the last field */
    .kq-step, .kq-rule { grid-template-columns: minmax(0, 1fr); }
    .kq-step .kq-row-del, .kq-rule .kq-row-del { justify-self: start; }
    .page-head:has(.studio-head) { grid-template-columns: minmax(0, 1fr); }
    .studio-head__actions { grid-column: 1; grid-row: 3; justify-content: flex-start; }
    .studio-head__action { align-items: flex-start; }
}
/* ==== END SECTION studio ==== */
/* ==== SECTION test-call ==== */
/* OBSOLETE: .wtc-bar, .wtc-bar[hidden], .wtc-text, .wtc-status, .wtc-status.ok, .wtc-status.bad, .wtc-ask .in
   (kinetiq.css phone block ~2157-2158 `.wtc-ask .in`), .content:has(.wtc-bar:not([hidden])) (~2765),
   @keyframes wtc-pulse and the fade-out rules — the drawer replaced the fixed bar, its <style> block is gone
   and nothing renders those names any more. */

/* TEST CALL (AGT-TESTCALL, wave 2). The browser test-call drawer: Pages/Admin/VoiceBots/_WebTestCall.cshtml.
   .kq-drawer already gives the panel its place, width, glass and the phone bottom sheet; this section is only
   what sits inside it. No animation anywhere: a pulsing dot used to say «live» before anything was live. */

/* Everything the script toggles uses the hidden attribute, and several of these elements set a display. */
.wtc [hidden] { display: none; }
/* THE DRAWER SITS ON THE SAVE BAR. Measured: at 1440 the 460px panel covers 100% of «שמירה»
   (66.6 × 34.1px) and elementFromPoint at its centre returns the drawer; at 390 the sheet covers
   the whole bar, and the content padding above cannot help a bar that is sticky to the scrollport.
   A control that is painted and unreachable is worse than one that is not there, and a call is
   refused while the form is dirty (webtestcall.js:794), so the bar has nothing to commit while it
   is away; the drawer's own close button brings it back. */
html:has(.kq-drawer:not([hidden])) .kq-savebar { display: none; }

.wtc:focus { outline: none; }
.wtc:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }

/* ── Head: title and status at the start, time and actions at the end ─────────────────────────── */
.wtc__head {
    display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
    padding: var(--s4) var(--s5);
    border-block-end: 1px solid var(--line);
}
.wtc__lead { display: flex; flex-direction: column; gap: var(--s1); min-width: 0; }
.wtc__act { display: flex; align-items: center; gap: var(--s4); flex: none; }
/* The handset turned down: the mockup's hang-up glyph, from the same phone icon. */
#wtc-stop > svg { rotate: 135deg; }

/* THE NAME IS THE POINT OF THE TITLE, so it is the one part that may not be squeezed to nothing.
   Measured at 1440 it kept 50.6px of a 220px name, because the glyph and the fixed lead are both
   flex:none and the name was the only item left to shrink. The lead gives up its rigidity and the
   row may wrap, so a long name takes a second line (191.8px) instead of vanishing. The basis is
   `auto` and not a fixed 8rem: a fixed basis wraps the row for SHORT names too, which is how a
   24px head becomes a 47px one for every ordinary agent. */
.wtc__title {
    display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2); min-width: 0; margin: 0;
    font-size: var(--t-lg); font-weight: 700; line-height: 1.3; color: var(--ink);
}
.wtc__title > svg { flex: none; width: 18px; height: 18px; }
.wtc__title-text { flex: 0 1 auto; white-space: nowrap; }
#wtc-bot { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.wtc__status {
    display: flex; align-items: center; gap: var(--s2); margin: 0; min-height: 1.6em;
    font-size: var(--t-md); line-height: 1.5; color: var(--ink-2);
}
.wtc__status[data-kind="live"] { color: var(--ok-ink); font-weight: 600; }
.wtc__status[data-kind="bad"] { color: var(--danger-ink); font-weight: 600; }

/* The state dot: a 7px circle in the state's ink, always followed by its sentence. Absent when the
   drawer is only asking a question or reporting an ordinary end. */
.wtc-dot {
    display: none; flex: none; align-self: flex-start; margin-block-start: calc((1.5em - 7px) / 2);
    width: 7px; height: 7px; border-radius: var(--r-circle); background: var(--muted);
}
.wtc-dot.is-connecting,
.wtc-dot.is-live,
.wtc-dot.is-bad { display: inline-block; }
.wtc-dot.is-connecting { background: var(--muted); }
.wtc-dot.is-live { background: var(--ok-ink); }
.wtc-dot.is-bad { background: var(--danger-ink); }

/* Elapsed time: digits that do not jump while they count. */
.wtc-timer {
    font-size: var(--t-lg); font-weight: 700; line-height: 1.3; color: var(--ink);
    font-variant-numeric: tabular-nums;
}
.wtc-timer:empty { display: none; }

/* ── The caller number, for an agent that identifies callers ─────────────────────────────────── */
.wtc-ask {
    display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3);
    padding: var(--s4) var(--s5);
    border-block-end: 1px solid var(--line);
}
.wtc-ask .lbl { margin: 0; white-space: nowrap; }
.wtc-ask .control { width: 150px; }

/* ── The microphone meter ─────────────────────────────────────────────────────────────────────── */
.wtc__mic {
    display: flex; align-items: center; gap: var(--s3);
    padding: var(--s3) var(--s5);
    border-block-end: 1px solid var(--line);
    color: var(--ink-2);
}
/* Icons.cs emits a 24px box and nothing in this sheet sizes an svg globally, so the three rules
   in this section that set only flex and colour were drawing every glyph in the drawer a third
   larger than the same glyph anywhere else. 18px beside the 16px title, 16px beside 13px text. */
.wtc__mic > svg { flex: none; width: 18px; height: 18px; color: var(--kq-btn-bg); }
/* Fills from the inline start, like every level in a right-to-left page (TestCall mockup). */
.wtc-meter { display: flex; align-items: center; gap: 3px; }
.wtc-meter__bar { width: 6px; height: 14px; border-radius: 2px; background: var(--line); }
.wtc-meter__bar.is-on { background: var(--kq-btn-bg); }
.wtc__mic-text { font-size: var(--t-md); }

/* ── The call's summary, once the call log has it ─────────────────────────────────────────────── */
.wtc__result {
    display: flex; flex-direction: column; gap: var(--s2);
    padding: var(--s4) var(--s5);
}
.wtc__result-title { margin: 0; font-size: var(--t-md); font-weight: 600; line-height: 1.5; color: var(--ink-2); }
.wtc__summary { margin: 0; font-size: var(--t-base); line-height: 1.6; color: var(--ink); white-space: pre-line; overflow-wrap: anywhere; }
.wtc__duration { margin: 0; font-size: var(--t-md); color: var(--muted); }
.wtc__duration:empty { display: none; }
.wtc__link {
    display: inline-flex; align-items: center; gap: var(--s2); align-self: flex-start;
    font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink);
}
.wtc__link svg { width: 16px; height: 16px; }
@media (hover: hover) {
    .wtc__link:hover { color: var(--kq-link-hover); }
}

/* ── Phones: the drawer is a bottom sheet, and the page makes room for it ────────────────────────
   The script writes the sheet's real height into --wtc-h on the root while it is open (A11Y-19); a
   fixed guess does not fit a head that wraps onto two lines. Desktop is a side panel, so nothing pads. */
@media (max-width: 820px) {
    .wtc__head { flex-wrap: wrap; padding-inline: var(--s4); }
    .wtc__lead { flex: 1 1 100%; }
    .wtc__act { flex: 1 1 100%; justify-content: flex-end; gap: var(--s3); }
    .wtc-ask, .wtc__mic, .wtc__result { padding-inline: var(--s4); }
    .wtc-ask .control { width: auto; flex: 1 1 140px; min-width: 0; }

    html:has(#wtc-bar:not([hidden])) .content { padding-block-end: calc(var(--wtc-h, 0px) + var(--s5)); }
    html:has(#wtc-bar:not([hidden])) { scroll-padding-block-end: calc(var(--wtc-h, 0px) + var(--s3)); }
}

/* High contrast: the dot and the meter are told apart only by their fill. */
@media (forced-colors: active) {
    .wtc-dot, .wtc-meter__bar { forced-color-adjust: none; background: GrayText; }
    .wtc-dot.is-live, .wtc-dot.is-bad, .wtc-meter__bar.is-on { background: CanvasText; }
}
/* ==== END SECTION test-call ==== */
/* ==== SECTION tool-editor ==== */
/* OBSOLETE: none — every older tool-editor selector (.params-rows, .param-row, .grid-method-url,
   .tt-args, .tt-result, .tt-head, .tt-label, .tt-pre) is still rendered by Tool.cshtml. */

/* TOOL EDITOR (AGT-TOOL, wave 2) — /Admin/VoiceBots/Tool.
   The one long card with dividers and inline-styled h3s became a stack of cards, one per question
   the operator answers in order: what the tool is, when the agent uses it, the request, its
   parameters, whether it runs and how long it waits, and the test. Everything here is layout for
   that markup; the components themselves (.field, .control, .badge, .kq-notice, .kq-savebar) come
   from the component layer. No motion, no hover rules. */

.tool-editor { display: grid; gap: var(--s5); min-width: 0; }
.tool-editor > .card { min-width: 0; }
.tool-editor .card__head h2 svg { width: 18px; height: 18px; color: var(--muted); }

/* One vertical rhythm inside a card body; the older .form-row bottom margin does not add to it. */
.tool-stack { display: grid; gap: var(--s5); min-width: 0; }
.tool-stack > .form-row { margin: 0; }
.tool-editor .form-grid > .field { min-width: 0; }
.tool-narrow { max-width: 340px; }

/* ── Authentication ───────────────────────────────────────────────────────────────────────── */
.tool-auth { display: grid; gap: var(--s4); min-width: 0; }
/* the credential choice belongs to the select above it: framed, so it reads as its detail */
.tool-auth__detail {
    display: grid; gap: var(--s4); min-width: 0;
    padding: var(--s4); border: 1px solid var(--line); border-radius: var(--r-card);
}
.tool-radios { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s5); }

/* «הסוד לא נשמר — הקלידו אותו שוב.» under an empty password field after a post */
.tool-lost { display: flex; align-items: flex-start; gap: var(--s1); color: var(--warn-ink); font-weight: 600; }
.tool-lost > svg { flex: none; width: 16px; height: 16px; margin-top: 2px; }

/* ── Stored headers: names only, as chips above the box that replaces them ─────────────────── */
.tool-stored {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
    margin: 0 0 var(--s2);
}
.tool-stored__label { font-size: var(--t-sm); color: var(--muted); }
.tool-chip {
    display: inline-flex; align-items: center; max-width: 100%;
    padding: 1px var(--s3); border: 1px solid var(--neutral-edge); border-radius: var(--r-pill);
    background: var(--neutral-bg); color: var(--ink-2);
    font-size: var(--t-sm); font-weight: 600; line-height: 1.6;
    unicode-bidi: isolate; overflow-wrap: anywhere;
}

/* ── Request body: the non-blocking JSON note under the body field ─────────────────────────── */
.tool-json-hint { margin: var(--s1) 0 0; font-size: var(--t-sm); font-weight: 600; color: var(--warn-ink); }
.tool-json-hint:empty { display: none; }

/* ── Parameters ───────────────────────────────────────────────────────────────────────────── */
.tool-params { display: grid; gap: var(--s2); min-width: 0; }
.tool-params__hint { margin: 0 0 var(--s1); }
.tool-params > .btn { justify-self: start; }
.tool-params .params-rows { margin: 0; }
.tool-params .params-rows:empty { display: none; }
/* Column names over the rows, only where the row is one line. Both grids get the same fixed
   columns so the names sit over their fields; below this width the rows wrap (720px, 560px rules
   above) and each field is named by its own aria-label and placeholder. */
.tool-editor .param-head { display: none; }
@media (min-width: 1024px) {
    .tool-editor .param-row,
    .tool-editor .param-head:not([hidden]) {
        grid-template-columns: 150px 128px minmax(0, 1fr) 5.5rem 5.5rem;
    }
    .tool-editor .param-head:not([hidden]) {
        display: grid; gap: var(--s2);
        font-size: var(--t-sm); font-weight: 600; color: var(--muted);
    }
}

/* ── Running: the enabled switch and the waiting time on one row ──────────────────────────── */
.tool-timing { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s4) var(--s7); }
/* level with the timeout box, whose label sits above it: label line + label gap + half the
   difference between a 40px box and the 22px switch */
.tool-switch {
    display: flex; align-items: center; gap: var(--s3);
    padding-block-start: calc(var(--t-md) * 1.6 + var(--s2) + 9px);
}
.tool-switch label { font-size: var(--t-md); font-weight: 600; color: var(--ink-2); cursor: pointer; }
.tool-timeout { min-width: 0; }
.tool-timeout .control.num { max-width: 10ch; }
@media (max-width: 560px) {
    .tool-timing { flex-direction: column; gap: var(--s4); }
    .tool-switch { padding-block-start: 0; }
}

/* ── Test ─────────────────────────────────────────────────────────────────────────────────── */
.tool-editor .tt-args { margin: 0; }
.tool-noargs { margin: 0; }
.tool-test-act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }

/* The result and the POST confirmation are what the test posts back to (#tt-result): they must
   land clear of the sticky top bar and of the save bar pinned under the form. */
.tool-editor .tt-result,
.tt-confirm {
    scroll-margin-block-start: calc(var(--kq-topbar-h) + var(--s4));
    scroll-margin-block-end: calc(var(--kq-savebar-h) + var(--s4));
}
.tool-editor .tt-result { margin: 0; }
.tool-editor .tt-head { flex-wrap: wrap; }
.tool-editor .tt-label { margin: var(--s4) 0 var(--s1); }
.tool-editor .tt-result .kq-notice { margin: var(--s2) 0 0; }
.tt-auth { margin: 0 0 var(--s3); font-size: var(--t-md); font-weight: 600; color: var(--ink-2); overflow-wrap: anywhere; }
.tt-sub { margin: var(--s2) 0 var(--s1); font-size: var(--t-sm); color: var(--muted); }
.tt-empty { margin: 0 0 var(--s2); }
.tt-error {
    display: flex; align-items: flex-start; gap: var(--s2); margin: 0;
    font-size: var(--t-md); font-weight: 600; color: var(--danger-ink);
}
.tt-error > svg { flex: none; width: 18px; height: 18px; margin-top: 2px; }

/* A real POST is about to be sent: a danger frame with the one button that sends it. */
.tt-confirm {
    display: flex; align-items: flex-start; gap: var(--s3);
    padding: var(--s4); border: 1px solid var(--danger-edge); border-radius: var(--r-card);
    background: var(--danger-wash); color: var(--ink);
}
.tt-confirm > svg { flex: none; width: 20px; height: 20px; margin-top: 1px; color: var(--danger-ink); }
.tt-confirm__body { display: grid; gap: var(--s3); min-width: 0; }
.tt-confirm__text { margin: 0; font-size: var(--t-base); font-weight: 600; color: var(--danger-ink); }
.tt-confirm__act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }

@media (forced-colors: active) {
    .tool-auth__detail, .tool-chip, .tt-confirm { border-color: CanvasText; }
}

/* Last in the section on purpose: the blocks this page shows and hides carry display rules of
   their own (.form-grid, .tool-stack, .tt-args), which would otherwise beat the hidden attribute. */
.tool-editor [hidden] { display: none; }
/* ==== END SECTION tool-editor ==== */
/* ==== SECTION calllog ==== */
/* OBSOLETE (no view writes them after CALL-LOG; check before deleting):
   .log-block, .log-block .table-wrap (820px), .log-when (+ .calltbl .log-when, 820px), .log-who, .log-num,
   .log-dur, .log-dir, .log-res, .log-foot,
   .calltbl tbody tr[data-calllog-row]:hover, .calltbl > tbody > tr[data-calllog-row] (820px card),
   .calltbl > tbody > tr > td:nth-child(1..6) placements (820px, the old column order),
   .calltbl > tbody > tr > td:nth-child(6) .btn { width: 100% } (820px),
   .calltbl > tbody > tr[data-calllog-detail] > td (820px), .calltbl > tbody > tr[hidden] (820px),
   table.tbl-sortable, .ct-when, .ct-dur.
   KEEP: `table.tbl th a, .calltbl th a` in the 820px block (mobile-test greps it) and .ct-tx
   (Platform/ServerHealth still uses it). */

/* ═══ THE CALL LOG ═════════════════════════════════════════════════════════════════════════
   One glass filter card, the tabs, one status line, and the results in a glass card that becomes
   a stack of call cards on a phone. The in-place update itself needs nothing here: the fade of a
   replaced region, [data-list-region][aria-busy=true], .list-status and .list-order are already
   global rules (Lists section) and are reduced-motion aware. */

/* The export button sits in its own region so its words follow the count; the wrapper draws nothing. */
.cl-export { display: contents; }

/* ── The filter card ─────────────────────────────────────────────────────────────────────── */
.ci-filterbar.cl-filters {
    display: flex; flex-direction: column; gap: var(--s3);
    margin-bottom: var(--s4); padding: var(--s4) var(--s5);
    border-radius: var(--r-card);
}
/* Labelled controls on a grid rather than a strip of unnamed ones: every field says what it filters. */
.cl-filters .cl-filters__row {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(min(9.5rem, 100%), 1fr));
    gap: var(--s3) var(--s4); align-items: end;
}
.cl-field { display: flex; flex-direction: column; min-width: 0; }
.cl-field .lbl { margin-bottom: var(--s1); font-size: var(--t-xs); color: var(--n-8); }
.cl-field--q { grid-column: span 2; }
/* The toolbar rule pushed the search to the far edge of a flex row; on the grid it fills its cell. */
.cl-filters .search-box { margin: 0; flex: none; min-width: 0; }

/* The phone's sort select: the table headers are gone there, so this is the way to order the list. */
.cl-filters .list-order { display: none; }

/* An empty chips region takes no gap; the chips row keeps its hairline but not a second margin. */
.cl-chips:not(:has(*)) { display: none; }
.cl-chips .ci-chips { margin-top: 0; }

/* ── The results ─────────────────────────────────────────────────────────────────────────── */
.cl-card { overflow: hidden; margin-bottom: var(--s5); }
.cl-card > .kq-pager { border-top: 1px solid var(--line-2); }

table.calltbl th {
    padding: var(--s3) var(--s4); font-size: var(--t-sm); font-weight: 600; color: var(--muted);
    border-bottom: 1px solid var(--line); white-space: nowrap;
}
/* The cell's padding has ONE home — `table.tbl td` in the Tables section, at the same (0,1,2) —
   so the journal takes the 36px row with every other table and the coarse-pointer block can give
   the lead back to a finger on this table too. */
table.calltbl td { border-bottom: 0; color: var(--ink-2); vertical-align: middle; }

/* SORTABLE HEADERS. The state is aria-sort on the th; the chevron is drawn, not typed (no ↑↓ read
   aloud), and shows on the sorted column — pointing up once the order is ascending. */
.calltbl th a.cl-sort { display: inline-flex; align-items: center; gap: var(--s1); color: inherit; text-decoration: none; }
.cl-sort svg {
    flex: none; width: 14px; height: 14px; opacity: 0;
    transition: rotate var(--dur-fast) var(--e-standard), opacity var(--dur-fast) linear;
}
.calltbl th a.cl-sort.is-sorted { color: var(--ink-2); }
.cl-sort.is-sorted svg { opacity: 1; }
.cl-sort.is-asc svg { rotate: 180deg; }
.cl-sort:focus-visible svg { opacity: .6; }
@media (hover: hover) {
    /* ON THE BAND, NOT ON PAPER. This is (0,3,2) and the frame's own
       `table.tbl > thead > tr > th a:hover` at :1732 is (0,2,5), so this one wins and used to
       paint --kq-link-hover (#07555B) on the near-black strip — 1.32:1 under the pointer. It
       takes the frame's hover ink instead, which is what :1732 was already saying. */
    .calltbl th a.cl-sort:hover { color: var(--frame-ink); }
    .cl-sort:hover svg { opacity: .6; }
    .cl-sort.is-sorted:hover svg { opacity: 1; }
}

/* WHO — the customer is the linked side of every row: a name from the caller file over the number,
   or the number itself. */
.cl-customer { min-width: 0; }
.cl-customer__name,
.cl-customer__link {
    display: inline-flex; align-items: center; gap: var(--s2); max-width: 100%;
    font-weight: 600; color: var(--kq-link-ink); text-decoration: none;
}
.cl-customer__name .kq-name { overflow-wrap: anywhere; }
.cl-customer__num { display: block; font-size: var(--t-sm); color: var(--muted); }
.cl-customer__none { color: var(--muted); }
.cl-flag { display: inline-flex; flex: none; }
@media (hover: hover) {
    .cl-customer__name:hover,
    .cl-customer__link:hover { color: var(--kq-link-hover); text-decoration: underline; text-underline-offset: 3px; }
}

/* THE AGENT AND THE LINE — the direction is a word on the second line, the icon only repeats it. */
.cl-agent { min-width: 0; }
.cl-agent__name { display: block; color: var(--ink-2); overflow-wrap: anywhere; }
.cl-line { display: inline-flex; align-items: center; gap: var(--s1); font-size: var(--t-sm); color: var(--muted); }
.cl-line svg { flex: none; width: 14px; height: 14px; }

.cl-when { color: var(--ink); white-space: nowrap; }
.cl-len { color: var(--ink); white-space: nowrap; }
.cl-len .is-zero { color: var(--n-7); }
.cl-outcome { white-space: nowrap; }
.cl-act { text-align: end; white-space: nowrap; }

@media (max-width: 520px) {
    .cl-filters .cl-filters__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cl-field--q { grid-column: 1 / -1; }
    .cl-tabs .tab { padding-inline: var(--s3); }
}
@media (max-width: 360px) {
    .cl-filters .cl-filters__row { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 820px) {
    .ci-filterbar.cl-filters { padding: var(--s3) var(--s4); }
    .cl-filters .list-order { display: flex; }

    /* ONE SURFACE: on a phone the rows are the cards, so the card around the table stands down.
       Everything below is keyed on .is-cardable, which kinetiq.js adds (and re-adds after an in-place
       update): if the script never runs, the journal stays a table that scrolls sideways instead of
       a stack of unlabelled values. */
    /* The surface half of this — background, edge and shadow — now lives in the global MOB-26 rule,
       which grew the `card holding the .table-wrap directly` shape this card is an instance of.
       What is left is what is the journal's own: the wrapper must stop clipping (the row actions
       open a menu out of it) and the pager loses its side padding once there is no card to inset
       it from. */
    .cl-card:has(> .table-wrap > table.is-cardable) { overflow: visible; }
    .cl-card:has(> .table-wrap > table.is-cardable) > .kq-pager { padding-inline: 0; }
    /* Until the obsolete unkeyed .calltbl card rules earlier in the file are deleted, they would stack
       an unmarked table too; this keeps it a table. */
    .calltbl:not(.is-cardable) { display: table; }
    .calltbl:not(.is-cardable) > thead { display: table-header-group; }
    .calltbl:not(.is-cardable) > tbody { display: table-row-group; }
    .calltbl:not(.is-cardable) > tbody > tr { display: table-row; }
    .calltbl:not(.is-cardable) > tbody > tr > td { display: table-cell; }

    /* THE CALL CARD, in the order a person reads a call record: when and how it ended; who; the
       agent and line beside how long it ran; then the way in. Placed by class, not by column
       position, so a column moving in the table can never scramble the card. */
    table.tbl.is-cardable.calltbl, table.tbl.is-cardable.calltbl > tbody { display: block; }
    table.tbl.is-cardable.calltbl > thead { display: none; }
    /* The first column keeps room for the time; a long outcome wraps inside the second instead of
       pushing the time off the card. */
    table.tbl.is-cardable.calltbl > tbody > tr {
        display: grid; grid-template-columns: minmax(min(9rem, 50%), 1fr) minmax(0, auto); align-items: center;
        gap: var(--s2) var(--s3); margin-bottom: var(--s3); padding: var(--s3) var(--s4);
        /* PAPER, and this is the one surface that was not even glass: it took --surface-material at
           68% with NO backdrop-filter, so on a phone the rows were translucent over the raw wash
           with nothing blurring it — transparency spent, no depth cue bought. */
        background: var(--surface-material); border: 1px solid var(--surface-edge); border-radius: var(--r-card);
        box-shadow: var(--surface-seat);
    }
    /* The generic card labels each value with its column name; these values say what they are. */
    table.tbl.is-cardable.calltbl > tbody > tr > td {
        display: block; grid-template-columns: none; padding: 0; border: 0; text-align: start;
    }
    table.tbl.is-cardable.calltbl > tbody > tr > td::before { content: none; }

    table.tbl.is-cardable.calltbl > tbody > tr > td.cl-when { grid-column: 1; grid-row: 1; }
    table.tbl.is-cardable.calltbl > tbody > tr > td.cl-outcome { grid-column: 2; grid-row: 1; justify-self: end; }
    table.tbl.is-cardable.calltbl > tbody > tr > td.cl-customer {
        grid-column: 1 / -1; grid-row: 2; padding-block: var(--s2); border-block: 1px solid var(--line-2);
    }
    table.tbl.is-cardable.calltbl > tbody > tr > td.cl-agent { grid-column: 1; grid-row: 3; }
    table.tbl.is-cardable.calltbl > tbody > tr > td.cl-len { grid-column: 2; grid-row: 3; justify-self: end; }
    table.tbl.is-cardable.calltbl > tbody > tr > td.cl-act { grid-column: 1 / -1; grid-row: 4; justify-self: start; }
    table.tbl.is-cardable.calltbl > tbody > tr > td.cl-act .btn { width: auto; }
    .calltbl.is-cardable .cl-outcome .kq-outcome { white-space: normal; }
}

@media (prefers-reduced-motion: reduce) {
    .cl-sort svg { transition: none; }
}

/* ═══ THE NUMBER'S HISTORY (/Admin/Contacts/Details) ══════════════════════════════════════ */
.ct-promises { list-style: none; margin: 0 0 var(--s4); padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.ct-promise { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); font-size: var(--t-md); color: var(--ink-2); }
.ct-promise > svg { flex: none; width: 16px; height: 16px; color: var(--muted); }
.ct-promise__t { font-weight: 600; color: var(--ink); }

.kq-figs.ct-figs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ct-fig-date { font-size: var(--t-xl); }
.ct-partial { margin: calc(-1 * var(--s2)) 0 var(--s4); font-size: var(--t-sm); color: var(--muted); }

.ct-sec { margin-bottom: var(--s5); }
.ct-sec .card__head h2 svg { width: 18px; height: 18px; color: var(--muted); }
.ct-sec > .kq-pager { border-top: 1px solid var(--line-2); }

/* THE TIMELINE — one item per call on hairlines, newest first. */
.ct-list { list-style: none; margin: 0; padding: 0 var(--s5); }
.ct-list > .ct-item { padding: var(--s3) 0; border-bottom: 1px solid var(--line-2); }
.ct-list > .ct-item:last-child { border-bottom: 0; }
.ct-item .ct-head { gap: var(--s2) var(--s3); }
.ct-time { color: var(--ink); white-space: nowrap; }
.ct-dir { display: inline-flex; align-items: center; gap: var(--s1); font-size: var(--t-sm); color: var(--muted); }
.ct-dir svg { flex: none; width: 15px; height: 15px; }
.ct-agent { min-width: 0; font-size: var(--t-md); color: var(--ink-2); overflow-wrap: anywhere; }
.ct-len { margin-inline-start: auto; color: var(--ink); white-space: nowrap; }
/* A browser test is on the timeline but not in the figures above it, and it reads as secondary. */
.ct-item.is-test .ct-time,
.ct-item.is-test .ct-agent,
.ct-item.is-test .ct-len,
.ct-item.is-test .ct-sum { color: var(--muted); }
.ct-link {
    display: inline-flex; align-items: center; gap: var(--s1); margin-top: var(--s2);
    font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink); text-decoration: none;
}
.ct-link svg { flex: none; width: 16px; height: 16px; }
@media (hover: hover) {
    .ct-link:hover { color: var(--kq-link-hover); text-decoration: underline; text-underline-offset: 3px; }
}
@media (pointer: coarse) {
    /* the one way from a number's history into a call, and it is a line of its own — not a link
       inside a sentence, so WCAG 2.5.8's 24px floor applies to it. 20.1px before this. */
    .ct-link { min-block-size: var(--tap); }
}

@media (max-width: 720px) {
    .kq-figs.ct-figs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 820px) {
    .ct-list { padding-inline: var(--s4); }
    .ct-len { margin-inline-start: 0; }
}
@media (max-width: 420px) {
    .kq-figs.ct-figs { grid-template-columns: minmax(0, 1fr); }
}
/* ==== END SECTION calllog ==== */
/* ==== SECTION insights ==== */
/* OBSOLETE: none added by this section. The new markup still renders every .ci-* selector above
   this section (.ci-card, .ci-head, .ci-who, .ci-caller, .ci-bot, .ci-meta, .ci-when, .ci-summary,
   .ci-fields, .ci-field, .ci-k, .ci-v, .ci-foot, .ci-filterbar, .ci-filterrow, .ci-advtoggle,
   .ci-advpanel, .ci-advfield, .ci-chips, .ci-chip). Note for UI-FINAL: the first half of the
   selector list `.ci-advtoggle:checked ~ .ci-advpanel` never matched — the toggle sits inside
   .ci-filterrow and the panel does not — only the `:has()` half opens the panel. */

/* ═══ CALL SUMMARIES (CALL-INSIGHTS, wave 2) ═══════════════════════════════════════════════════
   What the redesigned /Admin/CallInsights markup needs beyond the component layer: the card heading
   that replaced an inline-styled span, the direction badge icon, extracted values as lists, the
   staff-only token line, the link to the call page, the operator hint under the empty state, and the
   phone layouts of the card and of the advanced panel. The card's hover shadow is already neutral
   above (UI-CSS), so it is not repeated here. */

/* The agent select stands down to the width of a name; the search keeps the rest of the row. */
.ci-botsel { max-width: 220px; }

/* Each card's number is its h2, so a screen reader moves between calls by heading. The heading
   keeps the size the caller line always had and loses the document heading's margin and weight. */
.ci-h {
    margin: 0;
    font-size: var(--t-lg);
    font-weight: 700;
    line-height: 1.35;
    color: var(--ink);
    min-width: 0;
}
.ci-callerline {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    min-width: 0;
    max-width: 100%;
}
.ci-callerline > svg { flex: none; }
a.ci-caller { color: var(--kq-link-ink); }
@media (hover: hover) {
    a.ci-caller:hover { color: var(--kq-link-hover); text-decoration: underline; text-underline-offset: 2px; }
}

/* A direction is a fact, not a status: the badge carries a small line icon instead of a dot. */
.ci-dir svg { width: 13px; height: 13px; flex: none; }
.ci-meta { min-width: 0; max-width: 60%; }
/* The outcome sentence can be long («בקשה לשיחה חוזרת נשלחה — לא ידוע אם נקבעה»); inside a card it
   wraps rather than pushing the card wider than the screen. */
.ci-meta .kq-outcome,
.ci-foot .badge { white-space: normal; max-width: 100%; }

/* Extracted fields (INS-16, DS-V01). A long field name wraps instead of running out of the card, and
   takes at most a little under half the line; it is told apart by weight and colon, not by colour. */
.ci-k {
    white-space: normal;
    max-inline-size: 45%;
    color: var(--ink-2);
    font-weight: 600;
    flex: 0 1 auto;
}
.ci-v { min-width: 0; }
/* several values of one field — a plain list, never brackets and quotes */
.ci-v--list {
    margin: 0;
    padding: 0;
    padding-inline-start: var(--s4);
    list-style: disc;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.ci-v--list li { overflow-wrap: anywhere; }

/* Kinetiq staff only: the raw tool tokens on a line of their own, quiet, left to right. Assistant
   like the rest of the card — the one code face in the product belongs to .tt-pre. */
.ci-raw {
    display: block;
    margin-top: var(--s2);
    font-family: inherit;
    font-size: var(--t-sm);
    color: var(--muted);
    background: none;
    padding: 0;
    text-align: end;
    overflow-wrap: anywhere;
}

/* The way to the call's own page, at the end of every card. */
.ci-more {
    margin: var(--s3) 0 0;
    font-size: var(--t-md);
    font-weight: 600;
}

/* The pager here is not a card's foot — the cards ARE the results — so it takes neither the
   footer face nor the rule the component gives it inside a card. */
.ci-grid + .kq-pager {
    background: none;
    border-block-start: 0;
    padding-inline: 0;
}

/* The value box of «שדה שנחלץ» appears once a field is chosen — read from the select's own state,
   because the filter bar is not re-rendered by the in-place list update. */
.ci-advpanel:has(select[name="fk"] option[value=""]:checked) .ci-fvfield { display: none; }

/* Empty state: the operator's one link sits under the sentence, centred with it. */
.ci-empty:has(.ci-empty__act) .kq-empty { padding-block-end: var(--s2); }
.ci-empty__act {
    margin: 0;
    padding: 0 var(--s4) var(--s6);
    text-align: center;
    font-size: var(--t-md);
    font-weight: 600;
}

/* PHONE (INS-16). The card head stacks: the number and agent first, then direction, outcome and
   time on one wrapping row. The open advanced panel uses two full-width columns instead of one
   narrow column eight fields long. */
@media (max-width: 520px) {
    .ci-head { flex-direction: column; align-items: stretch; }
    .ci-meta {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        max-width: none;
    }
    .ci-advtoggle:checked ~ .ci-advpanel,
    .ci-filterbar:has(.ci-advtoggle:checked) .ci-advpanel {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
/* ==== END SECTION insights ==== */
/* ==== SECTION call-page ==== */
/* OBSOLETE: none of this section's own. The call page is new; the older call-detail rules (.ct-tx and the call
   log's inline detail row) belong to CALL-LOG, which lists them. */

/* CALL PAGE (CALL-PAGE, wave 2) — /Admin/Calls/Call. Also the shared call parts other screens render:
   the transcript list written by _Transcript (.call-tx*), the player kinetiq.js builds over an
   audio[data-kq-player] (.call-player*) and the sentence that replaces a recording that cannot play
   (.call-audio-missing). Those are deliberately NOT scoped under .callpage. */

/* ── Header: the meta line and the outcome badges sit in the page header's status slot ── */
.call-head .d {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s2);
    margin-block-start: var(--s1);
}
.call-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s1) var(--s3);
    min-width: 0;
    color: var(--muted);
    font-size: var(--t-md);
}
/* the customer's number, when the heading is their name */
.call-meta > bdi.fig { font-weight: 600; color: var(--ink-2); }
.call-meta__sep { color: var(--line-strong); }
.call-meta__dir { display: inline-flex; align-items: center; gap: var(--s1); }
.call-meta__dir svg { flex: none; width: 16px; height: 16px; }
.call-badges { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }
/* The copy field is visually hidden; kq-core focuses and selects it only when the clipboard refuses the
   copy, and its notice then asks the reader to copy the selection — so it must be visible at that moment. */
.sr-only.call-link:focus {
    position: static;
    inline-size: min(360px, 100%);
    block-size: auto;
    margin: 0;
    padding: var(--s1) var(--s2);
    overflow: visible;
    clip-path: none;
    white-space: nowrap;
    border: 1px solid var(--line-strong);
    border-radius: var(--r);
    font: inherit;
    font-size: var(--t-md);
    color: var(--ink);
}

/* ── Two columns: the call at the start, the customer at the end; one column under 1024px ── */
.callpage {
    display: grid;
    grid-template-columns: minmax(0, 1fr) min(368px, 100%);
    gap: var(--s5);
    align-items: start;
}
.callpage__main,
.callpage__side {
    display: flex;
    flex-direction: column;
    gap: var(--s5);
    min-width: 0;
}
@media (max-width: 1023px) {
    .callpage { grid-template-columns: minmax(0, 1fr); }
}

/* ── Sections ── */
.call-sec { min-width: 0; }
.call-sec__h { min-width: 0; overflow-wrap: anywhere; }
/* (0,2,2), because `.card__head h2 > svg` at :820 is (0,1,2) and the plain `.call-sec__h svg`
   this replaces was (0,1,1) — so every section icon on the call page rendered 17px in the faint
   tier while Contacts/Details, one click away, drew the same glyph 18px in the muted one. Same
   descendant form and the same pair of values as `.ct-sec .card__head h2 svg` at :4288. */
.card__head h2.call-sec__h svg { flex: none; width: 18px; height: 18px; color: var(--muted); }
.call-summary {
    margin: 0;
    max-width: 80ch;
    font-size: var(--t-base);
    line-height: 1.7;
    color: var(--ink);
    white-space: pre-line;
    overflow-wrap: anywhere;
}
.call-none { margin: 0; font-size: var(--t-md); color: var(--muted); }
.call-verdicts { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s3) 0 0; }

/* What was collected: label over value, as many columns as fit — four on the approved desktop board */
.call-facts {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(160px, 100%), 1fr));
    gap: var(--s4) var(--s5);
    margin: 0;
}
.call-facts__item { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.call-facts dt { font-size: var(--t-sm); color: var(--muted); overflow-wrap: anywhere; }
.call-facts dd {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: var(--t-base);
    font-weight: 600;
    color: var(--ink);
    overflow-wrap: anywhere;
}

/* What the agent did: a dot in the status ink, always followed by the sentence */
.call-did { list-style: none; margin: 0; padding: var(--s1) var(--s5); }
.call-did__item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: var(--s3);
    padding: var(--s3) 0;
}
.call-did__item + .call-did__item { border-block-start: 1px solid var(--line); }
.call-did__text { font-size: var(--t-base); color: var(--ink); overflow-wrap: anywhere; }

/* ── Recording ── */
/* The native player, until kinetiq.js has built its own over it (it then hides this one). */
.call-audio { display: block; inline-size: 100%; }
.call-audio-missing { margin: 0; font-size: var(--t-md); color: var(--muted); }

.call-player { display: flex; align-items: center; gap: var(--s4); min-width: 0; }
.call-player__play {
    flex: none;
    inline-size: 44px;
    block-size: 44px;
    padding: 0;
    border-radius: var(--r-circle);
}
.call-player__play svg { width: 22px; height: 22px; }
.call-player__seek { flex: 1 1 auto; display: flex; align-items: center; min-width: 0; }
.call-player__range {
    inline-size: 100%;
    margin: 0;
    accent-color: var(--kq-btn-bg);
    cursor: pointer;
}
.call-player__time {
    flex: none;
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--ink-2);
    white-space: nowrap;
}
.call-player__speed {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--ink-2);
}
.call-player__speed .control { inline-size: auto; padding: var(--s1) var(--s2); }
@media (max-width: 560px) {
    /* play and position on the first row, time and speed under them */
    .call-player { flex-wrap: wrap; gap: var(--s3); }
    .call-player__seek { flex-basis: calc(100% - 44px - var(--s3)); }
    .call-player__time { margin-inline-end: auto; }
}
@media (pointer: coarse) {
    .call-player__range { block-size: var(--tap); }
    .call-player__speed .control { min-block-size: var(--tap); }
}

/* ── Transcript: one row per turn, the page is the only scroll ── */
.call-tx {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.call-tx__line {
    display: grid;
    grid-template-columns: minmax(0, 5em) minmax(0, 1fr);
    align-items: baseline;
    gap: var(--s3);
    padding: var(--s1) var(--s3);
    border-radius: var(--r);
}
/* display:grid above outranks the browser's own [hidden] rule */
.call-tx__line[hidden] { display: none; }
.call-tx__who {
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--ink);
    overflow-wrap: anywhere;
}
.call-tx__line.is-agent .call-tx__who { color: var(--ink-2); }
.call-tx__text {
    font-size: var(--t-base);
    line-height: 1.6;
    color: var(--ink);
    overflow-wrap: anywhere;
}
.call-tx__more { margin-block-start: var(--s3); }
.call-tx__more[aria-expanded="true"] svg { rotate: 180deg; }
@media (max-width: 560px) {
    .call-tx__line { grid-template-columns: minmax(0, 1fr); gap: 0; }
}

/* ── Kinetiq staff: engine figures, raw tool results and the technical log ── */
.call-staff { border-color: var(--info-edge); }
.call-staff__figs {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr));
    gap: var(--s3) var(--s5);
    margin: 0;
}
.call-staff__figs > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.call-staff__figs dt { font-size: var(--t-sm); color: var(--muted); }
.call-staff__figs dd { margin: 0; font-size: var(--t-base); font-weight: 700; color: var(--ink); }
.call-staff__h {
    margin: var(--s5) 0 var(--s2);
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--ink-2);
}
.call-staff__text {
    margin: 0;
    font-size: var(--t-md);
    line-height: 1.6;
    color: var(--ink-2);
    overflow-wrap: anywhere;
}
.call-staff__raw { margin: 0; }

.techlog__panel {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    margin-block-start: var(--s3);
}
.techlog__panel[hidden] { display: none; }
.techlog__filter { max-inline-size: 420px; }
/* was an inline style with two literal colours; now the ink ramp, so it follows the tokens */
.techlog__pre {
    margin: 0;
    max-block-size: 420px;
    overflow: auto;
    background: var(--ink);
    border-color: var(--ink);
    color: var(--n-3);
    font-size: var(--t-xs);
    line-height: 1.45;
    word-break: break-all;
}

/* ── Side: the customer's history ── */
.call-hist { list-style: none; margin: 0; padding: 0 var(--s5); }
.call-hist__item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    padding: var(--s3) 0;
}
.call-hist__item + .call-hist__item { border-block-start: 1px solid var(--line); }
.call-hist__top {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s2) var(--s3);
}
.call-hist__when { font-size: var(--t-base); font-weight: 600; color: var(--kq-link-ink); }
@media (hover: hover) {
    .call-hist__when:hover,
    .call-hist__foot a:hover { color: var(--kq-link-hover); text-decoration: underline; }
}
.call-hist__sum {
    margin: 0;
    font-size: var(--t-md);
    color: var(--ink-2);
    overflow: hidden;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}
.call-hist__meta {
    margin: 0;
    display: flex;
    align-items: center;
    gap: var(--s1);
    min-width: 0;
    font-size: var(--t-sm);
    color: var(--muted);
}
.call-hist__meta svg { flex: none; width: 14px; height: 14px; }
.call-hist__foot {
    padding: var(--s3) var(--s5) var(--s4);
    border-block-start: 1px solid var(--line);
}
.call-hist__foot a {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--kq-link-ink);
}
.call-hist__foot svg { flex: none; width: 18px; height: 18px; }

/* ── Side: promises still open with this number ── */
.call-promise__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--s4);
}
.call-promise__item { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.call-promise__when {
    margin: 0;
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-size: var(--t-base);
    font-weight: 600;
    color: var(--ink);
}
.call-promise__state,
.call-promise__note {
    margin: 0;
    padding-inline-start: var(--s4);
    font-size: var(--t-sm);
    color: var(--muted);
    overflow-wrap: anywhere;
}
.call-promise__go { margin-block-start: var(--s4); }

/* ── Side: quality as words ── */
.call-quality { margin: 0; padding: 0 var(--s5); }
.call-quality__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
    padding: var(--s3) 0;
}
.call-quality__row + .call-quality__row { border-block-start: 1px solid var(--line); }
.call-quality dt { font-size: var(--t-md); color: var(--ink-2); }
.call-quality dd { margin: 0; font-size: var(--t-md); font-weight: 600; color: var(--ink); }
/* ==== END SECTION call-page ==== */
/* ==== SECTION callbacks ==== */
/* OBSOLETE: none — a new page; no earlier selector served it. */

/* CALLBACKS (wave 2). The callbacks home /Admin/Calls/Callbacks: a glass card holding a rule list of
   promises, one row of four term/description groups each — customer, what they asked for, when, agent.
   No action column: the page is read-only. Up to 1100px a row is two columns, customer and request
   beside time and agent; below 820px it stacks into customer + time on one line, the request under
   them and the agent last. */

.cbk { overflow: hidden; }

.cbk-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.cbk-row {
    display: grid;
    grid-template-columns: minmax(0, 150px) minmax(0, 1fr) minmax(0, 190px) minmax(0, 100px);
    column-gap: var(--s5);
    row-gap: var(--s3);
    padding: var(--s3) var(--s5);
    border-block-start: 1px solid var(--line);
    /* A link that opens one promise («#cb-{id}») lands below the sticky top bar, not under it. */
    scroll-margin-block: var(--s4);
}
.cbk-list > .cbk-row:first-child { border-block-start: 0; }

/* The row a link pointed at: a plain frame, no animation, so it reads the same with reduced motion. */
.cbk-row:target {
    outline: 2px solid var(--kq-btn-bg);
    outline-offset: -2px;
    background: var(--kq-action-soft);
}

.cbk-cell {
    margin: 0;
    min-width: 0;
}
.cbk-cell dt {
    font-size: var(--t-xs);
    line-height: 1.45;
    color: var(--n-7);
}
.cbk-cell dd {
    margin: 0;
    font-size: var(--t-base);
    line-height: 1.5;
    color: var(--ink);
    overflow-wrap: anywhere;
}
.cbk-cell .cbk-main { font-weight: 600; }
.cbk-cell--who .cbk-main a { font-weight: 600; }
.cbk-cell--agent dd { color: var(--ink-2); }

.cbk-sub,
.cbk-cell dd.cbk-sub {
    font-size: var(--t-sm);
    line-height: 1.65;
    color: var(--muted);
}
.cbk-none { color: var(--muted); font-weight: 400; }

/* Where it was promised: the source, the moment, and the link to that call. */
.cbk-source {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: var(--s2);
    row-gap: 0;
}
.cbk-source > svg {
    flex: none;
    width: 14px;
    height: 14px;
    color: var(--n-7);
}
.cbk-source span[aria-hidden="true"] { color: var(--line-strong); }
.cbk-source a { font-weight: 600; }

/* How late or how soon, then anything that changes whether the call goes out. A reason can be a full
   sentence, so these badges wrap inside their column instead of pushing the row wider. */
.cbk-flags {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--s1);
    padding-block-start: 2px;
}
.cbk-flags .badge {
    align-items: flex-start;
    max-width: 100%;
    white-space: normal;
    text-align: start;
    line-height: 1.5;
    overflow-wrap: anywhere;
}
.cbk-flags .badge .kq-dot { margin-block-start: .45em; }

/* «בהמשך היום» under the due list: a quiet band, not a second card — and A BAND IS A
   BAND. It was the last surface-like strip in the sheet still drawn with a tint: --n-2 at 55%
   over a white card composites to #F5F7F9, 1.06:1, which is not a band, and it would composite
   to something else again over any other surface it was ever moved onto. --tbl-head-band is the
   token the material already gives the strip behind the column names: opaque --n-2, 1.13:1
   against the card, the same step the page ground is. */
.cbk-group-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s1) var(--s3);
    padding: var(--s2) var(--s5);
    border-block-start: 1px solid var(--line);
    background: var(--tbl-head-band);
}
/* THE GROUP BAND IS THE FRAME (--tbl-head-band, :330), so its two inks are the frame's, not the
   page's: --ink-2 measured 1.53:1 here and --muted 2.29:1 — the heading and the count of the
   promises hidden in a collapsed group, both unreadable. 17.30:1 and 9.59:1 now, and they are the
   pair `table.tbl > thead > tr > th` already takes on the same strip. */
.cbk-group-head h2 {
    margin: 0;
    font-size: var(--t-md);
    font-weight: 600;
    line-height: 1.6;
    color: var(--frame-ink);
}
.cbk-group-count {
    font-size: var(--t-sm);
    color: var(--frame-ink-2);
    font-variant-numeric: tabular-nums;
}
.cbk-group-head + .cbk-list > .cbk-row:first-child { border-block-start: 1px solid var(--line); }

/* The pager belongs to the list above it, inside the same card. */
.cbk .kq-pager { border-block-start: 1px solid var(--line); }

/* Above 820px the rail keeps its width, and the three capped columns with their gaps take 500px
   before the request column gets any: about 58px at a 900px window, nothing at 821px. Up to a wide
   window the row is two columns instead — the customer beside the time, the request beside the
   agent. The phone layer below redraws it again. */
@media (max-width: 1100px) {
    .cbk-row {
        grid-template-columns: minmax(0, 1fr) minmax(0, 190px);
        /* one line: css-integrity-test reads a line with no {}:;, as prose that escaped a comment */
        grid-template-areas: "who when" "what agent";
    }
    .cbk-cell--who { grid-area: who; }
    .cbk-cell--what { grid-area: what; }
    .cbk-cell--when { grid-area: when; }
    .cbk-cell--agent { grid-area: agent; }
}

@media (max-width: 820px) {
    .cbk-row {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        /* one line: css-integrity-test reads a line with no {}:;, as prose that escaped a comment */
        grid-template-areas: "who when" "what what" "agent agent";
        column-gap: var(--s4);
        padding: var(--s3) var(--s4);
    }
    .cbk-cell--who { grid-area: who; }
    .cbk-cell--what { grid-area: what; }
    .cbk-cell--when { grid-area: when; }
    .cbk-cell--agent {
        grid-area: agent;
        display: flex;
        align-items: baseline;
        gap: var(--s2);
    }
    .cbk-group-head { padding-inline: var(--s4); }
}
/* ==== END SECTION callbacks ==== */
/* ==== SECTION custom-candidates ==== */
/* CUSTOM — «מועמדים», one customer's page (Services/Custom, 2026-10-08). The list is the callbacks list above; the
   one thing it adds is a strip of seven status tabs, wider than a phone — measured 611px at a 390px screen — so the
   strip scrolls inside itself instead of widening the page, as the reseller ledger's does. */
.cand-tabs > .tabs { overflow-x: auto; scrollbar-width: thin; }
.cand-tabs > .tabs .tab { white-space: nowrap; }
/* What no agent collected — «שם לא נאסף», «לא נמסרה», «אין שיחות» — reads as an absence, not as a value: the
   callbacks list's muted «—», on a dd here, where the list's own dd rules would otherwise make it ink and bold. */
.cand-list .cbk-cell dd.cbk-none { color: var(--muted); font-weight: 400; }
/* Per-row Arabic indicator: a 3 px color stripe on the inline-start edge of the row.
   Red (.cand-ar) = Arabic signals detected; blue (.cand-he) = none detected. */
.cand-list .cand-ar { border-inline-start: 3px solid var(--danger-ink); }
.cand-list .cand-he { border-inline-start: 3px solid var(--info-ink); }
/* ==== END SECTION custom-candidates ==== */
/* ==== SECTION dashboard ==== */
/* OBSOLETE: .dash-head, .dash-head h1, .dash-kpis, .dash-kpis .dash-fig, .dash-kpis .dash-fig:first-child,
   .dash-kpis .dash-fig .v (560px and 340px blocks), .dash-fig.is-live .v, .dash-fig.is-live .v::before,
   .dash-fig .d.up::before, .dash-fig .d.down::before, .dash-quota, .dash-quota__row, .dash-quota__row b (both copies),
   .dash-quota__track, .dash-quota__track > span, .dash-quota__track > span.is-high, .dash-quota__track > span.is-full,
   .dash-quota__warn, .dash-todo, .dash-todo h2, .dash-todo ul, .dash-todo li, .dash-todo .dot, .dash-todo .t-bad .dot,
   .dash-todo .t-warn .dot, .dash-todo .t-live .dot (the live halo), .dash-todo .ttl, .dash-todo .det, .dash-todo a,
   .dash-clear svg, .dash-chart, .dash-chart__x, .dash-calls, .dash-calls li (the per-row grid, and its 720px copy),
   .dash-calls li:last-child, .dash-calls .t, .dash-calls .n, .dash-calls .b (and its 720px display:none), .dash-calls .d,
   .dash-calls .s, .dash-calls .s.is-miss, .bshare-list, .bshare-top, .bshare-name, .bshare-nums, .bshare-bar,
   .bshare-bar span, .chart-scroll (the daily chart's scroll wrapper — also the .chart-scroll part of the
   grouped `.table-wrap, .chart-scroll, .ct-tx` rule).
   Pages/Admin/Index.cshtml was the only view that wrote any of them.
   STILL IN USE, keep: .dash-fig, .dash-figs, .dash-block, .dash-empty (campaign, contact, distributor and customer detail
   pages); .dash-two, .dash-month, .dash-mbtn, .dash-hours, .dash-hours__ax, .dash-clear (this page); .dash-grid2 and
   `.dash-grid2 > *` and `.dash-block { min-width: 0; }` (mobile-test.sh greps them). mobile-test.sh also names
   `.dash-todo h2` and `.dash-head h1` in its letter-spacing check — that list changes when those rules go. */

/* DASHBOARD (wave 2). The tenant dashboard: three or four figure tiles — the fourth only for an
   account that works in both directions — «דורש טיפול», the allocation line, recent calls, the daily
   chart, the hour profile and, while the service is closed, the agents. Component classes
   (.kq-figs, .kq-attn, .kq-progress, .kq-steps, .tbl, .card) come from the component layer; this
   section only lays the page out and adds the two things no other screen has — the allocation line
   and the chart. */

/* ── The page: one column of blocks, one gap between them ─────────────────────────────────── */
.dash { display: flex; flex-direction: column; gap: var(--s5); min-width: 0; }
.dash > * { margin-block: 0; min-width: 0; }
.dash .dash-two { margin-top: 0; gap: var(--s5); }

.dash .card__head h2 { font-size: var(--t-lg); font-weight: 700; line-height: 1.4; color: var(--ink); }
.dash .card__head h2 svg,
.dash-shortcuts .card__head h2 svg { flex: none; width: 18px; height: 18px; }

/* the «to the whole list» link at the end of a card head */
.dash-headlink {
    display: inline-flex; align-items: center; gap: var(--s2);
    font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink); text-decoration: none; white-space: nowrap;
}
.dash-headlink svg { flex: none; width: 16px; height: 16px; }
@media (hover: hover) {
    .dash-headlink:hover { color: var(--kq-link-hover); text-decoration: underline; text-underline-offset: 3px; }
}

/* ── The month group in the page header ───────────────────────────────────────────────────── */
/* role="group": two arrow buttons and the month they move, read as ONE segmented control, and
   the arrows carry no frame of their own — so this box is the control's boundary. */
.dash-month {
    padding: 2px; color: var(--ink-2);
    background: var(--ghost-bg); border: 1px solid var(--control-edge); border-radius: var(--r);
}
.dash-month__label { display: inline-flex; flex-direction: column; align-items: center; padding-inline: var(--s3); line-height: 1.25; }
.dash-month__name { font-size: var(--t-md); font-weight: 600; color: var(--ink); white-space: nowrap; }
.dash-month__sub { font-size: var(--t-xs); color: var(--muted); white-space: nowrap; }
.dash-mbtn { color: var(--ink-2); }
.dash-mbtn.is-off { opacity: .35; }
@media (hover: hover) {
    a.dash-mbtn:hover { background: var(--n-2); color: var(--ink); }
}

/* ── Figures ──────────────────────────────────────────────────────────────────────────────── */
/* As many equal columns as there are tiles; two across on a phone, where an odd last tile takes
   the whole row instead of leaving a hole beside it. */
@media (min-width: 721px) {
    .dash .kq-figs { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
}
@media (max-width: 720px) {
    .dash .kq-figs > .kq-fig:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
/* `.dash .kq-fig__label svg { 16px; --n-7 }` was here and is gone: it is the same (0,2,1) as the
   `.kq-fig .kq-fig__label svg` chip rule in the component layer, so with both in one sheet this
   later copy would have won and the dashboard would be the one screen with no coloured chips. */
.dash .kq-fig__sub bdi { font-variant-numeric: tabular-nums; }
/* «2 באיחור»: the reason the tile wants attention, in words beside its dot */
.dash-late { display: inline-flex; align-items: center; gap: var(--s1); color: var(--warn-ink); font-weight: 600; }

/* ── «דורש טיפול» ─────────────────────────────────────────────────────────────────────────── */
.dash-count {
    display: inline-flex; align-items: center; justify-content: center; min-width: 1.6em;
    padding: 0 var(--s2); border-radius: var(--r-pill);
    background: var(--neutral-bg); color: var(--ink-2);
    font-size: var(--t-xs); font-weight: 600; line-height: 1.6; font-variant-numeric: tabular-nums;
}
.dash-attn__body { padding-block: var(--s1); }
.kq-attn__body { display: block; min-width: 0; }
/* the detail is the line that says WHICH call or agent — never hidden, at any width (A11Y-18) */
.kq-attn .det { display: block; }
.kq-attn > li > .btn { white-space: nowrap; }
/* the activation path takes a row of its own, full width */
.kq-attn > li.dash-attn__steps { display: block; padding-block: var(--s4); }
.dash-attn__steps > .kq-attn__t { margin: 0 0 var(--s3); }
.dash-attn__steps .kq-steptrack__head { margin-bottom: var(--s3); }
.dash-clear { padding-block: var(--s3); font-size: var(--t-base); color: var(--ink-2); }
.dash-clear .kq-dot { flex: none; }

/* ── The allocation: one line — the numbers, the bar, when it resets ──────────────────────── */
.kq-quota { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2) var(--s4); padding: var(--s4) var(--s5); }
.kq-quota > svg { flex: none; width: 18px; height: 18px; color: var(--muted); }
.kq-quota__text { margin: 0; font-size: var(--t-base); white-space: nowrap; font-variant-numeric: tabular-nums; }
.kq-quota__text strong { font-weight: 700; }
.kq-quota__period { color: var(--muted); }
.kq-quota .kq-progress { flex: 1 1 10rem; }
.kq-quota__note { margin: 0; font-size: var(--t-md); color: var(--muted); white-space: nowrap; }
/* spent: the full consequence, on its own line, in the danger ink */
.kq-quota__note.is-over { flex-basis: 100%; white-space: normal; color: var(--danger-ink); font-weight: 600; }
@media (max-width: 720px) {
    .kq-quota { padding: var(--s3) var(--s4); }
    .kq-quota__text, .kq-quota__note { white-space: normal; }
    .kq-quota .kq-progress { flex-basis: 100%; }
}

/* ── Recent calls ─────────────────────────────────────────────────────────────────────────── */
@media (min-width: 821px) {
    .dash-recent__tbl th:first-child, .dash-recent__tbl td:first-child { padding-inline-start: var(--s5); }
    .dash-recent__tbl th:last-child, .dash-recent__tbl td:last-child { padding-inline-end: var(--s5); }
    .dash-recent__tbl td { white-space: nowrap; }
}
.dash-when { display: inline-flex; align-items: center; gap: var(--s2); }
.dash-when svg { flex: none; width: 16px; height: 16px; color: var(--n-7); }
.dash-when bdi { font-variant-numeric: tabular-nums; }
/* The direction in words, beside its icon: the word used to be for screen readers only, which left
   an outbound-first account reading a column of identical-looking arrows. «יוצאת» takes the same
   blue the live board gives an outgoing leg, so one product says one thing. */
.dash-when__dir { font-size: var(--t-sm); color: var(--muted); }
.dash-when__dir.is-out { color: var(--info-ink); }
.dash-contact { display: block; font-weight: 600; line-height: 1.5; }
.dash-contact__sub { display: block; font-size: var(--t-sm); line-height: 1.5; color: var(--muted); }

/* ── The daily chart ──────────────────────────────────────────────────────────────────────── */
/* Bars run in the page's direction: oldest day at the right on a Hebrew page, the same way as the
   hour profile beside it (DATA-16). The scale has two lines, its top and its middle, with their
   values on the start side; a bar's height is its step in twentieths of that top.
   Above the scale's top sits a strip of headroom (--kq-chart-head) that holds the tooltip, so the
   tooltip never covers the top of the busiest day or the top edge of a focused bar's ring. */
.dash-days { --kq-chart-y: 2.25rem; --kq-chart-h: 220px; --kq-chart-head: calc(var(--s6) + var(--s1)); }
.dash-days__unit { font-size: var(--t-sm); color: var(--muted); }
.kq-chart { display: flex; gap: var(--s2); min-width: 0; }
.kq-chart__y {
    flex: none; width: var(--kq-chart-y); height: var(--kq-chart-h); margin-block-start: var(--kq-chart-head);
    display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start;
    font-size: var(--t-xs); line-height: 1; color: var(--n-7);
}
.kq-chart__y > :first-child { translate: 0 -50%; }
.kq-chart__y > :last-child { translate: 0 50%; }

/* The grid's content box is the scale (its padding is the headroom), and the two scale lines are
   placed in that content box, not over the headroom. */
.kq-bars {
    position: relative; flex: 1 1 auto; min-width: 0;
    height: calc(var(--kq-chart-h) + var(--kq-chart-head)); padding-block-start: var(--kq-chart-head);
    display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); align-items: end;
    border-block-end: 1px solid var(--n-5);
    background-image: linear-gradient(var(--line), var(--line)), linear-gradient(var(--line), var(--line));
    background-size: 100% 1px, 100% 1px;
    background-position: 0 0, 0 50%;
    background-origin: content-box;
    background-repeat: no-repeat;
}
.kq-bar {
    display: flex; align-items: flex-end; justify-content: center;
    height: 100%; min-width: 0; margin: 0; padding: 0 2px;
    font: inherit; color: inherit; background: transparent; border: 0; border-radius: var(--radius-xs);
    cursor: pointer;
}
.kq-bar__fill {
    display: block; width: 100%; max-width: 14px; height: var(--h, 0%);
    border-radius: 3px 3px 0 0; background: var(--dv-1);
}
.kq-bar[data-h="0"] .kq-bar__fill { height: 2px; background: var(--n-4); }
.kq-bar.is-today .kq-bar__fill,
.kq-bar.is-active .kq-bar__fill { background: var(--kq-btn-bg); }
.kq-bar:focus-visible { background: var(--kq-action-soft); }
@media (hover: hover) {
    .kq-bar:hover .kq-bar__fill { background: var(--kq-btn-bg); }
}
@media (prefers-reduced-motion: no-preference) {
    .kq-bar__fill { transition: background-color var(--dur-fast) var(--e-standard); }
}

.kq-bar[data-h="0"] { --h: calc(0 * 5%); }
.kq-bar[data-h="1"] { --h: calc(1 * 5%); }
.kq-bar[data-h="2"] { --h: calc(2 * 5%); }
.kq-bar[data-h="3"] { --h: calc(3 * 5%); }
.kq-bar[data-h="4"] { --h: calc(4 * 5%); }
.kq-bar[data-h="5"] { --h: calc(5 * 5%); }
.kq-bar[data-h="6"] { --h: calc(6 * 5%); }
.kq-bar[data-h="7"] { --h: calc(7 * 5%); }
.kq-bar[data-h="8"] { --h: calc(8 * 5%); }
.kq-bar[data-h="9"] { --h: calc(9 * 5%); }
.kq-bar[data-h="10"] { --h: calc(10 * 5%); }
.kq-bar[data-h="11"] { --h: calc(11 * 5%); }
.kq-bar[data-h="12"] { --h: calc(12 * 5%); }
.kq-bar[data-h="13"] { --h: calc(13 * 5%); }
.kq-bar[data-h="14"] { --h: calc(14 * 5%); }
.kq-bar[data-h="15"] { --h: calc(15 * 5%); }
.kq-bar[data-h="16"] { --h: calc(16 * 5%); }
.kq-bar[data-h="17"] { --h: calc(17 * 5%); }
.kq-bar[data-h="18"] { --h: calc(18 * 5%); }
.kq-bar[data-h="19"] { --h: calc(19 * 5%); }
.kq-bar[data-h="20"] { --h: calc(20 * 5%); }

/* ── Two directions in one chart ──────────────────────────────────────────────────────────── */
/* An account that calls out as well as in gets both series in the SAME button — one tab stop, one
   tooltip, one day per column — with the direction the account works in first in reading order.
   A one-direction account keeps the chart it had, colours included: the two colours below only
   exist where there are two series to tell apart. */
/* The gap INSIDE a day stays smaller than the gap between days, or the pairs stop reading as pairs. */
.kq-bars.is-pair .kq-bar { gap: 2px; padding: 0 3px; }
.kq-bars.is-pair .kq-bar__fill { max-width: 6px; background: var(--fill); }
.kq-bars.is-pair .kq-bar__fill.is-in { --fill: var(--dv-1); }
.kq-bars.is-pair .kq-bar__fill.is-out { --fill: var(--dv-2); }
.kq-bars.is-pair .kq-bar__fill.is-second { height: var(--h2, 0%); }

.kq-bar[data-h2="0"] { --h2: calc(0 * 5%); }
.kq-bar[data-h2="1"] { --h2: calc(1 * 5%); }
.kq-bar[data-h2="2"] { --h2: calc(2 * 5%); }
.kq-bar[data-h2="3"] { --h2: calc(3 * 5%); }
.kq-bar[data-h2="4"] { --h2: calc(4 * 5%); }
.kq-bar[data-h2="5"] { --h2: calc(5 * 5%); }
.kq-bar[data-h2="6"] { --h2: calc(6 * 5%); }
.kq-bar[data-h2="7"] { --h2: calc(7 * 5%); }
.kq-bar[data-h2="8"] { --h2: calc(8 * 5%); }
.kq-bar[data-h2="9"] { --h2: calc(9 * 5%); }
.kq-bar[data-h2="10"] { --h2: calc(10 * 5%); }
.kq-bar[data-h2="11"] { --h2: calc(11 * 5%); }
.kq-bar[data-h2="12"] { --h2: calc(12 * 5%); }
.kq-bar[data-h2="13"] { --h2: calc(13 * 5%); }
.kq-bar[data-h2="14"] { --h2: calc(14 * 5%); }
.kq-bar[data-h2="15"] { --h2: calc(15 * 5%); }
.kq-bar[data-h2="16"] { --h2: calc(16 * 5%); }
.kq-bar[data-h2="17"] { --h2: calc(17 * 5%); }
.kq-bar[data-h2="18"] { --h2: calc(18 * 5%); }
.kq-bar[data-h2="19"] { --h2: calc(19 * 5%); }
.kq-bar[data-h2="20"] { --h2: calc(20 * 5%); }

/* A day with none of that direction stays the neutral sliver, per series. */
.kq-bars.is-pair .kq-bar[data-h="0"] .kq-bar__fill:not(.is-second),
.kq-bars.is-pair .kq-bar[data-h2="0"] .kq-bar__fill.is-second { height: 2px; background: var(--n-4); }

/* THE EMPHASIS MOVES OFF THE FILL. Painting today's fill one colour would turn a two-colour column
   into what reads as two bars of the second series; the highlight becomes the button's own ground. */
.kq-bars.is-pair .kq-bar.is-today,
.kq-bars.is-pair .kq-bar.is-active { background: var(--kq-action-soft); }
@media (hover: hover) {
    .kq-bars.is-pair .kq-bar:hover { background: var(--kq-action-soft); }
}

/* The legend: a 7px dot and a word, as every colour on this product carries. Kept apart from
   .kq-dot so a legend dot is never read as a status dot. */
.kq-key { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s1) var(--s3); font-size: var(--t-sm); color: var(--muted); }
.kq-key__i { display: inline-flex; align-items: center; gap: var(--s2); white-space: nowrap; }
.kq-key__dot { display: inline-block; flex: none; width: 7px; height: 7px; border-radius: var(--r-circle); }
.kq-key__dot.is-in { background: var(--dv-1); }
.kq-key__dot.is-out { background: var(--dv-2); }

/* The tooltip, in the headroom above the scale. kq-bars.js measures the bar and writes the tooltip's
   left edge into --tip-x; a measured offset is a physical distance, so this is the one physical
   `left` in the section. */
.kq-bars__tip {
    position: absolute; top: 0; left: var(--tip-x, 0); z-index: 2;
    padding: 2px var(--s2); border-radius: var(--r);
    background: var(--frame-bg); color: #fff;
    font-size: var(--t-sm); font-weight: 600; line-height: 1.6; white-space: nowrap;
    font-variant-numeric: tabular-nums; box-shadow: var(--shadow-pop); pointer-events: none;
}
.kq-bars__tip[hidden] { display: none; }

.kq-chart__x {
    display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
    margin-inline-start: calc(var(--kq-chart-y) + var(--s2)); margin-top: var(--s1);
    font-size: var(--t-xs); line-height: 1.6; color: var(--n-7); text-align: center; white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.kq-chart__x > span { min-width: 0; overflow: visible; }
.dash-days__table { margin-top: var(--s3); }
.dash-days__table .table-wrap { margin-top: var(--s2); }

@media (max-width: 720px) {
    .dash-days { --kq-chart-h: 160px; --kq-chart-y: 1.75rem; }
    .kq-bar { padding: 0 1px; }
    /* Still a PAIR on a phone, never a stack: stacking would turn two answers into one total. */
    .kq-bars.is-pair .kq-bar { gap: 1px; padding: 0 2px; }
    .kq-bars.is-pair .kq-bar__fill { max-width: 5px; }
}

@media (forced-colors: active) {
    .kq-bar__fill { forced-color-adjust: none; background: CanvasText; }
    .kq-bar.is-today .kq-bar__fill, .kq-bar.is-active .kq-bar__fill { background: Highlight; }
    .kq-bars__tip { border: 1px solid CanvasText; }
    /* With the palette gone the two series still have to differ, and by direction — not by slot. */
    .kq-bars.is-pair .kq-bar__fill.is-in, .kq-key__dot.is-in {
        forced-color-adjust: none; background: CanvasText; border: 1px solid CanvasText;
    }
    .kq-bars.is-pair .kq-bar__fill.is-out, .kq-key__dot.is-out {
        forced-color-adjust: none; background: Canvas; border: 1px solid CanvasText;
    }
}

/* ── The hour profile, when the account works both ways ───────────────────────────────────── */
/* «When is the line busy» is a SUM, so here the two directions stack inside one hour instead of
   standing side by side: inbound at the bottom in the chart's own teal, outbound above it in the
   blue the product already gives an outgoing leg. --h is the hour's share of the busiest hour,
   --h2 the outbound share of that hour. The plain .dash-hours .hr rules still draw the single
   series of a one-direction account, untouched. */
.dash-hours .hr.is-pair { display: flex; flex-direction: column; overflow: hidden; }
.dash-hours .hr.is-pair > .hr__out { flex: none; height: var(--h2, 0%); background: var(--info-ink); }
/* The SAME #1AC4CC as the legend dot beside it and as the daily chart's inbound series — not the
   plain --kq-cyan of the single-series bar, which would leave the key showing one colour and the
   thing it names another. A one-direction account has no key and keeps its own bar untouched. */
.dash-hours .hr.is-pair > .hr__in { flex: 1 1 auto; background: var(--kq-cyan-bright); }
.dash-hours .hr.is-zero.is-pair > .hr__out,
.dash-hours .hr.is-zero.is-pair > .hr__in { background: var(--n-4); }
@media (forced-colors: active) {
    .dash-hours .hr.is-pair > .hr__in { forced-color-adjust: none; background: CanvasText; }
    .dash-hours .hr.is-pair > .hr__out { forced-color-adjust: none; background: Canvas; border: 1px solid CanvasText; }
}

/* ── While the service is closed: the agents, each with what it does now ──────────────────── */
.dash-agents__list { list-style: none; margin: 0; padding: 0; }
.dash-agents__list > li {
    display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: var(--s2) var(--s3);
    padding: var(--s3) 0; border-top: 1px solid var(--line);
}
.dash-agents__list > li:first-child { border-top: 0; padding-top: 0; }
.dash-agents__who { display: flex; flex-direction: column; min-width: 0; }
.dash-agents__who a { font-weight: 600; overflow-wrap: anywhere; }
.dash-agents__meta { font-size: var(--t-sm); color: var(--muted); }
.dash-agents__off { display: flex; align-items: flex-start; gap: var(--s2); margin: var(--s3) 0 0; font-size: var(--t-md); color: var(--ink-2); }
.dash-agents__off svg { flex: none; width: 16px; height: 16px; margin-top: 3px; color: var(--muted); }
@media (max-width: 560px) {
    .dash-agents__list > li { grid-template-columns: minmax(0, 1fr) auto; }
    .dash-agents__list > li > .btn { grid-column: 1 / -1; justify-self: start; }
}

/* ── A reader who sees no call data ───────────────────────────────────────────────────────── */
.dash-noperm p { margin: 0; font-size: var(--t-base); color: var(--ink-2); }
/* ==== END SECTION dashboard ==== */
/* ==== SECTION live-board ==== */
/* OBSOLETE: none in kinetiq.css. The old board wrote its layout as inline style="" (the player card's
   margin, flex row and audio size) and no rule was ever keyed on it; /Admin/LiveCalls no longer
   renders .empty, [data-listen-panel] or the player .card — .empty stays for the other screens. */

/* LIVE BOARD (LIVE, wave 2) — /Admin/LiveCalls. The board merges rows in place (kq-livecalls.js),
   so nothing here may depend on a row being rebuilt: a new row fades in once, a stale board dims
   (.is-stale comes from the component layer), and the player is a full-width row under its call. */

/* The count line and «עודכן» read as the header's status sentence, directly under the title. */
.kq-live__meta {
    display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
    gap: var(--s1) var(--s4);
    margin-block: calc(var(--s3) * -1) var(--s4);
    min-width: 0;
}
.kq-live__summary { margin: 0; font-size: var(--t-md); font-weight: 600; color: var(--ink-2); }
.kq-live__updated { font-size: var(--t-sm); color: var(--muted); }

/* «העדכון החי נעצר» — the sentence and its retry stacked in the notice's text column. No close
   button: the state lasts until the board is live again, and then the notice leaves by itself. */
.kq-live__stalled { margin-block-end: var(--s4); }
.kq-live__stalled .kq-notice__text {
    display: flex; flex-direction: column; align-items: flex-start; gap: var(--s2);
}
.kq-live__stalled .kq-notice__text > p { margin: 0; }

/* The session-ended sentence and «השיחה עם … הסתיימה». Empty = no room: it is a live region, so it
   is never display:none (a region that appears together with its text is not reliably announced). */
.kq-live__status { margin: 0; }
.kq-live__status:not(:empty) { margin-block-start: var(--s3); }

/* ── columns ───────────────────────────────────────────────────────────────────────────── */
table.kq-live td { white-space: nowrap; }
table.kq-live td[data-cell="agent"] { white-space: normal; overflow-wrap: anywhere; }
table.kq-live td[data-cell="action"] { text-align: end; }

/* Direction: a mark AND a word, never a colour alone. */
.kq-live__dir { display: inline-flex; align-items: center; gap: var(--s2); color: var(--ink-2); }
.kq-live__dir > svg { flex: none; width: 16px; height: 16px; color: var(--n-6); }
.kq-live__dir.is-out > svg { color: var(--info-ink); }

/* A call that just started fades in once. Opacity only; a reader who asked for less motion gets
   the row at once. Nothing on the status marks moves. */
table.kq-live > tbody > tr.is-arriving { transition: opacity var(--dur-base) var(--e-out); }
@starting-style {
    table.kq-live > tbody > tr.is-arriving { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    table.kq-live > tbody > tr.is-arriving { transition: none; }
}

/* ── the player, in a row directly under the call being listened to ──────────────────────── */
table.kq-live > tbody > tr:has(+ tr.kq-live__player) { border-bottom-color: transparent; }
table.kq-live > tbody > tr.kq-live__player > td { padding: 0 var(--s4) var(--s3); }
@media (hover: hover) {
    table.kq-live > tbody > tr.kq-live__player:hover { background: none; }
}
.kq-live__player [hidden] { display: none; }
.kq-live__player-box {
    display: grid; gap: var(--s3); min-width: 0;
    padding: var(--s3) var(--s4);
    border: 1px solid var(--line); border-radius: var(--r-card);
    background: var(--card);
    white-space: normal;
}
.kq-live__player-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3); min-width: 0; }
.kq-live__player-head .badge .kq-ic { flex: none; width: 14px; height: 14px; }
.kq-live__player-who { margin: 0; font-size: var(--t-md); font-weight: 600; color: var(--ink); }
.kq-live__player-controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); min-width: 0; }
.kq-live__audio { flex: 1 1 260px; min-width: 0; height: 38px; }
.kq-live__player-controls .btn .kq-ic { flex: none; width: 16px; height: 16px; }
.kq-live__player-msg { margin: 0; font-size: var(--t-sm); color: var(--muted); }

/* ── phone: the generic cards, with the listen button across the card ─────────────────────── */
@media (max-width: 820px) {
    table.kq-live td { white-space: normal; }
    table.kq-live.is-cardable > tbody > tr > td[data-cell="action"] { text-align: start; }
    table.kq-live.is-cardable > tbody > tr > td[data-cell="action"] > .btn { width: 100%; }
    /* the card above keeps its own edge; the player hangs under it as its own panel */
    table.kq-live.is-cardable > tbody > tr:has(+ tr.kq-live__player) { border-bottom-color: var(--line); margin-bottom: var(--s2); }
    table.kq-live.is-cardable > tbody > tr.kq-live__player > td { padding: 0; }
    table.kq-live.is-cardable .kq-live__player-box { margin-bottom: var(--s3); }
    .kq-live__player-controls .btn { flex: 1 1 auto; }
}
/* ==== END SECTION live-board ==== */
/* ==== SECTION campaign-list ==== */
/* OBSOLETE: none unique to the campaign screens — the old list and details used only shared classes
   (.page-head markup, .empty, .dash-figs/.dash-fig on details, .alert.alert-warn on the list). Whether
   those still have other users is for UI-FINAL to decide; the campaign views no longer render them. */

/* CAMPAIGN LIST AND DETAILS (CMP-LIST, wave 2). The components come from the kit layer — tabs, pager,
   empty state, notices, badges, cards, the filter bar; this section only arranges them for the two
   campaign screens: the state beside a badge, the progress cell, the header actions, the funnel,
   the settings list and the recipients head. Tokens only, logical properties only. */

/* ── Shared: a status and its reason, the next dial and its kind ─────────────────────────────── */
.cm-state {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s1) var(--s2);
    min-width: 0;
}
.cm-reason,
.cm-sub {
    font-size: var(--t-sm);
    color: var(--muted);
    overflow-wrap: anywhere;
}
.cm-reason:empty,
.cm-sub:empty { display: none; }
.cm-next { font-variant-numeric: tabular-nums; }
.cm-sub { display: block; }

/* The progress bar under «212 מתוך 1,180» and in the funnel: 6px, the action colour on a quiet track.
   aria-hidden in the markup — the words beside it carry the number. */
progress.cm-progress {
    display: block;
    inline-size: 100%;
    block-size: 6px;
    margin-block-start: var(--s1);
    border: 0;
    border-radius: var(--r-pill);
    background: var(--n-3);
    color: var(--kq-btn-bg);
    accent-color: var(--kq-btn-bg);
    overflow: hidden;
    -webkit-appearance: none;
    appearance: none;
}
progress.cm-progress::-webkit-progress-bar { background: var(--n-3); border-radius: var(--r-pill); }
progress.cm-progress::-webkit-progress-value { background: var(--kq-btn-bg); border-radius: var(--r-pill); }
progress.cm-progress::-moz-progress-bar { background: var(--kq-btn-bg); border-radius: var(--r-pill); }
@media (forced-colors: active) {
    progress.cm-progress { forced-color-adjust: auto; border: 1px solid CanvasText; }
}

/* A control that cannot be used right now, with its reason beside it (the list's «קמפיין חדש», the
   details' «המשך»). The reason is always visible, never a tooltip. */
.cm-blocked {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
    margin: 0;
}
.page-head__actions > .hint.cm-blocked { margin: 0; }
.cm-blocked__why,
.hint.cm-blocked {
    font-size: var(--t-sm);
    color: var(--muted);
}

/* ── The list ─────────────────────────────────────────────────────────────────────────────────── */
.cm-botfilter { max-inline-size: 220px; }

/* The order select a phone needs once the headers are gone; .list-order shows it below 820px, and
   the display is set only there so the component rule keeps it hidden on a wide screen. */
.cm-order {
    align-items: center;
    gap: var(--s2);
    font-size: var(--t-md);
    color: var(--ink-2);
}
.cm-order label { font-weight: 600; white-space: nowrap; }

.cm-table .n { font-weight: 600; color: var(--kq-link-ink); text-decoration: none; overflow-wrap: anywhere; }
@media (hover: hover) {
    .cm-table .n:hover { color: var(--kq-link-hover); text-decoration: underline; text-underline-offset: 2px; }
}
.cm-fraction { white-space: nowrap; }
.cm-table progress.cm-progress { max-inline-size: 160px; }

/* ── The details page: header ─────────────────────────────────────────────────────────────────── */
.cm-headstate {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s1) var(--s3);
    margin-block-start: var(--s1);
}
.cm-stateline {
    font-size: var(--t-md);
    color: var(--ink-2);
    overflow-wrap: anywhere;
}

.cm-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
}
.cm-actions > form { margin: 0; display: inline-flex; }

/* A notice that names its fix: the link sits at the end of the row, in the notice's own colour. */
.cm-note { align-items: center; }
.cm-note__act {
    flex: none;
    font-size: var(--t-md);
    font-weight: 600;
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    white-space: nowrap;
}

/* ── The details page: progress and settings ──────────────────────────────────────────────────── */
.cm-grid2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s5);
    margin-block: var(--s4) var(--s5);
    align-items: start;
}
.cm-grid2 > .card { min-width: 0; }
.cm-asof {
    margin: 0;
    font-size: var(--t-sm);
    color: var(--muted);
    white-space: nowrap;
}
.cm-asof bdi { font-variant-numeric: tabular-nums; }

/* The funnel: each step taken from the one before it, a chevron between them in reading order. */
.cm-funnel {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s4);
}
.cm-funnel > li {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    min-width: 0;
}
.cm-funnel > li:not(:last-child)::after {
    content: "";
    position: absolute;
    inset-block-start: 18px;
    inset-inline-end: calc(var(--s4) / -2 - 3px);
    inline-size: 6px;
    block-size: 6px;
    border-block-start: 1.5px solid var(--n-6);
    border-inline-end: 1.5px solid var(--n-6);
    transform: rotate(45deg);
}
/* The borders are logical, so they already mirror; only the turn differs. In a right-to-left page the
   next step is to the left, so the chevron points left. */
[dir="rtl"] .cm-funnel > li:not(:last-child)::after { transform: rotate(-45deg); }
.cm-funnel__label { font-size: var(--t-md); color: var(--muted); }
.cm-funnel__value {
    font-size: var(--t-xl);
    font-weight: 700;
    line-height: 1.2;
    color: var(--ink);
    font-variant-numeric: tabular-nums slashed-zero;
    overflow-wrap: anywhere;
}
.cm-funnel__of { font-size: var(--t-md); font-weight: 400; color: var(--muted); }
.cm-funnel__sub { font-size: var(--t-sm); color: var(--muted); }
.cm-funnel__sub:has(> bdi:empty) { visibility: hidden; }

/* The settings: a term and its value per row, the terms in one column. */
.cm-settings {
    margin: 0;
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    column-gap: var(--s5);
}
.cm-settings > div { display: contents; }
.cm-settings dt,
.cm-settings dd {
    margin: 0;
    padding-block: var(--s2);
    border-block-start: 1px solid var(--line);
}
.cm-settings > div:first-child > dt,
.cm-settings > div:first-child > dd { border-block-start: 0; padding-block-start: 0; }
.cm-settings dt { font-size: var(--t-md); font-weight: 600; color: var(--ink-2); }
.cm-settings dd { font-size: var(--t-base); color: var(--ink); overflow-wrap: anywhere; }
.cm-settings dd .cm-sub { display: inline; margin-inline-start: var(--s1); }

/* The calling days at a glance, for the eye only — the sentence beside them is what is read out. */
.cm-daychips {
    display: inline-flex;
    gap: var(--s1);
    margin-inline-end: var(--s2);
    vertical-align: middle;
}
.cm-daychip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-inline-size: 26px;
    block-size: 22px;
    padding-inline: var(--s1);
    border: 1px solid var(--line);
    border-radius: var(--r);
    font-size: var(--t-xs);
    font-weight: 600;
    color: var(--muted);
    background: var(--n-1);
}
.cm-daychip.is-on {
    border-color: var(--kq-btn-bg);
    background: var(--kq-action-soft);
    color: var(--kq-link-ink);
}

/* ── The details page: callbacks and recipients ───────────────────────────────────────────────── */
.cm-section { margin-block-end: var(--s5); }
.cm-cardnote { margin: 0; padding: var(--s3) var(--s5) 0; }

.cm-rcp-head { flex-wrap: wrap; }
.cm-rcp-search {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin: 0;
    min-width: 0;
}
.cm-rcp-search .search-box { flex: 0 1 240px; min-width: 0; }
.cm-rcp-search .control { padding-block: var(--s2); font-size: var(--t-md); }
.cm-rcp > .tabs {
    margin: 0;
    padding-inline: var(--s5);
    overflow-x: auto;
    scrollbar-width: thin;
}
.cm-rcp > .tabs .tab { white-space: nowrap; }

/* ── Phones and small tablets ─────────────────────────────────────────────────────────────────── */
@media (max-width: 820px) {
    .cm-grid2 { grid-template-columns: minmax(0, 1fr); gap: var(--s4); }
    .cm-funnel { grid-template-columns: minmax(0, 1fr); gap: var(--s3); }
    .cm-funnel > li:not(:last-child)::after { display: none; }

    /* Secondary actions stay in one wrapping row; each keeps its own width. */
    .cm-actions { inline-size: 100%; }
    .cm-actions > .btn,
    .cm-actions > form .btn { flex: 0 0 auto; }

    .cm-settings { grid-template-columns: minmax(0, 1fr); }
    .cm-settings dt { padding-block-end: 0; }
    .cm-settings dd { border-block-start: 0; padding-block-start: var(--s1); }

    .cm-rcp-search { inline-size: 100%; }
    .cm-rcp-search .search-box { flex: 1 1 auto; }
    .cm-rcp > .tabs { padding-inline: var(--s3); }
    .cm-cardnote { padding-inline: var(--s3); }

    .cm-order.list-order { display: inline-flex; flex: 1 1 100%; }
    .cm-order .control { flex: 1 1 auto; min-width: 0; }

    .cm-table progress.cm-progress { max-inline-size: none; }
}
/* ==== END SECTION campaign-list ==== */
/* ==== SECTION campaign-forms ==== */
/* OBSOLETE: .cm-retrydays, .cm-retrydays > span, .cm-retrydays label, .cm-hours, .cm-winhead, .cm-winhead .lbl,
   .cm-retryrow, .cm-retrytoggle, .cm-advwintoggle, .cm-advwinpanel,
   .field:has(.cm-retrytoggle:checked) .cm-retrypanel, .field:has(.cm-advwintoggle:checked) .cm-advwinpanel,
   .field:has(.cm-advwintoggle:checked) .cm-retrydays, .field:has(.cm-advwintoggle:checked) .cm-hours,
   .cm-facts > span, .cm-facts b, .cm-win > select (inside the 560px block), and the bare .cm-retrypanel
   { display: none } — the create and edit screens no longer render any of them. .cm-facts, .cm-win and
   .cm-retrypanel themselves are still used and are redefined below. */

/* CAMPAIGN CREATE AND EDIT (CMP-FORMS, wave 2). A stack of cards (CampaignCreate.dc.html); the
   components — cards, fields, switches, notices, the error summary, the save bar — come from the kit
   layer. This section arranges them and adds what only these forms have: the start choice, the day
   chips, the per-day ranges, the recipient counter and the summary bar. The CMP-LIST section shares
   the cm- prefix; the names here were chosen not to meet its .cm-actions, .cm-blocked, .cm-daychip(s). */

.cm-form {
    display: flex;
    flex-direction: column;
    gap: var(--s5);
}
.cm-form > .kq-errsum,
.cm-form > .kq-notice { margin: 0; }
.cm-notice { margin-block-end: var(--s5); }

/* Script-driven visibility: `hidden` must win over the display the kit gives these components. */
.cm-form .field-error[hidden],
.cm-form .hint[hidden],
.cm-form .btn[hidden],
.cm-form .cm-win[hidden],
.cm-form .cm-warn[hidden],
.cm-form details[hidden] { display: none; }

.cm-card .card__head h2 > svg {
    inline-size: 18px;
    block-size: 18px;
    color: var(--kq-btn-bg);
}

/* Two columns inside a card (20px rows, 28px columns), one below 820px. */
.cm-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s5) var(--s6);
    align-items: start;
}
.cm-full { grid-column: 1 / -1; }
.cm-grid > .field,
.cm-grid > fieldset { min-width: 0; margin: 0; }

/* A switch and its label on one line. */
.cm-switchrow {
    display: flex;
    align-items: center;
    gap: var(--s3);
    min-width: 0;
}
.cm-switchrow > label {
    margin: 0;
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--ink-2);
    cursor: pointer;
}

/* ── Recipients ─────────────────────────────────────────────────────────────────────────────── */
/* A column of numbers typed left to right starts at the left edge — not the right-aligned single-line
   rule, which would ragged every line of a pasted list. */
.cm-form textarea.control[dir="ltr"] {
    text-align: start;
    min-block-size: 12rem;
    resize: vertical;
    tab-size: 6;
    font-variant-numeric: tabular-nums;
}

.cm-count {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s2) var(--s4);
    margin-block-start: var(--s3);
    padding-block-start: var(--s3);
    border-block-start: 1px solid var(--line);
}
.cm-count:has(> .cm-count__text:empty):has(> .cm-invalid[hidden]) { display: none; }
.cm-count__text {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
    margin: 0;
    font-size: var(--t-md);
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
}
.cm-count__text:empty { display: none; }
.cm-count__part strong { font-weight: 700; color: var(--ink); }
.cm-sep { color: var(--line-strong); }

.cm-invalid { min-width: 0; }
.cm-invalid ol {
    max-block-size: 12lh;
    overflow: auto;
    margin: var(--s2) 0 0;
    padding-inline-start: var(--s5);
    font-size: var(--t-sm);
    color: var(--ink-2);
    overflow-wrap: anywhere;
}

/* A warning, not an error: the form still sends. */
.cm-warn {
    display: flex;
    align-items: flex-start;
    gap: var(--s1);
    margin: var(--s2) 0 0;
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--warn-ink);
}
.cm-warn:empty { display: none; }
.cm-warn > svg {
    flex: none;
    inline-size: 16px;
    block-size: 16px;
    margin-block-start: 2px;
}

/* ── Who calls and when ─────────────────────────────────────────────────────────────────────── */
.cm-radios {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2) var(--s6);
    min-block-size: 44px;
}
.cm-radio {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
}
.cm-radio > input[type="radio"] {
    inline-size: 18px;
    block-size: 18px;
    margin: 0;
    accent-color: var(--kq-btn-bg);
    cursor: pointer;
}
.cm-radio > label {
    font-size: var(--t-base);
    color: var(--ink);
    cursor: pointer;
}

/* The date and time exist only for «לתזמן»; the submit names what will happen. */
.cm-when { display: none; }
.cm-form:has(#cm-start-later:checked) .cm-when {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s3);
    margin-block-start: var(--s3);
}
.cm-go-later { display: none; }
.cm-form:has(#cm-start-later:checked) .cm-go-now { display: none; }
.cm-form:has(#cm-start-later:checked) .cm-go-later { display: inline; }

/* ── The schedule: days and hours, or ranges per day ────────────────────────────────────────── */
.cm-sched {
    display: grid;
    gap: var(--s4);
    min-width: 0;
}
.cm-sched > .hint { margin: 0; }
.cm-simple {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s4) var(--s6);
    align-items: start;
}
.cm-sched:has(#cm-advwin:checked) .cm-simple { display: none; }

/* Day chips: seven native checkboxes, each laid over its own pill. The box itself is transparent and
   fills the pill, so the click, the keyboard and the posted value are the browser's; the pill shows
   the state and the focus ring. They are a multi-select, not seven settings, so they are not switches. */
.cm-days__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
}
.cm-day {
    position: relative;
    display: inline-flex;
}
.cm-day input.cm-daychip__input {
    position: absolute;
    inset: 0;
    z-index: 1;
    inline-size: 100%;
    block-size: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.cm-day__label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-inline-size: 48px;
    block-size: 44px;
    padding-inline: var(--s3);
    border: 1px solid var(--control-edge);
    border-radius: var(--r);
    background: var(--control-bg);
    color: var(--ink-2);
    font-size: var(--t-base);
    font-weight: 600;
    user-select: none;
}
.cm-daychip__input:checked + .cm-day__label {
    border-color: var(--kq-btn-bg);
    background: var(--kq-btn-bg);
    color: var(--kq-btn-on);
}
.cm-daychip__input:focus-visible + .cm-day__label {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
    box-shadow: var(--focus-halo);
}
@media (hover: hover) {
    /* --line-strong is now LIGHTER than --control-edge, so this hover was fading the chip. */
    .cm-daychip__input:not(:checked):hover + .cm-day__label { border-color: var(--n-7); }
}
@media (forced-colors: active) {
    .cm-daychip__input:checked + .cm-day__label {
        forced-color-adjust: none;
        border-color: Highlight;
        background: Highlight;
        color: HighlightText;
    }
}
.cm-days > .field-error { margin-block-start: var(--s2); }

.cm-hourpair {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 var(--s4);
    align-items: start;
}
.cm-hourpair > .field-error { grid-column: 1 / -1; }

.cm-winpanel {
    display: grid;
    gap: var(--s3);
    justify-items: start;
    padding-block-start: var(--s3);
    border-block-start: 1px solid var(--line);
}
.cm-sched:has(#cm-advwin:not(:checked)) .cm-winpanel { display: none; }
.cm-winpanel > .hint { margin: 0; }
.cm-winpanel > .cm-wins,
.cm-winpanel > .field-error { justify-self: stretch; }

.cm-wins {
    display: grid;
    gap: var(--s3);
    min-width: 0;
}
.cm-wins:focus-visible { outline-offset: 4px; }

/* One range: day, from, to, «עד סוף היום», remove. The column labels show on the first row only —
   they head the grid — and stay in the accessibility tree on every row; below 560px every row
   carries its own. */
.cm-form .cm-win {
    display: grid;
    grid-template-columns: minmax(96px, 1.1fr) minmax(96px, 1fr) minmax(96px, 1fr) auto auto;
    gap: var(--s2) var(--s3);
    align-items: end;
    margin: 0;
    padding: 0;
    border: 0;
}
.cm-wincell {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    min-width: 0;
    margin: 0;
}
.cm-winlbl {
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--muted);
}
@media (min-width: 561px) {
    .cm-win:not(:first-child) .cm-winlbl {
        position: absolute;
        inline-size: 1px;
        block-size: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}
.cm-wineod {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    min-block-size: 42px;
    font-size: var(--t-md);
    color: var(--ink-2);
    white-space: nowrap;
    cursor: pointer;
}
.cm-form .cm-win .kq-row-del { align-self: center; }
.cm-form .cm-win > .cm-win-err {
    grid-column: 1 / -1;
    margin: 0;
}

/* ── When they do not answer ────────────────────────────────────────────────────────────────── */
.cm-retry .cm-retrypanel {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s5) var(--s6);
    margin: 0;
    padding: 0;
    border: 0;
}
.cm-retry:has(#cm-retry:not(:checked)) .cm-retrypanel { display: none; }
.cm-switchrow + .hint { padding-inline-start: calc(40px + var(--s3)); }

/* The callback switch: one sentence for ON, one for OFF, and only the true one shows. */
.cm-cb .hint {
    margin-block-start: var(--s1);
    padding-inline-start: calc(40px + var(--s3));
}
.cm-cbhint-on,
.cm-cbhint-off { display: none; }
.cm-cb:has(#cm-agentcb:checked) .cm-cbhint-on { display: inline; }
.cm-cb:has(#cm-agentcb:not(:checked)) .cm-cbhint-off { display: inline; }

/* ── The summary bar (create) ───────────────────────────────────────────────────────────────── */
.kq-savebar.cm-summary {
    margin-block-start: 0;
    padding-block-end: max(var(--s3), env(safe-area-inset-bottom));
}
.cm-summary__text {
    flex: 1 1 20rem;
    min-width: 0;
    margin: 0;
    font-size: var(--t-md);
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}

/* ── Edit: the facts that cannot change ─────────────────────────────────────────────────────── */
.cm-facts__title {
    margin: 0 0 var(--s2);
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--muted);
}
.cm-form dl.cm-facts {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s3) var(--s6);
    margin: 0;
}
.cm-fact { min-width: 0; }
.cm-fact dt {
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--muted);
}
.cm-fact dd {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s1) var(--s2);
    margin: 2px 0 0;
    font-weight: 600;
    color: var(--ink);
    overflow-wrap: anywhere;
}
.cm-fact__link {
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--kq-link-ink);
}
@media (hover: hover) {
    .cm-fact__link:hover { color: var(--kq-link-hover); }
}
.cm-formactions { margin: 0; }

/* ── Narrow screens ─────────────────────────────────────────────────────────────────────────── */
@media (max-width: 820px) {
    .cm-grid,
    .cm-simple,
    .cm-retry .cm-retrypanel,
    .cm-form dl.cm-facts { grid-template-columns: minmax(0, 1fr); }
    .cm-full { grid-column: auto; }
}
@media (max-width: 560px) {
    .cm-form:has(#cm-start-later:checked) .cm-when { grid-template-columns: minmax(0, 1fr); }
    .cm-form .cm-win {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        padding-block-end: var(--s3);
        border-block-end: 1px solid var(--line);
    }
    .cm-form .cm-win > .cm-wincell:first-child { grid-column: 1 / -1; }
    .cm-form .cm-win .kq-row-del { justify-self: end; }
    .cm-switchrow + .hint,
    .cm-cb .hint { padding-inline-start: 0; }
}
/* ==== END SECTION campaign-forms ==== */
/* ==== SECTION auth-pages ==== */
/* OBSOLETE, rewritten 2026-09-20 when the sign-in split came back. .auth__brand, .auth__hero,
   .auth__hero img, .auth__panel, .auth__card .lead and the 980px lines are LIVE again and were
   struck from this list; they are declared in the component layer beside .auth, not here. So is
   the PAINT .auth__brand::after used to carry: the hero's two radials are live again as
   background-image on .auth__brand itself (see the note there), which needs no pseudo-element and
   no z-index, so the SELECTOR stays obsolete while the picture it drew does not.
   Still obsolete: .auth__brand::after and .auth__brand > * (the radials moved onto the element);
   .auth__hero-name and .auth__hero-sub (no view has ever rendered
   either, before the redesign or after); .auth__mobilelogo, .auth__mobilelogo img and
   .auth__mobilelogo .name, replaced by .auth__mark, which renders the same 200px box through
   _BrandMark and so cannot show the platform's logo to a reseller.
   .auth:has(> .auth__brand) was never in the pre-redesign sheet at all — it was a transitional
   selector of the redesign's own and is simply gone.
   .pw-meter, .pw-meter > span and .pw-meter span in the forced-colours block — only once
   Pages/Admin/Users/Reset and Pages/Platform/Users/Reset (wave 3) stop rendering data-pw-meter-for,
   the last two users. */

/* AUTH PAGES (AUTH, wave 2): sign-in, password reset, invitation, password change, access denied.
   Everything else these pages wear — the card, the notices, the rules checklist, the field error
   state — is the component layer; this section only fits the pieces together. */

/* A heading with its one sentence under it («איפוס סיסמה», the invitation, the dead link). */
.auth__intro {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    min-width: 0;
}
.auth__intro p {
    margin: 0;
    color: var(--ink-2);
    font-size: var(--t-base);
    overflow-wrap: anywhere;
}

/* A notice written into the card body — the sign-in error, «מחוברים כעת כ…». The card's own gap
   spaces it; the notice's margin would double the space under it. */
.auth__card > .kq-notice { margin-bottom: 0; }

/* A second form right under the primary one: «התנתקות» on the forced password change. It belongs
   to the action above it, so it sits closer than the card's gap. */
.auth__card > .auth__second { margin-block-start: calc(var(--s3) * -1); }

/* THE EYE TOGGLE (AUTH-21). A 36px target, one of two icons shown by the pressed state the script
   keeps in aria-pressed — eye while hidden, eye-off while shown. The field reserves the toggle's
   width on the side the toggle sits: the toggle is placed by the RTL row, but a password field is
   dir="ltr", and its own inline-start is the LEFT, where the toggle is. */
.input-pw .control { padding-inline-end: calc(36px + var(--s3)); }
.input-pw .control[dir="ltr"] {
    padding-inline-start: calc(36px + var(--s3));
    padding-inline-end: var(--s3);
}
.input-pw .toggle {
    inset-inline-end: var(--s1);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
}
.input-pw .toggle svg {
    flex: none;
    width: 18px;
    height: 18px;
}
.input-pw .toggle[aria-pressed="true"] .pw-show,
.input-pw .toggle:not([aria-pressed="true"]) .pw-hide { display: none; }

/* In-shell pages built as one card: the voluntary password change and access denied. */
.acct-card { max-width: 520px; }
.acct-card .form-actions > form { margin: 0; }

/* Access denied: who is signed in, the one sentence, the ways on. */
.acct-denied p {
    margin: 0 0 var(--s3);
    overflow-wrap: anywhere;
}
.acct-denied__who { color: var(--ink-2); }
.acct-denied .form-actions { margin-top: var(--s4); }
/* ==== END SECTION auth-pages ==== */
/* ==== SECTION lookup ==== */
/* OBSOLETE (no view uses these after AGT-LOOKUP; listed for UI-FINAL, not deleted here):
   in the `studio` section, the whole `@supports selector(:has(*))` block that starts at
   `#ep-lookup .lkcfg { display: none; }` — `.lkcfg`, `.lksrc-api`, `.lksrc-sheet`, `.lksrc-api-i`,
   `.lksrc-sheet-i`, `.lkmap`, `.lkmap-row`, `.lkmap-col`, `.lkmap-let`, `.lkmap-name`, `.lkmap-eg`
   and their 640px media rule. Every one of them is replaced below under a new name, in this
   section, so the two never fight over the same selector while both are in the file. */

/* ══ THE CALLER-IDENTIFICATION PANEL (AGT-LOOKUP, wave 3) — /Admin/VoiceBots/Edit #p-lookup ══
   The panel is read top to bottom in the order it is worked in: the switch, where the caller's
   details come from, the file and its mapping or the request, what the agent will say, and only
   then the test and the save. The blocks are separated by a rule rather than by a card each, so
   the whole tab is still one column of work.

   SHOWING AND HIDING IS CSS ONLY (the CSP is script-src 'self'). The settings are revealed by the
   switch and the source blocks by their radio, through :has() on the panel root — and the whole
   mechanism is wrapped in @supports, so a browser without :has() shows EVERYTHING rather than
   hiding settings it can never reveal. */

/* Block, not a flex column: three of the children are forms that hold nothing but hidden fields, and
   a flex gap would reserve a row of space for each of them at the end of the panel. */
#ep-lookup { max-width: 62rem; }
#ep-lookup .lk-form { margin: 0; }
/* The upload, the test and the delete forms: their controls sit in the layout above and reach them
   through form="…", so the forms themselves take no room. */
#ep-lookup .lk-sub { margin: 0; }
#ep-lookup .lk-result { margin-top: var(--s5); }

#ep-lookup .lk-cfg { display: flex; flex-direction: column; gap: var(--s5); margin-top: var(--s5); }
#ep-lookup .lk-block { display: flex; flex-direction: column; gap: var(--s4); }
#ep-lookup .lk-block + .lk-block { border-top: 1px solid var(--line); padding-top: var(--s5); }
#ep-lookup .lk-h {
    margin: 0;
    font-size: var(--t-base); font-weight: 700; line-height: 1.6; color: var(--ink);
}
#ep-lookup .lk-switch { font-weight: 600; }
#ep-lookup .lk-stack { display: flex; flex-direction: column; gap: var(--s4); }
#ep-lookup .lk-narrow { max-width: 22rem; }
#ep-lookup .lk-radios { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4); }
#ep-lookup .lk-auth { display: flex; flex-direction: column; gap: var(--s4); }
#ep-lookup .lk-auth__detail { display: flex; flex-direction: column; gap: var(--s4); }
/* A placeholder name inside a hint: left to right and a shade quieter, but in the page's own face —
   the one code face in the product belongs to .tt-pre. */
#ep-lookup .lk-tok {
    font-family: inherit; font-weight: 600;
    direction: ltr; unicode-bidi: isolate;
    overflow-wrap: anywhere;
}
#ep-lookup .lk-line { margin: 0; }
#ep-lookup .lk-line + .lk-line { margin-top: var(--s1); }
/* «the secret did not travel» sits under the name of a credential being created */
#ep-lookup .lk-lost { display: flex; align-items: flex-start; gap: var(--s2); color: var(--warn-ink); font-weight: 600; }
#ep-lookup .lk-lost svg { flex: none; width: 16px; height: 16px; margin-top: 2px; }

@supports selector(:has(*)) {
    /* The settings and the test follow the switch. The SAVE never does: it lives outside .lk-cfg,
       because turning the feature off is itself a change that has to be savable. */
    #ep-lookup:not(:has(#lk-on:checked)) .lk-cfg,
    #ep-lookup:not(:has(#lk-on:checked)) .lk-actions__test { display: none; }
    #ep-lookup:not(:has(#lk-src-sheet:checked)) .lk-sheet { display: none; }
    #ep-lookup:not(:has(#lk-src-api:checked)) .lk-api { display: none; }
}

/* ── the uploaded file ──────────────────────────────────────────────────────────────────────
   What is in the account right now, before anything asks for a new file. */
#ep-lookup .lk-file {
    padding: var(--s3) var(--s4);
    border: 1px solid var(--line); border-radius: var(--r-card);
    background: var(--n-1);
}
#ep-lookup .lk-file__count { margin: 0; font-size: var(--t-base); }
#ep-lookup .lk-file__meta { margin: var(--s1) 0 0; font-size: var(--t-sm); color: var(--muted); overflow-wrap: anywhere; }

/* file box, phone column and the button on one line; the button stays level with the controls,
   not with their labels, and the row stacks before anything can be squeezed. */
#ep-lookup .lk-upload {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto;
    gap: var(--s4);
    align-items: end;
}
#ep-lookup .lk-upload__go { justify-self: start; }
#ep-lookup .lk-upload .hint { margin-top: var(--s1); }

/* the delete, in its own frame: the only action on this panel that destroys something */
#ep-lookup .lk-danger { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }

/* ── the mapping grid ───────────────────────────────────────────────────────────────────────
   One row per column of the file: the column on the reading side, the variable name on the other.
   The label IS the column — letter, header and one of its values — so each box is announced with
   the column it names. */
#ep-lookup .lk-map { display: grid; gap: var(--s2); }
#ep-lookup .lk-map__row {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 14rem);
    gap: var(--s3); align-items: center;
}
#ep-lookup .lk-map__col {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2);
    min-width: 0; cursor: pointer;
}
#ep-lookup .lk-map__let { min-width: 1.6rem; font-size: var(--t-sm); font-weight: 600; color: var(--muted); }
#ep-lookup .lk-map__name { font-weight: 600; }
#ep-lookup .lk-map__eg { min-width: 0; font-size: var(--t-sm); color: var(--muted); overflow-wrap: anywhere; }

/* ── test and save ──────────────────────────────────────────────────────────────────────────
   One row: the number to try, the test, and the save at the end. «בדיקה» runs against what is
   SAVED, so while the form holds unsaved changes it is refused and says why, in place.
   The row is a sibling of the settings block, not a child of it, so it draws its own separator. */
#ep-lookup .lk-actions {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s4);
    margin-top: var(--s5); border-top: 1px solid var(--line); padding-top: var(--s5);
}
#ep-lookup .lk-actions__test { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s4); }
#ep-lookup .lk-actions__go { display: flex; flex-direction: column; gap: var(--s1); }
#ep-lookup .lk-actions__save { margin-inline-start: auto; }
/* With the switch off the save is the only control in the row, and it belongs beside the switch
   it answers to rather than pushed to the far edge of an empty line. */
@supports selector(:has(*)) {
    #ep-lookup:not(:has(#lk-on:checked)) .lk-actions__save { margin-inline-start: 0; }
}
#ep-lookup .lk-reason { margin: 0; font-size: var(--t-sm); font-weight: 600; color: var(--warn-ink); }

/* ── what came back ─────────────────────────────────────────────────────────────────────────
   The status line travels in TempData; the full text of the test is read from the row. It is the
   agent's own sentences and the caller's own fields — Hebrew prose, so it is set in the page font
   and wrapped, never in a code box. */
#ep-lookup .lk-result:focus { outline: none; }
#ep-lookup .lk-result:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; box-shadow: var(--focus-halo); }
#ep-lookup .lk-result .kq-notice { margin-bottom: 0; }
#ep-lookup .lk-result__text {
    margin: var(--s2) 0 0;
    white-space: pre-wrap; overflow-wrap: anywhere;
    font-family: inherit; font-size: var(--t-md); line-height: 1.7;
    max-height: 26rem; overflow-y: auto;
}
#ep-lookup .lk-last {
    padding: var(--s3) var(--s4);
    border: 1px solid var(--line); border-radius: var(--r-card);
    background: var(--n-1);
}
#ep-lookup .lk-last .eyebrow { margin-bottom: var(--s1); }

/* ── narrow ─────────────────────────────────────────────────────────────────────────────────
   Below 820px every two-up row becomes one column: a file input, a 14rem name box and a button
   cannot share a phone's width without one of them becoming unusable. */
@media (max-width: 820px) {
    #ep-lookup .lk-upload { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
    #ep-lookup .lk-upload__go { justify-self: stretch; }
    #ep-lookup .lk-map__row { grid-template-columns: minmax(0, 1fr); }
    #ep-lookup .lk-narrow { max-width: none; }
    #ep-lookup .lk-actions,
    #ep-lookup .lk-actions__test { align-items: stretch; }
    #ep-lookup .lk-actions__save { margin-inline-start: 0; }
    #ep-lookup .lk-actions .field,
    #ep-lookup .lk-actions__test,
    #ep-lookup .lk-actions__go,
    #ep-lookup .lk-actions__save { width: 100%; }
}

/* LAST IN THIS SECTION ON PURPOSE. Three of the blocks this panel shows and hides through the
   `hidden` attribute carry a display of their own (.lk-auth__detail, .lk-stack, .lk-lost), and a
   rule written above them would lose to the later, equally specific one — the credential block, the
   new-credential fields and «הסוד לא נשמר» would all sit on the screen for ever. The tool editor's
   section ends the same way, for the same reason. */
#ep-lookup [hidden] { display: none; }
/* ==== END SECTION lookup ==== */
/* ==== SECTION users ==== */
/* OBSOLETE: .pw-meter, .pw-meter > span and the .pw-meter lines in the forced-colours block — this
   package's temporary-password screen was one of the two that rendered data-pw-meter-for, and
   PLAT-MAIL-USERS dropped the other, so nothing in Pages asks for the meter any more. (The
   auth-pages section names the same rules with a condition that is now met.)
   Nothing else: these screens only ever styled themselves with style= attributes, which are now the
   .us-* classes below. */

/* THE ACCOUNT'S USERS (ADM-USERS, wave 3): the list, the user card, adding a user, and the
   temporary password. Everything these screens wear — table, card, badges, choice cards, the
   password rules, the notices — is the component layer; this section only arranges them and gives
   the three inline `max-width` and `align-items` declarations a name. */

/* Form rhythm. A kq-field renders a bare .field with no margin of its own, so the form spaces its
   own rows; .form-actions then needs no second gap above it. */
.us-form { display: grid; gap: var(--s4); }
.us-form > .form-actions { margin-top: var(--s2); }

/* First and last name side by side — one column as soon as two would leave each of them narrower
   than the name it holds. */
.us-names { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); }

/* The role choice: its legend is the question, the cards are the answers. */
.us-roles { min-width: 0; }

/* A form card is read, not filled edge to edge: a line of a name or an address stops being
   readable long before the content column ends. */
.us-card { max-width: 680px; }
.us-card--narrow { max-width: 520px; }

/* The two cards of the user page keep their own heights instead of stretching to the taller one. */
.us-cards { align-items: start; }

/* THE LIST. An address is the one value on this screen the browser will not break by itself, so a
   long one pushed six columns past the card and left the whole row scrolling sideways. A date is the
   opposite: it must never break mid-stamp, whatever the column is given. */
.us-table td > bdi[dir="ltr"]:not(.fig) { overflow-wrap: anywhere; }
.us-table .fig { white-space: nowrap; }

/* THE STATUS CELL holds up to three things — the state, when the invitation went out, and a lockout
   that is still running — and each of them is a whole word, so they wrap as a block rather than
   pushing the column wider. */
.us-status { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); min-width: 0; }
.us-status__sent { font-size: var(--t-sm); color: var(--muted); white-space: nowrap; }

/* The security card: the facts, then the actions that change them. */
.us-kv { margin: 0; }
.us-actions { display: grid; gap: var(--s3); margin-top: var(--s5); }
.us-actions > form { margin: 0; }

@media (max-width: 820px) {
    .us-names { grid-template-columns: 1fr; }
    /* As a card, the status cell reads as a list under its label. */
    .us-status { gap: var(--s2); }
}
/* ==== END SECTION users ==== */
/* ==== SECTION apikeys ==== */
/* ADM-API — the keys screen, the key editor, the event destinations and the API reference.

   Everything here used to live in two page <style> blocks, one per view, and the two had already
   drifted (TADM-11): one copy of a tab bar, one copy of a dialog, and a checkbox rule that lost to
   the global switch rule and left a 22px blank band after every key.

   OBSOLETE, page-local only, nothing in the stylesheet defines them — listed for wave 4 so the
   JS that still knows the old dialog can be cleaned up with them:
   .apitabs, .kdlg, .kdlg__t, .kdlg__box, .kdlg__head, .kdlg__x, .kdlg__foot, .keyrow__t,
   .keyrow__edit, .docs, .docs__toc, .docs .grp, .ep, .ep__m, .ep__p, .ep__w, .ep__s, .params */

:root {
    /* The one code surface in the product: a dark block inside a page made of light glass. The
       values are declared here because a literal colour anywhere else in this stylesheet is a
       defect, and a code block cannot be painted from the status tokens — it needs a palette that
       tells a key from a string at a glance. */
    --code-bg: #1b2027;
    --code-ink: #e6ebf1;
    --code-key: #5fe3d0;
    --code-str: #ffd591;
    --code-comment: #8b95a1;
}

/* ══ the create dialog ══════════════════════════════════════════════════════════════════════ */

/* The toggle, its button and the overlay have to be siblings — the CSS-only open state and the
   script's focus handling both walk from the input to the next elements — while the button itself
   belongs in the page header's action row. display: contents puts it there without a box. */
.kq-modal-host { display: contents; }

/* A narrow dialog scrolls as a WHOLE OVERLAY rather than inside its own card (MOB-27): dvh does
   not shrink for an open on-screen keyboard, so a card capped at 90dvh hides its own submit
   button the moment a phone keyboard appears. The backdrop is fixed so it still covers the screen
   once the overlay has been scrolled. */
.kq-modal:has(> .kq-modal-card.is-narrow) {
    align-items: start; overflow-y: auto; overscroll-behavior: contain;
}
.kq-modal:has(> .kq-modal-card.is-narrow) > .kq-modal-bg { position: fixed; }
.kq-modal-card.is-narrow {
    width: min(520px, 100%); max-height: none; margin-block: auto;
}
.kq-modal-card.is-narrow > h2 { margin: 0 0 var(--s4); font-size: var(--t-lg); }

/* The close control is a real button now, not a <label>: a label is not in the tab order, so the
   dialog could be opened from the keyboard and not closed from it (A11Y-08, DS-06). */
button.kq-modal-x { border: 0; font: inherit; color: var(--muted); }

.kq-newkey-form { display: grid; gap: var(--s4); }
.kq-newkey-form .form-actions { margin-top: var(--s1); }

/* ══ the key that is shown once ═════════════════════════════════════════════════════════════ */

.kq-secret-card { border-color: var(--kq-btn-bg); }
.kq-secret-card h2 { margin: 0 0 var(--s2); font-size: var(--t-lg); }
.kq-secret-card > .card__body > p { color: var(--ink-2); margin: 0 0 var(--s4); }

/* The field takes the row and the button keeps its width, so «העתקה» does not move when it
   becomes «הועתק»; below about 390px the button drops to its own line instead of squeezing the
   key into six characters. */
.kq-secret-card__row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.kq-secret-card__row .control { flex: 1 1 260px; min-width: 0; }
.kq-secret-card__row .btn { flex: none; min-width: 124px; }
.kq-secret-card__skill { margin-top: var(--s4); }
.kq-secret-card .hint { margin: var(--s1) 0 0; }
.kq-secret-card .form-actions { margin-top: var(--s5); }

/* ══ the keys table ═════════════════════════════════════════════════════════════════════════ */

.kq-keys td .badge { margin-inline-start: var(--s2); }
/* Why a key is disabled, under its name. No row opacity anywhere here: the dates in a disabled
   key's row are what answer «which integration stopped» (TADM-15). */
.kq-keys .kq-keyrow__why {
    display: block; margin-top: 2px; font-size: var(--t-sm); color: var(--muted);
}

.kq-api-usage { margin: 0 0 var(--s4); }
.kq-api-block { margin-bottom: var(--s4); }

/* ══ the key editor ═════════════════════════════════════════════════════════════════════════ */

.kq-keyedit { display: grid; gap: var(--s4); }
.kq-keyedit .control { max-width: 420px; }
.kq-keyedit .form-actions { margin-top: var(--s1); }

/* ══ event destinations ═════════════════════════════════════════════════════════════════════ */

.kq-wh-event { display: inline-block; }
.kq-wh-event + .kq-wh-event { margin-inline-start: var(--s2); }

/* ══ the API reference ══════════════════════════════════════════════════════════════════════ */

.kq-docs {
    display: grid;
    /* minmax(0, 1fr) AND NOT 1fr — this page measured 230px wider than a 390px phone, and this
       one token was the whole reason. A bare `1fr` track takes its automatic minimum from its
       content, and the content here is code blocks with white-space: pre whose longest curl
       example is 591px; overflow-x: auto stops the block spilling but does not change the width
       the grid measures. minmax(0, …) releases that floor. */
    grid-template-columns: 210px minmax(0, 1fr);
    gap: var(--s6); align-items: start;
}
.kq-docs__toc {
    position: sticky; top: calc(var(--kq-topbar-h) + var(--s4));
    display: flex; flex-direction: column; gap: 1px; font-size: var(--t-md);
}
.kq-docs__toc a { padding: var(--s2) var(--s3); border-radius: var(--r); color: var(--ink-2); }
@media (hover: hover) {
    .kq-docs__toc a:hover { background: var(--n-2); color: var(--ink); }
}
/* No letter-spacing: the stylesheet's own rule, broken here for a year (A11Y-23). */
.kq-docs__grp {
    font-size: var(--t-xs); color: var(--muted); padding: var(--s3) var(--s3) var(--s1);
}

.kq-docs section { scroll-margin-top: calc(var(--kq-topbar-h) + var(--s5)); margin-bottom: var(--s7); }
.kq-docs h2 { font-size: var(--t-xl); margin: 0 0 var(--s2); }
.kq-docs h3 { font-size: var(--t-lg); margin: var(--s5) 0 var(--s2); }
.kq-docs p { color: var(--ink-2); max-width: 72ch; margin: 0 0 var(--s3); }

/* THE ONE MONOSPACE FACE IN THE PRODUCT, declared with .tt-pre so there is a single stack for
   both code surfaces — a sample the reader copies has to keep its own alignment, and the rest of
   the page stays in Assistant, digits included. */
.tt-pre,
.kq-docs .code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* white-space: pre is not optional — a div collapses newlines, so every curl example and every
   JSON body renders as one unreadable line. */
.kq-docs .code {
    background: var(--code-bg); color: var(--code-ink); border-radius: var(--r);
    padding: var(--s4); overflow-x: auto; white-space: pre;
    font-size: var(--t-sm); line-height: 1.65;
    direction: ltr; text-align: start; margin: 0 0 var(--s4);
}
.kq-docs .code .k { color: var(--code-key); }
.kq-docs .code .s { color: var(--code-str); }
.kq-docs .code .c { color: var(--code-comment); }

.kq-ep {
    display: grid; grid-template-columns: 74px minmax(0, 1fr); gap: var(--s3);
    align-items: start; padding: var(--s3) 0; border-bottom: 1px solid var(--line-2);
}
.kq-ep:last-child { border-bottom: 0; }
.kq-ep__m {
    font-size: var(--t-xs); font-weight: 700; text-align: center; padding: 3px 0;
    border-radius: var(--r);
}
/* The method's colour is the status palette, not a second one: reading is information, writing
   is a change, replacing is a caution, removing is a loss. */
.kq-ep__m.m-GET { background: var(--info-bg); color: var(--info-ink); }
.kq-ep__m.m-POST { background: var(--ok-bg); color: var(--ok-ink); }
.kq-ep__m.m-PUT { background: var(--warn-bg); color: var(--warn-ink); }
.kq-ep__m.m-DELETE { background: var(--danger-bg); color: var(--danger-ink); }
.kq-ep__p { direction: ltr; text-align: start; font-size: var(--t-md); color: var(--ink); font-weight: 600; }
.kq-ep__w { color: var(--ink-2); font-size: var(--t-sm); margin-top: 2px; }
.kq-ep__s { direction: ltr; text-align: start; font-size: var(--t-xs); color: var(--muted); margin-top: 3px; }

.kq-params { width: 100%; border-collapse: collapse; margin-bottom: var(--s4); }
.kq-params th {
    text-align: start; font-size: var(--t-xs); color: var(--muted); font-weight: 600;
    padding: 0 var(--s3) var(--s2); border-bottom: 1px solid var(--line);
}
.kq-params td {
    padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line-2);
    font-size: var(--t-sm); color: var(--ink-2); vertical-align: top;
}
.kq-params td:first-child { color: var(--ink); white-space: nowrap; }

/* The 900px block comes AFTER the base rules on purpose. In the page <style> it came first, so
   every property it set on the contents list — position, direction, gap — was overridden by the
   base rules below it at the same specificity, and the phone layout it was written for never
   applied. */
@media (max-width: 900px) {
    .kq-docs { grid-template-columns: minmax(0, 1fr); gap: var(--s4); }
    /* The contents list is fifteen links. Stacked, it becomes a full screen of navigation
       standing between the reader and the first line of documentation. Laid on its side it is one
       scrollable strip that reads as a tab bar — the group captions go, because on one line they
       separate nothing. */
    .kq-docs__toc {
        position: static; flex-direction: row; flex-wrap: nowrap;
        overflow-x: auto; overscroll-behavior-x: contain;
        gap: var(--s1); padding-bottom: var(--s2); margin-bottom: var(--s3);
        border-bottom: 1px solid var(--line);
    }
    .kq-docs__toc a { white-space: nowrap; }
    .kq-docs__grp { display: none; }
}

/* The parameter tables have no .table-wrap to scroll inside, and the nowrap field-name column is
   what sets their floor: measured 322px against a 305px column on a 320px screen. It fits from
   360px up, so the nowrap stays everywhere it can — a name like scheduledAtUtc reads worse broken
   in two. Below that, breaking the name beats scrolling the page. */
@media (max-width: 360px) {
    .kq-params td:first-child { white-space: normal; overflow-wrap: anywhere; }
}

@media (pointer: coarse) {
    /* The close control was a 2px-padded label, about 22px tall, in the corner of a dialog. */
    .kq-modal-x {
        display: inline-flex; align-items: center; justify-content: center;
        min-width: var(--tap); min-height: var(--tap); padding: 0;
    }
}
/* ==== END SECTION apikeys ==== */
/* ==== SECTION blocked ==== */
/* OBSOLETE: none — the three blacklist screens carried inline styles only, and every class they
   dropped (.empty, .alert, .alert-danger) is still used by screens later waves have not reached. */

/* BLACKLIST (wave 3). /Admin/BlockedNumbers — the list, the paste form and the one-entry editor.
   Almost everything here is already a component: the card, the table, the notices, the fields and
   the pager. What is left is the paste form's full-width rows, the result summary that a partly
   accepted paste comes back with, and the two-line «נוסף» cell. */

/* A row of the two-column form grid that owns the whole width: the paste box, the notices and the
   action row. Written once instead of the `style="grid-column: 1 / -1"` each of them carried. */
.bn-full { grid-column: 1 / -1; }

/* The grid's own gap spaces the rows, so a notice inside the form must not add its page margin.
   These notices hold a heading line and a list rather than the partial's single paragraph, so the
   paragraph inside them needs the reset the partial gets from .kq-notice__text itself — without it
   the browser's own paragraph margin pushes the first line away from the notice's icon. */
.bn-form .kq-notice { margin-block-end: 0; }
.bn-form .kq-notice__text > p { margin: 0; }

/* Room for a real paste without a scrollbar on the first ten numbers, and still resizable. */
.bn-numbers { min-height: 9rem; resize: vertical; }

/* Hint and «העתקת השורות שלא נקלטו» on one line while there is room, the button at the far edge;
   they wrap onto two lines on a narrow column without either of them shrinking. */
.bn-boxfoot {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s2);
}
.bn-boxfoot > .hint { flex: 1 1 20rem; min-width: 0; }
.bn-boxfoot > .btn { flex: none; margin-block-start: var(--s1); }

/* THE RESULT OF A PASTE. One line of three figures, then one line per reason — inside the notice,
   so the level carries how bad it is: warn while something landed, danger when nothing did. */
.bn-summary__line { font-variant-numeric: tabular-nums slashed-zero; }
.bn-reasons {
    list-style: none;
    margin: var(--s1) 0 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--s1) var(--s5);
    font-size: var(--t-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* The numbers an agent still transfers to: one per line, so a number and its agents stay together. */
.bn-conflicts {
    list-style: none;
    margin: var(--s1) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    overflow-wrap: anywhere;
}
.bn-conflicts .fig { font-variant-numeric: tabular-nums slashed-zero; }

/* The pager sits inside the card, under the table, and needs the rule the rows have between them. */
.bn-card > .card__body > .kq-pager { border-block-start: 1px solid var(--line); }

/* «נוסף» is a date and, under it, who added it — never an address (TADM-25). Two lines, so the
   name does not read as part of the timestamp. */
.bn-added { line-height: 1.5; }
.bn-by { display: block; }

/* The blocked-call count and «לאחרונה …» share a cell; the date drops under the pill when the
   column is narrow instead of pushing the row wider. */
.bn-table .badge + .muted { margin-inline-start: var(--s2); }

@media (max-width: 820px) {
    /* As cards, every cell is already «label · value»; the second line of «נוסף» keeps its own row
       and the pager loses the rule it shared with the table's last row. */
    .bn-added { line-height: 1.6; }
    .bn-table.is-cardable .badge + .muted { margin-inline-start: 0; display: block; }
    .bn-card:has(> .card__body > .table-wrap > table.is-cardable) > .card__body > .kq-pager { border-block-start: 0; }
}
/* ==== END SECTION blocked ==== */
/* ==== SECTION vault ==== */
/* OBSOLETE: none — the selectors these two screens stopped writing (.empty, .form-row,
   .alert.alert-info, .alert.alert-danger, .svc-icon[title]) are all still rendered by other
   screens. .kv-static, .table-wrap, .tbl, .cell-user, .row-actions, .tabs and .form-actions come
   from the component layer unchanged. */

/* CREDENTIAL VAULT (ADM-VAULT, wave 3) — /Admin/Credentials and /Admin/Credentials/Edit.
   Two small screens: a two-tab table of what exists and where it is used, and one short form.
   Everything below is the spacing the inline styles used to carry, plus the one read-only value
   row. The components themselves — .card, .tbl, .badge, .kq-notice, .field, .control — are the
   shared ones. No motion, no hover rules. */

/* One sentence between the page header and the tabs: where a new credential comes from. Shown
   only to whoever can open a tool editor, so it never points at a locked door. */
.vault-lead { margin: 0 0 var(--s4); color: var(--muted); font-size: var(--t-md); }

/* The stored one-line summary — a header or user name the tool sends. Technical, so it is small
   and quiet, and it wraps instead of stretching the table. */
.vault-preview { color: var(--muted); font-size: var(--t-sm); overflow-wrap: anywhere; }

/* Where the delete button would be for a credential no tool uses: the count as a link to the
   credential, whose page lists the tools by name. Sized like the small buttons beside it so the
   row does not change height. */
.vault-used {
    display: inline-flex; align-items: center; padding-block: var(--s1);
    color: var(--muted); font-size: var(--t-sm); text-decoration: underline;
    text-underline-offset: 2px;
}

/* ── The edit form ─────────────────────────────────────────────────────────────────────────── */
.vault-form { display: grid; gap: var(--s5); min-width: 0; }
.vault-form > .form-grid { margin: 0; }
.vault-form .form-grid > .field { min-width: 0; }
/* A type's own group holds a two-column row and a full-width field under it. Those rows used to be
   .form-row, and its bottom margin left with it, so «ערך המפתח» sat flush against the row above —
   the group carries the rhythm itself, in the same step the grid uses between its own rows. */
.vault-form [data-cred-fields] { display: grid; gap: var(--s4); min-width: 0; }
/* one attribute more than the rule above, so a group that is hidden stays hidden */
.vault-form [data-cred-fields][hidden] { display: none; }
/* the form is a grid with a gap of its own; the shared top margin would double the space */
.vault-form > .form-actions { margin-top: 0; }
/* the type is a value, not a control: its line is styled like a label without being one */
.vault-form p.lbl {
    display: block; margin: 0 0 var(--s2);
    font-weight: 600; font-size: var(--t-md); color: var(--ink-2);
}
/* the placement select holds two short words */
.vault-narrow { max-width: 240px; }
/* «הערך שהקלדתם לא נשמר» under an empty secret field after a refused save */
.vault-lost { display: flex; align-items: flex-start; gap: var(--s1); color: var(--warn-ink); font-weight: 600; }
.vault-lost > svg { flex: none; width: 16px; height: 16px; margin-top: 2px; }

/* Where this credential is in use — one notice, the tools named and linked. The notice's own
   bottom margin would add to the form's gap. The links take the notice's own ink: the action teal
   on the info wash is the one place the two tints meet, and the service strip already reads that
   way. */
.vault-usages { margin: 0; }
.vault-usages a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }

/* Last in the section on purpose: a hidden group must stay hidden even when a block inside it
   carries a display rule of its own. */
.vault-form [hidden] { display: none; }

@media (pointer: coarse) {
    /* a finger target beside the small buttons in the same row */
    .vault-used { min-height: var(--tap); }
}
/* ==== END SECTION vault ==== */
/* ==== SECTION numbers ==== */
/* OBSOLETE: none — the selectors these screens stopped using (.empty, .alert-danger, .mono,
   .table-wrap on its own) are all still rendered by other screens. */

/* NUMBERS AND SIP SERVERS (PLAT-NUMBERS, wave 3) — /Platform/VoiceNumbers, /Platform/SipTrunks.
   Layout only: the badge, the notice, the row form, the disclosure and the fields all come from the
   component layer. Two things here are not decoration:
   - the row a save returns to is highlighted and kept clear of the top bar, because every handler
     redirects to #n-{id} and the operator has to find that row again;
   - the second line under a badge is the freshness of the answer, so it is tabular and quiet — the
     eye reads the word first and the time only when it doubts it. */

/* ── The pool list ──────────────────────────────────────────────────────────────────────────── */
/* One control in the bar, so the search takes the row instead of being pushed to the far end. */
.vn-filter .search-box { margin-inline-start: 0; flex: 1 1 22rem; max-width: 28rem; min-width: 0; }

.vn-table { min-width: 0; }
.vn-table > tbody > tr { scroll-margin-top: calc(var(--kq-topbar-h) + var(--s5)); }
.vn-table > tbody > tr:target > td { background: var(--kq-action-soft); }

.vn-num { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
.vn-num__body { min-width: 0; }
.vn-label { display: block; margin-top: var(--s1); font-size: var(--t-sm); color: var(--muted); overflow-wrap: anywhere; }
.vn-none { color: var(--muted); }
/* a switched-off server beside its name: the badge in the registration column says what it costs */
.vn-off {
    margin-inline-start: var(--s2); font-size: var(--t-sm); font-weight: 600;
    color: var(--warn-ink); white-space: nowrap;
}
.vn-sub {
    display: block; margin-top: var(--s1); font-size: var(--t-sm); color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.vn-agents { margin: var(--s2) 0 0; font-size: var(--t-sm); color: var(--muted); overflow-wrap: anywhere; }
.vn-table .kv-static { padding-block: 0; font-size: var(--t-md); }
.vn-table .hint { margin-top: var(--s1); }
.vn-table .row-form { min-width: 0; }

/* The carrier's own sentence, folded away: it is detail for the one person who can act on it, and
   it was in a title attribute that no phone and no keyboard could open. */
.vn-why, .tr-why { margin-top: var(--s2); }
.vn-why > summary, .tr-why > summary { font-size: var(--t-sm); font-weight: 600; color: var(--muted); }
.vn-why__text, .tr-why__text {
    margin: var(--s2) 0 0; font-size: var(--t-sm); color: var(--ink-2); overflow-wrap: anywhere;
}

/* Deleting sits apart from the actions that only look at the row. */
.row-actions__sep { margin-inline-start: var(--s3); }

/* The save that was held back until the consequence was answered — the path with no dialog. */
.vn-pending .kq-notice__text > p { margin: 0; }
.vn-pending__act {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s3);
}

/* ── The number form, and the server form ───────────────────────────────────────────────────── */
.vn-form, .tr-form { max-width: 620px; }
/* One rhythm down the form; the older .form-row bottom margin does not add to it. */
.vn-form form, .tr-form form { display: grid; gap: var(--s5); min-width: 0; }
.vn-form .form-row, .tr-form .form-row { margin: 0; }
.vn-form .form-grid > .field, .tr-form .form-grid > .field { min-width: 0; }
.tr-narrow { max-width: 12rem; }

/* «סיסמה לרישום» carries whether one is stored, on the line the reader is looking at when they
   decide whether to type anything into the empty box. */
.vn-pwlbl { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

/* ── SIP servers ────────────────────────────────────────────────────────────────────────────── */
/* What this one screen reaches, above the fields that reach it. */
.tr-scope { margin: 0 0 var(--s5); font-size: var(--t-md); font-weight: 600; }
.tr-table .cell-user .n { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; min-width: 0; }
.tr-sub {
    display: block; margin-top: var(--s1); font-size: var(--t-sm); color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.tr-nodelete { display: inline-flex; align-items: center; gap: var(--s2); flex-wrap: wrap; min-width: 0; }
.tr-nodelete .hint { margin: 0; }

/* The held save names the customers before it runs. It lives inside the form, so its button posts
   the edited values with the answer. */
.tr-ask .kq-notice__text > p { margin: 0; }
.tr-ask__list {
    margin: var(--s2) 0 0; padding-inline-start: var(--s5);
    font-size: var(--t-md); color: var(--ink-2);
}
.tr-ask__list li { overflow-wrap: anywhere; }
.tr-ask__act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s3); }

/* ── Phones ─────────────────────────────────────────────────────────────────────────────────── */
@media (max-width: 820px) {
    /* The row form already stacks; the agent line under it keeps its own line rather than sharing
       the label column of the card cell. */
    .vn-agents, .vn-table .hint { grid-column: 1 / -1; }
    .vn-filter .search-box { flex: 1 1 100%; max-width: none; }
    .tr-nodelete { flex: 1 1 100%; }
    .tr-nodelete .btn { flex: none; }
}
/* ==== END SECTION numbers ==== */
/* ==== SECTION reseller-numbers ==== */
/* OBSOLETE on /Reseller/VoiceNumbers only — both are still used by other screens, so neither can be
   deleted on this package's word alone: .empty (the page now renders _Empty / .kq-empty),
   .mono (the number is written with .fig through Fmt.Phone). */

/* RESELLER NUMBERS (wave 3). /Reseller/VoiceNumbers — the reseller's allocated block: the number and
   its description, the agent answering on it, the registration badge, the customer picker and the
   registration check.

   Almost everything here is already a component: .tbl for the table, .row-form for the picker and its
   save button (including the phone rule that stacks them, which is why this file adds no 820px block
   of its own), .tabs for the views, .search-box for the filter, .badge for the state. What is left is
   the first column — a flag beside a number with the description under it — and the agent lines. */

.rn-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s3);
    margin-block-end: var(--s4);
}

/* The filter sits at the end edge of the row, as it does above every other list. */
.rn-search {
    margin-inline-start: auto;
    flex: 0 1 320px;
    min-width: 220px;
}
@media (max-width: 820px) {
    .rn-search { margin-inline-start: 0; flex: 1 1 100%; }
}

/* The number cell keeps .cell-num for its tabular digits, but its content is a flag beside a stacked
   number and description — so the end-alignment and the single line of the figure column are undone
   here rather than by weakening .cell-num for the columns that are only figures. */
.rn-table .cell-num { text-align: start; white-space: normal; }

.rn-num {
    display: flex;
    align-items: center;
    gap: var(--s2);
    min-width: 0;
}
.rn-num > div { min-width: 0; }

/* A description Kinetiq wrote on the line. It is free text and can be long: it wraps instead of
   widening the column, and a word with no break in it breaks rather than overflowing. */
.rn-note {
    margin-block-start: 2px;
    overflow-wrap: anywhere;
}

.rn-agent {
    min-width: 0;
    overflow-wrap: anywhere;
}
.rn-agent + .rn-agent { margin-block-start: 2px; }
/* ==== END SECTION reseller-numbers ==== */
/* ==== SECTION platform-engine ==== */
/* PLAT-ENGINE — /Platform/Settings: three independent cards, a held-back model shown read-only,
   and one capability that is not ordinary at all.
   OBSOLETE: none — this screen had no rules of its own; every inline style it carried is replaced
   by a class here. */

/* One readable column for a settings form. A card that runs the full width puts a 40-character
   field beside 900px of empty glass. */
.pe-card { max-inline-size: 760px; }
.pe-card + .pe-card { margin-block-start: var(--s5); }

/* The stamp under the page header: whose line it is, and whether it was an edit at all. */
.pe-stamp {
    margin: calc(var(--s2) * -1) 0 var(--s4);
    font-size: var(--t-sm); color: var(--muted);
}

/* The one sentence a card opens with — what a blank field means, when a change applies. */
.pe-lead { margin: 0 0 var(--s4); font-size: var(--t-md); color: var(--muted); }

/* A credential's name and its «מוגדר» pill on one line, wrapping together on a phone. */
.pe-card .form-row > label.lbl,
.pe-held > .lbl {
    display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
}
/* p.lbl, not label.lbl: the model is shown, not edited, so there is no field to label. */
.pe-held > .lbl {
    margin: 0 0 var(--s2);
    font-size: var(--t-md); font-weight: 600; color: var(--ink-2);
}
.pe-held .kv-static { font-size: var(--t-base); }

/* THE HAZARD FRAME. While diagnostic capture is on, every caller on every call is recorded, so
   the row wears the warning colour from the moment the switch is flipped — before the save, which
   is the state the reader can still undo. The transparent border and the pulled-in margin are what
   keep the layout from jumping when it turns on; there is no transition, because the control the
   reader just pressed must change at once. */
.feature.is-hazard {
    border: 1px solid transparent; border-bottom-color: var(--line-2);
    border-radius: var(--r);
    padding-inline: var(--s3); margin-inline: calc(var(--s3) * -1);
}
.feature.is-hazard:has(input[type="checkbox"]:checked) {
    border-color: var(--warn-edge);
    background: var(--warn-bg);
}
/* What is being recorded right now, in the warning ink, aligned under the switch's label like the
   row's own description. */
.pe-hazard-note {
    display: flex; align-items: flex-start; gap: var(--s2);
    margin: var(--s2) 0 0; padding-inline-start: var(--s5);
    font-size: var(--t-md); font-weight: 600; color: var(--warn-ink);
}
.pe-hazard-note > svg { flex: none; width: 18px; height: 18px; margin-top: 2px; }
.pe-hazard-note b { font-weight: 800; }

/* The credential row that carries a «הסרה» switch keeps its fields aligned with the rows above it
   while the frame it grows is pulled out into the card's padding. */
.pe-card .form-row.kq-clearable { margin-inline: calc(var(--s4) * -1); }

@media (max-width: 820px) {
    .pe-card { max-inline-size: none; }
    /* the pulled-out frames have no room to hang into on a phone */
    .feature.is-hazard { margin-inline: 0; }
    .pe-card .form-row.kq-clearable { margin-inline: 0; }
    .pe-hazard-note { padding-inline-start: 0; }
}
/* ==== END SECTION platform-engine ==== */
/* ==== SECTION platform-mail-users ==== */
/* OBSOLETE: .pw-meter, .pw-meter > span and the .pw-meter line in the forced-colours block — with
   this package no view under Pages renders data-pw-meter-for any more, so all three can go. */

/* MAIL SERVER AND PLATFORM OWNERS (PLAT-MAIL-USERS, wave 3).
   Two screens the owner alone sees: the one outgoing-mail server, and the people who administer
   the platform. Everything they wear — cards, fields, notices, badges, the eye toggle, the rules
   checklist — is the component layer; this section only fits the pieces together. */

/* ── Mail server ────────────────────────────────────────────────────────────────────────────── */

/* Two cards side by side, each as tall as its own content: the sender card is much shorter than
   the connection card and should not be stretched to match it. */
.ms-cards { align-items: start; }

/* THE ASTERISK IS CONDITIONAL. While the server is switched off, nothing here is required — the
   service accepts an empty host and an empty sender, and a required mark would be a lie the reader
   cannot satisfy. The type selector matters: the checkbox posts a hidden `false` field under the
   same name, and a hidden input is never :checked, so a nameonly selector would always match. */
.ms-form:has(input[type="checkbox"][name="Input.Enabled"]:not(:checked)) .lbl.req[data-req-when-on]::after {
    content: none;
}

/* The master switch and its sentence read as one line plus its consequence. */
.form-row.ms-switch { display: flex; align-items: center; gap: var(--s3); margin-bottom: 0; }
.ms-switch > label { margin: 0; font-size: var(--t-md); font-weight: 600; color: var(--ink-2); cursor: pointer; }
.ms-switch__hint { margin-block-end: var(--s4); }

/* «מחיקת הסיסמה השמורה»: the row's own frame turns red from .kq-clearable, the label only needs
   to sit level with the switch. */
.kq-clearable .kq-clear > label { margin: 0; color: var(--ink-2); cursor: pointer; }

/* The last attempt, with the server's own reason under it — operational detail, on the one screen
   where it is the reader's next action. */
.ms-last__why { display: block; margin-block-start: var(--s1); font-size: var(--t-sm); overflow-wrap: anywhere; }

/* ── The test send ──────────────────────────────────────────────────────────────────────────── */

.ms-test { margin-block-start: var(--s4); }

/* The address and the button on one line, bottom-aligned, the field wide enough for an address.
   .row-form caps a bare control at 14rem; here the control fills the field block instead. */
.ms-testform { align-items: flex-end; gap: var(--s3); }
.ms-testfield { flex: 1 1 18rem; max-inline-size: 22rem; }
.ms-testform .control { inline-size: 100%; max-inline-size: none; flex: none; }
.ms-testoff { margin: 0; }
/* The button stays operable — pressing it answers «שרת הדואר אינו מוגדר או אינו פעיל» in the line
   below — and it is dimmed by the component rule for an aria-disabled button, which deliberately
   spares a BUSY one. Repeating that rule here without its :not([aria-busy="true"]) would dim the
   button while the test is actually running, so nothing is added. */

/* The answer next to the button that asked for it — never a page the reader has to go back to. */
.ms-result {
    display: flex; align-items: flex-start; gap: var(--s2);
    margin: var(--s3) 0 0;
    font-size: var(--t-md); font-weight: 600; overflow-wrap: anywhere;
}
.ms-result > svg { flex: none; width: 18px; height: 18px; margin-block-start: 1px; }
.ms-result.is-ok { color: var(--ok-ink); }
.ms-result.is-bad { color: var(--danger-ink); }
/* It takes focus when it appears, so it must not draw a ring nobody asked for — while a keyboard
   user who reaches it still gets one. */
.ms-result:focus:not(:focus-visible) { outline: none; }

@media (max-width: 820px) {
    .ms-testfield { flex: 1 1 100%; max-inline-size: none; }
}

/* ── Platform owners ────────────────────────────────────────────────────────────────────────── */

/* An account can be active and locked out at the same time, so the cell holds a stack of facts
   rather than one badge. */
.pu-state { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); }

/* The three single-purpose forms are as wide as their fields need, not as wide as the screen. */
.pu-card { max-inline-size: 640px; }
.pu-card--narrow { max-inline-size: 560px; }

.form-row.pu-switch { display: flex; align-items: center; gap: var(--s3); }
.pu-switch > label { margin: 0; font-size: var(--t-md); font-weight: 600; color: var(--ink-2); cursor: pointer; }
/* ==== END SECTION platform-mail-users ==== */
/* ==== SECTION platform-dashboard ==== */
/* OBSOLETE: .au-t, .au-t th, .au-t td, .au-t thead th, .au-t td.n, .au-t tbody tr:hover, .au-tiles,
   .au-tile, .au-tile b, .au-tile span, .au-tile.cost b, .au-scroll, .au-head, .au-nav, .au-month
   — the AI-usage page's own <style> block, deleted with it; no rule for any of them exists in this
   stylesheet, and no view asks for them any more. */

/* ══ PLATFORM DASHBOARD, SERVER HEALTH AND AI USAGE ═══════════════════════════════════════════
   Three screens one person reads in one sitting: which customer needs a conversation, whether the
   machine can carry them, and what the calls cost. They share the card idiom (.tn-*, .card) that
   the rest of the product uses; everything below is what those three need on top of it. */

/* ── The customer capacity card (Pages/Shared/_TenantCapacityCard.cshtml) ────────────────────── */

/* Who the customer belongs to. A customer of a reseller is a conversation with the RESELLER, and
   the card used to give no sign of which kind it was. */
.tn-owner {
    display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
    margin: 0; font-size: var(--t-sm); color: var(--muted); min-width: 0;
}
.tn-owner svg { width: 16px; height: 16px; flex: none; color: var(--n-6); }
.tn-owner a { font-weight: 600; overflow-wrap: anywhere; }

/* The recommendation is a sentence; the link under it is where that sentence is acted on. */
.tn-act { margin: 0; font-size: var(--t-sm); }
.tn-act a { font-weight: 600; }
/* The card's lines are paragraphs now, and a paragraph brings a margin the flex gap already gives. */
p.tn-reco { margin: 0; }

/* THE BAR IS AN SVG RECT, NOT A SPAN WITH A WIDTH: the width would be an inline style, and a view
   written for this redesign may not carry one (voice-tests/inline-style-test.sh). The rect's width
   is the percentage itself, in a 0..100 viewBox, so nothing is rounded into a class. Fills mirror
   the .tn-bar span rules one for one — a healthy fill stays neutral. */
.tn-bar__svg { display: block; width: 100%; height: 100%; }
.tn-bar rect.b-ok { fill: var(--n-7); }
.tn-bar rect.b-warn, .tn-bar rect.b-yellow { fill: var(--warn-ink); }
.tn-bar rect.b-danger, .tn-bar rect.b-red { fill: var(--danger-ink); }
@media (forced-colors: active) {
    .tn-bar rect { forced-color-adjust: none; fill: Highlight; }
}

/* ── «דורש טיפול» on the platform board ─────────────────────────────────────────────────────── */
/* The rows themselves are the shared .kq-attn component; this only places the card. */
.pd-attn { margin-bottom: var(--s4); }

/* ── Server health ──────────────────────────────────────────────────────────────────────────── */

/* «הנתונים נכונים ל־…». It sits OUTSIDE the dimmed cards, because the sentence that explains the
   dimming must not be dimmed with them. */
.sh-asof {
    display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
    margin: 0 0 var(--s4); font-size: var(--t-md); font-weight: 600; color: var(--warn-ink);
}
.sh-asof svg { width: 18px; height: 18px; flex: none; }

/* The day chart's figures live in the table under it now, not in a title attribute no phone can
   show. The disclosure keeps them one tap away without a second wall of numbers. */
.fo-table-more { margin-bottom: var(--s4); }
.fo-table-more .table-wrap { margin-top: var(--s3); }
.fo-table-more table.tbl { font-variant-numeric: tabular-nums slashed-zero; }

/* ── AI usage ───────────────────────────────────────────────────────────────────────────────── */

/* Nine figures read as three rows of three; the shared four-column grid would leave one orphan. */
/* ONE ROW (owner 2026-09-24: «שיהיו ביותר בקטן ויהיו בשורה אחת ולא בכמה»). auto-fit
   rather than a fixed six: the trend figure is withheld until there is a fortnight to compare, so
   the strip is five or six depending on the window, and a fixed count would leave a hole on the
   day it is five. The 148px floor is what keeps it ONE row on a desktop and lets it wrap on a
   phone — six figures across 360px would be six columns of two characters each. */
.au-figs { grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: var(--s3); }
/* Smaller with it: this strip is now read across rather than down, and the dashboard's display
   size makes six of them shout. The label keeps its size — it is the part that says WHICH. */
.au-figs .kq-fig { padding: var(--s3) var(--s4); gap: var(--s1); }
.au-figs .kq-fig__value { font-size: var(--t-xl); }
.au-figs .kq-fig .kq-fig__label { min-block-size: 0; font-size: var(--t-sm); }
.au-block { margin-bottom: var(--s5); }

/* Money and token columns never wrap on a wide screen — a rate broken across two lines is not a
   rate. On a phone the table is a stack of labelled lines, where nowrap would push the page
   sideways, so the rule applies from the width the card layout stops at. */
@media (min-width: 821px) {
    .au-tbl th, .au-tbl td { white-space: nowrap; }
    .au-tbl th:first-child, .au-tbl td:first-child { white-space: normal; min-width: 0; }
}
.au-tbl td:first-child { overflow-wrap: anywhere; }
.au-tbl .badge { margin-inline-start: var(--s2); }

/* The second line of a name cell: the customer an agent belongs to, the reseller a customer
   belongs to. */
.au-owner { display: block; font-size: var(--t-sm); color: var(--muted); overflow-wrap: anywhere; }

.au-note { margin: 0 0 var(--s3); font-size: var(--t-sm); line-height: 1.6; color: var(--muted); max-width: 74ch; }
.au-note:last-child { margin-bottom: 0; }


@media (max-width: 720px) {
}
/* ==== END SECTION platform-dashboard ==== */
/* ==== SECTION distributors ==== */
/* OBSOLETE: .brand-logo-box, .brand-logo-box img, .brand-logo-box .brand-logo-none — the dark box
   that showed the current logo on a colour it might never sit on is replaced by .brand-preview.
   Also .dash-figs / .dash-fig on this screen (now .kq-figs / .kq-fig), .alert.alert-danger and
   .alert.alert-info here (now .kq-notice), .empty here (now _Empty), .feature.split here, and
   .tag-copy on the reseller screens. Whether those still have other users is for UI-FINAL. */

/* RESELLERS — /Platform/Distributors list, details and the shared branding form
   (PLAT-DISTRIBUTORS, wave 3). The components come from the kit layer: cards, tables, figure
   tiles, tabs, badges, notices, the confirm dialog, .form-frame/.is-consequential. This section
   only adds what those cannot know: the live brand miniature, the bank's top-up row and ledger
   pager, and the balance column on the list. Tokens only, logical properties only. */

/* ── The live brand preview ────────────────────────────────────────────────────────────────────
   UI-CSS owns the frame (.brand-preview: 260×120, grid 88px + 1fr). Everything inside it is
   painted from --bp-* custom properties set on that element ALONE — by the server when the page
   is rendered, by kq-brand-form.js while a colour is being tried — so the screen around it never
   changes colour. Every fallback below is a kit token, never a literal: a preview that loses its
   properties still renders as Kinetiq's own theme rather than as nothing. */
.bp-colours {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
    gap: var(--s4) var(--s5);
    align-items: start;
}
/* the preview sits beside the two pickers on a wide screen and under them on a narrow one */
.bp-field { min-width: 0; }

.bp-rail {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    padding: var(--s3);
    min-width: 0;
    /* THE MINIATURE IS A MINIATURE OF THE FRAME. It used to paint the derived rail colour at full
       strength with dark text on it, i.e. the rail as it was before «מסגרת כהה» — so the one
       control an operator has while choosing a reseller's colour showed them the opposite of what
       the product would paint.
       AND NOW OF THE SAME FRAME. The mix used to be made here, 18 % of --bp-rail over
       --frame-base — the right material on the wrong input. The product paints the frame from
       BrandTheme.FrameTintSafe(rail), the clamp that stops a pale brand lightening it, and CSS has
       no step-until-a-floor, so a pale reseller landed the miniature on #3A4048 where the product
       paints #343C45. Web/BrandingForm.cs and wwwroot/js/kq-brand-form.js both emit the stepped
       colour as --bp-frame now — the server for the first paint, the script while the operator
       types — and the fallback is the frame this account is already painted with. */
    background: var(--bp-frame, var(--frame-bg));
}
.bp-mark {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    min-width: 0;
    block-size: 22px;
    margin-block-end: var(--s1);
}
.bp-logo {
    max-inline-size: 100%;
    max-block-size: 22px;
    object-fit: contain;
}
.bp-word {
    display: flex;
    align-items: center;
    gap: var(--s1);
    min-width: 0;
}
/* An author `display` beats the browser's own [hidden] rule whatever its specificity, because the
   author sheet wins over the user-agent one. Without this line the wordmark stays on screen beside
   the logo — server-rendered when a logo is stored, and again the moment one is picked. Every other
   component in the kit that sets `display` carries the same line for the same reason. */
.bp-word[hidden] { display: none; }
/* THE SAME SQUARE THE RAIL PAINTS. It read the ACTION colour, which was the brand colour itself
   until the action colours were derived (2026-09-19) — and then the miniature started promising a
   deep #047bb3 square for a brand whose rail shows the pale #7dd3fc one. The square is
   --kq-mark-bg wherever it is drawn; --bp-mark is that value for the colours ON THE FORM, which
   are not the ones this account is currently painted with. */
.bp-word__sq {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: 18px;
    block-size: 18px;
    border-radius: var(--radius-xs);
    background: var(--bp-mark, var(--kq-mark-bg));
    color: var(--bp-mark-on, var(--kq-mark-on));
    font-size: 11px;
    font-weight: 800;
    line-height: 1;
}
.bp-word__name {
    min-width: 0;
    /* the frame's ink, because the rail under it is the frame now — not --bp-ink, which is the
       ink derived for the OLD pale rail and measured 1.53:1 on this surface */
    color: var(--frame-ink);
    font-size: var(--t-xs);
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* the menu items: three bars, the first one active in the accent the rail really uses */
.bp-nav {
    block-size: 7px;
    border-radius: var(--r-pill);
    background: var(--frame-ink-2);
    opacity: .35;
}
.bp-nav:nth-of-type(3) { inline-size: 80%; }
.bp-nav:nth-of-type(4) { inline-size: 62%; }
.bp-nav--active {
    background: var(--bp-accent, var(--rail-ink));
    opacity: 1;
}

.bp-main {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s2);
    padding: var(--s3);
    min-width: 0;
    /* the work area beside the rail is the page ground, which is what the product paints there */
    background: var(--ground);
}
.bp-btn {
    display: inline-flex;
    align-items: center;
    padding: 3px var(--s3);
    border-radius: var(--r);
    background: linear-gradient(135deg, var(--bp-btn, var(--kq-btn-bg)) 0%, var(--bp-btn-2, var(--kq-btn-bg-hover)) 100%);
    color: var(--kq-btn-on);
    font-size: var(--t-xs);
    font-weight: 600;
    line-height: 1.6;
}
.bp-link {
    color: var(--bp-link, var(--kq-link-ink));
    font-size: var(--t-xs);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.bp-line {
    inline-size: 100%;
    block-size: 6px;
    border-radius: var(--r-pill);
    background: var(--n-3);
}
.bp-line--short { inline-size: 60%; }

/* The sentence that says what the product does with the chosen colour — uses it, or deepens it
   for the things a reader acts on. It is a live region, so it must keep its box when it changes
   length. The .is-warn variant went with the derivation (2026-09-19): an unreadable action colour
   is no longer a state this screen can reach, so a warning colour here would be a warning about
   nothing. */
.bp-contrast {
    margin: var(--s2) 0 0;
    font-size: var(--t-sm);
    color: var(--muted);
}
.bp-suggest { margin-block-start: var(--s2); }
.bp-suggest[hidden] { display: none; }

/* «הסרת הלוגו» is a switch above the file field, not a line of small print beside it */
.bp-remove {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin-block-end: var(--s2);
    font-size: var(--t-md);
}

/* A swatch is CHOSEN, never lifted: no transform and no transition on either state, so the row
   does not move under the pointer and a reader who turned motion off sees the same thing
   (SHELL-28). UI-CSS removed the old lift; this section comes later in the file and pins it. */
.sw,
.sw-row input[type="radio"]:checked + .sw {
    transform: none;
    transition: none;
}

/* ── The bank card ─────────────────────────────────────────────────────────────────────────── */
.bk-figs { margin-bottom: var(--s4); }

/* A credit that no screen can take back. .form-frame draws the edge; this only gives the row its
   own breathing space and keeps the two fields from stretching across the whole card. */
.bk-add { margin-block-start: var(--s4); }
.bk-add .grid { align-items: start; }
.bk-add .form-actions { margin-block-start: var(--s3); }

/* ── The bank ledger ───────────────────────────────────────────────────────────────────────── */
/* The kind filter sits inside a .tight card body, which has no padding of its own */
.bk-filters {
    padding-inline: var(--s4);
    padding-block-start: var(--s3);
}
/* .bk-pager had a padding of its own here — 13px where every other pager in the product sits at
   18px, which is two lists of the same movements indented differently. It predates .kq-pager
   having any padding at all; the component's own is the one home for it now. The class went with
   the rule: the one element that carried it (the bank ledger's pager) is plain .kq-pager, so
   nothing in the product writes .bk-pager any more and no rule here should be added back for it. */

/* ── The reseller list ─────────────────────────────────────────────────────────────────────── */
/* The balance and its unit read as one value, with the alert badge after them */
.ds-bank {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s1) var(--s2);
    white-space: nowrap;
}
.ds-bank__unit { color: var(--muted); font-size: var(--t-sm); }
/* the sortable header is a link, and a header link must not read as body text */
.ds-sort {
    color: inherit;
    font-weight: inherit;
    text-decoration: underline;
    text-underline-offset: 3px;
}
/* THE SORTED COLUMN IS ON THE FRAME. This is (0,2,1) and the band's own
   `table.tbl > thead > tr > th a` is (0,1,5), so it wins — and --kq-link-ink measured 2.77:1 on
   the near-black band while the six names beside it sat at 9.59:1. --frame-ink is 17.30:1, and it
   is exactly what the call journal's own sorted column takes at :955. */
th[aria-sort="ascending"] .ds-sort { color: var(--frame-ink); }

/* ── The create screen ─────────────────────────────────────────────────────────────────────── */
.ds-card { margin-block-start: var(--s4); }
/* kq-field.js writes «רק אותיות באנגלית ומקפים» here for two seconds when a keystroke is dropped;
   an empty live region must keep existing, so it is hidden by having nothing in it, not by display */
.ds-allow {
    margin: var(--s1) 0 0;
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--warn-ink);
    min-block-size: 0;
}
.ds-allow:empty { margin: 0; }

/* A count beside a card's heading — the same pill the tabs use, including the step and the edge
   the tab's copy was given at :1009-1016: --neutral-bg is 1.14:1 on a card head, so this was a
   bare number floating beside the heading while the identical number on the tab strip a few rows
   down wore a pill. It is written as «משתמשי המפיץ 4» rather than «(4)», which the copy rules
   forbid. */
.card__head .count {
    font-size: var(--t-xs);
    font-weight: 600;
    color: var(--ink-2);
    background: var(--n-3);
    border: 1px solid var(--n-4);
    border-radius: var(--r-pill);
    padding: 0 var(--s2);
    line-height: 1.6;
    font-variant-numeric: tabular-nums;
}

/* ── Phones ────────────────────────────────────────────────────────────────────────────────── */
@media (max-width: 820px) {
    .bp-colours { grid-template-columns: minmax(0, 1fr); }
    /* the filter row only: it sits in a .tight card body with no padding of its own, where the
       pager brings the component's */
    .bk-filters { padding-inline: var(--s3); }
    .ds-bank { white-space: normal; }
}
/* ==== END SECTION distributors ==== */
/* ==== SECTION platform-tenants ==== */
/* OBSOLETE: none. The old screen was built entirely from shared components (.card, .feature.split,
   .kv, .dz-list, .danger-zone), and every one of them is still in use here. */

/* PLATFORM — CUSTOMER RECORD (PLAT-TENANTS, wave 3): /Platform/Tenants and its tabbed details.
   The parts themselves are components; this section only places them, and adds the three things a
   record split into tabs needs — a header line, a tab row that survives a phone, and an anchor that
   clears the sticky top bar. */

/* ── HEADER ──────────────────────────────────────────────────────────────────────────────── */

/* The status line under the h1: the badge, then the two facts that place the customer. Wraps
   whole phrases, so «נוצר 03/08/2026» never breaks across two lines. */
.pt-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2) var(--s3);
    min-width: 0;
}
.pt-head__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
    min-width: 0;
}
.pt-head__meta > span { white-space: nowrap; }
/* the «·» between two facts: --line-strong is a BOUNDARY token and read 2.69:1 on the flat
   ground, so the two facts ran together as one phrase. 4.73:1 on the faint tier. */
.pt-head__meta [aria-hidden="true"] { color: var(--n-7); }

/* A state notice that carries one link — suspended, in provisioning, failed. The link sits at the
   end of the row on a wide screen and under the sentence on a narrow one. */
.pt-note { align-items: center; }
.pt-note__act {
    margin-inline-start: auto;
    flex: none;
    font-size: var(--t-md);
    font-weight: 600;
    color: inherit;
    text-decoration: underline;
}

/* ── TABS ────────────────────────────────────────────────────────────────────────────────── */

/* Six tabs fit one line on a desktop and none at all on a phone, where they scroll sideways
   instead of stacking — a stacked tab row stops reading as a row of views. */
.pt-tabs { flex-wrap: wrap; }
.pt-tabs .count { flex: none; }
.pt-tabs__danger.active,
.pt-tabs__danger[aria-current="page"] {
    color: var(--danger-ink);
    border-bottom-color: var(--danger-edge);
}
@media (hover: hover) {
    .pt-tabs__danger:hover { color: var(--danger-ink); }
}
@media (max-width: 820px) {
    .pt-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: thin;
        -webkit-overflow-scrolling: touch;
    }
    .pt-tabs .tab {
        white-space: nowrap;
        padding-inline: var(--s3);
    }
}

/* ── OVERVIEW ────────────────────────────────────────────────────────────────────────────── */

.pt-overview .kq-fig { margin: 0; }

/* The one figure tile stands beside the activation card, so it stretches to its height rather than
   floating at the top of an empty column. */
.pt-fig {
    justify-content: center;
    gap: var(--s2);
}

/* A number's registration: the badge, then how fresh the answer is, on its own quiet line. */
.pt-numbers__sub {
    display: block;
    margin-block-start: var(--s1);
    font-size: var(--t-sm);
    color: var(--muted);
    overflow-wrap: anywhere;
}
.pt-numbers td { vertical-align: top; }
.pt-numbers .row-actions { align-items: flex-start; }

/* The danger preview on the overview: what the danger tab holds, and the way in. */
.pt-dangerprev {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s2) var(--s5);
}
.pt-dangerprev__t {
    grid-column: 1 / -1;
    margin: 0;
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-size: var(--t-lg);
    color: var(--danger-ink);
}
.pt-dangerprev__t svg { flex: none; width: 18px; height: 18px; }
.pt-dangerprev__d { margin: 0; color: var(--ink-2); font-size: var(--t-base); }
.pt-dangerprev .btn { justify-self: end; }
@media (max-width: 820px) {
    .pt-dangerprev { grid-template-columns: minmax(0, 1fr); }
    .pt-dangerprev .btn { justify-self: stretch; }
}

/* ── QUOTAS AND BILLING ──────────────────────────────────────────────────────────────────── */

/* THE PRODUCT SWITCHES — the pair on the create form and the pair on the customer card. Two
   switches, not a select: an account may hold KIVOICE and KICHAT at once, and an exclusive
   control cannot say so.
   A FLEX ROW WITH ITS OWN GAP, because two adjacent inline-flex .checkbox labels are held apart
   by one text space and nothing else: measured in the browser, 2.81px between KIVOICE's word and
   KICHAT's track, which reads as one control cut in half. s4 (13px) is the gap between them; the
   ROW gap is the smaller s2, because once the pair wraps, s4 between the two lines would read as
   two separate settings rather than one. */
.pt-products { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); }

/* The resume form is linked to from the list and from the suspended notice, so the anchor has to
   clear the sticky top bar — otherwise the browser puts the card's own heading behind it. */
#resume,
#golive { scroll-margin-block-start: calc(var(--kq-topbar-h, 61px) + var(--s5)); }

.pt-resume { margin-block-start: var(--s4); }
.pt-resume .form-row { max-width: 260px; }

/* A row whose save would cross the allocation. `.is-consequential` paints itself from the state of
   a switch inside it; this row has no switch — the server already knows the answer. */
.pt-consequential {
    border-color: var(--danger-edge);
    background: var(--danger-wash);
}

/* The change that was held back, with the button that sends it again. */
.pt-pending {
    margin-block-start: var(--s3);
    flex-wrap: wrap;
}
.pt-pending__act {
    flex-basis: 100%;
    margin-inline-start: calc(18px + var(--s3));
}

/* The reseller's total allowance beside the field that spends it. */
.pt-alloc {
    margin-block-start: var(--s2);
    font-variant-numeric: tabular-nums slashed-zero;
}

/* ── USERS, API, ENGINE ──────────────────────────────────────────────────────────────────── */

/* The count beside a card heading: the tab pill, one level quieter. */
.pt-count {
    flex: none;
    min-width: 18px;
    padding-inline: var(--s2);
    border-radius: var(--r-pill);
    background: var(--neutral-bg);
    color: var(--ink-2);
    font-size: var(--t-xs);
    font-weight: 600;
    text-align: center;
    font-variant-numeric: tabular-nums slashed-zero;
}

/* THE REQUEST LEDGER covers the screen because it is rendered at the root of its partial, outside
   every card: a card carries a backdrop filter, which makes it the containing block of anything
   fixed inside it, and the dialog used to be trapped in the card (MOB-08). Nothing here moves it —
   it only widens the close control where a finger has to find it. */

/* THE COST OF MOVING IT: the shared focus rule is `input.kq-modal-t:focus-visible + label.btn`,
   which needs the trigger to be the checkbox's very next sibling. The checkbox now sits at the
   page root and the trigger stays in the card, so the checkbox — visible only through its label —
   became a focus stop with no visible ring at all. Bound by id instead, the way the API-keys
   screen binds its own detached trigger, and on its own rule so a browser without `:has()` drops
   nothing else. */
body:has(#apilog:focus-visible) label[for="apilog"] {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
    box-shadow: var(--focus-halo);
}

@media (pointer: coarse) {
    .pt-apilog .kq-modal-x {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: var(--tap);
        min-height: var(--tap);
    }
}

.pt-engine__sel { max-width: 220px; }

/* ── THE LIST AND THE CREATE FORM ────────────────────────────────────────────────────────── */

/* «אותיות באנגלית ומקפים» while a refused character is being typed: the field's own live region,
   empty until the script writes into it, so the row does not jump when it fills. */
.pt-allow {
    margin: var(--s1) 0 0;
    min-height: 1em;
    font-size: var(--t-sm);
    color: var(--warn-ink);
}
.pt-allow:empty { min-height: 0; }

/* ── DANGER TAB ──────────────────────────────────────────────────────────────────────────── */

.pt-danger { gap: var(--s5); }
.pt-danger__t {
    margin: 0 0 var(--s3);
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-size: var(--t-lg);
    color: var(--danger-ink);
}
.pt-danger__t svg { flex: none; width: 18px; height: 18px; }
.pt-danger .kq-notice { margin-block-end: var(--s4); }
.pt-danger .form-row .control { max-width: 340px; }
.pt-danger .danger-zone { max-width: none; margin-block-start: 0; }
/* ==== END SECTION platform-tenants ==== */
/* ==== SECTION platform-search ==== */
/* OBSOLETE: none — /Platform/Search is a new page and no earlier selector served it. */

/* PLATFORM SEARCH (wave 3, IA-13). /Platform/Search: the query field, then one card per kind of
   answer — the line and what a call to it would meet right now, the calls on it, where the caller
   has called, and the customers and resellers a name matched. Everything below the field is a
   glass card of the shared kit; only the field row, the fact list, the verdict spacing and the
   in-cell entry form are particular to this screen. */

.ps-sec { margin-bottom: var(--s5); }
.ps-sec .card__head h2 svg { width: 18px; height: 18px; color: var(--muted); }

/* THE FIELD. A card of its own so the page opens on the one thing there is to do. The topbar
   search posts here too, so this one is the wide version of the same field, not a second feature. */
.ps-search {
    display: block;
    margin-bottom: var(--s5);
    padding: var(--s4) var(--s5);
}
.ps-search .lbl { display: block; margin-bottom: var(--s2); }
.ps-search__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
}
.ps-search__row .control {
    flex: 1 1 18rem;
    min-width: 0;
    max-width: 34rem;
}
.ps-search__row .btn { flex: none; }

/* How far the sweep across the customer databases got, and who is missing from it. Two short
   sentences, side by side while there is room. */
.ps-note {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s1) var(--s3);
    margin: 0 0 var(--s5);
    font-size: var(--t-sm);
    color: var(--muted);
}

/* The card head carries at most two controls: the number's own screen and the entry into the
   customer's account. */
.ps-headact {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
}

/* THE FACTS. A value can hold a link and a status word together, so each description is a row of
   its own that wraps instead of pushing the grid wider. */
.ps-facts { row-gap: var(--s3); }
.ps-facts dd {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
}

/* THE VERDICT. The shared notice, sitting under the facts it was read from. */
.ps-verdict { margin-top: var(--s4); }

/* The one line that says a refused call leaves no trace in the customer's journal. */
.ps-fine {
    margin: var(--s2) 0 0;
    font-size: var(--t-sm);
    color: var(--muted);
}

/* Numbers whose digits contain what was typed, offered when none of them is an exact line. */
.ps-similar__list {
    display: grid;
    gap: var(--s2);
    margin: 0;
    padding: 0;
    list-style: none;
}
.ps-similar__list li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s2);
}
.ps-similar__who {
    font-size: var(--t-sm);
    color: var(--muted);
}

/* THE CALL ROWS. The direction rides in front of the time as an icon with its word for a screen
   reader, so the table keeps one column for «when» instead of two. */
.ps-calls .ps-dir {
    display: inline-flex;
    margin-inline-end: var(--s2);
    color: var(--n-7);
    vertical-align: middle;
}
.ps-calls .ps-dir svg {
    width: 16px;
    height: 16px;
}
.ps-act { white-space: nowrap; }
.ps-enter { margin: 0; }

@media (max-width: 820px) {
    .ps-search__row .control {
        flex: 1 1 100%;
        max-width: none;
    }
    /* Once the table is cards, the entry is the card's last line and takes its width. */
    .ps-calls .ps-act .btn {
        width: 100%;
        justify-content: center;
    }
}
/* ==== END SECTION platform-search ==== */
/* ==== SECTION reseller-tenants ==== */
/* RES-TENANTS — the reseller's customers: the list, the create form and the customer page.
   OBSOLETE: none. The three screens carried their layout in ~25 inline style attributes, not in
   classes, so nothing in the stylesheet is left unused by this package.
   Already in the file and deliberately NOT repeated here: `.grid.top { align-items: start }` and
   `.btn-row { flex-wrap: wrap }` — the brief asked for both and both already exist. */

/* ── The customers list ───────────────────────────────────────────────────────────────────── */

/* The client-side filter over the rows already on the page. It sits between the tabs and the
   table, so it carries the same gap the filter bars use. */
.res-tenants__search {
    max-width: 22rem;
    margin-block-end: var(--s3);
}

/* ── The create form ─────────────────────────────────────────────────────────────────────── */

/* One vertical rhythm inside a card body; the older .form-row bottom margin does not add to it. */
.res-stack { display: grid; gap: var(--s5); min-width: 0; }
.res-stack > .form-row { margin: 0; }

/* «רק אותיות באנגלית ומקפים», written beside the identifier for two seconds when a typed
   character is dropped (PLAT-18). The line is reserved even while it is empty: a message that
   pushes the card down as it appears is a message that moves the field under the finger. */
.res-allow {
    min-height: 1.5em;
    margin-block-start: var(--s1);
    color: var(--warn-ink);
    font-weight: 600;
}

/* ── The customer page ───────────────────────────────────────────────────────────────────── */

/* Saving a limit returns to #limits, an invitation to #users. The top bar is sticky, so without
   this the card's own heading lands underneath it (PLAT-13). */
.res-customer .card[id] { scroll-margin-top: calc(var(--kq-topbar-h) + var(--s3)); }

/* A quota field is a number, not a bar: wider than the shared 14ch cap, because «1,000,000» and
   its placeholder «ללא הגבלה» both have to fit. */
.res-customer .feature.split .control.num { width: 9rem; max-width: none; }

/* «נותרו 8 להקצאה» belongs to the field above it, on its own line — beside the button it read
   as part of it. */
.res-customer .feature.split form > .hint { flex: 1 0 100%; margin: 0; }
.res-customer .hint.is-over { color: var(--danger-ink); font-weight: 600; }

/* The title of a settings row on a screen that may not edit it: what label.lbl looks like,
   without claiming a field that is not rendered. */
.res-rowtitle {
    display: block;
    margin: 0 0 var(--s2);
    font-weight: 600;
    font-size: var(--t-md);
    color: var(--ink-2);
}

/* The one link in a card head — to the screen that owns the action (ONE ACTION, ONE PLACE). */
.res-headlink {
    display: inline-flex; align-items: center; gap: var(--s2);
    font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink);
    text-decoration: none; white-space: nowrap;
}
.res-headlink svg { flex: none; width: 16px; height: 16px; }
@media (hover: hover) {
    .res-headlink:hover { color: var(--kq-link-hover); text-decoration: underline; text-underline-offset: 3px; }
}

/* The numbers table holds one line per agent on a number; two agents on one number must not
   read as one name. */
.res-customer #numbers .tbl td > div + div { margin-block-start: 2px; }
/* ==== END SECTION reseller-tenants ==== */
/* ==== SECTION reseller-dashboard ==== */
/* RES-DASH (wave 3). The reseller board — the bank tile with its runway and its thirty-day shape,
   the allowance tiles, «דורש טיפול» and the customers table — and the bank's movement log.

   Everything else on both pages is component layer: .kq-figs/.kq-fig, .kq-attn, .kq-dot, .tabs,
   .search-box, .tbl, .tn-bar, .badge, .kq-pager, .kq-empty. This section only lays the two pages
   out and adds the one thing no other screen has: the sparkline.

   OBSOLETE once /Platform/Index stops writing them too (PLAT-DASH, same wave — do NOT delete while
   that page still draws cards): .tn-grid, .tn-card, .tn-card.sev-red, .tn-card.sev-yellow,
   .tn-card.sev-ok, .tn-head, .tn-name, .tn-chips, .tn-metric, .tn-metric-row, .tn-lbl, .tn-val,
   .tn-reco, .tn-reco.r-yellow, .tn-reco.r-red, and the 640px .tn-grid block.
   .tn-bar and its fills STAY — the customers table draws them. */

/* ── The page: blocks in one column, one gap between them ─────────────────────────────────── */
.res-board { display: flex; flex-direction: column; gap: var(--s5); min-width: 0; }
.res-board > * { margin-block: 0; min-width: 0; }
.res-board > .kq-figs { margin-bottom: 0; }

.res-board .card__head h2 svg,
.res-ledger .card__head h2 svg { flex: none; width: 18px; height: 18px; }
.res-board .card__head h2,
.res-ledger .card__head h2 { font-weight: 700; line-height: 1.4; color: var(--ink); }

/* ── The tiles ────────────────────────────────────────────────────────────────────────────── */
/* The bank is the figure the reseller opens the page for, so it takes twice the width and carries
   the trend beside it; the three allowance tiles share the rest. */
.kq-figs.res-kpis { grid-template-columns: minmax(0, 2fr) repeat(3, minmax(0, 1fr)); }

/* A tile says «act» only with its dot and a word. The edge is coloured for the same reason the
   customer cards' edge is (RES-12): a healthy tile stays neutral so an unhealthy one is the only
   thing the eye is drawn to. */
.res-kpis .kq-fig.is-warn { border-inline-start: 4px solid var(--warn-ink); }
.res-kpis .kq-fig.is-danger { border-inline-start: 4px solid var(--danger-ink); }
.res-kpis .kq-fig.is-warn .kq-fig__sub { color: var(--warn-ink); }
.res-kpis .kq-fig.is-danger .kq-fig__sub { color: var(--danger-ink); }

.res-bank { flex-direction: row; align-items: stretch; justify-content: space-between; gap: var(--s4); }
.res-bank > div { display: flex; flex-direction: column; min-width: 0; }
/* Pinned to the bottom of the text column so it lines up with the sparkline's caption. */
.res-bank__link {
    margin-block-start: auto; padding-block-start: var(--s2); align-self: flex-start;
    display: inline-flex; align-items: center; gap: var(--s1);
    font-size: var(--t-md); font-weight: 600; color: var(--kq-link-ink); text-decoration: none;
}
.res-bank__link svg { flex: none; width: 16px; height: 16px; }
@media (hover: hover) {
    .res-bank__link:hover { color: var(--kq-link-hover); text-decoration: underline; text-underline-offset: 3px; }
}

/* ── The thirty-day shape ─────────────────────────────────────────────────────────────────── */
/* A shape, not a table: no axis, no value, no tooltip. The sentence beside it holds the numbers,
   and the two dates under it say which way time runs — oldest at the right, like every chart here. */
.dash-spark {
    margin: 0; inline-size: 168px; flex: none;
    display: flex; flex-direction: column; justify-content: flex-end; gap: var(--s1);
}
.dash-spark__k { font-size: var(--t-xs); color: var(--muted); }
.dash-spark__svg { display: block; inline-size: 168px; block-size: 52px; }
/* A DATA LINE TAKES THE DATA RAMP, not --kq-azure: that token is DECORATION («the exact brand
   colour, never moved», Web/BrandTheme.cs) and has no contrast floor at all, so on a pale
   reseller brand the one chart on the board washed out to 2.17:1 on the white tile. --dv-1 is a
   literal no brand block reaches, so it is 4.95:1 on the tile under EVERY brand, and it is the
   ramp member the first tile's own chip already takes. */
.dash-spark polyline { fill: none; stroke: var(--dv-1); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.dash-spark line { stroke: var(--line); stroke-width: 1; }
.dash-spark figcaption {
    display: flex; justify-content: space-between; gap: var(--s2);
    font-size: var(--t-xs); color: var(--muted); font-variant-numeric: tabular-nums;
}

/* ── The customers ────────────────────────────────────────────────────────────────────────── */
.res-customers .card__head { flex-wrap: wrap; }
.res-search { flex: 0 1 280px; min-width: 200px; }
.res-search .control { padding-block: var(--s2); font-size: var(--t-md); }
/* The tabs sit between the head and the table, flush with the card's own padding. */
.res-customers > .tabs,
.res-ledger > .tabs { margin: 0; padding-inline: var(--s5); overflow-x: auto; scrollbar-width: thin; }
.res-customers > .tabs .tab,
.res-ledger > .tabs .tab { white-space: nowrap; }

/* A customer name can be a domain with no space in it, which would otherwise set a floor the whole
   page inherits and push the board sideways on a phone (GAP3-07). */
.res-name { font-weight: 600; color: var(--kq-link-ink); text-decoration: none; overflow-wrap: anywhere; }
@media (hover: hover) { a.res-name:hover { color: var(--kq-link-hover); } }
.res-sub { display: block; font-size: var(--t-sm); color: var(--muted); overflow-wrap: anywhere; }
/* The week in both directions, one under the other. The cell keeps .cell-num for its tabular digits
   but not its end alignment: each figure now carries its direction in words, and a value pushed to
   the far side of a column whose header sits at the near side reads as a different column. */
.res-callcell { text-align: start; white-space: normal; }
.res-calls { display: block; font-variant-numeric: tabular-nums slashed-zero; }
.res-use__t { display: block; font-size: var(--t-md); font-variant-numeric: tabular-nums slashed-zero; }
.res-use .tn-bar { margin-block-start: var(--s1); max-inline-size: 180px; }

/* ── The bank's movements ─────────────────────────────────────────────────────────────────── */
/* The noun after the figure keeps the figure's own line and never joins its isolate. */
.res-ledger__tbl .cell-num span { color: var(--ink-2); }

/* ── Narrow ───────────────────────────────────────────────────────────────────────────────── */
@media (max-width: 980px) {
    .kq-figs.res-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .kq-figs.res-kpis > .res-bank { grid-column: 1 / -1; }
}
@media (max-width: 820px) {
    .res-search { flex: 1 1 100%; }
    .res-customers > .tabs,
    .res-ledger > .tabs { padding-inline: var(--s3); }
    .res-use .tn-bar { max-inline-size: none; }
}
@media (max-width: 520px) {
    .kq-figs.res-kpis { grid-template-columns: minmax(0, 1fr); }
    /* On a phone the tile has room for the balance and the sentence, not for a shape as well. */
    .res-bank .dash-spark { display: none; }
}
/* ==== END SECTION reseller-dashboard ==== */
/* ==== SECTION reseller-users ==== */
/* THE RESELLER'S OWN STAFF (RES-USERS, wave 3): the list at /Reseller/Users and the form that adds
   a colleague. Everything these two screens wear — table, badges, fields, the row form, the consequence
   dialog — is the component layer; this section only arranges them, in place of the four style=
   attributes the screens used to carry.
   OBSOLETE: none — the classes these screens dropped (.empty here, .chip-role on an editable row) are
   still used by other screens. */

/* THE STATUS CELL can hold two words at once — the account state and a lockout that is still
   running — so they wrap as a block instead of pushing the column wider. */
.ru-state { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); min-width: 0; }

/* The name and, on the signed-in reader's own row, the «אני» chip beside it. The name is its own
   flex item, so it needs the zero minimum back before a long one can break inside a phone card. */
.ru-name { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2); }
.ru-name > bdi { min-width: 0; }

/* A role is two words, and the picker shares a table cell with its save button: narrower than the
   10rem a form row gives it, so the column does not push the actions off the screen. Only on the
   wide layout — under 820px .row-form already stacks both controls to the full width. */
@media (min-width: 821px) {
    .ru-role .control { flex: 0 1 11rem; }
}

/* THE ADD FORM. A kq-field renders a bare .field with no margin of its own, so the card body spaces
   its own rows. */
.ru-form { display: grid; gap: var(--s4); }

@media (max-width: 820px) {
    /* As a card, the status cell reads as a list under its label. */
    .ru-state { gap: var(--s2); }
}
/* ==== END SECTION reseller-users ==== */

/* ==== SECTION knowledge-picker ==== */
/* מאגרי ידע on an agent's form (owner 2026-09-25). A tenant may keep a dozen libraries and an
   agent carries a few of them, so the picker is a LIST and not a dozen separate form rows: the
   switch, the name and the size sit on one line each, inside one frame, and the whole group reads
   as one control the way the rules list above it reads as one group.
   The frame is the sunk field face, because this IS a field — the same surface every control on
   the form stands on, so the picker does not read as a card floating inside a card. */
.kb-pick {
    display: grid; gap: 0;
    border: 1px solid var(--surface-edge); border-radius: var(--r);
    background: var(--surface-sunk); overflow: hidden;
}
/* Rows are separated by their own top border rather than a gap: a hairline between two rows is
   what says «one list», where a gap says «two controls that happen to be near each other». */
.kb-pick__row { display: flex; align-items: center; gap: var(--s3); padding: var(--s3) var(--s4); }
.kb-pick__row + .kb-pick__row { border-top: 1px solid var(--surface-edge); }
@media (hover: hover) { .kb-pick__row:hover { background: var(--surface-material); } }
/* The text takes the rest of the row and the size goes to the far end — a column of sizes is
   scannable, a size that floats after each name is not. min-width:0 so a long library name wraps
   inside the row instead of widening the frame past the form. */
.kb-pick__txt { display: flex; align-items: baseline; gap: var(--s3); flex: 1 1 auto; min-width: 0; }
.kb-pick__txt > b { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.kb-pick__txt > span { margin-inline-start: auto; color: var(--muted); font-size: var(--t-sm); white-space: nowrap; }
/* ==== END SECTION knowledge-picker ==== */

/* ==== SECTION knowledge-upload ==== */
/* טעינה מקובץ — the file picker and its button on one line, because they are one action: picking
   a file and not pressing the button leaves nothing loaded, and a button on its own row reads as
   something else to do later. It wraps on a phone rather than shrinking the picker, whose own
   width is the browser's and cannot be usefully squeezed. */
.kb-load { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }
.kb-load > .control[type="file"] { flex: 1 1 16rem; min-width: 0; }
/* ==== END SECTION knowledge-upload ==== */
/* ==== SECTION whatsapp-threads ==== */
/* «שיחות WhatsApp» (owner 2026-10-06: «שיראה בפועל ממש כמו צ'אט ווטסאפ»). WhatsApp Web's shape and
   palette, scoped to .wa so nothing else in the product moves: the chat list beside the open thread,
   bubbles with tails on the first of a run, the time inside the bubble, day pills, the composer bar.
   In Hebrew WhatsApp the customer's bubbles sit at the start edge (right) and the account's at the
   end edge (left) — flex-start / flex-end in this RTL page.

   THE PALETTE IS DECLARED ON :root because a literal colour anywhere else in this stylesheet is a
   defect (ds-gate DS08). It is WhatsApp's light theme, with four values darkened until the small
   text passes AA on every surface it sits on — WhatsApp's own grey for times and endings is 2.6:1
   on the chat background, its unread green 3.1:1 on white, white on its badge green 2:1, and its
   name blue 4.1:1 on the account's bubble. The product's own tokens carry what is the product's
   rather than WhatsApp's: the sentence under the box, the failed tick and the «מושהה» pill. */
:root {
    --wa-panel: #F0F2F5;
    --wa-chat: #EFEAE2;
    --wa-paper: #FFFFFF;
    --wa-out: #D9FDD3;
    --wa-ink: #111B21;
    --wa-ink-soft: #54656F;
    --wa-meta: #5B6B75;
    --wa-line: #E9EDEF;
    --wa-hover: #F5F6F6;
    --wa-green: #00A884;
    --wa-on-green: #FFFFFF;
    --wa-badge: #25D366;
    --wa-unread: #0E7A3B;
    --wa-avatar: #DFE5E7;
    --wa-from-agent: #01699A;
    --wa-from-person: #A62C71;
    --wa-intro-icon: #B8C2C8;
    --wa-intro-ink: #41525D;
    --wa-shadow: 0 1px .5px rgba(11, 20, 26, .13);
}
.wa {
    display: grid;
    grid-template-columns: clamp(240px, 32%, 360px) minmax(0, 1fr);
    height: clamp(520px, calc(100dvh - 190px), 1000px);
    border: 1px solid var(--wa-line);
    border-radius: var(--r);
    overflow: hidden;
    background: var(--wa-panel);
    color: var(--wa-ink);
}
.wa [hidden] { display: none !important; }

/* the list */
.wa-list { display: flex; flex-direction: column; min-height: 0; min-width: 0; background: var(--wa-paper); border-inline-end: 1px solid var(--wa-line); }
.wa-search { display: flex; align-items: center; gap: 8px; margin: 8px 12px; padding: 0 12px; background: var(--wa-panel); border-radius: 8px; }
.wa-search:focus-within { box-shadow: 0 0 0 2px var(--wa-green); }
.wa-search__icon { display: inline-flex; }
.wa-search__icon svg { width: 16px; height: 16px; color: var(--wa-meta); }
.wa-search__input { border: 0; background: transparent; padding: 8px 0; width: 100%; font: inherit; font-size: 14px; color: var(--wa-ink); }
.wa-search__input:focus { outline: none; }
/* the rows are redrawn in place (kq-wa.js); their wrapper takes no box, so the list scrolls as before */
.wa-list__rows { display: contents; }
.wa-list__status { margin: 0 12px 8px; font-size: 13px; color: var(--danger-ink); }
.wa-list__status:empty { display: none; }
.wa-items { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1; min-height: 0; }
.wa-item { display: grid; grid-template-columns: 48px minmax(0, 1fr); gap: 12px; align-items: center; padding: 10px 14px; color: var(--wa-ink); text-decoration: none; border-bottom: 1px solid var(--wa-line); }
@media (hover: hover) {
    .wa-item:hover { background: var(--wa-hover); }
}
.wa-item.is-active { background: var(--wa-panel); }
.wa-item:focus-visible { outline: 2px solid var(--wa-green); outline-offset: -2px; }
.wa-avatar { width: 48px; height: 48px; border-radius: 50%; background: var(--wa-avatar); color: var(--wa-ink-soft); display: grid; place-items: center; font-weight: 600; font-size: 15px; flex: none; }
.wa-item__body { display: grid; gap: 2px; min-width: 0; }
.wa-item__top, .wa-item__bottom { display: flex; align-items: center; gap: 8px; min-width: 0; }
.wa-item__name { flex: 1; min-width: 0; font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wa-item__time { font-size: 12px; color: var(--wa-meta); flex: none; }
.wa-item__time.is-waiting { color: var(--wa-unread); font-weight: 600; }
.wa-item__preview { flex: 1; min-width: 0; font-size: 14px; color: var(--wa-meta); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wa-item__tick svg { width: 14px; height: 14px; vertical-align: -2px; color: var(--wa-meta); }
.wa-item__tick.is-failed svg { color: var(--danger); }
.wa-badge { min-width: 20px; height: 20px; padding: 0 6px; border-radius: 99px; background: var(--wa-badge); color: var(--wa-ink); font-size: 12px; font-weight: 700; display: grid; place-items: center; flex: none; }
.wa-pill { font-size: 11.5px; padding: 0 7px; border-radius: 99px; background: var(--warn-bg); color: var(--warn-ink); flex: none; }
.wa-list__empty { margin: 0; padding: 16px; color: var(--wa-meta); }
.wa-list__pager { display: flex; justify-content: space-between; gap: 8px; padding: 8px 14px; border-top: 1px solid var(--wa-line); font-size: 13.5px; }

/* the pane */
.wa-pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--wa-chat); }
.wa-pane--intro { display: grid; place-items: center; background: var(--wa-panel); border-bottom: 6px solid var(--wa-green); }
.wa-intro { text-align: center; display: grid; gap: 8px; justify-items: center; color: var(--wa-meta); max-width: 420px; padding: 24px; }
.wa-intro p { margin: 0; }
.wa-intro__icon svg { width: 72px; height: 72px; color: var(--wa-intro-icon); }
.wa-intro__title { font-size: 24px; color: var(--wa-intro-ink); }
.wa-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 10px 16px; background: var(--wa-panel); border-bottom: 1px solid var(--wa-line); }
.wa-head .wa-avatar { width: 40px; height: 40px; font-size: 14px; }
/* a 160px floor before the name gives way: below it the buttons wrap under the name instead of
   squeezing it to a few letters (a 360px phone, a tablet with the menu open) */
.wa-head__who { min-width: 0; flex: 1 1 160px; }
.wa-head__who h2 { font-size: 16px; font-weight: 500; margin: 0; color: var(--wa-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wa-head__sub { margin: 0; font-size: 13px; color: var(--wa-meta); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wa-head__actions { display: flex; gap: 8px; flex: none; }
.wa-head__actions form { margin: 0; }
.wa-back { display: none; color: var(--wa-meta); }
.wa-back svg { width: 22px; height: 22px; }

/* the thread */
.wa-body { list-style: none; margin: 0; padding: 12px 6%; overflow-y: auto; flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 2px; }
.wa-day { align-self: center; margin: 12px 0 8px; }
.wa-day span { display: inline-block; background: var(--wa-paper); color: var(--wa-ink-soft); font-size: 12.5px; padding: 5px 12px; border-radius: 7.5px; box-shadow: var(--wa-shadow); }
.wa-msg { display: flex; flex-direction: column; max-width: min(75%, 560px); }
.wa-msg.is-first { margin-top: 8px; }
.wa-msg--in { align-self: flex-start; align-items: flex-start; }
.wa-msg--out { align-self: flex-end; align-items: flex-end; }
.wa-bubble { position: relative; background: var(--wa-paper); color: var(--wa-ink); border-radius: 7.5px; padding: 6px 9px 7px; box-shadow: var(--wa-shadow); font-size: 14.2px; line-height: 1.45; min-width: 64px; }
.wa-msg--out .wa-bubble { background: var(--wa-out); }
/* The tail, on the first bubble of a run. Physical coordinates on purpose: this page is always RTL,
   so the customer's tail is on the RIGHT edge and the account's on the LEFT. */
.wa-msg--in.is-first .wa-bubble { border-top-right-radius: 0; }
.wa-msg--out.is-first .wa-bubble { border-top-left-radius: 0; }
.wa-msg.is-first .wa-bubble::before { content: ""; position: absolute; top: 0; width: 8px; height: 13px; }
.wa-msg--in.is-first .wa-bubble::before { right: -8px; background: var(--wa-paper); clip-path: polygon(0 0, 100% 0, 0 100%); }
.wa-msg--out.is-first .wa-bubble::before { left: -8px; background: var(--wa-out); clip-path: polygon(0 0, 100% 0, 100% 100%); }
.wa-from { display: block; font-size: 12.8px; font-weight: 600; color: var(--wa-from-agent); margin-bottom: 2px; }
.wa-from--person { color: var(--wa-from-person); }
.wa-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.wa-text--kind { font-style: italic; color: var(--wa-meta); }
/* A photo (owner 2026-10-07): «תמונה» is a button that opens the picture while the number's agent
   reads photos, and the caption, if any, sits under it. The picture opens in a wider dialog. */
.wa-photo { display: inline-flex; align-items: center; gap: 6px; padding: 0; border: 0; background: none; font: inherit; font-style: italic; color: var(--wa-from-agent); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
.wa-photo svg { width: 16px; height: 16px; flex: none; }
.wa-photo:focus-visible { outline: 2px solid var(--wa-green); outline-offset: 2px; border-radius: 4px; }
.wa-caption { display: block; margin-top: 2px; }
.wa-photo-dialog .kq-dialog__box { width: min(760px, 100%); }
.wa-photo-view { margin: var(--s4) var(--s5) 0; display: grid; place-items: center; min-height: 120px; }
.wa-photo-view img { display: block; max-width: 100%; max-height: 70vh; border-radius: var(--r); }
.wa-photo-view img[hidden] { display: none; }
.wa-photo-note { margin: 0; color: var(--muted); text-align: center; }
.wa-photo-note:empty { display: none; }
.wa-meta { display: flex; justify-content: flex-end; align-items: center; gap: 3px; font-size: 11px; color: var(--wa-meta); margin-top: 2px; }
.wa-tick { display: inline-flex; }
.wa-tick svg { width: 14px; height: 14px; color: var(--wa-meta); }
.wa-tick.is-failed svg { color: var(--danger); }
.wa-ending { font-size: 11.5px; color: var(--wa-meta); margin: 2px 4px 0; }

/* the composer */
.wa-foot { background: var(--wa-panel); padding: 8px 16px 10px; display: grid; gap: 4px; }
.wa-status { margin: 0; font-size: 13px; color: var(--danger-ink); }
.wa-status:empty { display: none; }
.wa-composer { display: flex; align-items: flex-end; gap: 8px; margin: 0; }
/* the way into the box while the agent has the conversation: one full-width action, the product's own button */
.wa-takeover { margin: 0; }
.wa-takeover .btn { width: 100%; justify-content: center; }
.wa-input { flex: 1; min-width: 0; min-height: 42px; max-height: 140px; resize: none; border: 0; border-radius: 8px; padding: 10px 12px; font: inherit; font-size: 15px; line-height: 1.4; background: var(--wa-paper); color: var(--wa-ink); }
.wa-input:focus { outline: 2px solid var(--wa-green); outline-offset: 0; }
.wa-send { width: 42px; height: 42px; border-radius: 50%; border: 0; background: var(--wa-green); color: var(--wa-on-green); display: grid; place-items: center; cursor: pointer; flex: none; }
.wa-send svg { width: 20px; height: 20px; }
.wa-send:disabled { opacity: .5; cursor: default; }
.wa-send:focus-visible { outline: 2px solid var(--wa-ink); outline-offset: 2px; }
.wa-note { margin: 0; font-size: 12px; color: var(--wa-meta); }
.wa-closed { margin: 0; padding: 10px 12px; text-align: center; font-size: 13.5px; color: var(--wa-ink-soft); background: var(--wa-paper); border-radius: 8px; }

/* iOS Safari zooms the whole page into a field under 16px (see the 820px block's note 4) */
@media (max-width: 820px), (pointer: coarse) {
    .wa-input { font-size: 16px; }
}
/* One screen at a time below 1000px — a phone, and a tablet whose 240px menu leaves the thread too
   little room beside the list: the list, or the open thread with its way back, as in the app. */
@media (max-width: 1000px) {
    .wa { grid-template-columns: minmax(0, 1fr); }
    .wa .wa-pane--intro { display: none; }
    .wa.is-open .wa-list { display: none; }
    .wa-back { display: inline-flex; }
}
@media (max-width: 760px) {
    .wa { height: calc(100dvh - 150px); }
    /* the agent's state is the line that matters here; on a phone it takes a line of its own
       under the number rather than being cut */
    .wa-head__sub { white-space: normal; }
    .wa-head__dot { display: none; }
    .wa-head__sub [data-wa-text="agent-line"] { display: block; }
    .wa-body { padding: 10px 3%; }
    .wa-msg { max-width: 88%; }
}
/* ==== END SECTION whatsapp-threads ==== */

/* ==== SECTION calendars ==== */
/* «יומנים» and everything that names a calendar by its colour (owner 2026-10-07: several calendars
   per account, chosen per agent, and a screen to look at them by day, week and month).

   ONE COLOUR PER CALENDAR, BY ITS PLACE IN THE ACCOUNT'S OWN ORDER — the order it connected them in —
   so a calendar is the same colour on the calendar screen, in an agent's settings and in the vault.
   Eight hues, chosen to stay apart from each other and to read as text on a white card; past eight
   they repeat. The hues are tokens in :root; each class only sets --cal from one of them, and
   every surface below mixes from it. */
.cal-c0 { --cal: var(--cal-0); }
.cal-c1 { --cal: var(--cal-1); }
.cal-c2 { --cal: var(--cal-2); }
.cal-c3 { --cal: var(--cal-3); }
.cal-c4 { --cal: var(--cal-4); }
.cal-c5 { --cal: var(--cal-5); }
.cal-c6 { --cal: var(--cal-6); }
.cal-c7 { --cal: var(--cal-7); }
.calp-dot {
    display: inline-block; flex: none; width: 10px; height: 10px; border-radius: var(--r-circle);
    background: var(--cal, var(--muted));
}

/* ── an agent's calendars (Pages/Shared/_AgentCalendars.cshtml) ───────────────────────────────── */
/* At the top of «מה הסוכן יכול לעשות», above the agent's own tools, closed off from them by a rule. */
.calp { margin: 0 0 var(--s5); padding-bottom: var(--s5); border-bottom: 1px solid var(--line); }
.calp__title { display: flex; align-items: center; gap: var(--s2); margin: 0 0 var(--s3); font-size: var(--t-md); font-weight: 600; }
.calp__title svg { width: 18px; height: 18px; }
.calp__minutes .control { max-width: 140px; }
/* The same sunk list face as the knowledge picker: one control, several rows. */
.calp-list {
    display: grid; border: 1px solid var(--surface-edge); border-radius: var(--r);
    background: var(--surface-sunk); overflow: hidden; margin-bottom: var(--s3);
}
.calp-row { padding: var(--s3) var(--s4); }
.calp-row + .calp-row { border-top: 1px solid var(--surface-edge); }
.calp-row__head { display: flex; align-items: center; gap: var(--s3); margin: 0; }
.calp-row__txt { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.calp-row__txt > b { font-weight: 600; overflow-wrap: anywhere; }
.calp-row__txt > span { color: var(--muted); font-size: var(--t-sm); overflow-wrap: anywhere; }
/* A calendar's two switches and its instructions open with its own switch, in CSS alone; the whole
   list closes with «הסוכן עובד עם יומנים». A browser without :has() shows everything — untidy,
   never unreachable — the rule every reveal on these forms follows. */
.calp-details { display: none; margin-top: var(--s3); padding-inline-start: var(--s6); }
.calp-row:has([data-cal-on]:checked) .calp-details { display: block; }
.calp:has(#Cal_Enabled:not(:checked)) .calp__body { display: none; }
@supports not selector(:has(*)) {
    .calp-details { display: block; }
}
.calp-roles { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); margin-bottom: var(--s3); }
.calp-roles .checkbox { margin: 0; }

/* ── the vault's calendar screen: the calendars an account holds ─────────────────────────────── */
.calc-list { display: grid; }
.calc-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); padding: var(--s3) var(--s4); }
.calc-row + .calc-row { border-top: 1px solid var(--line); }
.calc-row__name { display: flex; align-items: center; gap: var(--s2); flex: 1 1 280px; min-width: 0; margin: 0; }
.calc-row__name .control { max-width: 320px; min-width: 0; }
.calc-row__meta { flex: 1 1 200px; min-width: 0; color: var(--muted); font-size: var(--t-sm); overflow-wrap: anywhere; }
.calc-row__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }
.calc-row__actions form { margin: 0; }

/* ── «יומנים»: the screen ─────────────────────────────────────────────────────────────────────── */
.calv { display: grid; gap: var(--s4); min-width: 0; }
.calv-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s3); }
.calv-nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); min-width: 0; }
.calv-title { margin: 0; margin-inline-start: var(--s3); font-size: var(--t-lg); font-weight: 600; }
.calv-views { margin: 0; }
.calv-legend { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); }
.calv-legend__item { display: inline-flex; align-items: center; gap: var(--s2); margin: 0; }
.calv-notice { margin: 0; }
/* The week is wide: on a narrow screen it scrolls sideways inside its card rather than squeezing
   seven days into a phone's width or pushing the page past the screen's edge. */
.calv-card { overflow-x: auto; }

/* HIDING A CALENDAR is a switch and a rule, no script: position i's switch, unchecked, hides every
   appointment carrying data-cal="i". Twelve positions; an account with more shows the rest always. */
.calv:has(#calv-show-0:not(:checked)) [data-cal="0"],
.calv:has(#calv-show-1:not(:checked)) [data-cal="1"],
.calv:has(#calv-show-2:not(:checked)) [data-cal="2"],
.calv:has(#calv-show-3:not(:checked)) [data-cal="3"],
.calv:has(#calv-show-4:not(:checked)) [data-cal="4"],
.calv:has(#calv-show-5:not(:checked)) [data-cal="5"],
.calv:has(#calv-show-6:not(:checked)) [data-cal="6"],
.calv:has(#calv-show-7:not(:checked)) [data-cal="7"],
.calv:has(#calv-show-8:not(:checked)) [data-cal="8"],
.calv:has(#calv-show-9:not(:checked)) [data-cal="9"],
.calv:has(#calv-show-10:not(:checked)) [data-cal="10"],
.calv:has(#calv-show-11:not(:checked)) [data-cal="11"] { display: none; }

/* An appointment: a tinted block edged in its calendar's colour, its time first. */
.calv-ev {
    display: flex; align-items: baseline; gap: var(--s1); width: 100%; min-width: 0;
    padding: 2px 6px; border: 0; border-inline-start: 3px solid var(--cal); border-radius: 4px;
    background: color-mix(in srgb, var(--cal) 14%, var(--n-0)); color: var(--ink);
    font: inherit; font-size: var(--t-xs); line-height: 1.35; text-align: start; cursor: pointer;
    overflow: hidden;
}
@media (hover: hover) { .calv-ev:hover { background: color-mix(in srgb, var(--cal) 24%, var(--n-0)); } }
.calv-ev:focus-visible { outline: 2px solid var(--cal); outline-offset: 1px; }
.calv-ev__time { flex: none; font-variant-numeric: tabular-nums; font-weight: 600; color: color-mix(in srgb, var(--cal) 70%, var(--ink)); }
.calv-ev__title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* «free» in the provider's own terms: shown, but as an outline — it does not take the time. */
.calv-ev.is-free { background: color-mix(in srgb, var(--cal) 6%, transparent); border-inline-start-style: dashed; }
/* Continues from the day before / into the day after: the edge on that side is squared off. */
.calv-ev--day.is-cont-start { border-start-start-radius: 0; border-end-start-radius: 0; }
.calv-ev--day.is-cont-end { border-start-end-radius: 0; border-end-end-radius: 0; }

/* ── the month ── */
.calv-month { width: 100%; min-width: 560px; border-collapse: collapse; table-layout: fixed; }
.calv-month th {
    padding: var(--s2); font-size: var(--t-sm); font-weight: 600; color: var(--muted); text-align: center;
    border-bottom: 1px solid var(--line);
}
.calv-month td { height: 116px; padding: var(--s1); vertical-align: top; border: 1px solid var(--line); }
.calv-month tbody tr:first-child td { border-top: 0; }
.calv-month td:first-child { border-inline-start: 0; }
.calv-month td:last-child { border-inline-end: 0; }
.calv-cell.is-out { background: var(--n-1); }
.calv-cell.is-out .calv-date { color: var(--muted); font-weight: 400; }
.calv-date {
    display: inline-grid; place-items: center; min-width: 26px; height: 26px; padding: 0 4px;
    border-radius: var(--r-pill); color: var(--ink); font-size: var(--t-sm); font-weight: 600; text-decoration: none;
}
@media (hover: hover) { .calv-date:hover { background: var(--n-2); } }
.calv-cell.is-today .calv-date { background: var(--kq-btn-bg); color: var(--kq-btn-on); }
.calv-chips { display: grid; gap: 2px; margin: var(--s1) 0 0; padding: 0; list-style: none; }
.calv-more { display: inline-block; margin-top: 2px; font-size: var(--t-xs); }
.calv-short { display: none; }
@media (max-width: 720px) {
    .calv-long { display: none; }
    .calv-short { display: inline; }
    .calv-month td { height: 92px; }
}

/* ── the week and the day: a time grid of 24 equal hours ── */
.calv-time { --hour: 48px; --days: 7; display: grid; min-width: 640px; }
.calv-time.is-day { --days: 1; min-width: 0; }
.calv-head, .calv-allday, .calv-grid {
    display: grid; grid-template-columns: 56px repeat(var(--days), minmax(0, 1fr));
}
/* The days' heads and the all-day line scroll WITH the hours and stick to the top: in one scrolling
   box the columns share one width, where a head outside it would sit a scrollbar's width off. */
.calv-top { position: sticky; top: 0; z-index: 3; background: var(--n-0); }
.calv-head { border-bottom: 1px solid var(--line); }
.calv-dayhead { display: grid; justify-items: center; gap: 2px; padding: var(--s2) 0; color: var(--muted); text-decoration: none; }
.calv-dayhead__dow { font-size: var(--t-xs); }
.calv-dayhead__num {
    display: grid; place-items: center; min-width: 34px; height: 34px; border-radius: var(--r-pill);
    color: var(--ink); font-size: var(--t-lg); font-weight: 600;
}
@media (hover: hover) { .calv-dayhead:hover .calv-dayhead__num { background: var(--n-2); } }
.calv-dayhead.is-today .calv-dayhead__num { background: var(--kq-btn-bg); color: var(--kq-btn-on); }
.calv-gutter { padding: var(--s1); color: var(--muted); font-size: var(--t-xs); text-align: center; }
.calv-allday { border-bottom: 1px solid var(--line); }
.calv-allday__col { display: grid; align-content: start; gap: 2px; min-width: 0; padding: 2px; border-inline-start: 1px solid var(--line); }
.calv-scroll { max-height: min(70vh, 720px); overflow-y: auto; }
.calv-hours { display: grid; grid-template-rows: repeat(24, var(--hour)); }
.calv-hours span { color: var(--muted); font-size: var(--t-xs); font-variant-numeric: tabular-nums; text-align: center; transform: translateY(-.6em); }
.calv-hours span:first-child { visibility: hidden; }
.calv-col {
    position: relative; height: calc(var(--hour) * 24); min-width: 0;
    border-inline-start: 1px solid var(--line);
    background-image: linear-gradient(to bottom, var(--line) 1px, transparent 1px);
    background-size: 100% var(--hour);
}
.calv-col.is-today { background-color: color-mix(in srgb, var(--kq-btn-bg) 5%, transparent); }
/* Placed where the server says: top and height as percent of the day, side by side in --lanes
   columns where appointments overlap (Pages/Admin/Calendars/Index.cshtml.cs, AssignLanes). The
   numbers travel as data and kq-calendar.js turns them into these properties; until it has — or
   without it — a block stands in time order at the top of its day, which is still readable. */
.calv-col > .calv-ev--time { flex-direction: column; align-items: stretch; gap: 0; padding: 1px 4px; margin-bottom: 2px; }
.calv-col > .calv-ev--time[data-placed] {
    position: absolute; z-index: 1; top: var(--top); height: var(--h); margin: 0;
    inset-inline-start: calc((100% - 4px) / var(--lanes, 1) * var(--lane, 0) + 2px);
    width: calc((100% - 4px) / var(--lanes, 1) - 2px);
}
.calv-col > .calv-ev--time .calv-ev__title { white-space: normal; }
.calv-now { position: absolute; z-index: 2; inset-inline: 0; top: var(--top); height: 2px; background: var(--danger); pointer-events: none; }
.calv-now:not([data-placed]) { display: none; }
.calv-now::before {
    content: ""; position: absolute; top: -4px; inset-inline-start: -5px;
    width: 10px; height: 10px; border-radius: var(--r-circle); background: var(--danger);
}

/* ── an appointment's details ── */
.calv-dl { display: grid; grid-template-columns: auto 1fr; gap: var(--s2) var(--s4); margin: var(--s4) var(--s5) 0; }
.calv-dl dt { color: var(--muted); }
.calv-dl dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
/* ==== END SECTION calendars ==== */
/* ==== SECTION whatsapp-templates ==== */
/* «תבניות WhatsApp» (owner 2026-10-07: «שליטה מלאה על התבניות»). The list is the product's own table;
   the editor is the form beside the message as the customer will read it, drawn in WhatsApp's own
   bubble from the whatsapp-threads section — its palette lives on :root there, so nothing here names
   a colour. One column on a narrow screen, the preview under the form. */
.tpl-account { display: flex; flex-wrap: wrap; align-items: end; gap: var(--s3); margin-bottom: var(--s4); }
.tpl-account .control { width: auto; min-width: 0; max-width: 100%; }
.tpl-cell { display: grid; gap: 2px; min-width: 0; }
.tpl-excerpt { display: block; overflow-wrap: anywhere; }
.tpl-actions { display: flex; flex-wrap: wrap; gap: var(--s2); justify-content: flex-end; }
.tpl-edit { display: grid; grid-template-columns: minmax(0, 1fr) minmax(16rem, 21rem); gap: var(--s5); align-items: start; }
.tpl-edit [hidden] { display: none !important; }
.tpl-edit__form { min-width: 0; }
.tpl-edit__preview { position: sticky; top: var(--s5); display: grid; gap: var(--s3); min-width: 0; }
.tpl-preview__title { margin: 0; font-size: var(--t-base); font-weight: 600; }
.tpl-preview { background: var(--wa-chat); border-radius: var(--r); padding: var(--s5) var(--s4); display: flex; flex-direction: column; }
.tpl-preview .wa-msg { max-width: 100%; }
.tpl-preview__head { display: block; margin-bottom: 4px; }
.tpl-preview__foot { display: block; margin-top: 4px; font-size: 12.5px; color: var(--wa-meta); }
.tpl-preview__btns { display: grid; gap: 2px; margin-top: 2px; width: 100%; }
.tpl-preview__btns:empty { display: none; }
.tpl-preview__btn { display: block; text-align: center; background: var(--wa-paper); color: var(--wa-from-agent); border-radius: 7.5px; padding: 8px; font-size: 14px; box-shadow: var(--wa-shadow); }
.tpl-examples { border: 0; margin: 0 0 var(--s4); padding: 0; min-width: 0; }
.tpl-examples__list { display: grid; gap: var(--s3); }
.tpl-fixed { margin: 0 0 var(--s4); display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--s2) var(--s4); align-items: center; }
.tpl-fixed dt { color: var(--muted); }
.tpl-fixed dd { margin: 0; min-width: 0; }
.tpl-btn { border: 1px solid var(--line-2); border-radius: var(--r); padding: var(--s4); margin: 0 0 var(--s3); min-width: 0; }
.tpl-btn__legend { padding: 0 var(--s2); font-weight: 600; }
.tpl-btn__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--s3); }
.tpl-btn__end { margin: var(--s3) 0 0; }
@media (max-width: 900px) {
    .tpl-edit { grid-template-columns: minmax(0, 1fr); }
    .tpl-edit__preview { position: static; }
}
/* A voice agent's WhatsApp messages (2026-10-07): the list in the studio's tools panel, and one card per
   template on the page that chooses them. */
.wa-tools { margin: 0 0 var(--s3); padding-inline-start: var(--s5); display: grid; gap: var(--s2); }
.wa-pick { margin-bottom: var(--s3); }
.wa-pick .muted { overflow-wrap: anywhere; }
/* ==== END SECTION whatsapp-templates ==== */
