/* ============================================================
   Jobs Scraper — design system
   Palette & feel ported from the single-user "Israel Job Tracker"
   (dashboard.php): navy header, #2E75B5 accent, colour-coded
   statuses, pill tabs, soft cards. Layered over Bootstrap 5.3 by
   retinting its component CSS variables (no recompile, no build).
   ============================================================ */
:root {
    --bg:#f4f6f8; --surface:#fff; --header:#1a1a2e;
    --accent:#2E75B5; --accent-dk:#255f9e;
    --text:#1a1a2e; --muted:#6b7280; --border:#e5e7eb;
    --radius:8px; --shadow:0 1px 4px rgba(0,0,0,.08);
    /* --c-new was --accent (#2E75B5), i.e. the same blue as the navbar, every
       link and the search card — so on a board of new jobs the one thing that
       varies per card was tinted like the chrome (Raz: "for new jobs there is
       plenty of blue on the screen"). Violet is what the "New jobs" TAB has
       always been (.jobtab-to_review, #7c3aed); the badge simply now agrees
       with it. White on #7c3aed is 5.6:1, better than the blue's 4.85:1. */
    --c-new:#7c3aed; --c-notified:#c0550c; --c-applied:#16a34a;
    --c-interviewing:#d97706; --c-discarded:#9ca3af;
    --contact:#0a66c2; --contact-dk:#08528f; --cv:#7c3aed;

    /* ── Three CTAs that must each read as their own thing (2026-07-31) ──
       The page was one shade of blue, so the two cards that carry the whole
       product (search, and the daily email) looked like ordinary content, and
       the guest entrance on /login read as a footnote. Each now owns a colour:
         --find   blue   — the search card ("Find jobs"), the primary action
         --alert  amber  — the daily-email card, a warm secondary offer
         --guest  green  — "browse without an account", the way IN for a
                           visitor with no invite
       `-ink` is the text colour that sits ON the solid fill: white in light
       mode, near-black in dark (where the fills are lightened, not darkened).
       Every one is re-tinted in the [data-bs-theme="dark"] block below. */
    --find:#2E75B5;  --find-dk:#255f9e;  --find-wash:#eaf2fb;
    /* -ink rides ON the solid fill (white on #2E75B5 = 4.85:1); -wash-on is the
       deeper wash a search bar takes while a filter is actually applied. */
    --find-ink:#fff; --find-wash-on:#d9e8f9;
    /* The hero card is a SOLID blue panel, so its own fill and its own CTA are
       a separate pair from --find (which frames the quieter results bar). */
    --find-card-a:#3c86c9; --find-card-b:#255f9e;
    --find-cta:#10395e; --find-cta-dk:#0a2843; --find-cta-ink:#fff;
    --alert:#c2410c; --alert-dk:#9a3412; --alert-wash:#fff5ec; --alert-ink:#fff;
    --guest:#15803d; --guest-dk:#116634; --guest-ink:#fff;

    /* ── One colour per card action (2026-08-05, Raz: "colour the buttons
       nicely, different colour for each button, for both registered and
       unregistered users") ────────────────────────────────────────────────
       The card footer carried four controls in two shades of the same blue,
       so nothing in it had a shape you could learn. Each action now owns a
       hue — and only APPLY is solid, because only one of them is the primary
       action. The rest are border+text on --surface, which is both calmer
       than four filled buttons in a row and far easier to keep above AA:
       every value below is measured on --surface in its own theme.
         --act-save     rose   ♡ Save        (light 6.2:1 / dark 5.8:1)
         --act-detail   slate  Details       (light 7.6:1 / dark 7.7:1)
         --act-contact  blue   👤 Contact    (light 5.7:1 / dark 7.0:1)
         --act-board    green  + Add to board(light 5.0:1 / dark 9.2:1)
       ⚠ --act-contact is a SEPARATE token from --contact, which is a solid
       FILL under white text (.btn-contact, .pnl-foot .btn-contact). Lightening
       --contact for dark mode would have put white on a pale blue; a token
       used as ink and a token used as a fill cannot be the same token. */
    --act-save:#be123c;    --act-save-wash:rgba(190,18,60,.08);
    --act-detail:#475569;  --act-detail-wash:rgba(71,85,105,.08);
    --act-contact:#0a66c2; --act-contact-wash:rgba(10,102,194,.08);
    --act-board:#15803d;   --act-board-wash:rgba(21,128,61,.08);

    /* Retint Bootstrap primary + links to the accent blue */
    --bs-primary:#2E75B5; --bs-primary-rgb:46,117,181;
    --bs-link-color:#2E75B5; --bs-link-color-rgb:46,117,181;
    --bs-link-hover-color:#255f9e; --bs-link-hover-color-rgb:37,95,158;
}

body {
    background:var(--bg);
    color:var(--text);
    font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
}

/* ── Layout: centred, width-constrained main (single-user `.main`) ── */
.app-main {
    max-width:1120px;
    margin:0 auto;
    padding:24px 16px;
}
@media (max-width:768px) { .app-main { padding:16px 10px; } }

/* ── Buttons: accent-tinted primary/outline ─────────────── */
.btn-primary {
    --bs-btn-bg:var(--accent); --bs-btn-border-color:var(--accent);
    --bs-btn-hover-bg:var(--accent-dk); --bs-btn-hover-border-color:var(--accent-dk);
    --bs-btn-active-bg:var(--accent-dk); --bs-btn-active-border-color:var(--accent-dk);
    --bs-btn-disabled-bg:var(--accent); --bs-btn-disabled-border-color:var(--accent);
}
.btn-outline-primary {
    --bs-btn-color:var(--accent); --bs-btn-border-color:var(--accent);
    --bs-btn-hover-bg:var(--accent); --bs-btn-hover-border-color:var(--accent);
    --bs-btn-active-bg:var(--accent); --bs-btn-active-border-color:var(--accent);
}
.form-control:focus, .form-select:focus, .chips-control:focus-within {
    border-color:var(--accent);
    box-shadow:0 0 0 .2rem rgba(46,117,181,.18);
}
.form-check-input:checked { background-color:var(--accent); border-color:var(--accent); }
.form-check-input:focus { border-color:var(--accent); box-shadow:0 0 0 .2rem rgba(46,117,181,.18); }
.text-bg-primary, .badge.text-bg-primary { background-color:var(--accent) !important; }

/* ── Navbar: navy header ────────────────────────────────── */
.navbar.bg-dark {
    background-color:var(--header) !important;
    box-shadow:0 1px 0 rgba(0,0,0,.2);
}
/* Constrain the navbar's content to the same centred column as `.app-main`
   (max-width 1120px, 16px gutters) so the brand + Dashboard/Settings/Admin
   links align with the dashboard table. The bar itself stays full-bleed. */
.navbar > .container-fluid {
    max-width:1120px;
    margin-inline:auto;
    padding-inline:16px;
}
@media (max-width:768px) {
    .navbar > .container-fluid { padding-inline:10px; }
}
.navbar-dark {
    --bs-navbar-color:#c7d0e0;
    --bs-navbar-hover-color:#ffffff;
    --bs-navbar-active-color:#ffffff;
    --bs-navbar-brand-color:#ffffff;
    --bs-navbar-brand-hover-color:#ffffff;
}
.navbar .navbar-brand { font-weight:700; letter-spacing:-.3px; }
.navbar .navbar-text { color:#8899b0 !important; }

/* ── The centred context strip (2026-08-05, Raz) ───────────────────────────
   What replaced `🇮🇱 Jobs Scraper`. Either the two-mode switch or the current
   screen's name — see app/nav_context.py.

   ⚠ ABSOLUTELY POSITIONED, PINNED TO THE TOP ROW, not vertically centred in
   its parent. `.container-fluid` is a wrapping flex row, so on a phone it grows
   to hold the EXPANDED hamburger menu — anything centred with `top:50%` would
   slide down into the middle of that menu the moment it opened. `top:0` plus a
   fixed height keeps it on the bar itself whatever the menu is doing.

   ⚠ It STRETCHES and centres its child, rather than being offset to the middle
   itself. The obvious way to centre — a 50% physical inset plus a negative-50%
   translate — pairs an offset that FLIPS with `dir` against a transform that
   does not, which is why the RTL lint rejects it; in Hebrew, the default locale
   here, getting that pair wrong lands the control off-centre. `inset-inline:0`
   + `justify-content:center` needs no transform and no `[dir="rtl"]` mirror at
   all: it is true page-centre in both directions. `pointer-events` keeps the
   stretched box from swallowing taps meant for the bar underneath it. */
.navbar > .container-fluid { position:relative; min-height:54px; }
.navctx {
    position:absolute; inset-inline:0; top:0; height:54px;
    display:flex; align-items:center; justify-content:center;
    pointer-events:none;           /* the empty half of the bar is not a target … */
}
.navctx > * {
    pointer-events:auto;           /* … but the control itself is */
    max-width:calc(100% - 96px);   /* never grow under the hamburger */
}

/* The screen's own name, on the screens that are not one of the two modes. */
.navctx-t {
    color:#fff; font-weight:700; font-size:16px; letter-spacing:-.2px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}

/* The two modes as a segmented control. A switch rather than two links because
   the point Raz was making is that these are ALTERNATIVES — a user who does not
   realise the board is a filtered view of the pool needs to see the other side
   of the choice, not just a link to somewhere else. */
.navsw {
    display:flex; align-items:center; gap:3px;
    background:rgba(255,255,255,.07);
    border:1px solid rgba(255,255,255,.16);
    border-radius:999px; padding:3px;
    max-width:100%;
}
.navsw-b {
    display:inline-flex; align-items:center; justify-content:center;
    padding:6px 14px; border-radius:999px;
    font-size:14px; font-weight:600; text-decoration:none;
    color:#c7d0e0; white-space:nowrap;
    overflow:hidden; text-overflow:ellipsis;
}
.navsw-b:hover, .navsw-b:focus-visible { color:#fff; background:rgba(255,255,255,.10); }
/* White on #2E75B5 is 4.85:1. The navbar is a fixed dark surface in BOTH
   themes (--header is #1a1a2e / #0b0f1a), so this pair is stated outright
   rather than tokenised — a token that re-tints for dark mode would lighten
   this fill under white text and break it. */
.navsw-b.is-on { background:#2E75B5; color:#fff; }
.navsw-b.is-on:hover { background:#2E75B5; color:#fff; }

/* ── The same switch, at PAGE level on PC (2026-08-07, Raz) ────────────────
   "On PC, the upper bar seems too dense … consider moving the Board/Search All
   Jobs toggle to the same level as Good Evening Raz0258 and Change Rolls and
   Areas. Make sure it is properly noticeable."

   Measured at 1280 before this: eight controls on one 70px bar, the switch
   wedged between a cluster ending 18px away and a 238px hole.

   ⚠ EXACTLY ONE COPY IS EVER VISIBLE. Both display rules live here, together,
   in source order — `.navsw-nav`/`.navsw-page` are 0-1-0 like `.navsw` itself
   and a media query adds NO specificity, so position is the only thing
   deciding, and splitting them across the file is how this stylesheet has
   silently lost six previous rules. Phones keep the navbar copy: that bar was
   cut from three rows to one on 2026-08-05c and has no page-level room.

   ⚠ IT NEEDS ITS OWN PALETTE, not the navbar's. `.navsw` is painted for a
   fixed dark surface — `rgba(255,255,255,.07)` on a #c7d0e0 label — and
   dropping that onto the light page body renders near-white text on near-white
   background. Tokens here, so it follows the theme; the ON state keeps the
   stated #2E75B5 + white (4.85:1) so both copies mark the current mode
   identically and neither moves when --accent re-tints for dark. */
.navsw-page { display:none; }
@media (min-width:768px) {
    .navsw-nav { display:none; }
    .navsw-page { display:flex; }
}
.navsw-page {
    background:var(--surface); border:1px solid var(--border);
    flex:0 0 auto;
}
/* 0-2-0 and 0-3-0, so these beat the navbar rules above on SPECIFICITY rather
   than on position — the lesson from `.navctx-guest > .navctx-t`. */
.navsw-page .navsw-b { color:var(--text); font-weight:700; }
.navsw-page .navsw-b:hover,
.navsw-page .navsw-b:focus-visible { color:var(--accent); background:var(--bg); }
.navsw-page .navsw-b.is-on,
.navsw-page .navsw-b.is-on:hover { background:#2E75B5; color:#fff; }

/* The default page-level slot (base.html), used by every switch-mode screen
   EXCEPT the dashboard — which puts the control on its greeting row instead.
   Hidden wholesale on phones so its margin cannot leave a gap where the
   control is display:none. */
.modebar { display:none; }
@media (min-width:768px) {
    .modebar { display:block; margin-block-end:16px; }
}
/* ⚠ The switch makes the dashboard greeting a THREE-child space-between row.
   Without a child that absorbs the slack, space-between scatters all three
   across the full 1088px — the 2026-08-06 board-card-title defect, one row up.
   The name block is the one that should grow, so it is the one that says so. */
.dash-greet-who { flex:1 1 auto; min-width:0; }

/* ── Top-bar spacing (2026-08-01) ──────────────────────────────────────────
   Raz: "חיפוש משרות♥שמורות6Englishהתחברות" — the guest bar's five controls
   ran together into one string. They were `d-flex gap-1` (4px) with
   `text-white-50`, so there was neither space nor a hover box to tell one
   item from the next, and at 390px it was a solid block.

   The fix is the pattern LinkedIn / Indeed / JobMaster all use: every item is
   its OWN padded, hoverable box (so the gap you see is the padding, and it
   doubles as the tap target), and a hairline rule fences the account actions
   off from the navigation. `.nav-actions` is shared by BOTH bars — signed in
   the same row carries Contact Us / Admin / language / greeting / Logout and
   had exactly the same problem. */
.nav-actions {
    display:flex; align-items:center; flex-wrap:wrap;
    justify-content:flex-end; gap:2px 4px;
}
/* ── The collapsed hamburger menu is a GRID, not a stack (2026-08-01f) ──────
   Raz: "its height is quite high — a table would be nicer and use the space
   better". It was: nine full-width rows (My board, Search, Saved, Settings,
   Contact Us, Admin, language, greeting, Logout) stacked one per line, so
   opening the menu covered the entire phone screen and the content behind it.

   Two columns halves that to five rows without shrinking a single tap target:
   each cell keeps a 46px minimum, which is above the 44px floor. Each is also
   given a visible box — a bare text link centred in a grid cell reads as
   floating, and the boxes are what make it scan as the table Raz asked for.

   Scoped to `#mainNav` on purpose: the GUEST bar shares `.nav-actions` but is
   never collapsed (it has no toggler), and must stay a single inline row. */
@media (max-width:767.98px) {
    #mainNav .navbar-nav {
        display:grid; grid-template-columns:1fr 1fr; gap:6px;
        margin-bottom:6px; padding-block:2px;
    }
    #mainNav .navbar-nav > .nav-item { display:flex; }
    #mainNav .navbar-nav .nav-link {
        flex:1; display:flex; align-items:center; justify-content:center;
        gap:6px; min-height:46px; padding:9px 10px;
        border:1px solid rgba(255,255,255,.14); border-radius:10px;
        background:rgba(255,255,255,.05); color:#d7dee9;
        font-size:14.5px; font-weight:500; text-align:center; line-height:1.2;
    }
    #mainNav .navbar-nav .nav-link:hover,
    #mainNav .navbar-nav .nav-link:focus-visible {
        background:rgba(255,255,255,.12); color:#fff;
    }
    #mainNav .navbar-nav .nav-link.active {
        background:rgba(255,255,255,.17); border-color:rgba(255,255,255,.38);
        color:#fff; font-weight:700;
    }
    /* The greeting is a label, not a destination — full width, above the
       cells, and without the box that says "this is tappable". */
    #mainNav .nav-greet-li {
        grid-column:1 / -1; order:-1;
        justify-content:center; padding-block:2px;
    }
    #mainNav .nav-greet { padding-inline:0; font-size:13px; }
    /* A vertical hairline between grid cells is meaningless — the grid gap
       already groups them, and the rule is a horizontal-row device.
       ⚠ Must be `> .nav-sep-li`, not `.nav-sep-li`: the `> .nav-item` rule
       above is one class MORE specific and was winning, so the separator kept
       an invisible-but-occupied CELL and knocked Logout onto a row of its own
       with a hole beside it. Caught from a screenshot, not from the CSS. */
    #mainNav .navbar-nav > .nav-sep-li { display:none; }
}
/* Bootstrap only zeroes `.nav-link` padding inside `.navbar-nav`, so the two
   bars start from different defaults — state it once for both. */
.navbar .nav-actions .nav-a {
    color:#c7d0e0;
    padding:9px 12px; border-radius:8px;
    font-size:14.5px; font-weight:500; line-height:1.2;
    white-space:nowrap; text-decoration:none;
    transition:background-color .12s ease, color .12s ease;
}
.navbar .nav-actions .nav-a:hover,
.navbar .nav-actions .nav-a:focus-visible {
    color:#fff; background:rgba(255,255,255,.10);
}
.navbar .nav-actions .nav-a.active { color:#fff; font-weight:700; }
/* The rule is the grouping cue; on a phone the row wraps and a floating
   divider mid-air reads as noise, so it goes with the extra width. */
.nav-sep {
    display:none; width:1px; height:20px; flex:0 0 auto;
    background:rgba(255,255,255,.20); margin-inline:8px;
}
.nav-greet { padding-inline:10px; white-space:nowrap; }
@media (min-width:576px) {
    .nav-actions { gap:2px; }
    .nav-sep { display:block; }
}
@media (max-width:575px) {
    /* The brand keeps the flag but stops competing for the row's width. */
    .navbar .navbar-brand { font-size:1rem; }
    .navbar .nav-actions .nav-a { padding:9px 9px; font-size:14px; }
}

/* ── The guest bar becomes ONE row on a phone (2026-08-05c) ─────────────────
   Raz: "I feel we need to improve the screen utilization in the top strip,
   where currently there is only Job Scraper and the Israeli flag … also omit
   the extra row currently captured by the הרשמה button."

   It was taking THREE rows, from two separate wraps with one cause — six
   controls on a bar with room for about three:

     row 1   🇮🇱 Jobs Scraper                    ← `.container-fluid` wraps
     row 2   חיפוש משרות · ♥ שמורות · English · התחברות
     row 3   הרשמה                              ← `.nav-actions` wraps too

   The answer is not to shrink six controls until they fit. Three of them are
   DESTINATIONS (search, saved, sign in) and belong in the bottom tab bar that
   signed-in users have had since 2026-07-30; what is left is the brand, the
   screen's name and one CTA — the three-zone bar every mobile app converges
   on. Desktop is untouched: all six stay on the row they are on today.

   ⚠ Rendered once, CSS picks. The tab bar is phone-only and these links are
   hidden at the same breakpoint, so no destination is ever a target twice on
   one screen — the duplicate-control defect the 2026-08-05 mode switch was
   careful to avoid, and the reason the phone filter sheet does not duplicate
   its pickers either. */
.navctx-guest { display:none; }
.lang-short { display:none; }
@media (max-width:767.98px) {
    /* ⚠ IN FLOW, NOT ABSOLUTE — the one place this differs from the signed-in
       strip, and a screenshot is what forced it. Absolute page-centring works
       there because the bar's side zones are a hamburger and nothing; here
       they are the brand and ~130px of actions, so true page-centre put the
       title hard against the brand and the two read as one string —
       "Jobs Scraperחיפוש משרות", the exact run-on the 2026-08-01 spacing pass
       was about. As a flex child between them it is centred in the space that
       is actually free, and it cannot collide however long either side gets.

       ⚠ `flex:1 1 0`, NOT `1 1 auto`. With a content-sized basis the title
       ASKS for its full width, the row overflows, and `.container-fluid`
       (flex-wrap:wrap) breaks it onto a second line — i.e. exactly the
       three-row bar this whole change exists to remove, measured at 114px
       tall again. A zero basis means it only ever takes what is left over. */
    .navctx-guest {
        display:flex; position:static; flex:1 1 0; min-width:0;
        padding-inline:8px; pointer-events:auto;
    }
    /* ⚠ `.navctx-guest > .navctx-t`, not `.navctx-guest > *`: the signed-in
       strip narrows `.navctx > *` to `calc(100% - 60px)` in a media query
       ~2,300 lines BELOW this one to keep the switch clear of the hamburger,
       and at equal specificity the later rule wins — it was clipping the guest
       title to 61px of a 91px label at 320px while the zone had 121px free.
       Winning on specificity instead makes this immune to source order.
       (Fifth appearance of the "a media query adds no specificity" trap.) */
    .navctx-guest > .navctx-t { max-width:100%; font-size:15px; }
    /* ⚠ The trade-off, stated rather than buried: the WORDMARK goes on phones
       and the flag stays as the home button. There is not room for both a
       137px wordmark and a screen name, and of the two only the screen name
       changes — the product's name is on the landing hero, the page <title>,
       the app icon and the install prompt, while "which screen am I on" is
       answerable nowhere else. The signed-in bar has had no wordmark at any
       width since 2026-08-05 for the same reason. Desktop keeps it. */
    .navbar-brand .brand-word { display:none; }
    /* "On deck" = the bottom tab bar carries this one down here. */
    .nav-ondeck { display:none; }
    /* The language control keeps its box and its tap target, and loses only
       the word. Both spans are aria-hidden; the name comes from aria-label.
       ⚠ Losing the word must not lose the TARGET: measured 35x35 with the
       short label, so the 44px floor is restated rather than inherited from a
       width the text used to provide. */
    .lang-long { display:none; }
    .lang-short { display:inline; }
    .navbar .nav-actions .lang-switch {
        display:inline-flex; align-items:center; justify-content:center;
        min-width:44px; min-height:44px; padding-inline:8px;
    }
    /* The brand is the home affordance and the only other control up here. */
    .navbar .navbar-brand { min-height:44px; }
}

/* ── Bootstrap tab nav (admin) ──────────────────────────── */
.nav-tabs { --bs-nav-tabs-link-active-color:var(--accent); }
.nav-tabs .nav-link { color:var(--muted); }
.nav-tabs .nav-link:hover { color:var(--accent); }
.nav-tabs .nav-link.active { color:var(--accent); font-weight:600; }

/* ── Cards ──────────────────────────────────────────────── */
.card {
    border:1px solid var(--border);
    border-radius:var(--radius);
    box-shadow:var(--shadow);
}
.card-header { background:#fff; border-bottom:1px solid var(--border); }

/* ── Tables ─────────────────────────────────────────────── */
.table thead th {
    font-size:11px; font-weight:600; text-transform:uppercase;
    letter-spacing:.05em; color:var(--muted); background:#f9fafb;
    border-bottom:1px solid var(--border);
}
.table tbody tr.job-row:hover td { background:#f5f8ff; }
.table tbody tr.row-active td { background:#eff6ff; }

/* ── Status badges (colour-coded) ───────────────────────── */
.badge-new          { background-color:var(--c-new) !important;          color:#fff; }
.badge-notified     { background-color:var(--c-notified) !important;     color:#fff; }
.badge-applied      { background-color:var(--c-applied) !important;      color:#fff; }
.badge-interviewing { background-color:var(--c-interviewing) !important; color:#fff; }
.badge-discarded    { background-color:var(--c-discarded) !important;    color:#fff; }

/* Source badge */
.badge-source { background:#eff6ff; color:#2563eb; font-size:0.75em; font-weight:500; }

/* ── Colourful pill status tabs (dashboard) ─────────────── */
.jobtabs { display:flex; flex-wrap:wrap; gap:8px; }
.jobtab {
    display:inline-flex; align-items:center; gap:6px; padding:7px 15px;
    border-radius:20px; font-size:13px; font-weight:600; text-decoration:none;
    color:#fff; border:2px solid transparent; transition:.15s; white-space:nowrap;
}
.jobtab:hover { color:#fff; opacity:.92; transform:translateY(-1px); }
.jobtab.active { box-shadow:0 0 0 3px rgba(255,255,255,.55) inset, 0 2px 6px rgba(0,0,0,.15); }
.jobtab .n {
    background:rgba(255,255,255,.28); border-radius:10px;
    padding:1px 8px; font-size:11px; font-weight:700;
}
/* Three tabs since 2026-07-30. The legacy keys keep their colours so an old
   bookmarked URL still renders a sane pill rather than an unstyled one. */
.jobtab-to_review    { background:#7c3aed; }
.jobtab-applied      { background:var(--c-applied); }
.jobtab-all          { background:#475569; }
.jobtab-discarded    { background:var(--c-discarded); }
.jobtab-needs_review { background:#7c3aed; }
.jobtab-notified     { background:var(--c-notified); }
.jobtab-interviewing { background:var(--c-interviewing); }

/* ── Board filter bar (role · location · search) ────────── */
/* It used to be "one quiet strip so the colourful tabs stay the primary
   control". Raz's call (2026-07-31): searching IS the application, so the strip
   carries the `--find` blue identity — the same frame + wash as the public
   `.jsearch-bar`, so the search control looks identical on both surfaces
   whether you're signed in or not. The status tabs keep their own colours;
   they answer a different question (which pile), not "find me something".
   Padding drops 1px to absorb the thicker border and hold the outer size. */
.filter-bar {
    display:flex; flex-wrap:wrap; align-items:center; gap:10px;
    padding:9px 11px; margin-bottom:14px;
    background:var(--find-wash); border:2px solid var(--find);
    border-radius:var(--radius); box-shadow:0 3px 12px rgba(46,117,181,.15);
}
/* "A facet is applied" was signalled by tinting a white bar — which no longer
   distinguishes anything now the bar starts tinted. It gets a deeper wash and a
   ring instead, so the at-a-glance answer to "why am I only seeing 12 jobs?"
   survives. The chips below say WHICH; this only says THAT. */
.filter-bar-on {
    background:var(--find-wash-on);
    box-shadow:0 0 0 3px rgba(46,117,181,.18), 0 3px 12px rgba(46,117,181,.15);
}
.filter-field { display:flex; flex-direction:column; gap:3px; min-width:0; }
.filter-field-search { flex:1 1 14rem; max-width:22rem; }
/* The "Go" submit was `.btn-outline-secondary`: grey text on a transparent
   background, which composited against the new tinted bar at 2.53:1 in dark —
   the search card's own submit had become the least readable thing in it. Solid
   --find instead (white on it measures 4.85:1), which is also what it is: the
   action of the card. Retinted through Bootstrap's own button variables, the
   convention used at the top of this file. */
.filter-bar .input-group .btn {
    --bs-btn-color:var(--find-ink); --bs-btn-bg:var(--find);
    --bs-btn-border-color:var(--find-dk);
    --bs-btn-hover-color:var(--find-ink); --bs-btn-hover-bg:var(--find-dk);
    --bs-btn-hover-border-color:var(--find-dk);
    font-weight:600;
}
/* An escape hatch from a filter you didn't mean to stack — it has to be findable
   without being a second CTA. Darker than --accent, which is 4.48:1 on --bg. */
.filter-clear {
    margin-inline-start:auto; align-self:center; font-size:13px; font-weight:700;
    color:#1f5286; text-decoration:none; white-space:nowrap;
}
.filter-clear:hover { color:#12395f; text-decoration:underline; }

/* Multi-select dropdown built on <details> so it works without JS.
   Every surface here uses the design tokens (--surface/--text/--border), never a
   literal #fff: the app follows the OS dark mode, and a hardcoded white panel
   would render near-white --text on white and vanish. The few hand-coloured
   surfaces (menu foot, hover, accent tints) get explicit dark overrides below,
   matching the convention in the dark-mode block. */
.filter-drop { position:relative; }
.filter-summary {
    display:inline-flex; align-items:center; gap:8px; cursor:pointer;
    padding:8px 13px; font-size:13.5px; line-height:1.4; white-space:nowrap;
    color:var(--text); background:var(--surface);
    border:1px solid var(--border); border-radius:6px;
    list-style:none;
}
.filter-summary::-webkit-details-marker { display:none; }
.filter-summary:hover { border-color:var(--accent); }
.filter-summary:focus-visible {
    outline:none; border-color:var(--accent);
    box-shadow:0 0 0 .2rem rgba(46,117,181,.25);
}
.filter-summary-label { font-weight:600; color:var(--text); }
.filter-summary-value { color:#4b5563; }
/* An APPLIED filter pill goes solid, the way every job board does it (LinkedIn,
   Indeed, Glassdoor): a faint tint on an already-tinted bar reads as "hover",
   not as "this is on". White on --find measures 4.85:1, and --find-ink flips to
   near-black in dark mode where the fill is the lightened blue. */
.filter-summary.is-on {
    border-color:var(--find-dk); background:var(--find);
    color:var(--find-ink); font-weight:700;
}
.filter-summary.is-on .filter-summary-label,
.filter-summary.is-on .filter-summary-value,
.filter-summary.is-on .filter-caret { color:var(--find-ink); font-weight:700; }
.filter-summary.is-on:hover { background:var(--find-dk); }
.filter-caret { font-size:10px; color:#4b5563; }
.filter-drop[open] .filter-caret { transform:rotate(180deg); }

.filter-menu {
    position:absolute; z-index:1030; top:calc(100% + 6px); inset-inline-start:0;
    min-width:15rem; max-width:min(22rem, calc(100vw - 32px));
    color:var(--text); background:var(--surface);
    border:1px solid var(--border); border-radius:var(--radius);
    box-shadow:0 8px 24px rgba(0,0,0,.22);
}
.filter-menu-scroll { max-height:min(50vh, 22rem); overflow-y:auto; padding:8px 4px; }
.filter-group {
    margin:10px 10px 3px; font-size:11px; font-weight:700; text-transform:uppercase;
    letter-spacing:.05em; color:#4b5563;
}
.filter-group:first-child { margin-top:2px; }
.filter-opt {
    display:flex; align-items:center; gap:9px; cursor:pointer;
    padding:6px 10px; margin:0 4px; font-size:14px; color:var(--text);
    border-radius:5px;
}
.filter-opt:hover { background:#eef1f5; }
.filter-opt input { flex:0 0 auto; accent-color:var(--accent); }
/* How many jobs this option would show. Only the status picker carries one —
   the role and location pickers deliberately do not, because a per-option count
   there needs a description-scanning query per role on every page load (logged
   as "deliberate" on 2026-07-26). Status counts are already computed for the
   tab pills, so these are free. */
.filter-opt-n {
    margin-inline-start:6px; font-size:12.5px; font-weight:700;
    color:#4b5563;
}
[data-bs-theme="dark"] .filter-opt-n { color:var(--muted); }

/* ⚠ The STATUS picker is phone-only (2026-08-05, Raz's call: the desktop keeps
   its pills). An ID selector — specificity 1-0-0 — so it beats `.filter-drop`
   from anywhere in the file: this rule sits at line ~450 while the phone sheet
   it belongs to is at the very end, and a media query adds no specificity. */
@media (min-width:768px) { #drop-st { display:none; } }
.filter-menu-foot {
    display:flex; gap:8px; padding:8px 10px;
    border-top:1px solid var(--border); background:#f4f6f8;
    border-radius:0 0 var(--radius) var(--radius);
}

/* Active facets — one chip each, click to remove that one.
   THESE ARE THE USER'S OWN CHOICES, and a pale 12px tinted pill said otherwise:
   the board was filtered down to a fraction of itself and the reason for it was
   the faintest thing on the page. They are now SOLID pills (2026-07-31), the
   applied-filter convention on every major job board, big enough to read at a
   glance and to hit on a phone. Shared, deliberately, with the public results
   page's `.rfilter` — same object, same look, wherever you meet it. */
.active-filters {
    display:flex; flex-wrap:wrap; align-items:center; gap:7px;
    margin:-4px 2px 14px; font-size:13px;
}
.active-filters-label {
    color:#4b5563; font-size:11px; font-weight:700;
    text-transform:uppercase; letter-spacing:.06em;
}
.afilter {
    display:inline-flex; align-items:center; gap:8px; padding:6px 13px;
    font-size:13px; font-weight:700; text-decoration:none; border-radius:999px;
    color:var(--find-ink); background:var(--find);
    border:1px solid var(--find-dk);
}
/* The whole chip is the remove link, so hover darkens the chip rather than
   tinting the ✕ — a translucent patch behind white text would drop it under
   4.5:1, which is exactly what `opacity` on the old ✕ was doing. */
.afilter:hover { background:var(--find-dk); border-color:var(--find-dk); }
.afilter-x { font-size:12px; font-weight:700; }

/* Dark mode: re-tint only the hand-coloured surfaces above.
   The filter bar, the applied pill and the chips are now driven by the --find
   token family, which is already re-tinted at the top of this file — so their
   old dark overrides were DELETED rather than updated. Left in place they would
   have won on specificity (an attribute selector plus the class beats the class
   alone) and quietly restored the pale version in dark mode only. */
[data-bs-theme="dark"] .filter-summary-value,
[data-bs-theme="dark"] .filter-caret,
[data-bs-theme="dark"] .filter-group,
[data-bs-theme="dark"] .active-filters-label { color:var(--muted); }
[data-bs-theme="dark"] .filter-opt:hover     { background:#26324a; }
[data-bs-theme="dark"] .filter-menu-foot     { background:#141b28; }
/* The ring on an active bar is an accent-alpha literal, so it needs its own. */
[data-bs-theme="dark"] .filter-bar-on {
    box-shadow:0 0 0 3px rgba(77,148,214,.28), 0 3px 12px rgba(0,0,0,.35);
}
/* "Clear filters" is an action, not furniture — it keeps a link colour here
   rather than joining the muted group above. */
[data-bs-theme="dark"] .filter-clear       { color:#7fb5e6; }
[data-bs-theme="dark"] .filter-clear:hover { color:#cfe4f7; }

@media (max-width:575px) {
    /* Phone: the search box takes its own row; the two pickers share one. */
    .filter-bar { gap:8px; }
    .filter-field-search { flex:1 1 100%; max-width:none; }
    .filter-drop { flex:1 1 45%; }
    .filter-summary { width:100%; justify-content:space-between; }
    .filter-menu { min-width:min(18rem, calc(100vw - 32px)); }
    .filter-clear { margin-inline-start:0; align-self:flex-start; }
}

/* ── Action buttons: Contact (LinkedIn) ─────────────────── */
.btn-contact {
    --bs-btn-bg:var(--contact); --bs-btn-border-color:var(--contact);
    --bs-btn-hover-bg:var(--contact-dk); --bs-btn-hover-border-color:var(--contact-dk);
    --bs-btn-active-bg:var(--contact-dk); --bs-btn-active-border-color:var(--contact-dk);
    --bs-btn-color:#fff; --bs-btn-hover-color:#fff; --bs-btn-active-color:#fff;
}

/* ── Dashboard job table (single-user "Israel Job Tracker" parity) ── */
.result-bar { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:0 2px 10px; }
/* #4b5563, not --muted/#9ca3af: on the page background those measured 4.46:1
   and 2.34:1 — the row hint was effectively unreadable. Dark mode re-tints below. */
.result-info { font-size:13px; color:#4b5563; }
.result-hint { font-size:12px; color:#4b5563; }
[data-bs-theme="dark"] .result-info,
[data-bs-theme="dark"] .result-hint { color:var(--muted); }

.tbl-wrap {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); box-shadow:var(--shadow); overflow:hidden;
}
.tbl { width:100%; border-collapse:collapse; }
.tbl th {
    padding:11px 16px; text-align:start; font-size:11px; font-weight:600;
    text-transform:uppercase; letter-spacing:.05em; color:var(--muted);
    background:#f9fafb; border-bottom:1px solid var(--border);
}
.tbl td { padding:13px 16px; border-bottom:1px solid var(--border); vertical-align:middle; }
.tbl tr:last-child td { border-bottom:none; }
.tbl tbody tr { transition:background .1s; }
.tbl tbody tr.job-row { cursor:pointer; }
.tbl tbody tr:hover td { background:#f5f8ff; }
.tbl tbody tr.row-active td { background:#eff6ff; }
.tbl tr.updated td { animation:flash .7s ease-out; }
@keyframes flash { 0%{background:#dbeafe;} 100%{background:transparent;} }

.job-link { font-weight:600; color:var(--text); text-decoration:none; font-size:14px; }
.job-link:hover { color:var(--accent); }
.job-meta { font-size:12px; color:var(--muted); margin-top:2px; }
.meta-co { color:var(--muted); text-decoration:none; }
.meta-co:hover { color:var(--accent); text-decoration:underline; }

.src-badge {
    font-size:11px; padding:3px 8px; border-radius:10px;
    background:#eff6ff; color:#2563eb; font-weight:500; white-space:nowrap;
}

.status-cell { display:flex; flex-direction:column; gap:5px; margin-bottom:7px; align-items:flex-start; }
.status-badge {
    display:inline-flex; align-items:center; gap:3px; padding:3px 9px;
    border-radius:12px; font-size:11px; font-weight:600; color:#fff; white-space:nowrap;
}
.status-hint { font-size:11px; color:var(--muted); font-style:italic; }
.status-sel {
    border:1px solid var(--border); border-radius:6px; padding:5px 8px;
    font-size:12px; background:#fff; color:var(--text); cursor:pointer;
    outline:none; min-width:150px;
}
.status-sel:focus { border-color:var(--accent); box-shadow:0 0 0 .15rem rgba(46,117,181,.18); }
.status-sel:disabled { opacity:.45; cursor:not-allowed; }

/* ── The status control carries its own status's COLOUR (2026-08-01f) ───────
   Raz: "consider adding more colour to the status bar". On a phone the badge
   is now hidden and this select is the only thing on the card that says where
   the job stands — as a plain grey field it read as chrome, and three cards
   deep you could not tell an applied job from a new one without reading.

   It keys off `data-status`, not `:checked`/`value` — CSS cannot read a
   <select>'s value, so app.js repaints the attribute after every change (both
   on the card select and on the panel's, which mirror each other).
   Every literal here has a [data-bs-theme="dark"] counterpart below; the
   attribute selector outranks the plain `.status-sel` dark rule either way. */
.status-sel[data-status="new"],
.status-sel-lg[data-status="new"]       { border-color:#7c3aed; background:#f6f3ff; color:#5b21b6; font-weight:600; }
.status-sel[data-status="applied"],
.status-sel-lg[data-status="applied"]   { border-color:#16a34a; background:#f0fdf4; color:#15803d; font-weight:600; }
.status-sel[data-status="discarded"],
.status-sel-lg[data-status="discarded"] { border-color:#9ca3af; background:#f4f5f7; color:#4b5563; font-weight:600; }

/* ── The OPEN option list is NOT the closed control (2026-08-08) ────────────
   Raz, PC_QA.docx: "fonts on the job status dropdown are hardly visible."
   Chromium paints a native <option> list from the SELECT's own computed
   `background-color` and `color`. The rules above set a TRANSLUCENT tint
   (`rgba(124,58,237,.18)`) so the CLOSED control reads as a coloured field on
   --surface — but the popup composites that tint over the browser's own light
   popup base, not over --surface, so the wash turns pale while the text stays
   #d8c8ff. Measured ~1.3:1; the closed control is the 7.4:1 the block above
   records, and it was the only half anyone could see.

   ⚠ A NATIVE POPUP IS DRAWN OUTSIDE THE DOM, so `contrast_audit.py` is
   structurally blind to it — it can measure the control and never the list.
   That is why a component with a measured, documented ratio still shipped
   unreadable, and why every option states an OPAQUE pair of its own here.
   Applies to every styled select in the app, not just the status one: a
   translucent or tinted select background is the trigger, wherever it appears. */
.status-sel option, .status-sel-lg option,
.form-select option, .form-select optgroup {
    background-color: var(--surface);
    color: var(--text);
}

.actions { display:flex; gap:6px; flex-wrap:wrap; }
.tbl .btn {
    display:inline-flex; align-items:center; gap:4px; padding:5px 11px;
    border-radius:6px; font-size:12px; font-weight:600; text-decoration:none;
    border:none; cursor:pointer; transition:.15s; white-space:nowrap; color:#fff;
}
.tbl .btn:hover { opacity:.85; color:#fff; }
.tbl .btn-apply { background:var(--accent); }
.tbl .btn-contact { background:var(--contact); }

.date-col { color:var(--muted); font-size:13px; white-space:nowrap; }
.id-cell { color:var(--muted); font-size:12px; font-variant-numeric:tabular-nums; white-space:nowrap; }
.tbl .col-id { width:44px; }

/* Sortable column headers */
.tbl th .sort-link {
    display:inline-flex; align-items:center; gap:4px; color:inherit;
    text-decoration:none; cursor:pointer; font:inherit; letter-spacing:inherit;
    text-transform:inherit; white-space:nowrap;
}
.tbl th .sort-link:hover { color:var(--accent); }
.tbl th.sorted .sort-link { color:var(--accent); }
.sort-arrow { font-size:9px; opacity:.5; }
.tbl th.sorted .sort-arrow { opacity:1; }

/* Row-select checkbox column */
.tbl .col-check { width:36px; text-align:center; padding-inline-start:12px; padding-inline-end:0; }
.tbl .col-check input, #check-all { width:15px; height:15px; cursor:pointer; accent-color:var(--accent); }
.tbl tbody tr.row-selected td { background:#eaf2fd; }

/* Bulk-action bar (revealed once ≥1 row is checked) */
.bulk-bar {
    position:sticky; top:64px; z-index:20;
    display:flex; align-items:center; gap:10px; flex-wrap:wrap;
    margin-bottom:12px; padding:9px 14px;
    background:var(--surface); border:1px solid var(--accent);
    border-radius:var(--radius); box-shadow:var(--shadow);
}
.bulk-count { font-size:13px; color:var(--text); }
.bulk-label { font-size:12px; color:var(--muted); margin:0; }
.bulk-bar .status-sel { min-width:160px; }

.empty-tbl { text-align:center; padding:60px 20px; color:var(--muted); }
.empty-tbl .empty-icon { font-size:42px; display:block; margin-bottom:12px; }
.empty-tbl h3 { font-size:16px; color:var(--text); margin-bottom:6px; }

/* ── The board becomes CARDS on phones (Phase D, 2026-07-30) ──────────────
   Done purely in CSS on the SAME DOM, deliberately: every row already carries
   `id="row-N"`, `data-*` and the classes app.js binds to (.job-row click →
   panel, .status-sel, .row-check, [data-status-badge]). Rendering a second
   card markup would duplicate those ids and break the handlers, so the table
   is re-laid-out as a grid instead — zero JS changes, zero duplicated DOM.

   This also FIXES a real mobile defect: the old rules hid Source and Found at
   768px and the whole Actions cell below 500px, so on a phone there was no
   Apply button at all. The card has room for all of it. */
@media (max-width:768px) {
    .tbl-wrap {
        border:none; background:none; box-shadow:none;
        overflow:visible; border-radius:0;
    }
    .tbl, .tbl tbody { display:block; width:100%; }
    .tbl thead { display:none; }

    .tbl tbody tr {
        display:grid;
        grid-template-columns:auto 1fr;
        align-items:center;
        gap:6px 10px;
        background:var(--surface);
        border:1px solid var(--border);
        border-radius:var(--radius);
        box-shadow:var(--shadow);
        padding:13px 14px;
        margin-bottom:11px;
    }
    .tbl tbody tr:hover td, .tbl tbody tr.row-active td { background:none; }
    .tbl tbody tr.row-active { border-color:var(--accent); }
    .tbl tbody td { display:block; border:none; padding:0; }

    /* Row 1 — select box + the global job id, quiet. */
    .tbl .col-check { grid-column:1; grid-row:1; }
    .tbl .col-id    { grid-column:2; grid-row:1; display:block;
                      font-size:11.5px; color:#4b5563; text-align:start; }
    /* Row 2 — the hero: title + company · city. */
    .tbl .col-job   { grid-column:1 / -1; grid-row:2; }
    .tbl .col-job .job-link { font-size:16px; line-height:1.35; }
    /* Row 3 — provenance, re-shown (was hidden at this width). */
    .tbl .col-src, .tbl .col-date {
        display:inline-block; grid-row:3; font-size:12.5px; color:#4b5563;
    }
    .tbl .col-src  { grid-column:1; }
    .tbl .col-date { grid-column:2; text-align:start; }
    /* Row 4 — status: a native <select> so the OS picker can't clip. */
    .tbl .col-status { grid-column:1 / -1; grid-row:4; }
    .tbl .col-status .status-cell { display:none; }
    .tbl .status-sel { width:100%; margin:0; }
    /* Row 5 — Apply / Contact, restored on mobile. */
    .tbl .col-actions { display:block; grid-column:1 / -1; grid-row:5; }
    .tbl .col-actions .actions { display:flex; gap:8px; }
    .tbl .col-actions .actions > * { flex:1 1 0; text-align:center; }
    /* The explicit affordance is redundant once the whole card is tappable. */
    .tbl .job-open { display:none; }
}

/* ── Slide-in detail panel (single-user "Israel Job Tracker" parity) ── */
#detail-panel, #job-sheet {
    position: fixed;
    top: 0; inset-inline-end: 0;
    height: 100vh;           /* fallback for browsers without dvh */
    height: 100dvh;          /* dynamic vh: excludes mobile browser chrome so the pinned footer stays reachable */
    /* ── The reader's column scales with the screen (2026-08-08) ────────────
       Raz, PC_QA.docx: "consider widening the job's description side-bar."
       It was a flat 460px at every desktop width — 38% of his 1220px window
       and 24% of a 1920 monitor, i.e. the bigger the screen the worse it got.
       A larger CONSTANT just relocates that problem, so the default is
       viewport-relative; --panel-w is what the resizer overrides (app.js
       writes it from localStorage), and the fallback is the default. */
    width: var(--panel-w, clamp(420px, 46vw, 46rem));
    background: var(--surface);
    box-shadow: -4px 0 28px rgba(15,23,42,.15);
    z-index: 1050;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transform: translateX(102%);
    transition: transform 0.26s cubic-bezier(.4,0,.2,1);
}
/* `translateX` is PHYSICAL — it does not flip with `dir`, so the off-screen
   parking spot has to be mirrored by hand. Without this the panel sits at the
   left edge (inset-inline-end in RTL) and then slides RIGHT, i.e. straight into
   the middle of the viewport instead of out of it. The shadow is flipped for the
   same reason: it has to fall towards the content, not off the screen edge. */
[dir="rtl"] #detail-panel, [dir="rtl"] #job-sheet {
    transform: translateX(-102%);
    box-shadow: 4px 0 28px rgba(15,23,42,.15);
}
#detail-panel.open, #job-sheet.open { transform: none; }

/* ── The resize handle (2026-08-08) ────────────────────────────────────────
   Raz asked for "some sort of scroller/drag" on top of a wider default. It is
   a real ARIA window splitter (role="separator" + tabindex), NOT a drag-only
   affordance: WCAG 2.2 SC 2.5.7 (Dragging Movements) requires a single-pointer
   alternative, so app.js also drives it from the arrow keys, and Home resets.

   ⚠ It sits on the panel's INLINE-START edge — the side facing the board —
   which is the left edge in English and the RIGHT edge in Hebrew. The drag
   arithmetic has to mirror with it (see app.js); this is the same physical-vs-
   logical trap as the `translateX` note above, one element over.
   The panel is `position:fixed`, so it is already the containing block. */
.pnl-resizer {
    position: absolute;
    inset-inline-start: 0;
    top: 0;
    bottom: 0;
    width: 10px;
    z-index: 2;
    cursor: col-resize;
    background: transparent;
    border: 0;
    padding: 0;
    /* The grip is a centred bar drawn with a gradient rather than a child
       element, so the hit area stays a comfortable 10px while the visible
       line stays hairline. */
    background-image: linear-gradient(to right, transparent 4px, var(--border) 4px, var(--border) 6px, transparent 6px);
    touch-action: none;   /* we handle the pointer ourselves; without this the browser scrolls instead */
}
.pnl-resizer:hover,
.pnl-resizer:focus-visible {
    background-image: linear-gradient(to right, transparent 3px, var(--accent) 3px, var(--accent) 7px, transparent 7px);
    outline: none;
}
/* While dragging, kill the text selection the gesture would otherwise make
   across the whole page, and stop the panel animating its own width. */
body.pnl-resizing { user-select: none; cursor: col-resize; }
body.pnl-resizing #detail-panel, body.pnl-resizing #job-sheet { transition: none; }

/* Overlay behind panel */
#panel-overlay, #job-sheet-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(15,23,42,.3);
    z-index: 1040;
}
#panel-overlay.visible, #job-sheet-overlay.visible { display: block; }

/* Job row clickable cursor */
tr.job-row { cursor: pointer; }

/* panel header = the muted "context/satellite" section: status + date + why.
   Deliberately low-contrast (no fill, tight spacing) so it recedes and the job
   title + description below it are what catch the eye. */
.pnl-head {
    padding: 12px 20px 13px; border-bottom: 1px solid var(--border);
    display: flex; flex-direction: column; gap: 9px; flex-shrink: 0;
}
.pnl-head-top {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.pnl-head-left { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Status changer, top of the panel. Kept out of the pinned footer so the native
   <select> option list never opens past the bottom of the viewport (the last
   options were being clipped). */
.pnl-status-row { display: flex; align-items: center; gap: 8px; }
.pnl-status-row .status-sel-lg { flex: 1; min-width: 0; }
.pnl-date { font-size: 11px; color: var(--muted); }
.pnl-id { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.pnl-id:empty { display: none; }
.pnl-close {
    background: none; border: 1px solid var(--border); border-radius: 6px;
    cursor: pointer; color: var(--muted); font-size: 20px; line-height: 1;
    padding: 1px 9px; flex-shrink: 0; transition: .15s;
}
.pnl-close:hover { color: var(--text); border-color: #9ca3af; }

/* panel scrollable body */
/* overscroll-behavior-y (NOT the `contain` shorthand): the shorthand sets both
   axes, which also blocks horizontal scroll-chaining. When a user pinch-zooms
   the panel, panning sideways is a horizontal drag that this container can't
   consume (there is no overflow-x) — with the x axis contained the gesture is
   swallowed instead of chaining out to pan the visual viewport, so the zoomed
   content is unreachable. Keep containment on y only (stops the job list behind
   the panel from scrolling when the description hits its end). */
.pnl-body { flex: 1; overflow-y: auto; overscroll-behavior-y: contain; padding: 22px 20px 24px; display: flex; flex-direction: column; gap: 20px; }
.pnl-title { font-size: 23px; font-weight: 800; line-height: 1.25; letter-spacing: -.01em; color: var(--text); }
.pnl-where { font-size: 13.5px; color: var(--muted); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.pnl-section { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding-bottom: 8px; margin-bottom: 10px; border-bottom: 1px solid var(--border); }
.pnl-desc { font-size: 14px; line-height: 1.75; color: #374151; white-space: pre-wrap; word-break: break-word; }
.pnl-no-desc { font-size: 13px; color: #9ca3af; font-style: italic; padding: 20px; text-align: center; background: #f9fafb; border-radius: var(--radius); border: 1px dashed var(--border); }

/* panel footer (pinned) */
.pnl-foot { padding: 16px 20px; padding-bottom: max(16px, env(safe-area-inset-bottom)); border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 12px; flex-shrink: 0; background: #fafbfc; }
.pnl-foot-row { display: flex; align-items: center; gap: 10px; }
.pnl-foot-label { font-size: 12px; color: var(--muted); font-weight: 600; white-space: nowrap; }
.status-sel-lg {
    border: 1px solid var(--border); border-radius: 6px; padding: 7px 10px;
    font-size: 13px; background: #fff; color: var(--text); cursor: pointer;
    outline: none; flex: 1; min-width: 0;
}
.status-sel-lg:focus { border-color: var(--accent); box-shadow: 0 0 0 .15rem rgba(46,117,181,.18); }
.pnl-foot .btn {
    display: inline-flex; align-items: center; gap: 4px; padding: 6px 12px;
    border-radius: 6px; font-size: 13px; font-weight: 600; text-decoration: none;
    border: none; cursor: pointer; transition: .15s; white-space: nowrap; color: #fff;
}
.pnl-foot .btn:hover { opacity: .85; color: #fff; }
/* .pnl-foot .btn-apply moved to the inline-actions block at the end of this
   file, onto --find/--find-ink — `--accent` + white is 3.22:1 in dark. Left
   deleted rather than overridden: a stale rule for a component that has been
   re-tinted is the `.afilter` trap waiting to happen. */
.pnl-foot .btn-contact { background: var(--contact); }

/* Why-this-job-is-on-your-board.
   ⚠ THE RULES THAT WERE HERE ARE DELETED, NOT OVERRIDDEN (2026-08-08f).
   `.pnl-why` / `.pnl-why-icon` / `.pnl-why-text` / `.why-line` / `.why-chip`
   styled a muted 12px satellite line that was "kept quiet on purpose" — which
   is precisely why Raz reported the information as absent from the board. The
   panel now renders the SAME `.jd-why` component the public detail body does
   (see the block near `.jd-head-grid`), so there is one declaration to keep
   readable instead of two that drift. A stale rule for a component that has
   been re-tinted is the `.afilter` trap waiting to happen.

   The only thing the panel needs of its own is the container difference: on
   `/jobs/<id>` the box sits in a two-column grid beside the facts and is
   capped at 290px, while in the panel it is a full-width block.
   ⚠ 0-2-0 against that rule's 0-1-0, so it wins wherever it sits — a media
   query adds NO specificity, and this is the ninth time that has mattered.

   ⚠ `align-self` has to be restated too, and it is not cosmetic. `.jd-why`
   says `align-self:start` because on the detail page it is a GRID item that
   must not stretch to the height of the facts column beside it. `.pnl-body` is
   a flex COLUMN, where the cross axis is horizontal — so the same declaration
   means "shrink to your content's width", and the box measured 249px inside a
   561px panel (184px of a 390px phone sheet) before this line. Same physical-
   vs-logical family as the panel's own `translateX`: one declaration, two
   axes, depending on the container it lands in. */
.pnl-body > .jd-why { max-width: none; margin: 0; align-self: stretch; }

/* ── Settings: role picker ──────────────────────────────── */
.role-picker {
    max-height: 24rem;
    overflow-y: auto;
}
.role-picker .role-domain .btn { text-align: start; }
/* ⚠ `.btn-light` IS A HARDCODED LIGHT SLAB — it does not flip for dark mode.
   Found by the contrast audit while measuring this batch (2026-08-07): the
   domain header is --accent on Bootstrap's #f8f9fa, and on the hover/active
   shade (#d3d4d5) it measures **2.17:1 in dark**, on the one line in the picker
   that has to be readable. Pre-existing — the audit had only ever sampled the
   first domain in its resting state — but this batch is Raz's "all setting
   screens on PC required better UI", and an unreadable category header is that
   complaint's most literal form.

   Stated through `--bs-btn-*` so Bootstrap's own hover/active/focus states
   follow the tokens instead of being overridden one selector at a time — the
   same technique as `.nav-join` and `.jsearch-go`. Tokens, so there is no light
   literal here needing a [data-bs-theme="dark"] counterpart. Dark goes
   2.17 -> 6.5:1; light lands on the documented --accent-on-surface 4.48:1
   palette item, which is Raz's standing call and not this batch's to change. */
.role-picker .role-domain > .btn {
    --bs-btn-bg: var(--surface);        --bs-btn-border-color: var(--border);
    --bs-btn-color: var(--text);
    --bs-btn-hover-bg: var(--bg);       --bs-btn-hover-border-color: var(--accent);
    --bs-btn-hover-color: var(--text);
    --bs-btn-active-bg: var(--bg);      --bs-btn-active-border-color: var(--accent);
    --bs-btn-active-color: var(--text);
}
/* Raz: category headers (e.g. "דאטה ואנליטיקה") need to read as clearly
   heavier than the individual role titles under them — a category groups
   many titles and users couldn't tell the two apart at a glance. `fw-semibold`
   (600) at body size wasn't enough weight/size contrast against a plain
   .form-check-label. Bump the header, quiet the item labels. */
.role-picker .role-domain > .btn .fw-semibold {
    font-size: 15px; font-weight: 800; color: var(--accent);
    letter-spacing: -.01em;
}
.role-picker .form-check-label { font-size: 14px; font-weight: 400; color: var(--text); }
/* The two counts on a domain header (2026-08-07). They sit in one flex box so
   the header stays a TWO-child row — `.role-domain > .btn` is
   `justify-content:space-between`, and a third child there would hand the
   leftover middle to whichever item shrinks to fit, which is exactly how every
   board card ended up starting its title in a different place on 2026-08-06. */
.role-domain-meta { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; }
/* INK ON A SURFACE, not a fill — the same rule the card actions follow. The
   total is context, the selection is the answer, so only the selected badge
   gets a solid colour and the two never compete. Stating --text rather than
   --muted is deliberate: --muted on --bg measures 4.4:1 in LIGHT mode, under
   the 4.5 floor for 12px type, and this is a number Raz asked to be able to
   read. Both tokens flip with the theme, so no dark override is needed. */
.role-domain-total {
    font-weight: 600; color: var(--text);
    background: var(--bg); border: 1px solid var(--border);
}

/* ── Settings: layout + sections ────────────────────────── */
.settings-wrap {
    max-width: 860px;
    margin: 0 auto;
}
.settings-wrap .card-header h2 { line-height: 1.2; }

/* ── The preference screens on PC (2026-08-07, Raz) ────────────────────────
   "I believe all setting screens on PC required better UI. Perhaps larger
   fonts or some improved layout."

   Measured at 1280x900 before this: the column is 860px of a 1280px viewport
   and the role picker is a **384px inner scroller** inside it — a scroll area
   nested in a page that also scrolls, on a screen with ~500px of unused height
   either side of it. The 89 role checkboxes then wrapped into a tall grid
   inside that 384px window, so the only way to see a domain was to scroll a
   box to reach a box. Body text was 14px throughout, headings 15-16px.

   ⚠ >=768px ONLY, and the 24rem cap is deliberately RAISED rather than
   removed. The three-screen split (2026-08-03b) exists because Settings was
   3,808px on a phone with the daily-digest toggle at 2,449px behind those same
   checkboxes; dropping the cap on a desktop would put the Save bar back below
   a wall of them. A desktop has height to spare, not infinite height. */
@media (min-width: 768px) {
    /* 860 -> 1040: the same measure the account card takes, and it is what
       lets a domain's roles sit in three or four columns instead of two. */
    .settings-wrap { max-width: 1040px; }
    .role-picker { max-height: 34rem; }

    /* Larger, and with the size gap between a CATEGORY and a ROLE preserved —
       that gap is the 2026-08-04 fix ("users couldn't tell the two apart") and
       a flat bump would undo it. */
    .role-picker .role-domain > .btn .fw-semibold { font-size: 16.5px; }
    .role-picker .form-check-label { font-size: 15px; }
    .settings-wrap .card-header h2 { font-size: 18px; }
    .settings-wrap .form-label { font-size: 15.5px; font-weight: 600; }
    .settings-wrap .form-text { font-size: 14.5px; }
}

/* Settings: sticky save bar.
   ⚠ `margin-top` WAS `-0.5rem` and that was the overlap Raz reported
   (2026-08-10c): *"the new component overlaps 🔒 חשבון ואבטחה"*. The negative
   pull existed to hug the preferences card back when the bar was the form's
   LAST child and nothing followed it. Now that the bar is the last child of the
   PAGE, the 8px it pulled up landed it on top of whatever ends the page — and
   at full scroll it overlapped the Account & security link on **every tab, PC
   and phone** (measured: bar top 807 vs link bottom 815). It was only visible
   on Daily email because that tab is short enough to show it without scrolling.

   The gap must therefore stay POSITIVE and be at least the link's own
   descender room; a pinned bar may pass over content mid-scroll (that is what
   sticky is) but must never come to REST on top of any.

   ⚠ The lift is a SHADOW, not just the border — mid-scroll the bar has live
   text sliding underneath it, and a hairline alone does not separate the two.
   A neutral shadow is invisible on a dark page, so the dark theme gets its own
   (see the [data-bs-theme="dark"] rule). */
.settings-savebar {
    position: sticky;
    bottom: 0;
    background: #fff;
    border-top: 1px solid var(--border);
    padding: 0.75rem 0.25rem;
    margin-top: 1.25rem;
    box-shadow: 0 -6px 14px -8px rgba(16, 24, 40, .28);
    z-index: 20;
}

/* Settings: chip / tag inputs (progressive enhancement) */
.chips-control {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    align-items: center;
    cursor: text;
    padding: 0.3rem 0.5rem;
    min-height: calc(1.5em + 0.75rem + 2px);
}
.chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    background: #e7f1ff;
    color: #0a58ca;
    border-radius: 999px;
    padding-block: 0.1rem;
    padding-inline: 0.6rem 0.15rem;
    font-size: 0.85rem;
    line-height: 1.4;
}
.chip button {
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font-size: 1.05rem;
    line-height: 1;
    padding: 0 0.35rem;
    border-radius: 999px;
}
.chip button:hover { background: rgba(10, 88, 202, 0.15); }
.chips-control .chip-entry {
    flex: 1 1 6rem;
    min-width: 6rem;
    border: 0;
    outline: 0;
    padding: 0.15rem;
    background: transparent;
}
#role-summary:empty { display: none; }

/* ── Auth pages (login / register / invite) ─────────────── */
.auth-brand { font-size:2.4rem; line-height:1; display:block; margin-bottom:.4rem; }

/* ── Responsive ─────────────────────────────────────────── */
@media (max-width: 900px) {
    /* Full-screen slide-in on phones/tablets — respect the notch & rounded
       corners now that viewport-fit=cover lets content reach the edges. */
    #detail-panel, #job-sheet { width: 100%; }
    /* ⚠ No resizer below 900px: the panel is already the full width, so there
       is nothing to resize, and a 10px col-resize strip down the edge of a
       phone sheet only steals touches from the content behind it. This rule
       wins over the base one on source order (both are 0-1-0 and a media query
       adds no specificity) — it must stay after it. Note the base `width` is a
       custom-property FALLBACK, not an inline style, so `width:100%` here still
       beats a user-chosen --panel-w. */
    .pnl-resizer { display: none; }
    .pnl-head, .pnl-body, .pnl-foot {
        padding-left: max(20px, env(safe-area-inset-left));
        padding-right: max(20px, env(safe-area-inset-right));
    }
    .pnl-head { padding-top: max(16px, env(safe-area-inset-top)); }
}

/* ── Bottom SHEET on phones (Phase D, 2026-07-30) ─────────────────────────
   A sheet that rises from the bottom is the native-app idiom, and on a tall
   phone it also keeps the top of the list visible behind it so the user never
   loses their place. Slides on translateY instead of translateX; the panel is
   the same element and the same open/close JS — only the geometry changes. */
@media (max-width: 768px) {
    #detail-panel, #job-sheet {
        top: auto; bottom: 0; right: 0; left: 0;
        width: 100%;
        height: auto;
        max-height: 92vh;
        max-height: 92dvh;      /* excludes browser chrome so the footer stays reachable */
        border-radius: 16px 16px 0 0;
        box-shadow: 0 -6px 28px rgba(15,23,42,.28);
        transform: translateY(102%);
    }
    /* Re-assert the sheet geometry for RTL. `[dir="rtl"] #detail-panel` above is
       specificity 1-1-0 and a media query adds none, so the desktop translateX
       would otherwise beat this block's translateY and the phone sheet would fly
       in sideways. Same specificity here, declared later, so this wins. */
    [dir="rtl"] #detail-panel, [dir="rtl"] #job-sheet {
        transform: translateY(102%);
        box-shadow: 0 -6px 28px rgba(15,23,42,.28);
    }
    #detail-panel.open, #job-sheet.open { transform: none; }
    /* A grab handle reads as "drag me / dismissible" even though dismissal is
       by tapping the overlay or ×; it is the visual cue that this is a sheet. */
    #detail-panel::before, #job-sheet::before {
        content: ''; display: block; flex-shrink: 0;
        width: 38px; height: 4px; margin: 8px auto 2px;
        border-radius: 999px; background: var(--border);
    }
    .pnl-head { padding-top: 10px; }
    /* The body must be able to shrink, or a long description pushes the pinned
       footer off the bottom of the sheet. */
    .pnl-body { min-height: 0; }
    .pnl-foot { padding-bottom: max(14px, env(safe-area-inset-bottom)); }
}

/* Someone who prefers reduced motion gets the sheet without the slide. */
@media (prefers-reduced-motion: reduce) {
    #detail-panel, #job-sheet { transition: none; }
}

/* Skip link — hidden until focused */
.skip-link:focus {
    position: absolute;
    top: 0.5rem;
    inset-inline-start: 0.5rem;
    z-index: 1100;
    padding: 0.5rem 0.75rem;
    /* Tokens, not #fff: in dark mode a white chip carried the light-blue link
       colour at ~2.4:1 — invisible for the keyboard users who are its only
       audience. */
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 0.25rem;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

/* Respect reduced-motion for the slide-in panel */
@media (prefers-reduced-motion: reduce) {
    #detail-panel, #job-sheet { transition: none; }
    .jobtab:hover { transform: none; }
}

/* ============================================================
   Mobile (≤768px) — most traffic is phones. Bigger touch targets,
   no iOS zoom-on-focus, a decluttered job list, and an obvious
   "tap for details" affordance.
   ============================================================ */

/* "View details ›" opener — only rendered on mobile (d-md-none in the
   template). Wired to the existing .job-open handler in app.js. */
.btn-details {
    display: inline-block; margin-top: 6px; padding: 4px 0;
    background: none; border: 0; color: var(--accent);
    font-size: 13px; font-weight: 600; cursor: pointer;
}

@media (max-width: 768px) {
    /* Stop iOS Safari auto-zooming when a field < 16px gains focus. */
    .form-control, .form-select,
    .input-group-sm > .form-control, .input-group-sm > .form-select,
    .status-sel-lg, .chip-entry, .chips-control .chip-entry {
        font-size: 16px;
    }

    /* Thumb-friendly targets (WCAG 2.2 §2.5.8 ≥24px; Apple HIG 44px). */
    .jobtab { padding: 10px 16px; font-size: 14px; }
    .tbl .btn { padding: 10px 14px; font-size: 14px; }
    .input-group-sm > .btn { padding: 8px 12px; }
    .page-link { padding: 9px 15px; }
    .region-all, .region-clear,
    .role-select-all, .role-clear { padding: 6px 8px; }
    .navbar-nav .nav-link { padding-top: 10px; padding-bottom: 10px; }
    .btn-close { padding: 12px; }

    /* Change status right from the list on mobile (where most traffic is), no
       need to open a job first. The inline <select> is a NATIVE control, so a
       tap opens the OS status picker (iOS wheel / Android dialog) — which is
       never clipped off the bottom of the screen the way a CSS dropdown can be.
       Full-width so the long "🔵 New — not reviewed yet" option text fits. */
    .tbl .status-sel {
        display: block; width: 100%; min-width: 0; margin-top: 5px;
        padding: 10px 12px; font-size: 16px; border-radius: 8px;
    }
    /* Redundant on mobile: the <select> already spells out the current status. */
    .status-hint { display: none; }
    .status-cell { margin-bottom: 4px; }

    /* Tap hint + result bar can wrap on narrow screens. */
    .result-bar { flex-wrap: wrap; }
    .result-hint { font-size: 13px; color: var(--accent); }

    /* Larger panel controls. */
    .pnl-close { padding: 6px 13px; font-size: 24px; }
    .pnl-foot .btn { padding: 11px 16px; font-size: 15px; }
    .status-sel-lg { padding: 11px 12px; }

    /* Keep the centred column clear of the notch in landscape. */
    .app-main {
        padding-left: max(10px, env(safe-area-inset-left));
        padding-right: max(10px, env(safe-area-inset-right));
    }
}

/* ============================================================
   Dark mode — follows the OS setting via <html data-bs-theme>.
   Bootstrap 5.3 restyles its own components (cards, forms, tables,
   navbar, buttons, alerts, toasts, pagination); here we only need to
   re-tint our custom design tokens and the hand-coloured surfaces.
   ============================================================ */
[data-bs-theme="dark"] {
    --bg:#0f1420; --surface:#1a2130; --header:#0b0f1a;
    --text:#e6e9ef; --muted:#94a1b2; --border:#2b3547;
    --shadow:0 1px 4px rgba(0,0,0,.45);
    /* Lighten the accent so it keeps contrast on a dark background. */
    --accent:#4d94d6; --accent-dk:#3f83c2;
    /* The three CTA identities, lightened the same way. A light fill needs
       DARK ink, hence the -ink flip; the washes become deep tints of the hue
       so a card still reads as "the blue one" / "the amber one". */
    /* Raised 2026-07-31c. The first pass took these DOWN on the theory that a
       lightened blue slab is a floodlight on a dark page — but the page is
       #0f1420 and the amber card sitting next to it is #fb923c, so the blue was
       simply losing: dark-on-dark, while the warm card asserted itself. These
       are the values that make the two read as equals. */
    --find:#5ba4e5;  --find-dk:#4a8fcd;  --find-wash:#17334f;
    --find-ink:#08131f; --find-wash-on:#1e4269;
    /* Same correction for the hero card: bright enough to be the focal point of
       the page, with the inputs inside it left on --surface (dark) so they read
       as holes punched in it. The CTA stays inverted — see --find-cta. */
    --find-card-a:#2c72b6; --find-card-b:#1c4d80;
    /* Pushed lighter again now the card itself is brighter — the CTA has to
       out-rank the panel it sits on, and #5aa3e6 had become the same blue. */
    --find-cta:#93c8f7; --find-cta-dk:#b7ddfb; --find-cta-ink:#06121e;
    --alert:#fb923c; --alert-dk:#f97316; --alert-wash:#2a1a10; --alert-ink:#0b0f1a;
    --guest:#4ade80; --guest-dk:#3ac96d; --guest-ink:#0b0f1a;
    /* The four card-action hues, lifted off the dark --surface (#1a2130).
       These are INK values (border + text), never fills, so they get lighter
       here rather than darker — the opposite of what a fill token does. */
    --act-save:#fb7185;    --act-save-wash:rgba(251,113,133,.14);
    --act-detail:#a8b6c8;  --act-detail-wash:rgba(168,182,200,.14);
    --act-contact:#6cb2f0; --act-contact-wash:rgba(108,178,240,.14);
    --act-board:#4ade80;   --act-board-wash:rgba(74,222,128,.14);
    --bs-primary:#4d94d6; --bs-primary-rgb:77,148,214;
    --bs-link-color:#7fb5e6; --bs-link-color-rgb:127,181,230;
    --bs-link-hover-color:#a9cef0; --bs-link-hover-color-rgb:169,206,240;
    color-scheme: dark;
}

/* Hand-coloured surfaces that don't derive from the tokens above. */
[data-bs-theme="dark"] .table thead th,
[data-bs-theme="dark"] .tbl th        { background:#141b28; color:var(--muted); }
[data-bs-theme="dark"] .table tbody tr.job-row:hover td,
[data-bs-theme="dark"] .tbl tbody tr:hover td       { background:#20293a; }
[data-bs-theme="dark"] .table tbody tr.row-active td,
[data-bs-theme="dark"] .tbl tbody tr.row-active td  { background:#26324a; }
[data-bs-theme="dark"] .tbl tbody tr.row-selected td { background:#25344d; }
[data-bs-theme="dark"] .bulk-bar { background:#141b28; }
[data-bs-theme="dark"] .card-header,
[data-bs-theme="dark"] .settings-savebar,
[data-bs-theme="dark"] .card-header.bg-white        { background:var(--surface) !important; }
/* ⚠ A neutral drop shadow does nothing on a dark page (2026-07-31c). The bar
   has text sliding under it while pinned, so it needs a real edge: a deeper
   shadow plus a lighter top border than `--border`. */
[data-bs-theme="dark"] .settings-savebar {
    box-shadow:0 -8px 18px -6px rgba(0,0,0,.65); border-top-color:#3a4761;
}
/* ⚠ DARK ONLY, and measured rather than assumed: `--accent` + white is
   **3.22:1** on the dark page and **4.85:1** on the light one, so light already
   passes and re-tinting it would be a change with no reader behind it. This is
   the same pairing, and the same fix, as the panel Apply buttons on 2026-08-03
   — `--find-ink` exists precisely because the dark theme LIGHTENS a fill rather
   than darkening it, so the ink has to invert with it. ⚠ Darkening the fill
   back down instead would pass the ratio and be the floodlight mistake of
   2026-07-31d in reverse: a dark-blue slab on a dark page beside an amber card.
   ⚠ Scoped to this bar's primary, NOT `.btn-primary` app-wide — the palette
   findings on badges and "Send invite" are a standing decision of Raz's. */
[data-bs-theme="dark"] .savebar-acts .btn-primary {
    --bs-btn-bg:var(--find);
    --bs-btn-border-color:var(--find);
    --bs-btn-color:var(--find-ink);
    --bs-btn-hover-bg:var(--find-dk);
    --bs-btn-hover-border-color:var(--find-dk);
    --bs-btn-hover-color:var(--find-ink);
    --bs-btn-active-bg:var(--find-dk);
    --bs-btn-active-border-color:var(--find-dk);
    --bs-btn-active-color:var(--find-ink);
}
[data-bs-theme="dark"] .src-badge,
[data-bs-theme="dark"] .badge-source   { background:#1e3350; color:#8fc0f0; }
[data-bs-theme="dark"] .job-link       { color:var(--text); }
[data-bs-theme="dark"] .pnl-desc       { color:#c4ccd8; }
[data-bs-theme="dark"] .pnl-no-desc    { background:#141b28; color:#8b95a5; border-color:var(--border); }
[data-bs-theme="dark"] .pnl-foot       { background:#141b28; }
[data-bs-theme="dark"] .status-sel,
[data-bs-theme="dark"] .status-sel-lg  { background:var(--surface); color:var(--text); border-color:var(--border); }
/* Dark counterparts for the status-coloured control. The dark theme LIGHTENS a
   hue rather than darkening it, so the text goes pale-on-tint here instead of
   deep-on-wash; a straight reuse of the light values would put #5b21b6 on a
   near-black field. Measured on --surface (#121826): 7.4:1 / 7.9:1 / 8.1:1. */
[data-bs-theme="dark"] .status-sel[data-status="new"],
[data-bs-theme="dark"] .status-sel-lg[data-status="new"]       { border-color:#8b5cf6; background:rgba(124,58,237,.18); color:#d8c8ff; }
[data-bs-theme="dark"] .status-sel[data-status="applied"],
[data-bs-theme="dark"] .status-sel-lg[data-status="applied"]   { border-color:#22c55e; background:rgba(22,163,74,.18);  color:#a7f0c0; }
[data-bs-theme="dark"] .status-sel[data-status="discarded"],
[data-bs-theme="dark"] .status-sel-lg[data-status="discarded"] { border-color:#6b7280; background:rgba(156,163,175,.14); color:#c3cad4; }
[data-bs-theme="dark"] .chip           { background:#1e3350; color:#a9cef0; }
[data-bs-theme="dark"] .chip button:hover { background:rgba(169,206,240,.18); }
[data-bs-theme="dark"] .navbar .navbar-text { color:#8b95a5 !important; }
[data-bs-theme="dark"] tr.updated td   { animation:flash-dark .7s ease-out; }
@keyframes flash-dark { 0% { background:#1e3a5f; } 100% { background:transparent; } }

/* ---------- Dashboard: "change my preferences" call to action ----------
   ⚠ The whole `.pref-*` chip strip was DELETED here on 2026-08-05 (Raz: "don't
   mention current roles at the top … display one noticeable button to change
   the user's board preferences"). Everything it accumulated over 01/08b/04b —
   the chip truncation, the `+N` badge, the phone scroller with its RTL-mirrored
   fade, the two `min-width:0` clamps that fixed the 1221px page — went with it.
   Deleted rather than left dormant: dead CSS is what the next person spends an
   afternoon reading before discovering nothing renders it.

   ⚠ AMBER, not blue. The board is already blue everywhere (navbar, links, the
   mode switch, the filter bar), so a blue button here is camouflage — which is
   exactly what Raz reported. --alert is an existing token, re-tinted for dark
   mode, and the board carries no other amber. */
.prefs-cta {
    display:inline-flex; align-items:center; gap:8px;
    background:var(--alert); color:var(--alert-ink);
    border:1px solid var(--alert-dk); border-radius:999px;
    padding:8px 16px; min-height:40px;
    font-size:14px; font-weight:700; text-decoration:none; white-space:nowrap;
    max-width:100%;
}
.prefs-cta:hover { background:var(--alert-dk); color:var(--alert-ink); }

/* ---------- Invite-a-friend (Settings, "Daily email" screen) ---------- */
/* One row: the field, then the button. No disclosure, no quota badge. Moved
   off the dashboard 2026-08-04 (Raz) into a Settings card, whose header now
   carries the question — see settings/index.html. */
.invite-strip {
    display:flex; flex-wrap:wrap; align-items:center; gap:10px 14px;
}
.invite-strip-f { display:flex; gap:8px; flex:1 1 22rem; min-width:0; }
/* 16px so iOS does not zoom the page when this takes focus (see the i18n
   note); the visual size is held down with padding instead. */
.invite-strip-in { flex:1 1 auto; min-width:0; font-size:16px; }
.invite-strip-go { white-space:nowrap; }
.invite-strip-none { font-size:13px; color:var(--muted); }
@media (min-width:768px) { .invite-strip-in { font-size:14px; } }

/* ---------- Onboarding wizard ---------- */
.wiz-progress { display:flex; align-items:center; gap:10px; font-size:13px; color:var(--muted); }
.wiz-progress .wiz-bar { flex:1; height:6px; border-radius:999px; background:var(--border); overflow:hidden; max-width:260px; }
.wiz-progress .wiz-bar > span { display:block; height:100%; background:var(--accent); transition:width .25s ease; }
.wiz-step[hidden] { display:none; }

/* ---------- Invite landing page ---------- */
.landing { max-width: 960px; margin: 0 auto; padding: 8px 0 48px; }
.landing-brand { font-weight: 800; font-size: 20px; letter-spacing:.2px; color: var(--accent); }
.landing-hero { padding: 28px 16px 40px; }
.landing-invited-by {
    display:inline-block; background: rgba(46,117,181,.12);
    color: var(--accent); font-weight: 600; font-size: 14px;
    border-radius: 999px; padding: 6px 16px; margin-bottom: 20px; }
.landing-headline {
    font-size: clamp(28px, 5.5vw, 46px); font-weight: 800; line-height: 1.1;
    letter-spacing: -0.5px; margin: 0 auto 18px; max-width: 720px; color: var(--text); }
.landing-sub {
    font-size: clamp(15px, 2.2vw, 18px); line-height: 1.6; color: var(--muted);
    max-width: 640px; margin: 0 auto 28px; }
.landing-cta { padding: 12px 30px; font-size: 17px; font-weight: 700; box-shadow: var(--shadow); }
.landing-cta-note { margin-top: 12px; font-size: 13px; color: var(--muted); }

.landing-features {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
    margin: 8px 16px 48px; }
.landing-feature {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 22px 20px; }
.landing-feature-ico { font-size: 30px; line-height: 1; margin-bottom: 12px; }
.landing-feature-title { font-size: 17px; font-weight: 700; margin: 0 0 6px; color: var(--text); }
.landing-feature p { font-size: 14px; line-height: 1.55; color: var(--muted); margin: 0; }

.landing-how { margin: 0 16px 44px; }
.landing-how-title, .landing-final-title {
    text-align: center; font-size: 24px; font-weight: 800; margin: 0 0 24px; color: var(--text); }
.landing-steps { list-style: none; padding: 0; margin: 0 auto; max-width: 640px;
    display: grid; gap: 18px; }
.landing-steps li { display: flex; gap: 16px; align-items: flex-start; }
.landing-steps li > div { font-size: 15px; line-height: 1.55; color: var(--muted); padding-top: 4px; }
.landing-steps li strong { color: var(--text); }
.landing-step-n {
    flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
    background: var(--accent); color: #fff; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center; }

.landing-final {
    background: var(--header); border-radius: var(--radius); padding: 36px 20px; margin: 0 16px; }
.landing-final .landing-final-title { color: #fff; }
.landing-final .landing-cta-note { color: rgba(255,255,255,.75); }
.landing-final .landing-cta-note a { color: #fff; text-decoration: underline; }

@media (max-width: 560px) {
    .landing-features { grid-template-columns: 1fr; }
}

/* ---------- Clear-board modal ---------- */
.clear-board-btn { white-space: nowrap; }
.clear-opt {
    position: relative; display: flex; gap: 10px; align-items: flex-start;
    border: 1px solid var(--border); border-radius: var(--radius);
    padding: 12px 14px; margin-bottom: 10px; cursor: pointer; transition: border-color .15s, background .15s; }
.clear-opt:hover { border-color: var(--accent); }
.clear-opt:has(input:checked) { border-color: var(--accent); background: rgba(46,117,181,.06); }
.clear-opt.clear-opt-danger:has(input:checked) { border-color: var(--danger, #dc3545); background: rgba(220,53,69,.06); }
.clear-opt .form-check-input { margin-top: 3px; flex: 0 0 auto; }
.clear-opt .form-check-label { cursor: pointer; }
.clear-opt-title { display: block; font-weight: 600; color: var(--text); }
.clear-opt-title .badge { font-weight: 600; vertical-align: middle; }
.clear-opt-desc { display: block; font-size: 13px; line-height: 1.45; color: var(--muted); margin-top: 2px; }

/* ---------- Support / Messages (user↔admin) ---------- */
.support-wrap { max-width: 760px; margin: 0 auto; }
.min-w-0 { min-width: 0; }
.nav-badge { font-size: 10px; padding: 1px 6px; margin-inline-start: 4px; vertical-align: top; }
.unread-dot {
    display: inline-block; width: 8px; height: 8px; border-radius: 50%;
    background: #dc3545; margin-inline-end: 6px; vertical-align: middle; }

.support-list .support-subj { font-weight: 600; color: var(--text); }
.support-list .support-snip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.msg-thread { display: flex; flex-direction: column; gap: 10px; }
.msg-row { display: flex; }
.msg-row.msg-own { justify-content: flex-end; }
.msg-row.msg-other { justify-content: flex-start; }
.msg-bubble {
    max-width: 82%; border: 1px solid var(--border); border-radius: 12px;
    padding: 9px 14px; background: var(--surface); box-shadow: var(--shadow); }
.msg-own .msg-bubble { background: rgba(46,117,181,.10); border-color: rgba(46,117,181,.30); }
.msg-meta { display: flex; gap: 8px; align-items: baseline; font-size: 11px; color: var(--muted); margin-bottom: 3px; }
.msg-who { font-weight: 600; color: var(--text); }
.msg-body { font-size: 14px; line-height: 1.5; color: var(--text); white-space: pre-wrap; word-break: break-word; }

/* ---------- Login: split brand + form panel ---------- */
.auth-split {
    min-height: calc(100vh - 120px); display: flex;
    align-items: center; justify-content: center; padding: 24px 12px; }
.auth-card {
    display: flex; width: 100%; max-width: 900px; background: var(--surface);
    border: 1px solid var(--border); border-radius: 16px; overflow: hidden;
    box-shadow: 0 12px 44px rgba(26,26,46,.14); }
/* `justify-content: center` centred the pitch inside a panel whose HEIGHT is
   set by the (much taller) login form beside it, so the brand and headline
   started well below the fold of the card — Raz: "it is currently too low".
   Anchoring to the top lines the headline up with "Welcome back" opposite it,
   which is what "more centred" actually means here. (2026-08-01) */
.auth-hero {
    flex: 1 1 50%; padding: 40px 40px 44px; color: #fff;
    background: linear-gradient(155deg, #1a1a2e 0%, #24386b 100%);
    display: flex; flex-direction: column; justify-content: flex-start; }
.auth-hero-brand { font-size: 22px; font-weight: 800; letter-spacing: -.2px; margin-bottom: 20px; }
.auth-hero-title { font-size: 25px; font-weight: 800; line-height: 1.18; margin: 0 0 24px; }
/* 14px rather than 16: the list carries four named-evidence points now, and it
   has to stay inside the card without pushing the pitch back down the panel. */
.auth-hero-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 14px; }
.auth-hero-list li { display: flex; gap: 12px; align-items: flex-start; font-size: 14px; line-height: 1.5; color: rgba(255,255,255,.92); }
.auth-hero-list .ico { font-size: 20px; line-height: 1.2; flex: 0 0 auto; }
.auth-hero-list strong { color: #fff; }
/* Pinned to the foot of the panel: the pitch is top-anchored, and the panel's
   height comes from the login form beside it, so without this the bottom third
   is empty. Both colours are owned here — the hero is dark in both themes. */
.auth-hero-foot {
    margin: 28px 0 0; padding-top: 16px;
    border-top: 1px solid rgba(255,255,255,.16);
    font-size: 13px; color: rgba(255,255,255,.72);
}
.auth-hero-foot strong { color: #fff; font-weight: 800; font-size: 15px; }
@media (min-height: 620px) { .auth-hero-foot { margin-block-start: auto; } }

.auth-panel {
    flex: 1 1 50%; padding: 44px 40px; display: flex;
    flex-direction: column; justify-content: center; }
.auth-welcome { font-size: 24px; font-weight: 800; margin: 0 0 4px; color: var(--text); }
.auth-sub { color: var(--muted); font-size: 14px; margin: 0 0 24px; }
.auth-foot-note { margin: 20px 0 0; font-size: 12px; color: var(--muted); text-align: center; }

@media (max-width: 767px) {
    .auth-hero { display: none; }
    .auth-card { max-width: 430px; }
    .auth-panel { padding: 32px 26px; }
}

/* ── The account screens on PC (2026-08-07, Raz) ───────────────────────────
   "The Create User screen suffers from poor screen utilization. Additionally
   with the screen at 100%, I think the fonts are too small and not bold
   enough."

   ⚠ THE UTILISATION COMPLAINT IS A REAL, MEASURABLE BUG, not a taste call.
   `.auth-hero` and `.auth-panel` are both `flex: 1 1 50%`, so the card should
   split down the middle. Measured at 1280 it did not: hero **206px** in
   English and **228px** in Hebrew against a 692/670px panel. A flex item's
   default `min-width:auto` refuses to shrink below its CONTENT, and the
   panel's content is the three-control join row pinned to `flex-wrap:nowrap`
   above 860px — so the panel took its min-content width and the hero was left
   whatever remained. That is why the pitch wrapped to two or three words a
   line in Raz's screenshot while the panel beside it sat half empty. Third
   appearance of `min-width:auto` (the 1221px board, then `.join-pw`).

   Fixing the basis alone would only move the overflow, so the row is allowed
   to wrap again inside the panel — two rows, fields then a full-width button,
   which is the >=560px layout the form already ships and is also the honest
   answer to "not bold enough": the CTA becomes the widest thing on the screen
   rather than a 168px chip. "When a row is too narrow the answer is another
   row" (2026-08-05b).

   ⚠ EVERY RULE HERE IS >=768px. The phone layout is not up for renegotiation:
   2026-08-05b measured the guest door to 571px on a ~700px real phone screen
   by DELETING content, and a font bump would push it back under the fold. */
@media (min-width: 768px) {
    /* 50/50 for real. Both halves, because either one alone still lets the
       other set the split. */
    .auth-hero, .auth-panel { min-width: 0; }

    .auth-welcome { font-size: 28px; }
    .auth-sub { font-size: 15.5px; margin-bottom: 26px; }
    .auth-hero-title { font-size: 28px; }
    .auth-hero-list li { font-size: 15px; }
    .auth-alt { font-size: 14.5px; }
    .join-fine { font-size: 13px; }

    /* 0-2-0, so it beats the `min-width:860px` nowrap rule on SPECIFICITY and
       does not depend on sitting after it. Only the /join variant: the landing
       page's copy has the full page width and keeps its single row. */
    .auth-panel .join-row { flex-wrap: wrap; }
    .auth-panel .join-go {
        flex: 1 1 100%; font-size: 17px; font-weight: 800; min-height: 50px;
    }
}
@media (min-width: 992px) {
    /* The card was 900px on a 1280 screen — 70% of it margin. At 1040 the hero
       gets ~470px, which is where its four-line pitch stops breaking mid-phrase
       (measured), and the panel gets room for both fields on one line. */
    .auth-card { max-width: 1040px; }
}

/* ============================================================
   PUBLIC / GUEST SURFACE (2026-07-29)
   Jobmaster-style front door: hero What/Where search, browse
   tiles, and a card list that replaces the table for guests.
   Mobile-first — every rule below is the phone layout, and the
   min-width queries widen it for desktop.

   COLOUR RULE: tokens only (--surface/--text/--border/--bg/
   --muted/--accent). They are re-tinted wholesale under
   [data-bs-theme="dark"], so nothing here needs a dark
   counterpart. Never introduce a literal light colour.
   ============================================================ */

/* ── Hero ─────────────────────────────────────────────────── */
/* The band is transparent-bordered in light mode (navy on light grey already
   separates); the dark-mode override below gives it the edge it needs, because
   --header (#0b0f1a) and --bg (#0f1420) are near-identical there — the whole
   block had no boundary at all and the landing read as one flat dark field. */
.hero {
    background:var(--header);
    border:1px solid transparent;
    border-radius:var(--radius);
    padding:26px 16px 22px;
    margin-bottom:20px;
    text-align:center;
}
.hero-h {
    color:#fff;
    font-size:25px; font-weight:800; line-height:1.2;
    margin:0 0 16px;
}
.hero-note {
    color:rgba(255,255,255,.86);
    font-size:13.5px; margin:14px 0 0;
}
.hero-note strong { color:#fff; font-weight:800; }
/* Named evidence under the count (2026-08-01). Sits ON the navy hero, so it
   owns both halves of the colour pair and needs no dark-mode counterpart —
   the hero is dark in both themes. */
.hero-srcs {
    color:rgba(255,255,255,.72);
    font-size:12.5px; line-height:1.6; margin:6px 0 0;
}
.hero-srcs-n { color:rgba(255,255,255,.95); font-weight:700; }
/* The provenance line, moved out of the hero so the two fields stand alone. */
.lsec-lede {
    font-size:14px; line-height:1.55; color:#4b5563;
    text-align:center; max-width:640px; margin:0 auto 6px;
}
@media (min-width:768px) {
    .hero { padding:44px 32px 36px; margin-bottom:28px; }
    .hero-h { font-size:36px; margin-bottom:22px; }
}

/* ── Search bar ───────────────────────────────────────────── */
/* Modelled on JobMaster's mobile landing: the two fields are the centre of
   gravity, each with its own icon, inside a soft card, with one big CTA. No
   visible labels — the icon + placeholder carry it, and a visually-hidden
   <label> keeps screen readers whole. */
.jsearch {
    display:flex; flex-direction:column; gap:10px;
    max-width:820px; margin:0 auto; text-align:start;
}
.jsearch-field { position:relative; display:flex; min-width:0; }
.jsearch-ico {
    position:absolute; inset-inline-start:14px; top:50%;
    transform:translateY(-50%);
    display:flex; line-height:0; color:#4b5563; pointer-events:none; z-index:1;
}
/* The Where control is a flex row, so its icon is a normal child, not absolute. */
.jsearch-locsum .jsearch-ico { position:static; transform:none; flex:none; }
.jsearch-go {
    display:inline-flex; align-items:center; justify-content:center; gap:8px;
}
.jsearch-input, .jsearch-locsum {
    width:100%; box-sizing:border-box;
    background:var(--surface); color:var(--text);
    border:1px solid var(--border); border-radius:10px;
    /* 16px keeps iOS Safari from zooming the viewport on focus. */
    font-size:16px; min-height:52px;
    padding-block:13px;
    padding-inline:42px 14px;
}
.jsearch-input:focus, .jsearch-locsum:focus-visible {
    outline:none; border-color:var(--accent);
    box-shadow:0 0 0 .2rem rgba(46,117,181,.2);
}
.jsearch-loc { position:relative; width:100%; }
.jsearch-locsum {
    display:flex; align-items:center; gap:10px;
    cursor:pointer; list-style:none;
    padding-inline-start:14px;   /* icon is an inline child here, not absolutely placed */
}
.jsearch-locsum::-webkit-details-marker { display:none; }
.jsearch-locval {
    flex:1; min-width:0;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* The "nothing picked" state reads as a placeholder, matching the What field. */
.jsearch-locval.is-empty { color:#6b7280; }
.jsearch-caret { color:#4b5563; flex:none; }
.jsearch-locmenu {
    position:absolute; z-index:30; left:0; right:0; top:calc(100% + 4px);
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); box-shadow:var(--shadow);
    padding:8px; max-height:min(58vh,380px); overflow-y:auto;
}
.jsearch-lochint {
    font-size:12px; color:#4b5563;
    margin:2px 6px 8px; padding-bottom:8px;
    border-bottom:1px solid var(--border);
}
.jsearch-locopt {
    display:grid; grid-template-columns:auto 1fr; gap:2px 10px;
    align-items:center; padding:10px 8px; border-radius:6px;
    cursor:pointer; color:var(--text);
}
.jsearch-locopt:hover { background:rgba(46,117,181,.08); }
.jsearch-locopt input { width:18px; height:18px; grid-row:span 2; accent-color:var(--accent); }
.jsearch-locopt-label { font-size:15px; font-weight:600; }
.jsearch-locopt-hint { font-size:12px; color:#4b5563; grid-column:2; }
/* --find/--find-ink rather than --accent + white: identical in light mode
   (#2E75B5 carries white at 4.85:1), but in dark the accent-with-white pairing
   measures 3.22:1 — this button was both the least readable and the least
   prominent thing in a card whose whole job is to be pressed. */
.jsearch-go {
    background:var(--find); color:var(--find-ink);
    border:none; border-radius:10px;
    font-size:16.5px; font-weight:800; padding:14px 22px; min-height:52px;
    cursor:pointer;
}
.jsearch-go:hover { background:var(--find-dk); }

/* HERO (landing): the card that holds the pair, centred and unhurried.
   THE SEARCH CARD OWNS THE BLUE (2026-07-31). It was a white card on the navy
   hero — the same white as every other card on the site, so the one thing the
   page exists for looked like content. It is now a solid `--find` panel: the
   two white fields read as holes punched in a coloured block, which is what
   makes it legible AND loud at once. Tinting the card instead of the fields
   keeps every input on `--surface`, so contrast is unchanged. */
.jsearch-hero {
    background:linear-gradient(165deg, var(--find-card-a) 0%, var(--find-card-b) 100%);
    border:1px solid rgba(255,255,255,.28);
    border-radius:16px;
    /* A ring plus a coloured glow, not just a drop shadow: on a dark page a
       neutral shadow is invisible, and the card was relying on it to lift. */
    box-shadow:0 0 0 1px rgba(255,255,255,.10),
               0 14px 38px rgba(20,70,125,.45);
    padding:18px;
    gap:11px;
    max-width:600px;
}
/* The CTA has to out-rank the card it sits on, and a mid-blue button on a
   mid-blue panel does not — hence its own pair, inverted per theme. */
.jsearch-hero .jsearch-go { background:var(--find-cta); color:var(--find-cta-ink); }
.jsearch-hero .jsearch-go:hover { background:var(--find-cta-dk); }
@media (min-width:768px) {
    .jsearch-hero { padding:26px; gap:13px; max-width:660px; }
}

/* BAR (results / saved): navigation, not the destination — compact, and
   Where+Find share a row on phones. Stacked, the form pushed the first job
   ~600px down. It carries the SAME blue identity as the hero but as a frame
   and a wash rather than a solid fill: on the results page the jobs are the
   content, and a full blue slab above every list would fight them. */
.jsearch-bar {
    background:var(--find-wash); border:2px solid var(--find);
    border-radius:var(--radius); padding:13px; margin-bottom:14px;
    max-width:none;
    /* Coloured, so it reads as weight rather than as depth — a neutral shadow
       does nothing on the dark page, which is where this looked thin. */
    box-shadow:0 4px 16px rgba(46,117,181,.18);
}
@media (min-width:768px) {
    .jsearch-bar { flex-direction:row; align-items:center; gap:12px; }
    .jsearch-bar .jsearch-what  { flex:2 1 0; }
    .jsearch-bar .jsearch-where { flex:1 1 0; }
    .jsearch-bar .jsearch-go    { flex:none; }
}
@media (max-width:767px) {
    .jsearch-bar {
        display:grid; grid-template-columns:1fr auto; gap:8px 10px; padding:10px;
    }
    .jsearch-bar .jsearch-what { grid-column:1 / -1; }
    .jsearch-bar .jsearch-input,
    .jsearch-bar .jsearch-locsum { min-height:46px; padding-top:10px; padding-bottom:10px; }
    .jsearch-bar .jsearch-go { padding:11px 18px; min-height:46px; font-size:15px; }
}

/* ── Job cards ───────────────────────────────── */
/* Layout follows JobMaster's results card: title, "posted <when> by <who>",
   icon-led facts on their own lines, snippet, divider, actions. */
.jcards { display:flex; flex-direction:column; gap:12px; }
.jcard {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); box-shadow:var(--shadow);
    padding:15px 16px 0;
}
.jcard:hover { border-color:var(--accent); }
/* The whole public card opens the job at EVERY width since 2026-08-05 (it was
   ≥992px only, via `.rsplit-list .jcard`, so on a phone the card looked
   clickable and was not). `[data-card-job]` is the public card specifically —
   the board's `.bcard` has its own cursor rule and its own handler. */
.jcard[data-card-job] { cursor:pointer; }
.jcard-head { display:flex; gap:10px; align-items:flex-start; justify-content:space-between; }
.jcard-title { margin:0 0 5px; font-size:16.5px; font-weight:700; line-height:1.35; }
.jcard-title a { color:var(--text); text-decoration:none; }
.jcard-title a:hover { color:var(--accent); text-decoration:underline; }

.jcard-posted { margin:0 0 9px; font-size:13px; color:#4b5563; }
.jcard-co { font-weight:600; color:var(--text); }

.jcard-facts { list-style:none; margin:0 0 10px; padding:0;
               display:flex; flex-direction:column; gap:5px; }
.jcard-fact { display:flex; align-items:center; gap:7px;
              font-size:13.5px; color:#4b5563; min-width:0; }
.jcard-fact svg { flex:none; color:#6b7280; }
.jcard-fact span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ── The trust row: liveness + how long a listing has been up (2026-08-19) ──
   Rendered by partials/_trust_chips.html, on the public card, the board card
   and the detail body. Absent entirely when there is nothing honest to say, so
   a fresh unchecked listing costs no vertical space — see the partial for why
   an unchecked job gets no chip rather than a negative one.

   ⚠ Every colour is a token pair. The green is NOT `--c-applied` or any status
   colour: these chips sit on the same card as the status control and borrowing
   its palette would make "verified" read as a board state. */
:root {
    --tchip-ok-fg:   #1f6b4a;
    --tchip-ok-bg:   #e8f3ed;
    --tchip-ok-bd:   #b9dcc9;
    --tchip-old-fg:  #8a4520;
    --tchip-old-bg:  #fbeee5;
    --tchip-old-bd:  #f0d3bd;
    --tchip-mut-fg:  #4b5563;
    --tchip-mut-bg:  #f1f3f5;
    --tchip-mut-bd:  #dfe3e8;
}
[data-bs-theme="dark"] {
    --tchip-ok-fg:   #7fc4a2;
    --tchip-ok-bg:   #15271f;
    --tchip-ok-bd:   #2c4a3b;
    --tchip-old-fg:  #e0a074;
    --tchip-old-bg:  #2a1d14;
    --tchip-old-bd:  #4a3324;
    --tchip-mut-fg:  #9aa6b2;
    --tchip-mut-bg:  #1b222b;
    --tchip-mut-bd:  #2e3841;
}

.jcard-trust {
    list-style:none; margin:0 0 10px; padding:0;
    display:flex; flex-wrap:wrap; gap:6px;
}
.tchip {
    display:inline-flex; align-items:center; gap:5px;
    font-size:13px; line-height:1.3; font-weight:600;
    padding:3px 8px; border-radius:3px;
    border:1px solid transparent;
    /* ⚠ Not a tap target — these are labels, never controls, so the 44px floor
       does not apply and must not be applied. A padded, bordered chip that is
       not clickable is a false affordance if it is sized like a button. */
}
.tchip svg { flex:none; }
.tchip-ok  { color:var(--tchip-ok-fg);  background:var(--tchip-ok-bg);
             border-color:var(--tchip-ok-bd); }
.tchip-old { color:var(--tchip-old-fg); background:var(--tchip-old-bg);
             border-color:var(--tchip-old-bd); }
.tchip-mut { color:var(--tchip-mut-fg); background:var(--tchip-mut-bg);
             border-color:var(--tchip-mut-bd); }

/* ── The coverage line above search results (2026-08-19) ────────────────────
   ⚠ IT EXISTS TO STOP THE CHIPS LYING BY OMISSION. 64% of the pool had never
   been link-checked when this shipped, so a page where a third of cards carry
   a green tick and the rest carry nothing reads as "the others are suspect".
   This says, once and in words, that the rest are simply not swept yet. */
.vcover {
    margin:0 0 12px; font-size:13px; line-height:1.5; color:#4b5563;
    display:flex; align-items:flex-start; gap:7px;
}
/* The "only confirmed live" toggle, on the results search bar only.
   ⚠ The 44px floor DOES apply here — it is a control, unlike `.tchip`. The
   height comes from the label's own padding so the hit area covers the words
   as well as the box, which is what makes it usable one-handed. */
.jsearch-vonly {
    display:flex; align-items:center; gap:8px;
    min-height:44px; padding:4px 2px;
    font-size:13px; color:var(--text); cursor:pointer;
    grid-column:1 / -1;
}
.jsearch-vonly input { width:17px; height:17px; flex:none; cursor:pointer; accent-color:var(--find); }

/* ── Refine row: experience, level, "confirmed live" (2026-08-21) ──────────
   ⚠ `.jsearch-vonly` USED TO CARRY `grid-column:1 / -1` AS A DIRECT CHILD OF
   the form grid. Wrapping it in this row broke that placement — a grid-column
   on a non-child does nothing — so the span moves up here and the row lays its
   own children out with flex. That is the whole reason this block exists
   above the phone override rather than only inside it.
   ⚠ Defined AFTER `.jsearch-vonly` on purpose: a media query adds no
   specificity, and rules for a component defined late in this file must live
   late too (9 appearances of that bug in the vault). */
.jsearch-refine {
    grid-column:1 / -1;
    display:flex; flex-wrap:wrap; align-items:center; gap:6px 16px;
    /* ⚠⚠ `flex-basis:100%` IS LOAD-BEARING, and `grid-column` alone was not.
       `.jsearch-bar` is a FLEX row at desktop, not a grid, so the span did
       nothing and this row sat inline with the fields — measured at 1440px,
       it squeezed the keyword input from 410px to **90px**, wrapping the
       "data analyst" chip on top of its own placeholder. The lone checkbox
       this row replaced was narrow enough to get away with it; two selects
       are not. Basis 100% forces its own line in the flex parent, which is
       what the grid-column was trying and failing to express. */
    flex-basis:100%;
}
/* Required for the basis above to take effect: the bar is `nowrap` by default,
   and a nowrap parent has no line for a 100% item to break onto. Only the
   results-shaped bar is affected — the hero renders no refine row at all. */
.jsearch-bar { flex-wrap:wrap; }
.jsearch-refine .jsearch-vonly { grid-column:auto; }

.jsearch-sel {
    display:flex; align-items:center; gap:8px;
    min-height:44px; font-size:13px; color:var(--text); cursor:pointer;
}
.jsearch-sel-l { flex:none; }

/* ⚠ 16px IS NOT A STYLE CHOICE. Any font-size under 16px on a form control
   makes iOS Safari zoom the viewport on focus, and every other input on this
   site is already 16px for that reason. The label beside it stays 13px.
   ⚠ 44px min-height because this is a control, the same floor `.jsearch-vonly`
   documents — measured rendered, not asserted in CSS text. */
.jsearch-sel select {
    min-height:44px; font-size:16px; line-height:1.2;
    padding:6px 10px;
    /* ⚠⚠ `--surface`, NOT `var(--card, #fff)`. `--card` IS NOT A TOKEN IN THIS
       STYLESHEET, so that fallback resolved to a literal white at every theme
       — and `color:var(--text)` becomes #e6e9ef in dark mode, i.e. near-white
       text on white. Exactly the failure `contrast-audit` exists for (it
       shipped once already, on the 2026-07-26 dashboard dropdowns), and its
       static lint caught this one before it reached a user. */
    color:var(--text); background:var(--surface);
    border:1px solid var(--border); border-radius:8px;
    cursor:pointer; max-width:100%;
}
.jsearch-sel select:focus-visible { outline:2px solid var(--find); outline-offset:1px; }

/* ⚠⚠ THIS MEDIA QUERY LIVES HERE, AFTER THE BASE RULES, AND THAT IS THE ONLY
   REASON IT WORKS. It was first written 450 lines earlier inside the existing
   `@media (max-width:560px)` block — where it was DEAD, because a media query
   adds no specificity and the base `.jsearch-sel select` rules below it simply
   won. The symptom was subtle: the row still looked stacked at 390px, but from
   `flex-wrap` rather than from `flex-direction:column`, and the select never
   grew (measured 113px, not the full width). The vault lists this trap with
   nine prior appearances; it caught a tenth.

   ⚠ Below 560 the three controls cannot share a line without the selects
   shrinking under a usable width, so each takes its own row and the select
   fills it — a full-width native picker is the easiest phone target there is. */
@media (max-width:560px) {
    .jsearch-refine { flex-direction:column; align-items:stretch; gap:2px; }
    .jsearch-sel { justify-content:space-between; }
    .jsearch-sel select { flex:1 1 auto; min-width:0; margin-inline-start:10px; }
}

/* The note under the count when a filter is on. Same voice and colour as
   `.vcover`: it explains what is MISSING, not what is wrong. */
.rfilter-note {
    margin:0 0 12px; font-size:13px; line-height:1.5; color:#4b5563;
}
/* ⚠ The dark half, which `.vcover` two rules up already has and this was
   missing. #4b5563 on the dark background is far under 4.5:1; `--muted` is the
   tint chosen for exactly this text weight. Same pairing, same reason. */
[data-bs-theme="dark"] .rfilter-note { color:var(--muted); }

.vcover svg { flex:none; margin-top:2px; color:var(--tchip-ok-fg); }

/* ── Employer posting longevity, on /companies/<slug> (2026-08-19) ─────────
   Sits under the market stat strip. Renders only when app/lifespan.py has a
   publishable sample, so there is no empty state to style. */
.lifespan {
    margin:14px 0 0; padding:14px 16px;
    background:var(--surface-2, #f6f7f9); border:1px solid var(--border);
    border-radius:4px;
}
[data-bs-theme="dark"] .lifespan { background:#1a222b; }
.lifespan-h { margin:0 0 6px; font-size:15px; font-weight:600; color:var(--text); }
.lifespan-note { margin:0; font-size:12.5px; line-height:1.5; color:#4b5563; }

/* A board row whose posting has closed. Muted, never alarming: the row is
   still the user's own record and the job is simply no longer open. */
.bcard-closed {
    display:flex; align-items:center; gap:7px;
    margin:0 0 10px; padding:6px 9px;
    font-size:12.5px; line-height:1.4; font-weight:600;
    color:var(--tchip-mut-fg); background:var(--tchip-mut-bg);
    border:1px solid var(--tchip-mut-bd); border-radius:3px;
}
.bcard-closed svg { flex:none; }
[data-bs-theme="dark"] .lifespan-note { color:var(--muted); }
.vcover b { color:var(--text); font-variant-numeric:tabular-nums; }
[data-bs-theme="dark"] .vcover { color:var(--muted); }

.jcard-snip {
    margin:0 0 12px; font-size:13.5px; line-height:1.5; color:#4b5563;
}

/* Divider + action row, as on the reference card.
   ⚠ `flex-wrap:wrap` is LOAD-BEARING, not tidiness (2026-08-04, Raz: "the job's
   card should remain clean without any text or buttons outside its border").
   A SIGNED-IN visitor on /search gets four actions — Apply · + Add to board ·
   Save · Details — and measured at 390px they need 386px inside a 368px row, so
   without wrapping the last one (`.jcard-more`) rendered 17px OUTSIDE the card's
   own left border and leaked 7px past the viewport. A guest only gets three, so
   it fitted and the defect was invisible signed out.
   The board's `.bcard-foot` had already been given this rule for its own layout;
   the SHARED card never got it — the same "two list components drift apart"
   class of bug as the 44px pass on 2026-08-02. */
.jcard-foot {
    display:flex; align-items:center; flex-wrap:wrap; gap:8px;
    border-top:1px solid var(--border);
    margin:0 -16px; padding:10px 16px;
}
/* ── ONE ROW, ONE COLOUR EACH (2026-08-05, Raz) ───────────────────────────
   Two asks in one screenshot: "all the buttons on a job's card (Status, Apply
   and Contact) organized nicely in one row … I definitely believe this should
   be done for larger screens", and "the פרטים button is lower than the rest —
   שמירה, הגשת מועמדות — and it doesn't look good".

   Both were the same defect. The foot is a wrapping flex row, so whichever
   control did not fit dropped to a line of its own — on the board that was the
   status+Contact pair (which had `flex:1 1 100%` and so ALWAYS took its own
   row), and on the public card it was Details. A wrapped row does not read as
   "these buttons did not fit"; it reads as a mistake.

   The fix is to make every non-primary action small enough that four fit:
   they share one compact shape (`.jcard-act`), each carrying its own hue, and
   below 768px they drop their labels to the icon alone (`.act-l`). Apply keeps
   its words at every width — it is the one thing on the card the whole page
   exists to get you to, and an icon-only primary action is a guess.

   ⚠ flex-wrap STAYS. One row is the goal, not a guarantee: a 320px screen in
   Hebrew can still need two, and wrapping is the safe failure. Overflowing the
   card's border — the 2026-08-04b bug — is not. */
.jcard-apply {
    display:inline-flex; align-items:center; gap:7px;
    background:var(--find); color:var(--find-ink); text-decoration:none;
    font-size:14px; font-weight:600;
    padding:9px 16px; border-radius:6px; min-height:40px;
    white-space:nowrap;
}
.jcard-apply:hover { background:var(--find-dk); color:var(--find-ink); }

/* The shared shape of every SECONDARY action. Border + text in the action's
   own colour on --surface, never a fill: four filled buttons in a row is a
   carnival, and ink-on-surface is also what keeps all four above AA in both
   themes without a per-theme ink flip. */
.jcard-act {
    display:inline-flex; align-items:center; justify-content:center; gap:7px;
    background:none; border:1px solid currentColor; border-radius:6px;
    font-size:14px; font-weight:600; text-decoration:none; cursor:pointer;
    padding:9px 12px; min-height:40px; white-space:nowrap;
}
.jcard-more  { color:var(--act-detail); }
.jcard-more:hover  { background:var(--act-detail-wash);  color:var(--act-detail); text-decoration:none; }
.jcard-save  { color:var(--act-save); }
.jcard-save:hover  { background:var(--act-save-wash);    color:var(--act-save); }
.jcard-add   { color:var(--act-board); border-style:dashed; }
.jcard-add:hover   { background:var(--act-board-wash);   color:var(--act-board); }
.bcard-contact { color:var(--act-contact); }
.bcard-contact:hover { background:var(--act-contact-wash); color:var(--act-contact); }
/* Saved is a STATE, so it goes solid — the outline says "you could", the fill
   says "you did". Filling the heart alone was too quiet to notice on a card
   that now has three other outlined controls. */
.jcard-save.is-saved {
    background:var(--act-save-wash);
}
.jcard-save.is-saved svg { fill:currentColor; }
.jcard-add[disabled] { opacity:.6; cursor:default; }
@media (min-width:768px) { .jcard-title { font-size:18px; } }

/* ── Landing sections ─────────────────────────────────────── */
.lsec { margin:30px 0; }
.lsec-h { font-size:19px; font-weight:800; margin:0 0 14px; color:var(--text); }
.tiles { display:grid; grid-template-columns:repeat(2,1fr); gap:10px; }
.tile {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); padding:14px;
    text-decoration:none; display:flex; flex-direction:column; gap:3px;
}
.tile:hover { border-color:var(--accent); }
.tile-label { font-weight:700; font-size:15px; color:var(--text); }
.tile-hint { font-size:12px; color:var(--muted); line-height:1.4; }
@media (min-width:768px) { .tiles { grid-template-columns:repeat(4,1fr); } }

.rdomain { margin-bottom:16px; }
.rdomain-h {
    font-size:11px; font-weight:700; letter-spacing:.04em;
    text-transform:uppercase; color:#4b5563; margin:0 0 8px;
}
.rchips { display:flex; flex-wrap:wrap; gap:7px; }
.rchip {
    background:var(--surface); border:1px solid var(--border);
    border-radius:999px; padding:7px 13px;
    font-size:13.5px; color:var(--text); text-decoration:none;
}
.rchip:hover { border-color:var(--accent); color:var(--accent); }

.whys { display:grid; grid-template-columns:1fr; gap:14px; }
.why {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); padding:16px;
}
.why-i { font-size:22px; }
.why-h { font-size:15px; font-weight:700; margin:8px 0 5px; color:var(--text); }
.why-p { font-size:13.5px; line-height:1.5; color:var(--muted); margin:0; }
@media (min-width:768px) { .whys { grid-template-columns:repeat(4,1fr); } }

/* ── Results ──────────────────────────────────────────────── */
/* The search bar, the chips and the count are ONE block — "what I asked for and
   what it found". Tightened together (2026-07-31c) so they read that way rather
   than as three loose rows drifting above the results. */
.rchipbar { display:flex; flex-wrap:wrap; gap:7px; align-items:center; margin-bottom:10px; }
/* SOLID, matching the board's `.afilter` (2026-07-31) — see the note there.
   These are the search the visitor actually asked for; as a pale wash they were
   quieter than the result count above them. White on --find is 4.85:1, and
   --find-ink flips to near-black in dark mode where the fill is lightened.
   Unlike `.afilter` the chip is a span and only the ✕ removes, so the ✕ is the
   thing that reacts to hover. */
.rfilter {
    display:inline-flex; align-items:center; gap:8px;
    background:var(--find); color:var(--find-ink);
    border:1px solid var(--find-dk);
    border-radius:999px; padding:6px 13px; font-size:13px; font-weight:700;
}
.rfilter-x {
    color:inherit; text-decoration:none; font-size:12px; font-weight:700;
    line-height:1; padding:2px; margin-block:-2px; margin-inline:0 -3px;
    border-radius:50%;
}
/* No translucent chip behind it: a white patch on --find drops white text to
   3.3:1. Underline is the affordance that costs no contrast. */
.rfilter-x:hover { color:inherit; text-decoration:underline; }
.rfilter-clear { font-size:13px; font-weight:700; color:#1f5286; text-decoration:underline; }
.rfilter-clear:hover { color:#12395f; }
/* The answer to the search deserves to be read first — it was 16px inside a
   14px grey line and lost against the amber card immediately below it. */
.rcount { font-size:14.5px; color:#4b5563; margin-bottom:6px; }
.rcount strong { color:var(--text); font-size:20px; font-weight:800; }
/* The results count IS the page's <h1> (2026-08-07) — /search had no top-level
   heading at all. Promoting the line that already states the search, rather
   than adding a second one, costs no vertical space: this cancels the UA
   heading margin and weight so the h1 renders identically to the <div> it
   replaced. Scoped to `h1.` on purpose — `.rcount` is also a <p> on /saved,
   where the paragraph margin is doing real work. */
h1.rcount { margin-top:0; font-weight:400; }
/* Says what the count is a count OF. Deliberately quieter than the count and
   the job titles — it is a caption, not a pitch, and it sits directly above
   the amber signup card, which already owns the loud slot on this page.
   #4b5563 (not --muted, 4.83:1) keeps it AA at this size; re-tinted in dark
   alongside .rcount below. */
.rcount-note { font-size:13px; color:#4b5563; margin-bottom:16px; max-width:70ch; }
/* ⚠ `.enpage-note` JOINS THIS DECISION rather than using `--muted`, which
   measured 4.46:1 on the page background — a fail, and a new one, which is
   not something to ship while waiting on the standing palette question. Same
   literal and same dark pair as `.rcount-note`, so it is one decision in one
   place rather than a second opinion. */
.enpage-note { color:#4b5563; }
/* ⚠ MEASURED AT 4.48:1 AND FIXED IN PLACE RATHER THAN GLOBALLY (2026-08-15).
   The employer pages put ordinary inline links inside this note ("All companies
   hiring", "Search every open job"), which inherit the default link colour —
   and `--accent` (#2E75B5) on `--bg` is the site's long-standing 4.48:1 item,
   0.02 short of AA and logged as Raz's call because nudging the token repaints
   the whole app. Nudging it is still Raz's call; shipping three new failures
   while waiting for that decision is not.
   The literal is `.rfilter-clear`'s, deliberately: that selector is already an
   inline link in this same results chrome, already passes, and already carries
   the dark pair below — so this is one decision reused, not a second one. */
.rcount-note a, .enpage-note a, .enpage-honest a { color:#1f5286; }
.rcount-note a:hover, .enpage-note a:hover, .enpage-honest a:hover { color:#12395f; }
/* The "See all N openings" button on an employer page. A bare wrapper whose
   only job is the spacing `.rcount-note` would have given it — it is deliberately
   NOT that class, because the link colour above outranks `.rempty-b`'s own. */
.co-more { margin:0 0 16px; }

/* ── /en/english-jobs-israel (2026-08-16) ─────────────────────────────────────
   The English-first page. Prose, not chrome — so it needs a reading measure and
   a little vertical rhythm, and nothing else. Deliberately built from the
   existing tokens (`--text`, `--muted`, `--surface`, `--border`) with no new
   literals, which is also why `lint_css.py` has nothing to pair. */
.enpage { max-width:62ch; }
.enpage-h1 { font-size:30px; font-weight:800; margin:0 0 14px; color:var(--text); }
.enpage-h2 { font-size:19px; font-weight:700; margin:26px 0 10px; color:var(--text); }
.enpage-lede { font-size:16px; line-height:1.65; color:var(--text); margin:0 0 14px; }
.enpage-list { font-size:15px; line-height:1.7; color:var(--text); padding-inline-start:20px; margin:0 0 8px; }
.enpage-list li { margin-bottom:7px; }
.enpage-note { font-size:13px; margin:8px 0 0; }   /* colour: see the .rcount-note group */
/* The honest paragraph gets a frame so it reads as a statement rather than as
   small print — it is the claim the whole page's credibility rests on. */
.enpage-honest { background:var(--surface); border:1px solid var(--border);
                 border-radius:var(--radius); padding:16px 18px; margin:20px 0; }
.enpage-honest p { font-size:15px; line-height:1.65; color:var(--text); margin:0 0 10px; }
.enpage-honest p:last-child { margin-bottom:0; }
.enpage-honest .enpage-h2 { margin-top:0; }
.enpage-cta { margin:30px 0 10px; }

/* "More at <employer>" on a job page (2026-08-16). Plain links, deliberately —
   a second card list at the foot of a detail page competes with the posting the
   page exists for. Tokens only, so there is nothing for lint_css to pair. */
.jd-related-list { list-style:none; padding:0; margin:0; }
.jd-related-list li { padding:9px 0; border-bottom:1px solid var(--border); }
.jd-related-list li:last-child { border-bottom:0; }
.jd-related-list a { font-size:14px; font-weight:600; }
.jd-related-city { display:block; font-size:12.5px; color:var(--muted); margin-top:2px; }
.jd-related-all { margin:12px 0 0; font-size:13px; }

@media (max-width: 767.98px) {
    .enpage-h1 { font-size:25px; }
    .enpage-h2 { font-size:17px; }
}
/* --c-applied (#16a34a) is a BADGE colour — fine as a fill behind white, but
   only 3.04:1 as text on --bg. Darkened for use as text; the badge is unchanged. */
.rcount-new { color:#15803d; font-weight:600; }
.rhint { font-size:13px; color:var(--muted); }
.rpager {
    display:flex; align-items:center; justify-content:center;
    gap:14px; margin:22px 0 8px; flex-wrap:wrap;
}
.rpager-b {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); padding:10px 16px;
    text-decoration:none; color:var(--accent); font-weight:600; font-size:14px;
}
.rpager-b:hover { border-color:var(--accent); }
.rpager-at { font-size:13px; color:#4b5563; }
.rempty {
    background:var(--surface); border:1px dashed var(--border);
    border-radius:var(--radius); padding:34px 20px; text-align:center;
}
.rempty-h { font-size:17px; font-weight:700; margin:0 0 7px; color:var(--text); }
.rempty-p { font-size:14px; color:#4b5563; margin:0 0 16px; }
.rempty-b {
    display:inline-block; background:var(--accent); color:#fff;
    text-decoration:none; font-weight:600; font-size:14px;
    padding:11px 20px; border-radius:var(--radius);
}
.rempty-b:hover { background:var(--accent-dk); color:#fff; }

/* ── Job detail ───────────────────────────────────────────── */
.jdwrap { max-width:780px; margin:0 auto; }
.jd-back { font-size:13.5px; color:var(--muted); text-decoration:none; }
.jd-back:hover { color:var(--accent); }
.jd {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); box-shadow:var(--shadow);
    padding:20px 18px; margin-top:12px;
}
.jd-title { font-size:22px; font-weight:800; line-height:1.3; margin:0 0 8px; color:var(--text); }
.jd-meta { font-size:15px; color:var(--muted); margin:0 0 10px; }
.jd-co { font-weight:600; color:var(--text); }
.jd-tags { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin:0 0 16px; }
/* ── THE DETAIL SIDEBAR'S ACTION ROW — ONE HEIGHT FOR EVERY CONTROL ────────
   (2026-08-06, Raz: "on the job's description side-bar, align the buttons UI
   so they have similar height".)

   `.jd-actions` had NO rule at all, so it was a bare block and its children
   were inline boxes sitting on a shared TEXT BASELINE. Measured on the guest
   split pane at 1280: Apply 322x49, Save 83x30, and Save offset 9px DOWN from
   Apply's top edge — two different heights and two different baselines in a
   row of two buttons.

   ⚠ The height is stated HERE, on the row, not on each control. `.jcard-act`
   is 40px because it is sized for a CARD footer; `.jd-apply` is 48px because
   it is the primary action on a full page. Chasing them into agreement one
   selector at a time is how they drifted in the first place — the row owns the
   height, and `align-items:stretch` means a control added later inherits it
   without anyone remembering to. */
.jd-actions {
    display:flex; flex-wrap:wrap; align-items:stretch; gap:10px;
}
.jd-actions > * { min-height:48px; }
/* A stretched flex child does not centre its own text the way an inline-block
   with matching padding appears to, so every control in the row centres
   explicitly — otherwise equal heights just moves the misalignment inside the
   boxes. */
.jd-actions > .jcard-act { padding-block:0; font-size:15px; }
.jd-actions > select { padding-block:0; }
/* ⚠ THE SHEET IS NARROWER THAN THE ROW NEEDS, AND DELIBERATELY SO.
   Apply carries the source name ("Apply on Company Direct →", 322px), so the
   three controls need ~555px on one line while the sheet's body is ~549px at
   its default width. Rather than shave padding until it *just* fits (it does
   not, and a longer source name would break it again), the sheet keeps a
   two-row layout — the primary action on its own line, the two secondary ones
   beside each other under it. The standalone /jobs/<id> page is 722px and
   keeps all three on one row.
   ⚠ Retargeted from `.rsplit-inner` when the split pane became `#job-sheet`
   (2026-08-08). It is width-driven, not surface-driven, so it survives the
   move — but the sheet is now RESIZABLE, and dragging it wider does not undo
   this. That is intentional: a row that regrouped itself mid-drag would be
   worse than one that is consistently two rows. */
#job-sheet .jd-actions > .jd-apply { flex:1 1 100%; }
.jd-apply {
    display:inline-flex; align-items:center; justify-content:center;
    background:var(--accent); color:#fff;
    text-decoration:none; font-weight:700; font-size:15px;
    padding:13px 22px; border-radius:var(--radius); min-height:48px;
}
.jd-apply:hover { background:var(--accent-dk); color:#fff; }
.jd-sec { margin-top:22px; border-top:1px solid var(--border); padding-top:16px; }
.jd-sech {
    font-size:11px; font-weight:700; letter-spacing:.04em;
    text-transform:uppercase; color:#4b5563; margin:0 0 10px;
}
.jd-desc { font-size:14.5px; line-height:1.65; color:var(--text); white-space:pre-wrap; }
.jd-nodesc { margin-top:20px; font-size:14px; color:var(--muted); font-style:italic; }

/* Skill chips — what the posting NAMED, echoed back as labels.
   ⚠ NOT INTERACTIVE. No hover lift, no pointer cursor, no underline: there is
   no /skills/<slug> route yet, and this file has already watched a styled-like-
   a-link element that went nowhere read as broken. When facets land, add the
   anchor styling in the same commit as the route. */
.jd-skills { margin-top:22px; }
.jd-skills-h {
    font-size:11px; font-weight:700; letter-spacing:.04em;
    text-transform:uppercase; color:#4b5563; margin:0 0 9px;
}
.jd-skills-list {
    list-style:none; margin:0; padding:0;
    display:flex; flex-wrap:wrap; gap:7px;
}
.jd-skill {
    font-size:12.5px; line-height:1; padding:7px 11px;
    border:1px solid var(--border); border-radius:999px;
    background:#f7f8fa; color:var(--text);
    white-space:nowrap;
}
[data-bs-theme="dark"] .jd-skills-h { color:var(--muted); }
[data-bs-theme="dark"] .jd-skill { background:rgba(255,255,255,.05); }
@media (min-width:768px) { .jd { padding:30px 28px; } .jd-title { font-size:27px; } }

/* Small uppercase labels sit at #4b5563 in light (--muted is only 4.83:1);
   in dark the token is already light enough, so hand them back. */
[data-bs-theme="dark"] .rdomain-h,
[data-bs-theme="dark"] .rcount,
[data-bs-theme="dark"] .enpage-note,
[data-bs-theme="dark"] .rcount-note,
[data-bs-theme="dark"] .rpager-at,
[data-bs-theme="dark"] .rempty-p,
[data-bs-theme="dark"] .jd-sech,
/* Search-field furniture: icons, caret, the "all Israel" placeholder state and
   the picker hints. All #4b5563/#6b7280 in light; without these they'd stay
   near-black on the dark surface. */
[data-bs-theme="dark"] .jsearch-ico,
[data-bs-theme="dark"] .jsearch-caret,
[data-bs-theme="dark"] .jsearch-locval.is-empty,
[data-bs-theme="dark"] .jsearch-lochint,
[data-bs-theme="dark"] .jsearch-locopt-hint,
/* Board-as-cards on phones (Phase D). */
[data-bs-theme="dark"] .tbl .col-id,
[data-bs-theme="dark"] .tbl .col-src,
[data-bs-theme="dark"] .tbl .col-date,
[data-bs-theme="dark"] .lsec-lede,
[data-bs-theme="dark"] .jcard-posted,
[data-bs-theme="dark"] .jcard-fact,
[data-bs-theme="dark"] .jcard-fact svg,
[data-bs-theme="dark"] .jcard-snip { color:var(--muted); }
/* ⚠ The `.jcard-more` dark overrides were DELETED here, not updated
   (2026-08-05). It now takes `--act-detail`, which is already re-tinted in the
   dark token block — and an attribute+class selector outranks a plain class,
   so leaving the override behind would have silently restored the old blue in
   dark mode only, which no light-mode screenshot could ever show. Same trap as
   `[data-bs-theme="dark"] .afilter` (2026-07-31b). */

/* The dark-mode accent (#4d94d6) is light enough that WHITE label text on it is
   only 3.22:1. Near-black gives 5.94:1. Darkening the accent instead would cost
   the accent its own contrast against the dark page background. */
/* `.jsearch-go` is deliberately NOT in this list any more — it takes
   --find-ink, and an override here would outrank the token. */
/* `.jcard-apply` left this list on 2026-08-05 for the same reason `.jsearch-go`
   did: it takes --find-ink now, and an override here outranks the token. */
[data-bs-theme="dark"] .jd-apply,
[data-bs-theme="dark"] .rempty-b { color:#0b0f1a; }

/* ── Dark mode: make the search block ASSERT itself (2026-07-31c) ──────────
   Raz's screenshots were both dark mode, and the problem was the same in each:
   the blue was dark-on-dark while the amber card beside it was warm-on-dark and
   won every time. Three fixes, all dark-only — light mode already reads. */
/* 1. The hero band had NO EDGE: --header (#0b0f1a) against --bg (#0f1420) is a
      4-point difference, so the landing was one flat field with a card floating
      in it. Give the band its own gradient and a visible border. */
[data-bs-theme="dark"] .hero {
    background:linear-gradient(180deg, #131d30 0%, #0b0f1a 100%);
    border-color:#27374f;
}
/* 2. A neutral drop shadow does nothing on a dark page. Both search surfaces
      get a COLOURED glow instead, which is what actually reads as weight. */
[data-bs-theme="dark"] .jsearch-hero {
    border-color:rgba(160,205,250,.34);
    box-shadow:0 0 0 1px rgba(160,205,250,.18),
               0 16px 40px rgba(30,90,160,.50);
}
[data-bs-theme="dark"] .jsearch-bar {
    box-shadow:0 0 0 1px rgba(91,164,229,.22),
               0 6px 22px rgba(30,90,160,.38);
}
[data-bs-theme="dark"] .jcard-apply:hover,
[data-bs-theme="dark"] .jd-apply:hover,
[data-bs-theme="dark"] .rempty-b:hover { color:#0b0f1a; }
[data-bs-theme="dark"] .jsearch-locopt:hover { background:#26324a; }

/* The light-mode fixes above are DARK literals (#1f5286 navy, #15803d green).
   Left to inherit in dark mode they'd render dark-on-dark — present, selectable
   and invisible, the exact bug this file's rules exist to prevent. Hand each one
   a light counterpart. */
/* `.rfilter` / `.rfilter-x` are driven by the --find tokens now, so their old
   dark overrides are gone rather than updated — an attribute selector plus the
   class outranks the class alone, and they would have restored the pale chip in
   dark mode only. `.rfilter-clear` is a navy literal and still needs its pair;
   it is deliberately NOT in the muted group above — it's an action. */
[data-bs-theme="dark"] .rfilter-clear       { color:#7fb5e6; }
[data-bs-theme="dark"] .rfilter-clear:hover { color:#cfe4f7; }
/* The dark half of the `.rcount-note a` pair above — same two literals, because
   it is the same decision. Kept HERE, beside its twin, rather than beside the
   light rule: a dark override left orphaned from the group is how this file has
   twice ended up with a component that looks fixed in light and stale in dark. */
[data-bs-theme="dark"] .rcount-note a, [data-bs-theme="dark"] .enpage-note a,
[data-bs-theme="dark"] .enpage-honest a       { color:#7fb5e6; }
[data-bs-theme="dark"] .rcount-note a:hover, [data-bs-theme="dark"] .enpage-note a:hover,
[data-bs-theme="dark"] .enpage-honest a:hover { color:#cfe4f7; }
[data-bs-theme="dark"] .rcount-new          { color:#4ade80; }

/* ============================================================
   ALERT SIGNUP + ENGAGEMENT MODAL (Phase B/C, 2026-07-29)
   Tokens only — see the colour rule in the public block above.
   ============================================================ */

/* THE DAILY-EMAIL CARD OWNS THE AMBER (2026-07-31). It was a white card with a
   grey border and a blue button — visually identical to a job card, so the one
   offer on the page slid past the eye. Amber is chosen against the blue, not
   beside it: the search card is what you DO now, this is what arrives later,
   and two blues would have read as one component split in half. */
.asignup {
    background:var(--alert-wash); border:2px solid var(--alert);
    border-radius:var(--radius); padding:14px 15px; margin:0 0 16px;
}
.asignup-h { font-size:15.5px; font-weight:700; color:var(--text); margin:0 0 4px; }
.asignup-p { font-size:13.5px; line-height:1.5; color:#4b5563; margin:0 0 12px; }
.asignup-p strong { color:var(--text); }
.asignup-row { display:flex; flex-direction:column; gap:8px; }
.asignup-input {
    /* --surface, not --bg: the card is tinted now, so a grey field would read
       as disabled. White (dark: the panel surface) keeps it obviously typable. */
    flex:1 1 auto; min-width:0; box-sizing:border-box;
    background:var(--surface); color:var(--text);
    border:1px solid var(--border); border-radius:6px;
    font-size:16px; padding:11px 13px; min-height:46px;
}
.asignup-input:focus {
    outline:none; border-color:var(--accent);
    box-shadow:0 0 0 .2rem rgba(46,117,181,.18);
}
.asignup-go {
    flex:none; background:var(--alert); color:var(--alert-ink); border:none;
    border-radius:6px; font-size:15px; font-weight:700;
    padding:11px 18px; min-height:46px; cursor:pointer;
}
.asignup-go:hover { background:var(--alert-dk); }
.asignup-go:disabled { opacity:.6; cursor:default; }
.asignup-fine { font-size:12px; line-height:1.5; color:#4b5563; margin:9px 0 0; }
.asignup-done {
    background:rgba(22,163,74,.10); border:1px solid rgba(22,163,74,.35);
    border-radius:var(--radius); padding:14px 16px; margin:0 0 16px;
    font-size:14px; font-weight:600; color:#15803d;
}
.asignup-err { font-size:13px; color:#b42318; margin:9px 0 0; font-weight:600; }
@media (min-width:600px) {
    .asignup-row { flex-direction:row; align-items:center; }
    .asignup-input { flex:1 1 auto; }
}

/* The persistent strip used to be deliberately the quietest of the four — a
   dashed blue-tinted box. Raz's call (2026-07-31): it is the offer, so it gets
   the family's solid amber frame like the rest. Only its type stays smaller,
   because it sits directly under the result count and must not out-shout the
   jobs themselves. */
.asignup-bar .asignup-h { font-size:14.5px; }
/* The end-of-list and zero-result cards get a little more presence. */
.asignup-card, .asignup-empty { margin-top:18px; }
.asignup-empty { box-shadow:0 4px 16px rgba(194,65,12,.18); }

/* ── The one interruption ─────────────────────────────────── */
.amodal { position:fixed; inset:0; z-index:1080; display:flex;
          align-items:flex-end; justify-content:center; }
.amodal[hidden] { display:none; }
.amodal-back { position:absolute; inset:0; background:rgba(10,15,26,.55); }
.amodal-box {
    position:relative; z-index:1; width:100%; max-width:520px;
    background:var(--surface); border:1px solid var(--border);
    border-radius:14px 14px 0 0; box-shadow:0 -6px 28px rgba(0,0,0,.28);
    padding:20px 18px 18px; max-height:92vh; overflow-y:auto;
}
.amodal-x {
    position:absolute; top:8px; inset-inline-end:8px;
    background:none; border:none; font-size:17px; line-height:1;
    color:#4b5563; cursor:pointer; padding:9px; border-radius:6px;
    min-width:40px; min-height:40px;
}
.amodal-x:hover { background:var(--bg); color:var(--text); }
.amodal-box .asignup { border:none; padding:0; margin:0; background:none; }
.amodal-alts {
    display:flex; flex-direction:column; gap:2px;
    margin-top:14px; padding-top:12px; border-top:1px solid var(--border);
}
.amodal-alt {
    background:none; border:none; cursor:pointer;
    font-size:14px; padding:11px 6px; min-height:44px;
    color:var(--accent); font-weight:600; text-align:center;
}
.amodal-alt:hover { text-decoration:underline; }
/* "Continue as guest" is quiet on purpose — an easy, obvious way out is what
   keeps the prompt from feeling like a trap, and it RAISES conversion. */
.amodal-no { color:#4b5563; font-weight:500; }
@media (min-width:600px) {
    .amodal { align-items:center; }
    .amodal-box { border-radius:14px; padding:26px 26px 22px; }
    .amodal-alts { flex-direction:row; justify-content:center; gap:18px; }
}

/* ── Confirm / unsubscribe landing ────────────────────────── */
.astate {
    max-width:560px; margin:26px auto; text-align:center;
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); box-shadow:var(--shadow);
    padding:34px 22px;
}
.astate-i { font-size:38px; }
.astate-h { font-size:21px; font-weight:800; color:var(--text); margin:12px 0 8px; }
.astate-p { font-size:14.5px; line-height:1.6; color:#4b5563; margin:0 0 20px; }
.astate-p strong { color:var(--text); }
.astate-b {
    display:inline-block; background:var(--accent); color:#fff;
    text-decoration:none; font-weight:700; font-size:15px;
    padding:12px 22px; border-radius:var(--radius); min-height:46px;
}
.astate-b:hover { background:var(--accent-dk); color:#fff; }
.astate-note { font-size:12.5px; color:#4b5563; margin:18px 0 0; }

/* Dark counterparts for every literal above. */
[data-bs-theme="dark"] .asignup-p,
[data-bs-theme="dark"] .asignup-fine,
[data-bs-theme="dark"] .amodal-x,
[data-bs-theme="dark"] .amodal-no,
[data-bs-theme="dark"] .astate-p,
[data-bs-theme="dark"] .astate-note { color:var(--muted); }
/* The amber card family is fully tokenised (--alert / --alert-wash /
   --alert-ink), so it needs no per-rule dark counterpart — only this shadow,
   which is a literal. */
[data-bs-theme="dark"] .asignup-empty { box-shadow:0 4px 16px rgba(251,146,60,.16); }
[data-bs-theme="dark"] .asignup-done {
    background:rgba(74,222,128,.12); border-color:rgba(74,222,128,.38);
    color:#4ade80;
}
[data-bs-theme="dark"] .asignup-err { color:#fca5a5; }
[data-bs-theme="dark"] .astate-b { color:#0b0f1a; }
[data-bs-theme="dark"] .astate-b:hover { color:#0b0f1a; }

/* ============================================================
   BOTTOM TAB BAR (Phase D, 2026-07-30) — phones, signed in only
   The single biggest "this is an app" cue on mobile. Rendered only
   for authenticated users: a guest has two possible destinations
   (search, sign-in) and a 2-item bar is noise, not navigation.
   ============================================================ */
.tabbar { display:none; }

@media (max-width:768px) {
    .tabbar {
        display:grid; grid-template-columns:repeat(4,1fr);
        position:fixed; inset-inline:0; bottom:0; z-index:1030;
        background:var(--surface);
        border-top:1px solid var(--border);
        /* Clears the iOS home indicator. */
        padding-bottom:env(safe-area-inset-bottom);
        box-shadow:0 -2px 12px rgba(15,23,42,.08);
    }
    .tabbar-i {
        display:flex; flex-direction:column; align-items:center;
        justify-content:center; gap:2px;
        /* 56px+ keeps every target comfortably over the 44px minimum. */
        min-height:56px; padding:7px 4px 6px;
        position:relative;
        /* 12px, not 11: this is the app's permanent primary navigation, not a
           section label, so the small-caps exemption to the 13px floor never
           applied to it. Four one-word labels still fit at 320px. */
        font-size:12px; font-weight:600; line-height:1;
        color:#4b5563; text-decoration:none;
    }
    .tabbar-ico { display:flex; line-height:0; }
    .tabbar-i.is-on { color:var(--accent); }
    .tabbar-i.is-on::after {
        content:''; position:absolute; top:0; inset-inline:22%;
        height:2.5px; border-radius:0 0 3px 3px; background:var(--accent);
    }
    /* A guest's bar has three destinations, not four (2026-08-05c). Stated as
       its own class rather than `grid-auto-flow:column`, so the four-item bar
       keeps the explicit column count it was measured at. */
    .tabbar-3 { grid-template-columns:repeat(3,1fr); }

    /* ── The account door, not a destination (2026-09-05) ─────────────────
       Raz: *"make the tab bar sign-in stand out as an account action."* It was
       the third of three identical grey items, which is exactly what this
       file, `_join_cta.html` and `test_landing_signin_door_20260830` had all
       already written down about it — "the bottom tab bar's sits between
       Search and Saved and READS AS NAVIGATION" was the finding that justified
       adding the landing door, and nothing was then done to the tab itself.

       The pair Raz asked for on 2026-08-06 is split across the two zones a
       phone has — הרשמה in the top bar, התחברות down here. The top half has
       been a solid button since that day and this half was flat grey text, so
       the two never read as a pair.

       ⚠ OUTLINED, NOT FILLED. `.nav-join` is the primary door; a second solid
       accent block competes with it instead of partnering it. Secondary door,
       so it takes the ring and leaves the fill.

       ⚠ THE RING IS A PSEUDO-ELEMENT, NOT A BORDER ON THE LINK. A border needs
       an inset to clear the bar's own edges, the inset would have to be margin,
       and margin comes straight off the hit area: 56px − 2×5px = 46px, on a
       floor this file has already had to defend twice (`.refresh-offer-b`,
       `.tchip`). `::before` draws the same ring INSIDE an untouched 56px
       target. No z-index needed — the ring is at the perimeter and the icon and
       label are centred, the same reason `.tabbar-i.is-on::after` works.

       ⚠ `var(--accent)` IS CORRECT HERE AND WRONG ON `.nav-join`. The
       documented 3.22:1 failure is --accent BEHIND WHITE TEXT on the navbar,
       which is a fixed dark surface in both themes. This is --accent AS TEXT on
       `--surface`, which re-tints in step with it: 4.85:1 on #fff, 5.0:1 on
       #1a2130. `.tabbar-i.is-on` already ships exactly this pair.

       ⚠ Being permanently accent-coloured does NOT make it look permanently
       selected. `.is-on` is marked by the top rule in `::after`, which only the
       current tab gets and this ring never draws. */
    .tabbar-signin { color:var(--accent); font-weight:700; }
    .tabbar-signin::before {
        content:''; position:absolute; inset:5px 8px;
        border:1.5px solid var(--accent); border-radius:10px;
    }
    .tabbar-dot {
        position:absolute; top:8px; inset-inline-start:calc(50% + 8px);
        width:8px; height:8px; border-radius:50%;
        background:#dc2626; border:1.5px solid var(--surface);
    }
    /* The guest ♡ count. Sits where `.tabbar-dot` sits — same job, but this
       one carries the number. `app.js` unhides it only when there is one. */
    .tabbar-num {
        position:absolute; top:5px; inset-inline-start:calc(50% + 6px);
        min-width:17px; padding:0 4px; border-radius:999px;
        background:var(--accent); color:#fff;
        font-size:11px; font-weight:700; line-height:17px; text-align:center;
    }

    /* Nothing may hide behind the bar: pad the page, and lift anything that
       pins itself to the bottom (the Settings save bar) above it. */
    body { padding-bottom:calc(60px + env(safe-area-inset-bottom)); }
    /* ⚠ The LIFT AND THE CLEARANCE ARE ONE NUMBER. A sticky element un-pins
       only when the document still has room below its flow position — so with
       the bar held 60px up for the tab bar, it needs 60px of document BELOW it
       or it stays pinned forever and comes to rest on top of the last thing on
       the page. Measured at 320x740 on Roles: bar 611..680 sitting over
       `.settings-acct-link` 624..645, while 390 and 1280 were already clear.
       ⚠ Space above the bar cannot fix this — the overlapped content is above
       it, and only room BELOW lets it settle. Keep the two in step. */
    .settings-savebar { bottom:calc(60px + env(safe-area-inset-bottom)); }
    /* ⚠ The clearance is PADDING ON THE PARENT, not margin on the bar. A
       sticky element un-pins only when its CONTAINING BLOCK still has room
       below it, and `.settings-wrap` is that block — the bar's own
       `margin-bottom` sits outside the parent's content box and moved the
       content above it instead (measured: the link went 624→593 and the bar
       stayed pinned at 611..680, so the overlap survived the "fix"). */
    .settings-wrap { padding-bottom:calc(60px + env(safe-area-inset-bottom)); }
    .toast-container { bottom:calc(60px + env(safe-area-inset-bottom)) !important; }
    /* The detail sheet covers the whole bottom edge, so it sits ABOVE the bar
       (z-index 1050 > 1030) and the bar must not peek through its overlay. */
    #panel-overlay, #job-sheet-overlay { z-index:1040; }
}

[data-bs-theme="dark"] .tabbar-i { color:var(--muted); }
[data-bs-theme="dark"] .tabbar-i.is-on { color:var(--accent); }
/* ⚠ Pairs with `.tabbar-signin` above, which loses to the 0-2-0 rule two lines
   up and would go muted grey in dark without this. The ring keeps its colour
   either way — a `border` on `var(--accent)` re-tints on its own; only the
   text needed rescuing. */
[data-bs-theme="dark"] .tabbar-signin { color:var(--accent); }

/* ── Board chrome on phones (Phase D, 2026-07-30) ─────────────────────────
   The status pills wrapped to THREE rows on a 390px screen and the invite
   widget filled the rest, so the first job sat entirely below the fold — the
   board's own content was the last thing you reached. */
/* The phone rules for the preference chip strip were DELETED on 2026-08-05
   with the strip itself — the scroller, its RTL-mirrored mask fade, the
   per-chip ellipsis and the short edit label all styled markup that no longer
   renders. `.prefs-cta` needs none of it: one pill, one label, no overflow. */
@media (max-width:767.98px) {
    /* Permanent, and the only control on this row — so it takes the 44px floor
       the rest of the phone UI has. */
    .prefs-cta { min-height:44px; }
}

/* The invite widget's markup lives in a Settings card now (2026-08-04), whose
   header already carries the pitch — no phone-only text swap needed here
   any more; the old `.invite-strip-q`/`.invite-strip-q-short` pair is gone. */
@media (max-width:767.98px) {
    .invite-strip { gap:8px 12px; }
}

/* The invite disclosure (.invite-card/.invite-sum/.invite-caret) was DELETED
   on 2026-08-01, not just restyled — it is now a flat .invite-strip. Leaving
   the old rules behind would be dead weight, and a dark-mode override that
   outlives its component is exactly the trap logged for .afilter. */

/* The always-present strip is the quietest of the four offers, so it stays
   compact: one line of copy, and on wider phones the field and button share a
   row with it. It must never out-weigh the results it sits above. */
.asignup-bar { padding:12px 14px; }
.asignup-bar .asignup-h { font-size:14px; margin-bottom:8px; }
.asignup-fine-tight { margin-top:7px; font-size:11.5px; }

/* ⚠ BELOW 520px this strip was 204px tall on a 390x844 phone — the copy, then
   a stacked field, then a stacked button, then the fine print — and it sits
   between the result count and the first job. Measured: the first card started
   at 570px, so a guest who ran a search saw the count, the offer, and NOT ONE
   JOB without scrolling. On a job board that is the content.
   The ≥520px grid already puts the field and button on one line beside the
   copy; phones just never got the equivalent. They share a row here too, which
   is the whole difference (204px → ~120px). The offer keeps its frame, its
   colour and all four placements — it stops being the screen. */
@media (max-width:519.98px) {
    .asignup-bar { padding:10px 12px; }
    .asignup-bar .asignup-h { margin-bottom:6px; }
    .asignup-bar .asignup-row { flex-direction:row; align-items:center; gap:8px; }
    /* min-width:0 or the input's intrinsic width refuses to shrink and pushes
       the button off the row — the same flex trap as .bcard-actionrow. */
    .asignup-bar .asignup-input { flex:1 1 auto; min-width:0; }
    .asignup-bar .asignup-go { flex:0 0 auto; white-space:nowrap; padding-inline:14px; }
}

@media (min-width:520px) {
    .asignup-bar { display:grid; grid-template-columns:1fr auto; gap:6px 14px;
                   align-items:center; }
    .asignup-bar .asignup-copy { grid-column:1; grid-row:1; }
    .asignup-bar .asignup-row  { grid-column:2; grid-row:1 / span 2; }
    .asignup-bar .asignup-h    { margin-bottom:0; }
    .asignup-bar .asignup-fine-tight { grid-column:1; grid-row:2; margin-top:2px; }
    .asignup-bar .asignup-input { min-width:190px; }
}

/* ── Search <-> board bridge (2026-07-30) ─────────────────────────────────
   A signed-in user searching sees the job's board status, or a way to put it
   there. Without this the two surfaces are disconnected islands. */
/* `.jcard-add` now shares `.jcard-act`'s shape and takes --act-board (green);
   its own box rules moved there on 2026-08-05 so all four secondary actions
   are one component with four hues rather than four near-identical components. */
.jcard-onboard { font-size:12.5px; }

/* "Fold this search into my preferences" — the signed-in counterpart of the
   guest email offer. */
.adopt {
    display:flex; flex-direction:column; gap:10px;
    background:rgba(46,117,181,.06); border:1px dashed var(--border);
    border-radius:var(--radius); padding:13px 15px; margin-bottom:16px;
}
.adopt-copy { margin:0; font-size:13.5px; line-height:1.5; color:#4b5563; }
.adopt-copy strong { color:var(--text); }
.adopt-go {
    align-self:flex-start; background:var(--accent); color:#fff; border:none;
    border-radius:6px; font-size:14px; font-weight:600;
    padding:9px 16px; min-height:40px; cursor:pointer;
}
.adopt-go:hover { background:var(--accent-dk); }
@media (min-width:640px) {
    .adopt { flex-direction:row; align-items:center; justify-content:space-between; }
    .adopt-go { flex:none; }
}
[data-bs-theme="dark"] .adopt { background:rgba(77,148,214,.10); }
[data-bs-theme="dark"] .adopt-copy { color:var(--muted); }
[data-bs-theme="dark"] .adopt-go { color:#0b0f1a; }
/* (the `.jcard-add` dark override was deleted with the token move — see the
   `.jcard-more` note above for why an override here is never the right fix) */

/* ── Guest "save this job" (2026-07-30) ───────────────────────────────────
   The guest counterpart of "+ Add to board". Stored in the visitor's own
   localStorage (ids only) — see the note in app.js for why not a server row. */
/* `.jcard-save` shares `.jcard-act` and takes --act-save (rose) since
   2026-08-05 — a heart is red everywhere else a person has ever seen one, and
   it was previously the same blue as Apply beside it. */

.saved-link { display:inline-flex; align-items:center; gap:5px; }
.saved-count {
    background:rgba(255,255,255,.18); border-radius:999px;
    padding:1px 7px; font-size:12px; font-weight:700;
}
.saved-h { font-size:21px; font-weight:800; margin:0; color:var(--text); }
.saved-inline-heart { color:var(--accent); }
.saved-gone { color:#4b5563; }

/* "Loading your saved jobs…" — the state that exists because only the BROWSER
   knows what a guest saved (localStorage, ids only). The server renders this
   first and app.js swaps in either the list or the real empty state. */
.saved-loading { display:flex; flex-direction:column; align-items:center; gap:12px; }
.saved-loading .rempty-h { margin:0; }
.saved-spin {
    width:26px; height:26px; border-radius:50%;
    border:3px solid var(--border); border-top-color:var(--accent);
    animation:saved-spin .8s linear infinite;
}
@keyframes saved-spin { to { transform:rotate(360deg); } }
/* A spinner is decoration; the wording carries the meaning on its own. */
@media (prefers-reduced-motion: reduce) {
    .saved-spin { animation:none; }
}

/* Saved-jobs header: the title and the way back out to the full pool. Wraps to
   two rows on a phone rather than shrinking the tap target. */
.saved-head {
    display:flex; flex-wrap:wrap; align-items:center;
    justify-content:space-between; gap:10px; margin:0 0 14px;
}
.saved-exit {
    display:inline-flex; align-items:center; gap:6px;
    padding:7px 13px; border-radius:8px; text-decoration:none;
    font-size:13.5px; font-weight:600;
    /* #3a6ea5, not --accent (#2E75B5): the accent measures 4.48:1 on --bg and
       this is small text. Darkening it clears AA without touching the token. */
    color:#3a6ea5; background:var(--surface); border:1px solid var(--border);
}
.saved-exit:hover { color:#2b5580; border-color:#c9d3de; }
/* The two exits on /privacy (2026-08-07). No colours of their own — they reuse
   `.saved-exit` so there is one back-control in the app, which is also what
   keeps them inside the RTL arrow-flip group. Spacing only. */
.privacy-exit-top { margin-block-end:18px; }
.privacy-exit-end {
    margin-block-start:26px; padding-block-start:20px;
    border-top:1px solid var(--border);
}
.saved-search { margin:0 0 22px; }
.saved-search-h {
    font-size:11px; font-weight:700; text-transform:uppercase;
    letter-spacing:.06em; color:#4b5563; margin:0 0 7px;
}
[data-bs-theme="dark"] .saved-exit { color:#7fb5e6; }
[data-bs-theme="dark"] .saved-exit:hover { color:#a5cdf0; border-color:#3a4657; }
[data-bs-theme="dark"] .saved-search-h { color:var(--muted); }

/* (the three `.jcard-save` dark overrides were deleted with the token move —
   --act-save is already re-tinted in the dark block) */
[data-bs-theme="dark"] .saved-gone { color:var(--muted); }

/* ── "Browse as guest" on the login screen (2026-07-30) ───────────────────
   Accounts are invite-only, so without this the login page dead-ends anyone
   who doesn't have one — while the entire job search is open to them. */
.auth-guest { margin-top:18px; padding-top:16px; border-top:1px solid var(--border);
              text-align:center; }
.auth-guest-or {
    display:block; font-size:11px; font-weight:700; letter-spacing:.06em;
    text-transform:uppercase; color:#4b5563; margin-bottom:10px;
}
/* A LINK here was the bug (2026-07-31): accounts are invite-only, so for most
   arrivals this is the only door that opens — and it was rendered as small
   underlined text under a full-width primary button. It is now the same
   BUTTON SHAPE as "Log in" (full width, same padding/radius/min-height) in the
   green identity, so the two read as two ways in rather than an action and a
   footnote. Green, not a second blue: an outline or a blue twin would compete
   with the real primary; a different hue says "different route", not "less
   important". Wraps to two lines on the narrowest phones — deliberate, the
   sentence is the reassurance and the button grows to fit it. */
.auth-guest-link {
    display:flex; align-items:center; justify-content:center; gap:6px;
    width:100%; box-sizing:border-box; text-align:center;
    background:var(--guest); color:var(--guest-ink);
    border:none; border-radius:.5rem;
    /* A shade under btn-lg's 20px: the label is shorter than "Log in" is big,
       and the hierarchy is carried by type size while the equal footing is
       carried by the shape. */
    font-size:16px; font-weight:700; line-height:1.35;
    padding:12px 16px; min-height:48px;
    text-decoration:none;
}
.auth-guest-link:hover,
.auth-guest-link:focus { background:var(--guest-dk); color:var(--guest-ink); text-decoration:none; }
.auth-guest-note { font-size:12.5px; color:#4b5563; margin:9px 0 0; line-height:1.5; }
[data-bs-theme="dark"] .auth-guest-or,
[data-bs-theme="dark"] .auth-guest-note { color:var(--muted); }

/* ── THE GUEST DOOR HAS TO BE ABOVE THE FOLD (2026-08-05b, Raz) ────────────
   *"I want the option to Continue as Guest to be visible without the need to
   scroll down. I am scared that if users don't see it initially, they will
   navigate elsewhere without any engagement. Consider narrowing the spaces
   between the components or any other creative solution."*

   ⚠ MEASURED FIRST, and the measurement is why this is a real bug rather than
   a preference. At 390×844 the button sat at top=685, bottom=753 — inside the
   *viewport* and therefore easy to call fine from a desktop devtools frame.
   But a real phone gives the page ~650px, not 844: Chrome's URL bar, the tab
   strip and the gesture bar are all in Raz's screenshot. So the control that,
   for most arrivals, is the ONLY door that opens (accounts are invite-only)
   was reliably below the visible edge.

   Every cut below is space, not content — nothing was deleted except a
   duplicate. Measured after, identically in both locales and both themes:
   button **top 685 → 571, bottom 753 → 639** on /login (and 518/585 on /join,
   which shares the panel and gets the same benefit). Raz's own screenshot
   works out at roughly a 700px page window on his device, so that is ~60px of
   clearance where there was none.

   ⚠ These rules must sit AFTER the `.auth-guest-*` block above and after the
   `.auth-panel` rules near line 1300 — a media query adds no specificity, so a
   phone override written earlier in the file loses to the plain rule silently,
   with every test still green. Third time this trap is being noted. */
@media (max-width:767.98px) {
    /* ⚠ The biggest single win was DELETING a duplicate, and it is a template
       change rather than a rule here: `🇮🇱 Jobs Scraper` inside the panel is the
       same wordmark the navbar renders ~40px above it, so `.auth-panel-brand`
       is gone from login.html and join.html along with its two CSS rules. 86px.
       Don't re-add it — see the note in `auth/login.html`. */

    .auth-split { padding-block:12px; }
    .auth-panel { padding-block:20px; }
    .auth-sub { margin-bottom:14px; }
    /* Bootstrap's own field rhythm is generous for a form of two fields and a
       checkbox; it is the same 16/24px gap it would use on a settings page of
       thirty. Scoped to this panel so nothing else inherits the tightening. */
    .auth-panel .mb-3 { margin-bottom:.7rem; }
    .auth-panel .mb-4 { margin-bottom:.9rem; }
    .auth-panel .form-label { margin-bottom:.25rem; }
    .auth-welcome { font-size:21px; }
    .auth-guest { margin-top:12px; padding-top:12px; }
    .auth-guest-or { margin-bottom:6px; }
}

/* ── Keyword autocomplete: multi-select chips + bilingual menu (2026-07-30) ─
   Progressive enhancement over the plain `q` input, which stays in the DOM as
   the real field so the form works with JS off. */
.kw-wrap { position:relative; width:100%; }
.kw-real-hidden { position:absolute; opacity:0; pointer-events:none; width:1px; height:1px; }
.kw-box {
    display:flex; flex-wrap:wrap; align-items:center; gap:6px;
    width:100%; box-sizing:border-box; cursor:text;
    background:var(--surface); color:var(--text);
    border:1px solid var(--border); border-radius:10px;
    min-height:52px; padding-block:8px; padding-inline:40px 12px;
}
.kw-box:focus-within {
    border-color:var(--accent); box-shadow:0 0 0 .2rem rgba(46,117,181,.2);
}
/* Deliberately the LIGHT chip, unlike the solid `.rfilter`/`.afilter` pills:
   these live inside a text field, where a row of solid blue tokens reads as a
   wall and hides the caret you are meant to type next to. Weight and a defined
   edge carry "this is a selection" instead (2026-07-31). */
.kw-chip {
    display:inline-flex; align-items:center; gap:6px;
    background:rgba(46,117,181,.12); color:#1f5286;
    border:1px solid rgba(46,117,181,.45);
    border-radius:999px; padding-block:4px; padding-inline:11px 6px;
    font-size:13.5px; font-weight:700; max-width:100%;
}
.kw-chip-x {
    background:none; border:none; cursor:pointer; color:inherit;
    font-size:15px; line-height:1; padding:2px 5px; border-radius:50%;
}
.kw-chip-x:hover { background:rgba(46,117,181,.2); }
.kw-entry {
    flex:1 1 90px; min-width:90px; border:none; outline:none;
    background:none; color:var(--text); font-size:16px; padding:4px 0;
}
.kw-menu {
    position:absolute; z-index:40; left:0; right:0; top:calc(100% + 4px);
    margin:0; padding:5px; list-style:none;
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); box-shadow:var(--shadow);
    max-height:min(52vh,320px); overflow-y:auto;
}
.kw-opt {
    display:flex; align-items:center; justify-content:space-between; gap:12px;
    padding:10px 11px; border-radius:6px; cursor:pointer;
}
.kw-opt.is-active { background:rgba(46,117,181,.1); }
.kw-opt-en { font-size:14.5px; font-weight:600; color:var(--text); }
/* ── The domain catch-alls in the suggestion list (2026-08-07, Raz) ─────────
   "Parent Options" sit at the top of the menu, so they need to look like the
   heading of what follows rather than the first of a set of equals. A hairline
   under the last one is what separates the group from the specific roles —
   ⚠ `:last-of-type`, because they are always contiguous at the top (the server
   sorts them into one block), so this draws exactly one rule wherever the block
   ends, with no marker element and no count in the DOM. */
/* ⚠ #3a6ea5, not --accent (#2E75B5) — the same substitution `.saved-exit`
   makes, for the same reason: --accent measures 4.48:1 on this surface and
   this is 14.5px text, so it is 0.02 short of AA. That shortfall is a known,
   accepted palette item across the app and Raz's call to fix wholesale, but
   there is no reason to mint a NEW instance of it in a new component. */
.kw-opt-any .kw-opt-en { font-weight:800; color:#3a6ea5; }
[data-bs-theme="dark"] .kw-opt-any .kw-opt-en { color:#7fb5e6; }
.kw-opt-any:last-of-type { border-bottom:1px solid var(--border); margin-bottom:4px; padding-bottom:12px; }
/* What the parent covers, stated as text — never colour alone (WCAG 1.4.1). */
.kw-opt-covers {
    display:inline-block; margin-inline-start:8px;
    font-size:11.5px; font-weight:700; color:var(--text);
    background:var(--bg); border:1px solid var(--border);
    border-radius:4px; padding:1px 6px; vertical-align:middle;
}
/* The Hebrew equivalent is the point of the feature — visible, not a tooltip. */
.kw-opt-he { font-size:13px; color:#4b5563; text-align:end; }
@media (max-width:767px) {
    .jsearch-bar .kw-box { min-height:46px; padding-top:6px; padding-bottom:6px; }
}
[data-bs-theme="dark"] .kw-chip {
    background:rgba(77,148,214,.20); color:#cfe4f7; border-color:rgba(127,181,230,.55);
}
[data-bs-theme="dark"] .kw-chip-x:hover { background:rgba(77,148,214,.3); }
[data-bs-theme="dark"] .kw-opt.is-active { background:#26324a; }
[data-bs-theme="dark"] .kw-opt-he { color:var(--muted); }

/* ── Desktop split view: results left, detail right (2026-07-30) ──────────
   JobMaster puts the detail beside the list rather than on its own page; on a
   wide screen that is simply better use of the space. Mirrored from their RTL
   side to ours. Below 992px this collapses back to a plain list and the card
   links navigate to /jobs/<id> as before — the split is JS-only, so it must
   never be the only way to read a job. */
/* ── The job list is ONE full-width column (2026-08-08) ────────────────────
   Raz: *"I like much better the way the application currently displays jobs on
   the Board. It looks much cleaner than the display on Global search."*

   This was a two-column grid (`1fr / 1.15fr`) with a sticky detail pane. What
   the measurement showed is that the CARD was never the problem — search and
   the board render the same `.jcard` component — but the grid gave it **499px
   against the board's 1088px** at 1280, which pushed snippets from 2 lines to
   3, wrapped titles, and forced `.jcard-more` to be hidden to fit. The pane
   also held 53% of the width permanently and repeated the selected job's
   title, company, city and source beside the card already showing them.

   So: one column, and the detail arrives on demand in `#job-sheet`, which is
   painted by the board's own sheet rules above. `.jcards` already supplies the
   column, so this needs no layout of its own — the class stays as the hook the
   JS and the tests name. */
.joblist { display:block; }
/* ⚠ `.joblist` IS A `<ul>` ON THE GUIDE AND MARKET PAGES (2026-09-07) and stays
   a `<div>` in `partials/_job_list.html`, so both must render identically.
   The search partial interleaves a signup strip between cards, and a non-job
   element inside a list makes a screen reader announce one more item than
   there are jobs — so that one is deliberately not a list. These three rules
   are inert on a div and are what stop the ul growing markers and indentation
   on the other three. */
.joblist { list-style:none; margin:0; padding:0; }
.joblist > li { display:block; min-width:0; }

/* The card whose job the sheet is showing stays marked, so the list and the
   sheet stay legibly connected while scrolling. ⚠ No `cursor:pointer` here —
   `.jcard[data-card-job]` already carries it at every width (2026-08-05), and
   restating it scoped to this list is how the pre-2026-08-05 bug happened. */
.joblist .jcard.is-selected {
    border-color:var(--accent);
    box-shadow:0 0 0 1px var(--accent), var(--shadow);
}


.rsplit-loading { padding:24px; text-align:center; color:#4b5563; font-size:14px; }
[data-bs-theme="dark"] .rsplit-loading { color:var(--muted); }

/* ── Landing: trust strip, hi-tech pitch, who's hiring (2026-07-30) ───────
   Every figure is rendered from the live pool, so nothing here can quietly
   become untrue. */
.stats {
    display:grid; grid-template-columns:repeat(2,1fr); gap:10px;
    margin:0 0 22px;
}
.stat {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); padding:14px 12px; text-align:center;
}
.stat-n { display:block; font-size:22px; font-weight:800; color:var(--accent); line-height:1.1; }
.stat-l { display:block; font-size:12px; line-height:1.4; color:#4b5563; margin-top:5px; }
@media (min-width:768px) {
    .stats { grid-template-columns:repeat(4,1fr); gap:14px; }
    .stat { padding:18px 14px; }
    .stat-n { font-size:27px; }
    .stat-l { font-size:13px; }
}

.pitch {
    background:rgba(46,117,181,.06); border:1px solid var(--border);
    border-radius:var(--radius); padding:18px 16px;
}
.pitch-h { font-size:19px; font-weight:800; color:var(--text); margin:0 0 8px; }
.pitch-p { font-size:14px; line-height:1.6; color:#4b5563; margin:0 0 13px; }
.pitch-p strong { color:var(--text); }
/* Expectation management, so it reads as a note rather than more marketing:
   quieter than the pitch above it, with a rule to separate the two. */
.pitch-note {
    font-size:13px; line-height:1.6; color:#4b5563;
    margin:14px 0 0; padding-top:12px;
    border-top:1px solid var(--border);
}
[data-bs-theme="dark"] .pitch-note { color:var(--muted); }
.pitch-chips { margin:0; }

.lsec-sub { font-size:13.5px; color:#4b5563; margin:-8px 0 12px; }
.cologos { display:flex; flex-wrap:wrap; gap:8px; }
.cologo {
    background:var(--surface); border:1px solid var(--border);
    border-radius:8px; padding:9px 14px;
    font-size:13.5px; font-weight:700; color:var(--text); text-decoration:none;
}
.cologo:hover { border-color:var(--accent); color:var(--accent); }
/* A source chip is a FACT, not a destination — there is no per-source search,
   so it must not look clickable. Same chip, no hover, plus its live count. */
.cologo-src { cursor:default; }
.cologo-n { color:var(--accent); font-weight:800; margin-inline-start:2px; }
/* Sub-heading inside "Where these jobs come from": smaller than .lsec-h, which
   is the section's own title, so the two groups read as its parts. */
.srcgroup-h {
    font-size:12px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
    color:#4b5563; margin:16px 0 9px;
}
.lsec-srcs .srcgroup-h:first-of-type { margin-top:4px; }
[data-bs-theme="dark"] .srcgroup-h { color:var(--muted); }

[data-bs-theme="dark"] .stat-l,
[data-bs-theme="dark"] .pitch-p,
[data-bs-theme="dark"] .lsec-sub { color:var(--muted); }
[data-bs-theme="dark"] .pitch { background:rgba(77,148,214,.10); }

/* ── "Create an account" (2026-07-30) ─────────────────────────────────────
   Registration stays token-gated; this issues the token to whoever asks and
   emails it, so the link itself is the verification step. */
.join {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); padding:18px 16px; margin:22px 0;
}
.join-h { font-size:17px; font-weight:800; color:var(--text); margin:0 0 6px; }
.join-p { font-size:13.5px; line-height:1.55; color:#4b5563; margin:0 0 13px; }
.join-row { display:flex; flex-direction:column; gap:9px; }
.join-input {
    flex:1 1 auto; min-width:0; box-sizing:border-box;
    background:var(--bg); color:var(--text);
    border:1px solid var(--border); border-radius:6px;
    font-size:16px; padding:11px 13px; min-height:46px;
}
.join-input:focus {
    outline:none; border-color:var(--accent);
    box-shadow:0 0 0 .2rem rgba(46,117,181,.18);
}
.join-go {
    flex:none; background:var(--accent); color:#fff; border:none;
    border-radius:6px; font-size:15px; font-weight:700;
    padding:11px 20px; min-height:46px; cursor:pointer;
}
.join-go:hover { background:var(--accent-dk); }
.join-go:disabled { opacity:.6; cursor:default; }
/* ── The password field + its show/hide eye (2026-08-06) ───────────────────
   ⚠ `min-width:0` on the wrapper. It is a flex item once the row goes
   horizontal at 560px, and a flex item's default `min-width:auto` refuses to
   shrink below its content — the trap that made the whole board 1221px wide on
   2026-08-04b. Without it the placeholder ("Password — 8 characters or more")
   sets a floor that pushes the row past a 320px screen. */
.join-pw { position:relative; display:flex; flex:1 1 auto; min-width:0; }
.join-pw-input {
    /* ⚠ AN INPUT DOES NOT NECESSARILY INHERIT THE PAGE'S DIRECTION, and that
       is the whole bug (2026-08-07, Raz: "the eye icon in the registration
       screen overrides the password"). Browsers ship UA rules that force
       `direction:ltr` on inputs whose value is not natural-language text —
       Chromium already does exactly that to the EMAIL field on this very form
       (measured: `direction:ltr` on an otherwise RTL page), and Raz's browser
       does it to the password field too.

       When it fires, the two rules below resolve `inline-end` to OPPOSITE
       sides: `padding-inline-end` follows the INPUT (ltr -> right) while the
       eye's `inset-inline-end` follows the PAGE (rtl -> left). The gutter is
       reserved on the empty side and the button is painted on top of the first
       characters of the password. Screenshotted by Raz, reproduced exactly by
       forcing `direction:ltr` here.

       Restating the inherited direction is what keeps the pair in agreement:
       an author declaration beats the UA sheet, so `inline-end` means the same
       physical side for the padding and for the button, in both locales. Do
       not "simplify" this away — it looks redundant and is not. */
    direction:inherit;
    /* Room for the eye, on the END side so it flips with the writing
       direction — a physical `padding-right` would put it under the button in
       Hebrew, which is the 2026-07-31 `.jsearch-input` bug. */
    padding-inline-end:44px;
}
.join-pw-eye {
    position:absolute; inset-inline-end:2px; inset-block:2px;
    width:40px; display:flex; align-items:center; justify-content:center;
    background:none; border:none; border-radius:6px; padding:0;
    color:#4b5563; cursor:pointer;
}
.join-pw-eye:hover { color:var(--accent); }
.join-pw-eye[aria-pressed="true"] { color:var(--accent); }
.join-pw-eye:focus-visible {
    outline:2px solid var(--accent); outline-offset:-2px;
}
/* ⚠ The 44px floor for the "Send it again" button in the unconfirmed-email
   banner. It is a Bootstrap `btn-sm` and measured 31px at 390px — the fifth
   control here to land under the floor by inheriting a small-button class
   (`.jcard-add` 41px, `.nav-join` 33px, `.bcard-contact` 41px, the card select
   33px). Nothing else sets a min-height on this class, so source position is
   not load-bearing; it lives beside the feature it belongs to. */
@media (max-width:767.98px) {
    .confirm-resend-b { min-height:44px; }
}
/* ⚠ THE HONEYPOT, moved off-screen rather than `display:none` (2026-08-06d).
   A bot worth stopping skips `display:none` and `type=hidden`; the cheap ones
   that fill every reachable field do not check computed styles. Same technique
   as `.visually-hidden`, kept separate so nobody "tidies" it into that class
   and gives it a screen-reader-friendly meaning it must not have. */
.join-hp {
    position:absolute; width:1px; height:1px; overflow:hidden;
    clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}
/* Field-level signup errors, under the row that caused them — a flash at the
   top of the page is a long way from the input it is about. */
.join-err {
    margin:8px 0 0; font-size:13px; font-weight:600; color:#b91c1c;
}
.join-consent { margin-block-start:4px; }
.join-input.is-invalid { border-color:#b91c1c; }
.join-fine { font-size:12px; color:#4b5563; margin:9px 0 0; }
/* `.join-done` lived here for the async submit's "check your inbox" panel.
   Both are DELETED (2026-08-06): signup no longer ends in a message, it ends
   in a redirect to onboarding. Removed rather than left behind — an orphaned
   rule nobody dares touch is how this stylesheet accumulates dead weight. */
@media (min-width:560px) {
    /* ⚠ THREE controls on this row now, not two. At 560px email + password +
       button is a squeeze, so the fields share the first line and the button
       takes the second until there is genuinely room for all three. */
    .join-row { flex-direction:row; align-items:center; flex-wrap:wrap; }
    .join-row > .join-input { flex:1 1 200px; }
    .join-pw { flex:1 1 200px; }
}
@media (min-width:860px) {
    .join-row { flex-wrap:nowrap; }
}
/* On the landing page it's a closing CTA, so give it more presence. */
.join-panel { border-color:var(--accent); }
/* On /join the surrounding .auth-panel is already the card, so the form drops
   its own box — a card inside a card reads as two competing things and eats
   the width a 320px phone hasn't got. (The old .join-inline variant, which sat
   under the login form, is gone: that screen no longer offers registration.) */
.join-page {
    background:none; border:none; border-radius:0;
    padding:0; margin:0;
}

/* "Already have an account? Sign in →" — the escape hatch for someone who
   pressed the wrong one of the two top-bar doors. Deliberately quiet: on this
   screen they are the minority. */
.auth-alt {
    margin:16px 0 0; text-align:center;
    font-size:13.5px; color:#4b5563;
}
.auth-alt a { font-weight:700; white-space:nowrap; }
[data-bs-theme="dark"] .auth-alt { color:var(--muted); }

/* The sign-in door that sits under the signup form (2026-08-30, Raz: "enlarge
   the התחברות button to be more noticeable"). It is the LANDING page's only
   sign-in affordance on a phone — the top bar's is `.nav-ondeck`
   (display:none < 768px) and the tab bar's reads as navigation.

   ⚠ A MODIFIER, NOT `.auth-alt a`. That selector is also the password-recovery
   link on /login and /forgot, which are footnotes and were not reported;
   widening the rule would restyle three screens to fix one.

   ⚠ IT MUST STAY BELOW `@media (min-width:768px) { .auth-alt {font-size:14.5px} }`
   at ~line 1678. A media query adds NO specificity — the house trap, nine
   appearances — so `.auth-alt-strong` (0-1-0) beats it on ORDER alone and
   would silently lose if this block were moved up.

   ⚠ A BUTTON, NOT AN ENLARGED LINK (2026-09-05, Raz: *"it doesn't look like a
   button right now, looks like a link"*). The 2026-08-30 pass gave it size and
   an underline and stopped there, on the reasoning quoted below — that a colour
   change risked a tenth contrast finding. The size alone did not carry it: an
   underlined phrase in a paragraph is a link however large it is set, and the
   thing it has to compete with is a solid submit button 30px above it.

   ⚠ OUTLINED, so the hierarchy survives. The signup form's own submit is the
   primary action on both surfaces this partial renders on; this is the door for
   the minority who already have an account. Ring, not fill — the same call, and
   the same reason, as `.tabbar-signin`.

   ⚠ THE COLOURS ARE STATED PER THEME, and that is what answers the contrast
   objection the old comment raised rather than dodging it. Measured against
   BOTH backdrops this partial can sit on (`--bg` on the landing page, and
   `--surface`):
       light #255f9e (`--accent-dk`)  6.04:1 on #f4f6f8, 6.54:1 on #fff
       dark  #7fb5e6 (the dark link colour already in the palette)
                                      8.46:1 on #0f1420, 7.40:1 on #1a2130
   ⚠ `var(--accent)` was tried first and is the trap: #2E75B5 on `--bg` is
   4.478:1 — under 4.5 by two hundredths, and 16.5px/700 is NOT large text
   (WCAG large-scale bold starts at 18.66px), so it is a real fail and it is
   also what ships today via `--bs-link-color`.

   ⚠ The hover fill states #2E75B5 + #fff outright for the SAME reason
   `.navbar .nav-join` does — `--accent` behind white re-tints to the documented
   3.22:1 in dark, and this text is not large enough for the 3:1 threshold.

   ⚠ `display:flex` on the <p> puts the button on its own line. The label is an
   anonymous flex item, so no markup changes and the RTL mirror is free.

   ⚠ The under-767.98px `.auth-alt a` rule at ~line 4990 is 0-1-1, the same as
   `.auth-alt-strong a`, and it comes LATER — so its `min-height:44px` wins on a
   phone. Harmless: padding and line-height already make this box ~51px tall,
   and min-height is the floor, not the height. Do not "fix" it by widening the
   selector; that is the modifier rule above. */
.auth-alt-strong {
    font-size:15px;
    display:flex; flex-direction:column; align-items:center; gap:10px;
}
.auth-alt-strong a {
    display:inline-flex; align-items:center; justify-content:center;
    min-height:48px; padding:11px 26px;
    border:2px solid #255f9e; border-radius:9px;
    color:#255f9e; font-size:16.5px; font-weight:700;
    text-decoration:none;
}
.auth-alt-strong a:hover,
.auth-alt-strong a:focus-visible {
    background:#2E75B5; border-color:#2E75B5; color:#fff;
}
[data-bs-theme="dark"] .auth-alt-strong a { border-color:#7fb5e6; color:#7fb5e6; }
[data-bs-theme="dark"] .auth-alt-strong a:hover,
[data-bs-theme="dark"] .auth-alt-strong a:focus-visible {
    background:#2E75B5; border-color:#2E75B5; color:#fff;
}

.nav-join { padding:5px 13px; font-weight:700; }
/* ⚠ It STATES its colours instead of inheriting `.btn-primary`, for the same
   reason `.navsw-b.is-on` does: the navbar is a fixed dark surface in BOTH
   themes, but `--accent` re-tints under `[data-bs-theme="dark"]` and lightens
   this fill under white text — the documented `--accent`+white 3.22:1 failure
   (measured here on `הרשמה` in mob_dark and pc_dark). White on #2E75B5 is
   4.85:1 and does not move with the theme. Set through `--bs-btn-*` so
   Bootstrap's own hover/active/focus states follow. */
.navbar .nav-join {
    --bs-btn-bg:#2E75B5;        --bs-btn-border-color:#2E75B5;        --bs-btn-color:#fff;
    --bs-btn-hover-bg:#2a6aa5;  --bs-btn-hover-border-color:#2a6aa5;  --bs-btn-hover-color:#fff;
    --bs-btn-active-bg:#27619a; --bs-btn-active-border-color:#27619a; --bs-btn-active-color:#fff;
}
/* ⚠ Lives HERE, beside the rule it has to beat, not up with the rest of the
   one-row guest bar (2026-08-05c): `.nav-join` is defined near the end of the
   file and a media query adds NO specificity, so an identical selector written
   earlier would silently lose. Fourth time this trap has been recorded.
   Measured at 33px before this — it was one of five controls when that was
   written, and is now one of three, so it takes the same 44px floor as
   everything else on the bar. */
@media (max-width:767.98px) {
    .navbar .nav-join {
        display:inline-flex; align-items:center;
        min-height:44px; padding-inline:15px;
    }
}

[data-bs-theme="dark"] .join-p,
[data-bs-theme="dark"] .join-pw-eye,
[data-bs-theme="dark"] .join-fine { color:var(--muted); }
[data-bs-theme="dark"] .join-err { color:#f87171; }
[data-bs-theme="dark"] .join-go { color:#0b0f1a; }

/* ── "Why you're seeing this" in the detail pane (2026-07-30) ─────────────
   Sits in the empty space beside the location/source rows. A search result
   should never leave "why am I being shown this?" unanswered — and it is the
   same explain() the board's panel already uses. */
.jd-head-grid { display:block; }
.jd-why {
    background:rgba(46,117,181,.07);
    border:1px solid var(--border);
    border-radius:var(--radius);
    padding:11px 13px;
    margin:12px 0 0;
    align-self:start;
}
.jd-why-h {
    font-size:10.5px; font-weight:700; letter-spacing:.05em;
    text-transform:uppercase; color:#4b5563; margin:0 0 7px;
}
.jd-why-row {
    display:flex; align-items:flex-start; gap:7px;
    font-size:13px; line-height:1.5; color:#4b5563; margin:0 0 4px;
}
.jd-why-row:last-child { margin-bottom:0; }
.jd-why-ico { flex:none; display:flex; line-height:0; margin-top:3px; }
.jd-why-chip {
    display:inline-block; background:var(--surface);
    border:1px solid var(--border); border-radius:4px;
    padding:1px 7px; margin-block:0 2px; margin-inline:0 2px;
    font-size:12.5px; font-weight:600; color:var(--text);
}
/* Beside the facts once there is room — the empty space in the reference. */
@media (min-width:560px) {
    .jd-head-grid {
        display:grid; grid-template-columns:minmax(0,1fr) minmax(0,auto);
        gap:14px; align-items:start;
    }
    .jd-why { margin:0; max-width:290px; }
}
[data-bs-theme="dark"] .jd-why { background:rgba(77,148,214,.10); }
[data-bs-theme="dark"] .jd-why-h,
[data-bs-theme="dark"] .jd-why-row { color:var(--muted); }

/* ── Mobile touch targets & iOS input zoom (2026-07-31, measured) ──────────
   Found by measuring every tappable element at 320/390/768 in Hebrew RTL, not
   by eye. Three distinct problems, all functional rather than cosmetic:

   1. iOS Safari ZOOMS the whole page when a text input under 16px takes focus.
      `.jsearch-input` was already 16px for exactly this reason; the BOARD's
      filter inputs were missed and still zoomed on every tap.
   2. Small inline ✕ controls measured 14x16 — a third of the 44px floor. They
      are expanded with a pseudo-element so the HIT area grows without the
      visual chip changing size or the row re-flowing.
   3. Rows whose whole height was the target (nav links, link-buttons,
      checkbox rows) sat at 20-24px. */
@media (max-width: 768px) {
    /* 1 — never let iOS zoom on focus */
    .filter-bar input,
    .filter-bar select,
    .filter-bar .form-control,
    #bulk-status,
    #filter-q { font-size: 16px; }

    /* 2 — grow the hit area, not the chip */
    .rfilter-x, .afilter-x, .kw-chip-x, .saved-gone { position: relative; }
    .rfilter-x::after,
    .afilter-x::after,
    .kw-chip-x::after {
        content: ''; position: absolute;
        /* pulls the tappable box out to ~44px around a ~16px glyph */
        inset: -14px;
    }

    /* 3 — rows that ARE the target need a real height */
    .navbar .nav-link { padding-block: 10px; }
    .filter-menu .btn-link,
    .role-select-all, .role-clear,
    .region-all, .region-clear { padding-block: 11px; }
    /* ⚠ MEASURED AT 43.5px AND 43.0px, NOT 44 (2026-08-15). Both were padding
       arithmetic that lands just under the floor this whole block exists to
       enforce — 13px/19.5 line-height + 24px padding = 43.5, and 14px/21 + 22 =
       43.0. Invisible to a screenshot and to any test that checks the rule
       exists rather than what it produces; the sixth control in this project to
       be caught by measuring rather than reading.
       Found when the employer pages (`/companies`, `/companies/<slug>`) put
       both on a new surface — `.rfilter-clear` as the breadcrumb back to the
       directory, `.rempty-b` as "See all N openings". Stating the height is
       what makes it independent of the font size; `inline-flex` is required
       with it, because a stretched inline-block does not centre its label and
       `.rempty-b` carries an SVG in some of its uses. */
    .rfilter-clear { display: inline-flex; align-items: center;
                     min-height: 44px; padding-block: 12px; }
    .rempty-b { display: inline-flex; align-items: center;
                justify-content: center; min-height: 44px; }
    .saved-exit { padding-block: 5px; }
    .rchip, .tile { min-height: 44px; }

    /* A checkbox is easier to hit as a row than as a 16px square. Bootstrap
       already associates the label, so growing the row grows the target. */
    .form-check { min-height: 44px; display: flex; align-items: center; }
    .form-check-input { width: 1.15rem; height: 1.15rem; }
    .filter-opt, .jsearch-locopt { min-height: 44px; }

    /* 4 — the login card overflowed the viewport by 3px at 320 */
    .auth-panel { padding-inline: 20px; }
}

/* A count badge at 10.5px is below the readable floor even for a numeral. */
.role-domain-count, .role-domain-total, #match-total { font-size: 12px; }

/* ── 44px floor, second pass (2026-08-02) ──────────────────────────────────
   2026-08-01f raised `.bcard-status` and `.bcard-contact` to 44px, but the
   SHARED `.jcard` actions were never part of that pass — so the primary action
   on every job card, on the board AND on public search, stayed at 40px. All
   measured on a 390x844 phone: Apply 40, Save 41, Details 40, the board's
   filter submit/reset 31 (Bootstrap `btn-sm`), the sort pills 32, and the
   detail panel's close button 38 — the only way out of a sheet that covers the
   screen. These are the controls the product runs on. */
@media (max-width:767.98px) {
    /* ⚠ `.jcard-add` was missing from this list until 2026-08-05b and measured
       41px — the 2026-08-02 44px pass listed the three actions a card had at
       the time and "+ Add to board" was simply never added when it arrived.
       Found by measuring, not by reading: it is 3px short, so it looks right
       in a screenshot and passes every test that checks the rule exists. */
    .jcard-save, .jcard-more, .jcard-add { min-height:44px; }
    /* ⚠ Apply is deliberately BIGGER than the 44px floor, not equal to it
       (2026-08-05b, Raz: "I suggest keeping the button הגשת מועמדות larger
       then the rest of the buttons"). It is the one control the entire page
       exists to get you to; on a card where every other action is now also a
       labelled button, "primary" has to be expressed in size, because colour
       alone stopped carrying it once the row gained four hues. */
    .jcard-apply { min-height:48px; font-size:15px; padding-inline:18px; }
    .cologo { min-height:44px; display:inline-flex; align-items:center; }
    .filter-bar .btn-sm { min-height:44px; }
    .bsort-b { min-height:44px; }
    .pnl-close {
        min-height:44px; min-width:44px;
        display:inline-flex; align-items:center; justify-content:center;
    }

    /* ── ONLY THE HEART LOSES ITS WORD (2026-08-05b, Raz) ─────────────────
       ⚠ THIS REVERSES THE RULE THAT SHIPPED EARLIER THE SAME DAY. That one hid
       `.act-l` on EVERY `.jcard-act` so four labelled controls could share a
       358px row, and Raz's retest was immediate: *"beside the heart icon to
       save liked jobs, all other buttons should have clear text because it is
       not clear what the button does."*

       He is right, and the earlier reasoning was the wrong trade. A heart is a
       universal, learned affordance — every job board on the market uses it
       bare, so an unlabelled ♡ costs nothing. `ⓘ`, `👤` and a coloured pill
       are NOT: nothing about them says "Details", "Contact a recruiter" or
       "this job's status on your board", and the row had become four icons a
       first-time visitor has to decode.

       The width this cost is paid for by the two-row layout below rather than
       by deleting words: Apply + ♡ take the first row and the rest take the
       second, so every label fits at 320px. See `.jcard-foot-main`.

       ⚠ The label is still HIDDEN, not removed, on the heart — it keeps its
       aria-label at every width, so the accessible name is unchanged and a
       screen reader hears "Save this job", not "button". An icon-only control
       with no accessible name is a WCAG 4.1.2 failure and this is exactly
       where that mistake gets made. */
    .jcard-save .act-l { display:none; }
    .jcard-save { min-width:44px; padding-inline:11px; }

    /* The mode switch is permanent primary navigation, so it takes the 44px
       floor like the tab bar does — and loses a little padding to stay on one
       row at 320px, where "חיפוש כל המשרות" is the widest label in the app. */
    .navsw-b { min-height:44px; padding:6px 11px; font-size:13.5px; }
    .navctx > * { max-width:calc(100% - 60px); }

    /* ── The phone switch has to LOOK like a control (2026-08-09, Raz) ──────
       "On mobile, the Board/Global search must be more noticeable. Consider
       more contrast for this shape so it catches more user focus."

       ⚠ THE SHAPE WAS THE PROBLEM, NOT THE LABELS. Measured on the phone
       navbar first: the labels were already fine (#c7d0e0 on the track is
       8.9:1), but the TRACK was `rgba(255,255,255,.07)` over --header, which
       composites to ~#2a2a3d — **1.31:1 against the bar it sits on**. So the
       control had no visible edge at all, and the two modes read as two dim
       links rather than as the two halves of one choice. Raising the label
       contrast would have done nothing for that; the fix is the container.

       ⚠ THE TARGET IS WCAG 1.4.11 (NON-TEXT CONTRAST), NOT 4.5:1. This is a
       UI component boundary, not text — the bar is 3:1, and the border is now
       measured at 3.79:1 on light-theme --header (#1a1a2e) and 3.81:1 on dark
       (#0b0f1a). Both themes, because the navbar is a fixed dark surface in
       BOTH and a token that re-tints would break the pair.

       ⚠ A NEUTRAL SHADOW IS INVISIBLE ON A DARK BAR — the 2026-07-31d rule.
       The selected half gets a COLOURED glow plus a hairline ring, which is
       what reads as raised here; `box-shadow` with a black rgba would simply
       not be there.

       Phones only. The PC copy (`.navsw-page`) sits on the light page body
       against --surface/--border and was never in question — and it is
       `display:none` below 768px anyway, so nothing here can reach it. */
    .navsw-nav {
        background:rgba(255,255,255,.16);
        border-color:rgba(255,255,255,.40);
    }
    /* Unselected, but plainly still a button: up from #c7d0e0, and the same
       700 the desktop copy already uses, so the two halves read as equals
       until one of them is marked. */
    .navsw-nav .navsw-b { color:#e6ecf5; font-weight:700; }
    /* #2E75B5 + white stays (4.85:1, stated at the base rule and unchanged).
       What is added is the EDGE — without it the fill alone was competing
       with a track that had none. */
    .navsw-nav .navsw-b.is-on,
    .navsw-nav .navsw-b.is-on:hover {
        box-shadow:0 0 0 1px rgba(255,255,255,.55),
                   0 2px 10px rgba(46,117,181,.65);
    }
}
@media (max-width:359.98px) {
    .navsw-b { padding:6px 9px; font-size:13px; }
}

/* ── Type floor on phones (2026-08-02) ─────────────────────────────────────
   The project's own rule (the `contrast-audit` skill) is ≥13px for anything
   read repeatedly, with 11px allowed ONLY for uppercase-700 section labels.
   The exceptions had spread: measured 10px (the filter caret), 11px (tab count
   badges) and 12–12.5px across the board chrome, the landing tiles and the
   preferences strip. Phone-only, because that is where it was measured and
   where it bites; the desktop sizes are a visual-identity call. */
@media (max-width:767.98px) {
    .filter-caret { font-size:12px; }
    .jobtab .n { font-size:12px; }
    .bsort-l, .bsort-b, .bsort-arrow { font-size:13px; }
    .stat-l, .tile-hint, .hero-srcs, .hero-srcs-n { font-size:13px; }
    .asignup-fine-tight { font-size:12.5px; }
    /* The label on the detail sheet's status control — the sheet is the whole
       screen on a phone, so nothing on it is glanceable chrome. */
    .pnl-foot-label { font-size:13px; }
    /* These two explain how the location picker WORKS and are read at the
       moment of use, on the app's primary control. Not fine print.
       The genuinely-once-read fine print (.asignup-fine, .join-fine) is left
       at 12px on purpose: lifting it would flatten the hierarchy it exists to
       sit at the bottom of. */
    .jsearch-lochint, .jsearch-locopt-hint { font-size:13px; }
}

/* ── Small-caps is a LATIN convention (2026-08-02) ─────────────────────────
   `.rdomain-h`, `.srcgroup-h`, `.pnl-section` and `.filter-group` sit at
   11–12px because they are uppercase + 700 + letter-spacing, which is exactly
   the case the 13px floor exempts. HEBREW HAS NO UPPERCASE: `text-transform`
   is a no-op there, so on the default locale what actually renders is 11px
   body text — with tracking, which Hebrew does not use and which hurts it.
   Give the Hebrew page the size the convention was buying instead. */
html[lang="he"] .rdomain-h,
html[lang="he"] .srcgroup-h,
html[lang="he"] .pnl-section,
html[lang="he"] .filter-group,
html[lang="he"] .jd-sech {
    font-size:13px;
    letter-spacing:normal;
    /* ⚠ `.jd-sech` joined this group on 2026-08-20. It was 11px uppercase and
       tracked, which is the small-caps convention this whole block exists to
       undo for Hebrew — "תיאור" gets no capitals from `text-transform`, so it
       was simply an 11px heading. Adding it here rather than giving it a rule
       of its own keeps one decision in one place; the same reasoning already
       covers the four selectors above it. */
    text-transform:none;
}

/* ── Directional icons must mirror in RTL (2026-07-31) ─────────────────────
   The glyph audit covered TEXT arrows: `‹`/`›` are Bidi_Mirrored and flip for
   free. Inline SVG arrows are a third category that neither mechanism
   reaches — the Apply chevron kept pointing right (away from the text) on
   every Hebrew job card.

   ⚠⚠ AND THE AUDIT'S SECOND CLAIM WAS FALSE (corrected 2026-08-31f, Raz, who
   found it in the Hebrew alert flow). It used to read: "`→`/`←` live inside
   translatable strings so the Hebrew msgstr carries the right one." **Not one
   arrow in this codebase is inside a msgid** — every site appends the glyph
   after the translated text (`{{ _("Continue") }} →`), so no msgstr can carry
   it and every one of them points the wrong way in Hebrew. `→` is Bidi class
   ON: the algorithm REPOSITIONS it to the left end of an RTL line, which is
   why the layout looks right and made this easy to miss, but it does not
   mirror the glyph. ⚠ A `.fwd` span plus the flip below is the fix; the arrow
   stays out of the catalogue, where a translator could silently get it wrong
   and no test could see it. ⚠ ~24 sites carry the bare glyph — only the guest
   alert flow was converted in this batch.

   Scoped to the arrow-bearing controls only: the magnifier, map pin and
   list-lines icons are NOT directional and must not be flipped. */
[dir="rtl"] .jcard-apply svg,
[dir="rtl"] .saved-exit svg,
[dir="rtl"] .jd-apply svg,
[dir="rtl"] .tabbar-signin svg,
[dir="rtl"] .rempty-b svg { transform: scaleX(-1); }
/* Text arrows, same mechanism. ⚠ `inline-block` is load-bearing: `transform`
   does not apply to a non-replaced inline box, so without it the rule parses,
   validates and does nothing — the silent-no-op shape this stylesheet keeps
   meeting. */
.fwd { display:inline-block; }
[dir="rtl"] .fwd { transform: scaleX(-1); }

/* ---------- The board, as cards (2026-08-01) ---------- */
/* Raz: "the guest UI is better than the one for registered users". It was, and
   structurally so — search/saved/landing were rebuilt in the card shape while
   the board stayed a table. These rules deliberately add almost nothing: the
   card look comes from the shared .jcard family, and .bcard only supplies what
   a BOARD card has that a search card does not (a select control, a bulk
   checkbox, the status badge in the header). Keep it that way — every rule
   added here is a way for the two surfaces to drift apart again. */
.bcards { display:flex; flex-direction:column; gap:12px; }
.bcard { cursor:pointer; }
.bcard:hover { border-color:var(--accent); }
.bcard-head { display:flex; align-items:flex-start; gap:10px; }
/* ── THE TITLE OWNS THE ROW AT EVERY WIDTH ─────────────────────────────────
   (2026-08-06, Raz: "as a registered user the board looks significantly
   messier … change the registered board's layout to be similar to guests'".)

   The single most visible difference between the two cards, and it was a
   two-child rule meeting a three-child row. `.jcard-head` is
   `justify-content:space-between`; a GUEST card has one child (the title), so
   space-between does nothing and the title sits hard against the start edge. A
   BOARD card has three — checkbox, title, status badge — and the title, being
   shrink-to-fit, was handed the middle: measured at 1280 it spanned x 313-1008
   inside a 113-1167 card, floating with 141px of gap on one side and 200px on
   the other while the badge and the checkbox pinned the corners. Every card in
   the list started its title in a different place, which is what "messier"
   looked like.

   This exact rule already existed — trapped inside the phone media query
   below, where the badge is `display:none` and the row only ever had two
   children, so it was fixing a row that was not broken and never reached the
   one that was. It is unconditional now and the phone copy is gone. */
.bcard-head .jcard-title { flex:1 1 auto; min-width:0; }
.bcard-check { display:flex; align-items:center; padding-block-start:3px; cursor:pointer; }
/* 20px + the 44px hit area every other tiny control here gets: a bulk
   checkbox you cannot reliably hit makes bulk actions useless on a phone. */
.bcard-check input { width:18px; height:18px; cursor:pointer; }
.bcard-check { position:relative; }
.bcard-check::after { content:""; position:absolute; inset:-13px; }
/* The title is a <button> so the detail panel is keyboard-reachable, but it
   must read as the card's heading, not as a control. */
.bcard-title-btn {
    background:none; border:0; padding:0; margin:0; text-align:start;
    font:inherit; color:inherit; cursor:pointer;
}
.bcard-title-btn:hover { color:var(--accent); text-decoration:underline; }
.bcard-foot { flex-wrap:wrap; gap:8px; }
/* 16px at phone widths or iOS zooms the whole page when the select is tapped —
   the same rule the search inputs and the board filters already follow. */
/* `.jcard-status` is the SHARED class (2026-08-05b): the same control now
   renders on the public search card, where it used to be a static badge.
   `.bcard-status` stays as the board's own hook so the board-specific rules
   below keep working — but anything about the CONTROL belongs here, or the two
   surfaces drift apart again (the 2026-08-04 `.jcard-foot` lesson). */
.bcard-status, .jcard-status { font-size:16px; max-width:100%; }
@media (min-width:768px) { .bcard-status, .jcard-status { font-size:13px; } }
.bcard-contact { white-space:nowrap; }

/* Status + Contact on ONE line (2026-08-01f, Raz). The select wants the width
   it can get, Contact wants only what its text needs — so the select flexes
   and Contact does not. `min-width:0` because a flex item's default
   `min-width:auto` refuses to shrink below its content and would push Contact
   out of the row again at 320px, which is the exact bug this replaces. */
/* ⚠ `flex:1 1 100%` was DELETED here (2026-08-05, Raz: "perhaps we can have all
   the buttons on a job's card (Status, Apply and Contact) organized nicely in
   one row"). A 100% flex-basis does not mean "take the space left over" — it
   means "take a whole line", so this wrapper ALWAYS broke onto a second row
   regardless of how much room was free beside Apply and Save. That is the two
   rows in Raz's screenshot, and on a 1216px desktop card it was 100% wasted.
   It is now an ordinary flex item: `1 1 auto` lets it take the remainder of the
   row and shrink when there isn't one. `min-width:0` still matters for the
   reason it always did — a flex item's default `min-width:auto` refuses to
   shrink below its content, which is what pushed Contact off the row at 320px
   before the wrapper existed (and what widened the whole page on 2026-08-04b). */
.bcard-actionrow,
.jcard-actionrow {
    display:flex; align-items:center; gap:10px;
    flex:1 1 auto; min-width:0;
}
.bcard-actionrow .bcard-status { flex:1 1 auto; min-width:0; }
.bcard-actionrow .bcard-contact { flex:0 0 auto; }
.jcard-actionrow .jcard-status { flex:1 1 auto; min-width:0; }

/* ── THE STATUS CONTROL STOPS EATING THE ROW ON A PC ───────────────────────
   (2026-08-06, Raz: "keep the functionalities of registered users such as the
   job's Status bar which is currently too wide".)

   Measured on a 1088px card at 1280: the select was **672px** — wider than
   Apply (152), Save (93) and Contact (111) put together — because
   `.bcard-actionrow` is `flex:1 1 auto` and the select inside it is too, so
   between them they claimed every pixel the other three did not want. It also
   measured **29px tall** beside their 40-41px, which is the same "similar
   height" complaint Raz filed against the detail sidebar, on a control he
   described separately.

   ⚠ Both halves of the greed have to go. Capping the select alone leaves the
   wrapper holding the slack, so Contact drifts to the far end of the card with
   a gap where the select used to be. `flex:0 1 auto` on BOTH makes the pair
   content-sized, and the leftover space falls at the end of the row exactly as
   it does on the guest card.

   ⚠ Phones keep the greedy rule (the block above sets `min-width:104px` and
   44px there): a 390px card has no slack to give away, and a content-sized
   select next to Contact is a stub too narrow to read a status in.

   ⚠ This must sit AFTER the three rules above — identical specificity (0-2-0),
   and a media query adds none, so source order is the only thing deciding it.
   Sixth appearance of that trap in this stylesheet. */
@media (min-width:768px) {
    .bcard-actionrow, .jcard-actionrow { flex:0 1 auto; }
    .bcard-actionrow .bcard-status,
    .jcard-actionrow .jcard-status {
        flex:0 1 auto; width:auto; max-width:260px;
        min-height:40px; padding-block:8px;
    }
}

/* ── TWO ROWS ON A PHONE, GROUPED BY WHAT THEY DO (2026-08-05b, Raz) ───────
   *"The buttons in the screen below look good but should be differently
   arranged. Probably by category."*

   The grouping is the primary action and the shortlist gesture on one row —
   the two things you do when a job looks right — and the tracking/reference
   controls on the next: status, Details, Contact. It is also what buys the
   width to give every one of them its word back (see `.jcard-save .act-l`
   above): Apply + ♡ measure ~190px of a 358px row, and status + Details or
   status + Contact ~210px. Both fit at 320px in Hebrew, labels and all.

   ⚠ `display:contents` ABOVE the breakpoint, so the desktop row is untouched.
   Raz asked for one row on larger screens (2026-08-05) and got it; this must
   not quietly undo that. With `display:contents` the wrapper generates no box
   and Apply/Save stay direct flex children of `.jcard-foot` exactly as before
   — the same technique the phone filter sheet uses to leave the desktop
   filter bar byte-for-byte unchanged.

   ⚠ `flex:1 1 100%` here is deliberate and is the OPPOSITE of the bug fixed
   on the status wrapper earlier today. There it meant "always take a whole
   line" on a row that was supposed to share; here taking a whole line IS the
   requirement, and only below 768px. */
.jcard-foot-main { display:contents; }
@media (max-width:767.98px) {
    .jcard-foot-main {
        display:flex; align-items:center; gap:8px;
        flex:1 1 100%; min-width:0;
    }
    /* Apply takes the room left over; the heart is a fixed 44px square. */
    .jcard-foot-main .jcard-apply { flex:1 1 auto; justify-content:center; }
    .jcard-foot-main .jcard-save  { flex:0 0 auto; }
}
@media (max-width:767.98px) {
    /* Measured at 33px, a third under the 44px floor — and with the badge gone
       this select is now the ONLY status control on a phone card, so it is the
       last thing that should be hard to hit. The table-era rule that did this
       was `.tbl .status-sel`, which a card has never matched. */
    .bcard-actionrow .bcard-status,
    .jcard-actionrow .jcard-status { min-height:44px; padding-block:9px; }
    /* ⚠ `.jcard-actionrow` HAD TO BE ADDED HERE (2026-08-06). Contact now also
       renders on the GUEST card, whose wrapper is `.jcard-actionrow` — so this
       rule, written when Contact existed only on the board, did not reach it
       and it measured 111x41 beside a 44px Details on the same row. Fourth
       time a control has arrived on a surface without being added to the 44px
       list (see `.jcard-add` at 41px, 2026-08-05b). */
    .bcard-actionrow .bcard-contact,
    .jcard-actionrow .bcard-contact { min-height:44px; display:inline-flex; align-items:center; }

    /* ⚠ …AND A MEMBER'S PUBLIC-CARD Contact GOES AGAIN, right here (2026-08-08).
       Measured at 390px signed in: `+ Add to board` (148px) + `Contact` (102) +
       `Details` (104) ends at x=401 in a 390px viewport — 11px of overflow, the
       fifth control to arrive on a surface without being measured there.
       Wrapping instead costs ~44px on every card, which is the above-the-fold
       budget three earlier batches fought for.
       ⚠ IT MUST LIVE HERE, not beside `.joblist`, and be 0-2-0: the rule two
       lines above sets `display:inline-flex` at 0-2-0, so a bare
       `.jcard-contact-wide { display:none }` LOSES — measured, it changed
       nothing and the overflow stayed at exactly 401px. Eighth appearance of
       "write it beside the rule it has to beat".
       ⚠ A GUEST is unaffected: the class is only rendered for an authenticated
       visitor, and their row (Contact + Details) fits. */
    .jcard-actionrow .jcard-contact-wide { display:none; }

    /* The select can't go below this and stay readable; the row wraps rather
       than squeezing it into a stub that shows no status at all. */
    .bcard-actionrow .bcard-status,
    .jcard-actionrow .jcard-status { min-width:104px; }
}

@media (max-width:767.98px) {
    /* The card's own status marker goes; the coloured select below says the
       same thing in the same words, and this was eating the start of the
       title's line on every card. Kept in the DOM — app.js repaints it, and it
       is back at ≥768px where there is room for it. */
    .bcard-badge { display:none; }
    /* Which hands the whole row to the title — `.bcard-head .jcard-title` is
       unconditional since 2026-08-06 and lives beside `.bcard-head`, so there
       is nothing to restate here. */
}

/* ---------- The board sort control ---------- */
/* Sorting lived in the table headers, which a card list does not have. An
   explicit control is also more discoverable than a header you have to know is
   clickable — and on a phone the headers were hidden outright, so sorting was
   simply unavailable there. */
.bsort { display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin-bottom:10px; }
.bsort-l { font-size:12px; color:#4b5563; font-weight:600; }
.bsort-b {
    font-size:12.5px; padding:4px 11px; border-radius:999px;
    border:1px solid var(--border); background:var(--surface);
    color:var(--text); text-decoration:none; white-space:nowrap;
    min-height:32px; display:inline-flex; align-items:center; gap:4px;
}
.bsort-b:hover { border-color:var(--accent); color:var(--accent); }
/* Solid when applied — the same convention the filter pills already use. */
.bsort-b.is-on {
    background:var(--find); border-color:var(--find); color:var(--find-ink);
    font-weight:700;
}
.bsort-b.is-on:hover { color:var(--find-ink); }
[data-bs-theme="dark"] .bsort-l { color:var(--muted); }

/* ⚠ These two rules MUST live here, at the end, not up in the phone blocks
   with their siblings. `.bsort-b` sets its own `min-height:32px` and
   `font-size:12.5px` in this section, and a media query adds NO specificity —
   so an identical `.bsort-b` selector earlier in the file loses to the plain
   one below it. The first attempt at this did exactly that and measured 32px
   afterwards, unchanged, with no warning of any kind. */
@media (max-width:767.98px) {
    .bsort-l, .bsort-arrow { font-size:13px; }
    .bsort-b { font-size:13px; min-height:44px; padding-inline:14px; }
}

/* ── "Filters & sort" — the phone's ONE board control (2026-08-03) ─────────
   Raz: "after logging in on the mobile application the screen looks quite
   messy and confusing… simplify it and focus on seeing jobs that need review
   and/or starting a search."

   Seven chrome blocks sat above the first job. Four of them were controls
   competing with each other — the Roles picker, the Locations picker, the sort
   pill row and a destructive Clear board — so on a phone they collapse into
   one button that opens a bottom sheet, and the search row is left as the only
   visible control. The status tabs stay: they answer "which pile", which is
   the question this screen exists to answer.

   ⚠ THIS BLOCK MUST STAY AT THE END OF THE FILE. It turns off `.bsort`
   (defined immediately above), `.filter-menu` (line ~345), `.filter-clear`
   (~302) and `.clear-board-btn` (~1106) at the phone breakpoint, and A MEDIA
   QUERY ADDS NO SPECIFICITY — an identical selector written earlier in the
   file loses to the plain rule below it, silently and with every test green.
   That has bitten this stylesheet twice (`.bsort-b`, `[data-bs-theme] .afilter`).

   ⚠ NO JAVASCRIPT ANYWHERE HERE. The toggle is a real checkbox + <label>, so
   the sheet opens with JS off exactly like the <details> pickers inside it.
   The checkbox has NO `name`, so it is never submitted with the GET form. */

/* Focusable but invisible — `display:none` would take it out of the tab order
   and the button would be keyboard-dead. */
.btools-toggle {
    position:absolute; width:1px; height:1px; margin:0;
    opacity:0; pointer-events:none;
}
/* Desktop: the button doesn't exist and the panel is transparent to layout, so
   the two pickers stay direct flex children of .filter-bar — the desktop bar
   is byte-for-byte the arrangement it was before. */
.btools-btn { display:none; }
.btools-panel { display:contents; }
.btools-sheet { display:contents; }
.btools-backdrop,
.btools-head,
.btools-sec,
.btools-foot { display:none; }

@media (max-width:767.98px) {
    /* ⚠ The four status PILLS are switched off here (2026-08-05, Raz) and the
       status picker inside the sheet takes over. This declaration belongs in
       THIS block, not next to `.jobtabs` at line ~241, for the reason stated
       at the top of it: `.jobtabs { display:flex }` is a plain class rule and a
       media query adds no specificity, so an identical selector written up
       there would lose — silently, with every test green. Fourth occurrence of
       that trap; `test_the_phone_block_is_last_in_the_file` pins the ordering.

       The pills also solved a problem the sheet does not: they were the only
       thing that scrolled off the right edge at 390px (2026-08-02, "two of four
       tabs did not exist"). Inside a full-width sheet all four are simply
       visible, so the mask-fade that hinted at the overflow goes with them. */
    .jobtabs { display:none; }

    .btools-btn {
        display:inline-flex; align-items:center; gap:7px; cursor:pointer;
        padding:8px 13px; min-height:44px; font-size:13.5px; font-weight:600;
        color:var(--text); background:var(--surface);
        border:1px solid var(--border); border-radius:6px;
        white-space:nowrap; margin:0;
    }
    /* Same solid treatment an applied filter pill gets, for the same reason:
       "why am I only seeing 12 jobs?" has to be answerable at a glance once
       the pickers themselves are behind a door. */
    .btools-btn.is-on {
        background:var(--find); border-color:var(--find-dk);
        color:var(--find-ink); font-weight:700;
    }
    .btools-toggle:focus-visible + .btools-btn {
        border-color:var(--accent);
        box-shadow:0 0 0 .2rem rgba(46,117,181,.25);
    }
    .btools-n {
        display:inline-flex; align-items:center; justify-content:center;
        min-width:20px; height:20px; padding:0 6px; border-radius:999px;
        background:var(--find); color:var(--find-ink);
        font-size:12px; font-weight:700;
    }
    .btools-btn.is-on .btools-n { background:var(--find-ink); color:var(--find); }

    /* Below the modal (1050/1055) so Clear board's confirmation still lands on
       top, above the bottom tab bar (1030) so the sheet isn't cut by it. */
    .btools-panel { display:none; position:fixed; inset:0; z-index:1040; }
    .btools-toggle:checked ~ .btools-panel { display:block; }
    .btools-backdrop {
        display:block; position:absolute; inset:0;
        background:rgba(0,0,0,.45); cursor:pointer; margin:0;
    }
    .btools-sheet {
        display:block; position:absolute; inset-inline:0; bottom:0;
        max-height:88dvh; overflow-y:auto;
        background:var(--surface); color:var(--text);
        border-top:1px solid var(--border);
        border-radius:16px 16px 0 0;
        /* Split rather than a 4-value shorthand on purpose: the RTL lint reads
           `padding: a b c` with a max()/env() in it as four whitespace-split
           values and flags the 2nd vs 4th as an asymmetric physical gutter. */
        padding-block-start:4px;
        padding-inline:14px;
        padding-block-end:max(16px, env(safe-area-inset-bottom));
        box-shadow:0 -8px 28px rgba(0,0,0,.28);
    }
    .btools-head {
        display:flex; align-items:center; justify-content:space-between;
        gap:10px; padding:10px 0 12px;
        position:sticky; top:0; z-index:1;
        background:var(--surface); border-bottom:1px solid var(--border);
    }
    .btools-title { font-size:16px; font-weight:800; color:var(--text); }
    .btools-close {
        display:inline-flex; align-items:center; justify-content:center;
        width:44px; height:44px; margin:0; cursor:pointer;
        font-size:26px; line-height:1; color:var(--text);
        background:none; border:none; border-radius:8px;
    }

    /* The pickers become full-width accordion rows: inside a sheet an
       absolutely-positioned dropdown would hang off the edge of its own
       container. */
    .btools-sheet .filter-drop { display:block; margin-top:10px; }
    .btools-sheet .filter-summary {
        width:100%; justify-content:space-between; min-height:44px;
        font-size:15px;
    }
    .btools-sheet .filter-menu {
        position:static; min-width:0; max-width:none;
        margin-top:6px; box-shadow:none;
    }
    .btools-sheet .filter-menu-scroll { max-height:44vh; }

    .btools-sec { display:block; margin-top:14px; }
    .btools-clear { width:100%; min-height:44px; }
    .btools-foot {
        display:flex; gap:9px; margin-top:16px; padding-top:12px;
        border-top:1px solid var(--border);
    }
    .btools-foot .btn { flex:1 1 0; min-height:44px; }
    .btools-apply { font-weight:700; }

    /* What the sheet replaces. Each of these is defined earlier in the file,
       which is why this block has to be last. */
    .bsort { display:none; }
    .filter-clear { display:none; }
    .clear-board-btn { display:none; }

    /* ONE row: the button and the search field side by side. They each took a
       row of their own at first (the ≤575px rule below still forces the search
       to 100%), which put 56px of bar between the status tabs and the first
       job for no gain — the whole point of the sheet was to give that space
       back. `min-width:0` is load-bearing: a flex item defaults to
       `min-width:auto` and refuses to shrink below its content, so without it
       the input pushes the button off the row at 320px. */
    .filter-bar { gap:8px; flex-wrap:nowrap; }
    .filter-field-search { flex:1 1 auto; min-width:0; max-width:none; }
    .btools-btn { flex:0 0 auto; }
    /* At 320px the label costs more than it explains — the icon and the count
       carry it, and the sheet says "Filters & sort" at the top the moment it
       opens. */
    @media (max-width:359.98px) {
        .btools-btn-l { display:none; }
    }
}

[data-bs-theme="dark"] .btools-sheet { box-shadow:0 -8px 28px rgba(0,0,0,.55); }

/* ── Inline panel close — a second way out that survives a PINCH (2026-08-03,
   narrowed 2026-08-04) ───────────────────────────────────────────────────
   Apply/Contact used to be duplicated here too, but two visible rows of the
   same buttons read as a bug (Raz) — they now render ONCE, in the pinned
   `.pnl-foot` below. This inline Close stays: `position:fixed` anchors to the
   LAYOUT viewport, and a pinch-zoom creates a smaller VISUAL viewport inside
   it that fixed chrome does not follow, so the pinned `×` at the top of the
   panel can leave the reader's view during a pinch (measured 390x844: visible
   window 281px at 3x). Styled to match `.pnl-close` (the top-left ×) rather
   than a text link, per Raz's request. */
.pnl-inline-actions {
    display:flex; justify-content:flex-end;
    margin-block-start:22px; padding-block-start:16px;
    border-top:1px solid var(--border);
}
.pnl-foot .btn-apply {
    background:var(--find); border-color:var(--find-dk);
    color:var(--find-ink); font-weight:700;
}
.pnl-foot .btn-apply:hover { background:var(--find-dk); color:var(--find-ink); }
.pnl-inline-close.pnl-close { min-height:44px; min-width:44px; }

/* ── Card tap target + the detail screen's door (2026-08-03) ────────────────
   Raz, two reports: "every click on a card means the user is interested in the
   specific job — anything that is not a dedicated button should open the job's
   description", and "the job's description screen should have an X to close
   it; it is not clear how to navigate back".

   ⚠ Appended at the END on purpose. `.jd-back` and the `.jcard-co` family are
   defined earlier, and A MEDIA QUERY ADDS NO SPECIFICITY — a phone override
   written up beside them would lose to the plain rules that follow. */

/* The company name was the ONE part of a board card that did not open the job:
   the `.job-row` handler ignores `a`, so this link replaced the whole board
   with a company search. Both nodes ship; CSS picks one, so no JS is involved
   and the board still works with scripting off. */
.bcard-co-plain { display:none; }
@media (max-width:767.98px) {
    .bcard-co-link  { display:none; }
    .bcard-co-plain { display:inline; }
}

/* The LOCATION got the same treatment on 2026-08-18 (Raz: "make sure the
   behavior when clicking either the company, location or title is similar").
   Identical mechanism to the company pair directly above — deliberately, so
   there is one rule to remember and one place a phone override can go wrong. */
.bcard-loc-plain { display:none; }
@media (max-width:767.98px) {
    .bcard-loc-link  { display:none; }
    .bcard-loc-plain { display:inline; }
}

/* A card's employer/location links read as text until you want them: on a list
   of 25 cards, 50 blue links is noise, and the underline-on-hover is what says
   "this one is clickable" at the moment the question is being asked.

   ⚠ THE HOVER STATE CHANGES THE UNDERLINE, NOT THE COLOUR, and that is
   deliberate. A first version tinted it `var(--brand)` — a token that does not
   exist in this stylesheet, so the declaration was dropped and the rule was a
   silent no-op that `lint_css.py` cannot see (it looks for light literals with
   no dark override, not for undefined custom properties). The real token is
   `--accent`, and using it here would put hover text at the standing 4.48:1
   finding this palette already carries. Inheriting `--text` keeps both states
   at full contrast and still marks the affordance. */
.jcard-co.meta-co, .jcard-loc-link, .pnl-where-link {
    color:inherit; text-decoration:none;
}
.jcard-co.meta-co:hover, .jcard-co.meta-co:focus-visible,
.jcard-loc-link:hover, .jcard-loc-link:focus-visible,
.pnl-where-link:hover, .pnl-where-link:focus-visible {
    text-decoration:underline;
}

/* /jobs/<id> is a full PAGE, not the board's sheet — nothing on it said "you
   are inside one job, here is the door". The old `.jd-back` was 94x16px of
   13.5px grey text. */
.jd-close {
    display:inline-flex; align-items:center; gap:8px;
    min-height:44px; padding-inline:14px; padding-block:8px;
    margin-block-end:12px;
    background:var(--surface); border:1px solid var(--border);
    border-radius:999px;
    font-size:14px; font-weight:600; color:var(--text);
    text-decoration:none;
}
.jd-close:hover {
    border-color:var(--accent); color:var(--accent); text-decoration:none;
}
.jd-close svg { flex:none; }
/* The end-of-page copy: the one that is still reachable when the page is
   pinch-zoomed and the reader is at the bottom of a long description. */
.jd-close-end { margin-block:18px 0; }

/* The board sheet's × already met the 44px floor, but it was `--muted` on a
   transparent background with a hairline border — correct as quiet chrome,
   too quiet as THE way out of a full-screen sheet. Raz's report was about the
   /jobs/<id> page above, which had no × at all; this is the cheap half of the
   same lesson, so the door is obvious on whichever detail screen you land on.
   Surface fill + real text colour, still no accent (it is an exit, not an
   action). */
@media (max-width:767.98px) {
    .pnl-close {
        background:var(--surface); color:var(--text);
        border-color:var(--border);
    }
    .pnl-close:hover { background:var(--bg); }
}

/* ── Preferences: three screens instead of one 4.5-screen page (2026-08-03) ──
   Measured before the split: 3,808px on a 390x844 phone, with the daily-digest
   toggle at 2,449px — behind 89 role checkboxes. Tabs rather than wizard steps
   for a returning user, so the commonest edit is one tap and not three screens. */
/* ⚠ WRAP, don't scroll. As a horizontal scroller the SELECTED tab was clipped
   off the edge on a 390px phone — the browser does not scroll it into view, and
   CSS cannot. That is the same defect as the board's status pills ("two of four
   tabs did not exist"), and with only three short chips there is no reason to
   have a scroller at all. Wrapping to a second row is self-evident; a clipped
   row is not. */
/* Raz: the three tabs need to be more noticeable/bolder, and sticky so a user
   scrolled deep into a section (89 role checkboxes on Roles) stays aware of
   which screen they're on. Sticky at the same `top:64px` the bulk-action bar
   already uses to clear the fixed navbar; a solid background stops the page
   content showing through while it's pinned. */
/* ── The pinned row: the tabs, and the way out (2026-08-17, Raz) ───────────
   *"In the yellow space there should be an option to return to the Board/Global
   job search"* — the circled space was the empty two-thirds of this strip.

   The exit was already on the page (base.html's `.modebar`, above the heading)
   and it was still unreachable: this row pins at `top:64px` and that one did
   not, so on Roles — 1,523px of checkboxes — the only way back scrolled off
   within a flick. Moving it INTO the pinned row is what makes it survive the
   scroll, and it fills the space the chips leave rather than adding a strip.

   ⚠ THE STICKY MOVED UP ONE LEVEL, to this wrapper. `position:sticky` pins an
   element against its containing block, so leaving it on `.settings-tabs` would
   pin the chips and let the switch beside them ride away — two halves of one
   strip parting company mid-scroll. The background/padding/negative margin move
   with it for the same reason: they exist to stop page content showing through
   while pinned, and that is now this box's job. */
.settings-tabrow {
    display:flex; flex-wrap:wrap; align-items:center;
    justify-content:space-between; gap:6px 14px; margin-bottom:16px;
    position:sticky; top:64px; z-index:15;
    background:var(--bg); padding-block:8px; margin-inline:-4px; padding-inline:4px;
}
/* ⚠ `min-width:0`, because a flex item's default `min-width:auto` refuses to
   shrink below its content — three chips plus a two-button switch overflowed
   the wrap and widened the whole page at 768-820px, the fourth time that
   default has done exactly this here. */
.settings-tabs { display:flex; flex-wrap:wrap; gap:6px; min-width:0; }
/* The switch is `display:none` below 768px on its own (`.navsw-page`), where
   the phone's bottom tab bar already carries Board and Search — so this wrapper
   must not reserve a gap for a control that is not there. */
.settings-exit { display:none; }
@media (min-width:768px) {
    .settings-exit { display:block; }
}
.settings-tab {
    flex:0 0 auto; display:inline-flex; align-items:center; gap:6px;
    min-height:44px; padding-inline:15px;
    border:2px solid var(--border); border-radius:999px;
    background:var(--surface); color:var(--text);
    font-size:14.5px; font-weight:700; text-decoration:none; white-space:nowrap;
}
.settings-tab:hover { border-color:var(--accent); color:var(--accent); }
/* Solid when selected — the same convention the board's applied filters and
   sort pills already use, so "which screen am I on" reads the same way. */
.settings-tab.is-on {
    background:var(--find); border-color:var(--find-dk);
    color:var(--find-ink); font-weight:800;
}
.settings-tab.is-on:hover { color:var(--find-ink); }

/* Account is deliberately NOT a fourth tab: it is password and identity, not
   "what jobs do I want", so it sits as a quiet link under the three. */
.settings-acct-link { margin:18px 0 0; font-size:14px; }
.settings-acct-link a { font-weight:600; }

/* ── "Tab n of 3", in the sticky save bar (2026-08-10, Raz) ────────────────
   The tab row above is `top:64px` sticky, so it survives a scroll — but the
   save bar is `bottom:0` sticky and is the thing actually in front of you once
   you are down among the role checkboxes, and its start half was empty.

   ⚠ TEXT CARRIES THE ANSWER, the segments only repeat it. A row of pills is
   read as decoration by anyone who is not already looking for it (and by a
   screen reader, not at all — they are `aria-hidden`), and "which one is the
   filled one" is a colour-only distinction, which WCAG 1.4.1 does not accept
   on its own. The count is the primary; the filled segment is also WIDER than
   the other two, so shape agrees with colour.

   ⚠ `--text`, not `--muted`: `--muted` is 4.4:1 on this surface in light mode
   and this is 13px type, i.e. under the 4.5 floor. Raz asked for MORE visible,
   which is the opposite of a shade that only just fails.

   ── and then it became the NAVIGATION bar (2026-08-10b, Raz) ──────────────
   *"There should also be navigation buttons at the bottom of the screen along
   with the orientation information … I feel we need to utilize more the space
   I highlighted in yellow"*, with a progress-bar reference attached. The yellow
   loop was around the empty two-thirds of the bar, so the decorative segments
   became the control: a numbered step per screen, the current one carrying its
   own name, Back/Next beside them.

   ⚠ The step controls are SUBMIT BUTTONS (see the template) — they save before
   they move. That is a data-safety decision, not a styling one, and it is why
   they are styled here from scratch rather than as `.btn`s: a Bootstrap button
   on this bar would read as three more things to press beside "Save".

   ⚠ ORDER, not source position, places the primary button at the visual end.
   The DOM has it FIRST so that Enter inside a text field still saves. */
.savebar-row { display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
.savebar-acts {
    order:9; margin-inline-start:auto; flex:0 0 auto;
    display:flex; align-items:center; gap:.5rem;
}
/* ⚠ Found by MEASURING, not by review: the primary button was **38px** — under
   the 44px floor, on the one piece of Settings chrome that is always on screen,
   and the Nth control to arrive on a surface without being added to that list.
   `.btn`'s own padding is what set the height, so the floor has to be stated
   here. `inline-flex` because `min-height` on an inline-block leaves the label
   sitting at the top of the taller box. */
.savebar-acts .btn {
    min-height:44px;
    display:inline-flex; align-items:center; justify-content:center;
}
.setsteps { order:1; display:flex; align-items:center; min-width:0; flex:0 1 auto; }
/* ⚠ HIDDEN WHERE THE STEPPER RENDERS (2026-08-10c, Raz: *"delete the text
   לשונית 1 מתוך 3, it is redundant and this section seems crowded"*). It is —
   the steps are numbered 1..3 and the current one is filled AND carries its own
   name, so the sentence restates the row beside it.

   ⚠ It is NOT deleted, because below 768px the stepper is `display:none` and
   this is the ONLY thing answering "which of the three am I on" — removing it
   outright would silently undo the morning batch on phones. So: hidden here,
   re-shown in the phone block. The WCAG 1.4.1 argument still holds on PC
   without it, because the current step renders its NAME as text rather than
   relying on the fill colour. */
.settings-where-n {
    display:none;
    order:2; flex:0 0 auto; font-size:13px; font-weight:700;
    white-space:nowrap; color:var(--text);
}
.setnav { order:3; display:flex; align-items:center; gap:6px; flex:0 0 auto; }
#save-hint { order:4; }

.setstep {
    display:inline-flex; align-items:center; gap:7px;
    min-height:40px; padding:6px 12px;
    border:1px solid var(--border); border-radius:999px;
    background:var(--surface); color:var(--text);
    font-size:13px; font-weight:600; line-height:1.2;
    white-space:nowrap; text-align:start;
    transition:background .15s ease, border-color .15s ease;
}
button.setstep { cursor:pointer; }
button.setstep:hover { border-color:var(--find); background:var(--find-wash); }
.setstep-n {
    display:inline-flex; align-items:center; justify-content:center;
    flex:0 0 auto; width:20px; height:20px; border-radius:50%;
    background:var(--border); color:var(--text);
    font-size:11.5px; font-weight:800;
}
.setstep.is-on { background:var(--find); border-color:var(--find); color:var(--find-ink); }
.setstep.is-on .setstep-n { background:var(--find-ink); color:var(--find); }
.setstep.is-done .setstep-n { background:var(--find); color:var(--find-ink); }

/* The rail between the steps — what makes this read as a progress bar rather
   than three loose chips. Drawn on every step but the first, in the gap the
   margin opens up. */
.setstep + .setstep { position:relative; margin-inline-start:18px; }
.setstep + .setstep::before {
    content:""; position:absolute; inset-inline-start:-18px;
    top:50%; margin-top:-1px; width:18px; height:2px;
    background:var(--border);
}
/* ⚠ Same specificity as the rule above (0-2-1), so SOURCE ORDER is the only
   thing that makes the filled rail win. It must stay below.
   A rail is filled when the step it leads INTO is the current one or already
   behind it, which is what makes the fill stop at the current step. */
.setstep.is-on::before, .setstep.is-done::before { background:var(--find); }

.setnav-b {
    display:inline-flex; align-items:center; gap:5px;
    min-height:40px; padding:6px 12px;
    border:1px solid var(--border); border-radius:8px;
    background:var(--surface); color:var(--text);
    font-size:13.5px; font-weight:600; line-height:1.2; cursor:pointer;
}
.setnav-b:hover { border-color:var(--find); background:var(--find-wash); }
/* A chevron is directional, and `dir` does not mirror a glyph or a path. */
[dir="rtl"] .setnav-b svg { transform:scaleX(-1); }

@media (max-width:767.98px) {
    /* Tighter chips so three fit one row at 320px before wrapping is needed. */
    .settings-tab { padding-inline:12px; font-size:13.5px; }

    /* ⚠ Written AFTER the base rules: a media query adds no specificity, so
       position is the only thing that decides this (9th appearance).

       ⚠⚠ THE STEP CONTROLS ARE >=768px, AND THAT WAS A MEASUREMENT, NOT A
       PREFERENCE. Raz's report is a PC report about PC empty space — the
       screenshot is 1009px wide and the yellow loop is around a bar that is
       two-thirds blank. A phone has no such space: with the steps and Back/Next
       on it the bar WRAPPED to two rows and measured **125px against 69px on
       PC**, i.e. ~63px of extra permanently-stuck chrome on the screen the
       2026-08-02/08-03 batches spent two rounds shortening (first card 664px ->
       434px). Trading that back for a control the phone can already reach —
       the tab row is directly above, and the bottom tab bar carries Settings —
       is the wrong side of a trade this project has already made twice.

       Phones keep exactly what shipped this morning: the count, in text — which
       is why it is re-shown here after being hidden at the base (2026-08-10c).
       ⚠ Both rules are 0-1-0 and a media query adds no specificity, so this
       winning depends purely on being written LATER in the file. */
    .setsteps, .setnav { display:none; }
    .settings-where-n { display:inline; }
}

/* ── The guest's three-step email signup (2026-08-03) ───────────────────────
   The counterpart of the signed-in Preferences split: a registered user gets
   three screens for roles / locations / daily email, and a guest handing over
   an address used to get one inline box that silently adopted whatever search
   was on screen. Same three decisions, same shape, so both audiences learn one
   thing. State is in the query string — no session, no cookie on a visitor who
   has agreed to nothing — so every step is a real URL and works with JS off. */
.anew { max-width:40rem; margin-inline:auto; }
.anew-card {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); padding:22px 20px;
    box-shadow:var(--shadow);
}
.anew-step {
    display:flex; align-items:center; gap:10px;
    margin:0 0 14px; font-size:12.5px; font-weight:700; color:#4b5563;
}
.anew-h { font-size:22px; font-weight:800; color:var(--text); margin:0 0 6px; }
.anew-p { font-size:14px; line-height:1.55; color:#4b5563; margin:0 0 16px; }
.anew-recap { font-weight:600; color:var(--text); }
.anew-input {
    display:block; width:100%; box-sizing:border-box;
    background:var(--bg); color:var(--text);
    border:1px solid var(--border); border-radius:8px;
    /* 16px or iOS Safari zooms the whole page on focus. */
    font-size:16px; padding:12px 14px; min-height:48px;
}
.anew-input:focus {
    outline:none; border-color:var(--find);
    box-shadow:0 0 0 .2rem rgba(46,117,181,.18);
}
.anew-go {
    margin-block-start:14px; width:100%; min-height:48px;
    background:var(--find); color:var(--find-ink);
    border:1px solid var(--find-dk); border-radius:8px;
    font-size:16px; font-weight:700; cursor:pointer;
}
.anew-go:hover { background:var(--find-dk); }
.anew-locs { display:grid; grid-template-columns:1fr; gap:2px; }
@media (min-width:520px) { .anew-locs { grid-template-columns:1fr 1fr; } }
.anew-loc {
    display:flex; align-items:flex-start; gap:10px; cursor:pointer;
    min-height:44px; padding:9px 8px; border-radius:8px;
    font-size:15px; color:var(--text);
}
.anew-loc:hover { background:var(--bg); }
.anew-loc input { flex:none; margin-block-start:3px; accent-color:var(--accent); }
.anew-loc-hint { display:block; font-size:12.5px; color:#4b5563; }
.anew-fine { margin:10px 0 0; font-size:12px; color:#4b5563; }
.anew-back {
    display:inline-flex; align-items:center; min-height:44px;
    margin-block-start:10px; font-size:14px; font-weight:600;
}
/* The offer strips are now a single CTA — the address is asked for at step 3,
   once the visitor has seen what they are subscribing to. */
.asignup-row-cta { display:flex; }
.asignup-row-cta .asignup-go { flex:1 1 auto; }

[data-bs-theme="dark"] .anew-step,
[data-bs-theme="dark"] .anew-p,
[data-bs-theme="dark"] .anew-fine,
[data-bs-theme="dark"] .anew-loc-hint { color:var(--muted); }

/* ── Analytics consent banner (2026-08-09) ────────────────────────────────────
   Rendered only when a tracker id is configured, and served `hidden` — see
   partials/_consent_banner.html and static/js/consent.js.

   ⚠ EVERY COLOUR IS A TOKEN. This is a full-width surface that appears over
   the landing page in both themes; a hardcoded #fff here is exactly the
   "invisible but selectable text in dark mode" bug the contrast-audit skill
   exists to prevent.

   ⚠ LOGICAL PROPERTIES ONLY — Hebrew owns the bare path, so `inset-inline`
   rather than left/right, and the safe-area inset is the one allow-listed
   physical exception (it is a device notch, not a direction). */
.consent {
    position:fixed; inset-inline:0; bottom:0; z-index:1080;
    background:var(--surface);
    border-top:1px solid var(--border);
    box-shadow:0 -6px 24px rgba(0,0,0,.18);
    padding:14px 16px calc(14px + env(safe-area-inset-bottom));
}
.consent-in {
    max-width:1120px; margin:0 auto;
    display:flex; align-items:center; gap:16px; flex-wrap:wrap;
}
.consent-copy { flex:1 1 320px; min-width:0; }
.consent-t { margin:0 0 3px; font-size:15px; font-weight:800; color:var(--text); }
.consent-b { margin:0; font-size:13.5px; line-height:1.45; color:var(--text); }
.consent-b a { color:var(--accent); }
.consent-acts { display:flex; gap:10px; flex:0 0 auto; }
/* ⚠ EQUAL WEIGHT, DELIBERATELY. A prominent Accept beside a whispered Decline
   is the dark pattern regulators name by name, and it would contradict a
   /privacy page written to be true. Same height, same padding, same type; only
   the fill differs, and the outlined option is the one that does nothing. */
.consent-b-yes, .consent-b-no {
    min-height:44px; padding:10px 18px; border-radius:10px;
    font-size:14px; font-weight:700; cursor:pointer;
    border:1px solid var(--border);
}
.consent-b-yes { background:#2E75B5; color:#fff; border-color:#2E75B5; }
.consent-b-no  { background:var(--bg); color:var(--text); }
.consent-b-yes:hover { background:#255f95; border-color:#255f95; }
.consent-b-no:hover  { background:var(--surface); }

@media (max-width:767.98px) {
    /* The bottom tab bar owns the foot of a phone screen, so the banner sits
       above it rather than on top of it. */
    .consent { bottom:var(--tabbar-h, 56px); }
    .consent-in { gap:12px; }
    .consent-acts { width:100%; }
    /* Full-width buttons on a phone, still equal to each other. */
    .consent-b-yes, .consent-b-no { flex:1 1 0; }
}

[data-bs-theme="dark"] .consent { box-shadow:0 -6px 24px rgba(0,0,0,.55); }
[data-bs-theme="dark"] .consent-b-yes { background:#3d86c6; border-color:#3d86c6; color:#08121c; }
[data-bs-theme="dark"] .consent-b-yes:hover { background:#4e95d2; border-color:#4e95d2; }

/* ── Advanced disclosure on the daily-email screen (2026-08-17) ────────────
   Mailbox ingestion is a four-field block that almost nobody opens, sitting on
   the screen Raz measured down to 1.0 phone screen. A collapsed <details>
   keeps the digest toggle — the control people actually come here for — above
   the fold, the same trade the role/location pickers make.
   ⚠ NO JAVASCRIPT: native <details>, so it works with scripting off. */
.advset { border-top:1px solid var(--border); padding-top:14px; }
.advset-sum {
    display:flex; align-items:center; gap:8px;
    min-height:44px; padding:10px 2px;     /* the 44px tap floor */
    font-size:14px; font-weight:700; color:var(--text);
    cursor:pointer; list-style:none;
}
/* Chrome/Safari draw their own triangle from this pseudo-element; ours is the
   caret below, so the native one has to go or there are two. */
.advset-sum::-webkit-details-marker { display:none; }
.advset-sum::before {
    content:"›"; display:inline-block; color:var(--muted);
    /* ⚠ `›` is Bidi_Mirrored, so it points the right way in Hebrew for free —
       and the rotation below is applied to whichever direction it starts in. */
    transition:transform .15s ease;
}
.advset[open] > .advset-sum::before { transform:rotate(90deg); }
.advset-sum:focus-visible {
    outline:2px solid var(--accent); outline-offset:2px; border-radius:6px;
}
.advset-body { max-width:34rem; }

/* ── "How do I get these?" inside the mailbox block (2026-08-17, Raz) ──────
   A nested disclosure holding one copyable prompt per external setup step. It
   is nested rather than a sibling because the fields it explains are already
   two levels down; opening the parent must not also unfold three paragraphs of
   help on top of the four inputs someone came here to fill in. */
.setup-help { border:1px solid var(--border); border-radius:10px; padding:2px 12px; }
.setup-help-sum {
    display:flex; align-items:center; gap:8px;
    min-height:44px;                        /* the tap floor, same as .advset-sum */
    /* ⚠ `#3a6ea5`, NOT `var(--accent)`. The accent is the app's standing
       4.48:1 finding on `--bg` — 0.02 short of AA, a known palette item and
       Raz's call to fix app-wide — and a new component has no business adding
       a fresh instance of it. Measured 5.35:1 here. Same substitution the
       autocomplete's parent-role count made for the same reason. */
    font-size:13.5px; font-weight:700; color:#3a6ea5;
    cursor:pointer; list-style:none;
}
[data-bs-theme="dark"] .setup-help-sum { color:var(--accent); }
.setup-help-sum::-webkit-details-marker { display:none; }
.setup-help-sum::before {
    /* `›` is Bidi_Mirrored, so it points the right way in Hebrew unaided. */
    content:"›"; display:inline-block; color:var(--muted);
    transition:transform .15s ease;
}
.setup-help[open] > .setup-help-sum::before { transform:rotate(90deg); }
.setup-help-sum:focus-visible {
    outline:2px solid var(--accent); outline-offset:2px; border-radius:6px;
}
.setup-help-item { padding:10px 0; border-top:1px solid var(--border); }
.setup-help-t { font-size:13.5px; font-weight:700; margin-bottom:4px; }
/* The prompt is quoted text the user is about to paste somewhere else, so it
   is set apart from the surrounding copy rather than reading as instructions
   we are giving. ⚠ `overflow-wrap` — these are long unbroken sentences and, in
   a 34rem column on a 390px phone, one un-breaking run widens the whole page
   (the `min-width:auto` failure mode, arrived at from the other direction). */
.setup-help-p {
    margin:0 0 8px; padding:8px 10px; border-radius:8px;
    /* ⚠ `#4b5563` in light, the token in dark. `--muted` (#6b7280) measures
       4.46:1 on `--bg` — the documented "small muted text on the page ground"
       shortfall, and this is a paragraph of real prose rather than a label, so
       it has to clear AA outright. */
    background:var(--bg); color:#4b5563;
    font-size:13px; line-height:1.5; overflow-wrap:anywhere;
}
[data-bs-theme="dark"] .setup-help-p { color:var(--muted); }
.setup-help-acts { display:flex; flex-wrap:wrap; gap:8px; }
/* ⚠ Both are Bootstrap `btn-sm` and measured 31px on a 390px phone. `btn-sm`
   is the right SIZE here — this is help beside a form field, not an action —
   so the box grows and the type does not, the same trade `.confirm-resend-b`
   and `.refresh-offer-b` make. Written beside the component rather than in a
   phone block elsewhere: a media query adds no specificity. */
@media (max-width:767.98px) {
    .setup-help-acts .btn { min-height:44px; display:inline-flex;
                            align-items:center; }
}
/* ⚠ "Copy the question" STATES its colours instead of inheriting
   `.btn-outline-secondary`, which measured 3.29:1 in dark — Bootstrap's
   outline greys are a standing palette finding here (3.29–4.33:1) and this
   control is new, so it does not get to add another. Through `--bs-btn-*` so
   Bootstrap's own hover/active/focus states follow, the `.nav-join` technique. */
.setup-help-acts .copy-btn {
    --bs-btn-color:#3a6ea5;        --bs-btn-border-color:#c3d4e6;
    --bs-btn-hover-bg:#3a6ea5;     --bs-btn-hover-border-color:#3a6ea5;
    --bs-btn-hover-color:#fff;
    --bs-btn-active-bg:#2f5c8a;    --bs-btn-active-border-color:#2f5c8a;
    --bs-btn-active-color:#fff;
}
[data-bs-theme="dark"] .setup-help-acts .copy-btn {
    --bs-btn-color:#9cc4ea;        --bs-btn-border-color:#3b4a63;
}
/* Same for "Open Gemini" — Bootstrap's outline PRIMARY measured 3.43:1 in
   dark, the other half of the same standing finding. */
.setup-help-acts .btn-outline-primary {
    --bs-btn-color:#2f5c8a;        --bs-btn-border-color:#2f5c8a;
    --bs-btn-hover-bg:#2f5c8a;     --bs-btn-hover-border-color:#2f5c8a;
    --bs-btn-hover-color:#fff;
    --bs-btn-active-bg:#27507a;    --bs-btn-active-border-color:#27507a;
    --bs-btn-active-color:#fff;
}
[data-bs-theme="dark"] .setup-help-acts .btn-outline-primary {
    --bs-btn-color:#9cc4ea;        --bs-btn-border-color:#3b4a63;
}

/* ── The hiring index (2026-08-17) ─────────────────────────────────────────
   `/market`, `/market/roles/*`, `/market/regions/*` and the monthly reports.

   ⚠ AT THE END OF THE FILE ON PURPOSE. Every component here is new, and the
   documented trap in this stylesheet — nine appearances — is that a media query
   adds NO specificity, so a phone rule for a component defined late loses to
   the plain rule below it. Rules for a component defined late must live late.

   ⚠ EVERY COLOUR IS A TOKEN. A hardcoded light literal here would be invisible
   in dark mode while still being present and selectable, which shipped once
   already (the filter dropdowns). Nothing below states a hex except the two
   direction tints, which carry their own dark override immediately after. */

.mkt-h {
    font-size:17px; font-weight:800; color:var(--text);
    margin:26px 0 10px;
}

/* The headline figures. A grid rather than flex so the cells stay aligned
   across two rows on a phone; `minmax(0,1fr)` because `min-width:auto` on a
   flex/grid item is the trap that has widened this page's ancestors three
   times. */
.mkt-stats {
    display:grid; grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));
    gap:10px; margin:14px 0 18px;
}
.mkt-stat {
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); padding:12px 14px; min-width:0;
}
.mkt-stat-n {
    display:block; font-size:26px; font-weight:800; line-height:1.15;
    color:var(--text);
    /* Digits that sit in a row of cells should line up. */
    font-variant-numeric:tabular-nums;
}
/* ⚠ 11px/700 is the documented floor for a small uppercase label, and #4b5563
   rather than --muted because --muted is only 4.83:1 in light. */
.mkt-stat-k {
    display:block; margin-top:3px;
    font-size:11.5px; font-weight:700; letter-spacing:.04em;
    text-transform:uppercase; color:#4b5563;
}
[data-bs-theme="dark"] .mkt-stat-k { color:var(--muted); }
/* Hebrew has no uppercase, so the tracking + caps treatment renders as 11px
   body text with spacing the script does not use. Same rule the section
   headings already take. */
html[lang="he"] .mkt-stat-k {
    text-transform:none; letter-spacing:normal; font-size:13px;
}

.mkt-up   { color:#15803d; }
.mkt-down { color:#b91c1c; }
[data-bs-theme="dark"] .mkt-up   { color:#4ade80; }
[data-bs-theme="dark"] .mkt-down { color:#f87171; }
/* The weekly report's movers (2026-09-12): a sub-heading under `.mkt-h` and
   the muted "(openings)" beside a delta. Same pair as `.rcount-note`. */
.mkt-h3 { font-size:14px; font-weight:700; color:var(--text); margin:8px 0 6px; }
.mkt-muted { font-size:13px; color:#4b5563; }
[data-bs-theme="dark"] .mkt-muted { color:var(--muted); }

/* ⚠ The table scrolls inside its own box. Four columns of counts overflow a
   360px phone, and the page body must never scroll sideways — 0 horizontal
   overflow at 320/360/390 is a standing check on this project. */
.mkt-tablewrap {
    overflow-x:auto; border:1px solid var(--border);
    border-radius:var(--radius); background:var(--surface);
}
.mkt-table { width:100%; border-collapse:collapse; font-size:14.5px; }
.mkt-table th, .mkt-table td {
    padding:9px 14px; border-bottom:1px solid var(--border);
    text-align:start; vertical-align:middle;
}
.mkt-table thead th {
    font-size:11.5px; font-weight:700; letter-spacing:.04em;
    text-transform:uppercase; color:#4b5563; white-space:nowrap;
}
[data-bs-theme="dark"] .mkt-table thead th { color:var(--muted); }
html[lang="he"] .mkt-table thead th {
    text-transform:none; letter-spacing:normal; font-size:13px;
}
.mkt-table tbody tr:last-child td { border-bottom:none; }
.mkt-table .mkt-num {
    text-align:end; font-variant-numeric:tabular-nums; white-space:nowrap;
}
/* The business domains — real openings at technology employers, but not what
   this board is about. Softened rather than hidden: omitting a third of what
   was measured is a worse document than showing the split. */
.mkt-row-soft td { color:var(--muted); }
.mkt-share { width:110px; }
.mkt-bar {
    display:block; height:8px; border-radius:4px;
    background:var(--accent); min-width:2px;
}

/* Two columns on a desktop, stacked on a phone. */
.mkt-cols {
    display:grid; grid-template-columns:repeat(auto-fit, minmax(260px, 1fr));
    gap:0 28px;
}
.mkt-col { min-width:0; }

.mkt-list { margin:0 0 8px; padding-inline-start:22px; }
.mkt-list li {
    display:flex; align-items:baseline; gap:10px;
    padding:5px 0; font-size:14.5px; min-width:0;
}
.mkt-list li > a, .mkt-list li > span {
    flex:1 1 auto; min-width:0;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.mkt-list li > b {
    flex:0 0 auto; font-variant-numeric:tabular-nums; color:var(--text);
}
.mkt-list-wide li { font-size:15px; }

.mkt-crumb { font-size:13px; color:var(--muted); margin:0 0 8px; }
.mkt-crumb-sep { color:var(--border); }

.mkt-note {
    font-size:13px; color:#4b5563; margin:8px 0 16px; max-width:70ch;
}
[data-bs-theme="dark"] .mkt-note { color:var(--muted); }

.mkt-about {
    font-size:13.5px; color:#4b5563; max-width:74ch;
    padding-inline-start:20px; margin:0 0 18px;
}
[data-bs-theme="dark"] .mkt-about { color:var(--muted); }
.mkt-about li { padding:3px 0; }

@media (max-width:767.98px) {
    /* The 44px tap floor. Five controls have arrived on a surface without
       being added to this list; these are added with them. */
    .mkt-list li > a { min-height:44px; display:flex; align-items:center; }
    .mkt-stat-n { font-size:23px; }
    .mkt-cols { gap:0; }
}

/* The market block on an employer page sits between two paragraphs rather than
   under a heading, so it takes less air above it than the one on /market. */
.mkt-stats-inline { margin-top:6px; }

/* ── Quoted headlines about tracked employers (2026-08-17) ─────────────────
   `partials/_market_facts.html`. Deliberately quiet: these are somebody else's
   words on our page, so they read as citations rather than as our own copy. */
.mkt-facts { list-style:none; padding:0; margin:0 0 14px; }
.mkt-fact {
    display:grid;
    grid-template-columns:auto minmax(0, 1fr);
    gap:2px 10px;
    padding:9px 0; border-bottom:1px solid var(--border);
}
.mkt-fact:last-child { border-bottom:none; }
.mkt-fact-kind {
    grid-row:1 / span 2; align-self:start;
    font-size:11px; font-weight:700; letter-spacing:.03em;
    padding:3px 8px; border-radius:999px; white-space:nowrap;
    border:1px solid currentColor;
}
/* Semantic, and separate from the accent: money in, money out, and a change of
   owner are three different things to a job seeker. Each measured on
   --surface in its own theme. */
.mkt-fact-funding     { color:#15803d; }
.mkt-fact-layoff      { color:#b91c1c; }
.mkt-fact-acquisition { color:#6d28d9; }
[data-bs-theme="dark"] .mkt-fact-funding     { color:#4ade80; }
[data-bs-theme="dark"] .mkt-fact-layoff      { color:#f87171; }
[data-bs-theme="dark"] .mkt-fact-acquisition { color:#c4b5fd; }
.mkt-fact-h { font-size:14.5px; color:var(--text); min-width:0; }
.mkt-fact-src { font-size:12px; color:#4b5563; grid-column:2; }
[data-bs-theme="dark"] .mkt-fact-src { color:var(--muted); }

@media (max-width:767.98px) {
    /* The 44px tap floor — an outbound headline link is a real target. */
    .mkt-fact-h { min-height:44px; display:flex; align-items:center; }
}

/* ── Curated reading (2026-08-26) ──────────────────────────────────────────
   `/market`'s "Selected reading" block and the paginated `/market/articles`.

   ⚠⚠ EVERY ROW CARRIES ITS OWN `dir`, SET FROM THE STORED `article.lang` in
   `partials/_market_articles.html` — this list mixes Hebrew and English and a
   single page direction lays one of them out wrongly. So every rule here uses
   LOGICAL properties and `text-align:start`; a physical `left`/`padding-left`
   would follow the PAGE while the row follows itself, which is the exact
   mismatch that put the eye icon over the password field on 2026-08-07. */
.mkt-arts { list-style:none; padding:0; margin:0 0 14px; }
.mkt-art {
    padding:11px 0; border-bottom:1px solid var(--border);
    /* Follows the row's own dir, not the page's. */
    text-align:start;
}
.mkt-art:last-child { border-bottom:none; }
.mkt-art-h {
    display:block; font-size:15px; font-weight:600;
    color:var(--text); line-height:1.4;
    /* ⚠⚠ 44px AT EVERY WIDTH, AND THE REASON IS NOT "bigger is safer".
       Measured 21px at 768px by the mobile audit. `.mkt-fact-h` beside it has
       the same shape and is NOT flagged, which looks like an inconsistency and
       is not: the audit's `DENSE_FLOOR` drops to WCAG AA's 24px for a control
       with `DENSE_SIBLINGS` (8) or more like-shaped siblings, and the facts
       block is capped at exactly 8. This list holds 1-4 on /market and up to 10
       on /market/articles, so it would sit ABOVE the dense threshold on a full
       page and below it on a sparse one — i.e. the applicable floor would
       change with the number of articles Raz has added. A floor that moves with
       the content is not a floor, so this takes the standalone figure (SC 2.5.5
       / Apple HIG) unconditionally and stops depending on the row count. */
    min-height:44px;
}
.mkt-art-sum {
    margin:4px 0 3px; font-size:13.5px; line-height:1.5; color:#4b5563;
    /* Two lines is the standfirst, not the article. `-webkit-line-clamp` is the
       only cross-browser clamp; it needs all three declarations. */
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
    overflow:hidden;
}
[data-bs-theme="dark"] .mkt-art-sum { color:var(--muted); }
/* ⚠ 13px, not 12: the floor is not a phone rule and this label is Hebrew about
   half the time, which gets no small-caps exemption (there are no capitals). */
.mkt-art-src { font-size:13px; color:#4b5563; }
[data-bs-theme="dark"] .mkt-art-src { color:var(--muted); }

/* The pager. ⚠ Wraps rather than scrolls — a clipped page number is a
   destination that does not exist, the defect the board's status pills had. */
.mkt-pager {
    display:flex; flex-wrap:wrap; gap:6px; align-items:center;
    margin:14px 0 8px;
}
.mkt-pg {
    display:inline-flex; align-items:center; justify-content:center;
    min-width:36px; min-height:36px; padding:0 10px;
    border:1px solid var(--border); border-radius:8px;
    font-size:14px; color:var(--text); text-decoration:none;
}
a.mkt-pg:hover { border-color:var(--find); color:var(--find); }
/* ⚠ Not colour alone (WCAG 1.4.1) — the current page is also the only one that
   is not a link, and carries aria-current. */
.mkt-pg-cur {
    background:var(--find); color:var(--find-ink);
    border-color:var(--find); font-weight:700;
}
.mkt-pg-off { color:var(--muted); border-color:var(--border); opacity:.55; }
.mkt-pg-gap { border:none; min-width:18px; padding:0; color:var(--muted); }
/* ⚠ The step links carry a WORD, so they must not be squeezed to a box. */
.mkt-pg-step { padding:0 14px; }

@media (max-width:767.98px) {
    /* The 44px tap floor, same as the fact headlines above. The gap marker is
       not a target and is deliberately exempt. */
    /* ⚠ `.mkt-art-h` is deliberately NOT restated here — it now carries 44px
       at every width (see above), and a duplicate phone rule would be a second
       copy of one decision, drifting the moment either is edited. */
    .mkt-pg { min-width:44px; min-height:44px; }
    .mkt-pg-gap { min-width:18px; min-height:0; }
}

/* ── Role guides (2026-08-17) ──────────────────────────────────────────────
   `/guides` and `/guides/<slug>`. Prose, so it takes a reading measure rather
   than the dashboard treatment the market pages get. */
.mkt-lead {
    font-size:17px; line-height:1.55; color:var(--text);
    max-width:64ch; margin:10px 0 16px;
}
.mkt-body {
    font-size:15.5px; line-height:1.65; color:var(--text);
    max-width:64ch; margin:0 0 14px;
}

/* One question in a guide's FAQ block. ⚠ A REAL <h3>, not bold text in a
   paragraph — the heading is the whole reason the block exists (an extractor
   lifts a headed question with its answer; it cannot lift a <b>), and it is
   also what lets a screen reader jump between questions. Sized below `.mkt-h`
   so the section still reads as one section rather than six. */
.mkt-q {
    font-size:15.5px; font-weight:700; color:var(--text);
    max-width:64ch; margin:18px 0 6px;
}

/* The site footer. Deliberately quiet — its job is to be a link target for a
   crawler and a way out for a reader, not a second navigation. */
.site-foot {
    border-top:1px solid var(--border); margin-top:32px; padding:18px 16px;
}
.site-foot-links {
    display:flex; flex-wrap:wrap; gap:8px 18px;
    justify-content:center; max-width:1100px; margin:0 auto;
}
.site-foot-links a {
    font-size:13px; color:var(--muted); text-decoration:none;
}
.site-foot-links a:hover, .site-foot-links a:focus-visible {
    color:var(--text); text-decoration:underline;
}
@media (max-width:768px) {
    /* ⚠ The tab bar is `position:fixed; bottom:0` for signed-in users on a
       phone, so without this clearance the last row of links renders
       underneath it — visible in the DOM, unclickable on the screen. */
    .site-foot { padding-bottom:calc(72px + env(safe-area-inset-bottom)); }
}
.mkt-guides {
    display:grid; grid-template-columns:repeat(auto-fit, minmax(260px, 1fr));
    gap:12px; margin:16px 0 20px;
}
.mkt-guide {
    display:flex; flex-direction:column; gap:6px; min-width:0;
    background:var(--surface); border:1px solid var(--border);
    border-radius:var(--radius); padding:16px 18px;
    color:var(--text); text-decoration:none;
}
.mkt-guide:hover { border-color:var(--accent); }
.mkt-guide:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.mkt-guide-t { font-size:16px; font-weight:800; color:var(--accent); }
.mkt-guide-l { font-size:14px; line-height:1.5; color:#4b5563; }
[data-bs-theme="dark"] .mkt-guide-l { color:var(--muted); }
.mkt-guide-n {
    font-size:12px; font-weight:700; color:var(--text);
    font-variant-numeric:tabular-nums; margin-top:2px;
}

/* ── The board-refresh offer, without scripting (2026-08-17) ───────────────
   The no-JS twin of the modal: it only renders after a save that changed the
   search (`?changed=1`), and since that save deliberately does NOT redirect
   away — the offer is on this page — it also carries the way out.

   ⚠ THE 44px FLOOR. Both controls are Bootstrap `btn-sm` and measured 31px in
   a browser, which is the SIXTH control in this app to arrive under the floor
   by inheriting a small-button class (`.jcard-add` 41px, `.nav-join` 33px,
   `.confirm-resend-b` 31px…). `btn-sm` is right here — this is a banner, not a
   primary action — so the size stays and the target grows, phones only, exactly
   as `.confirm-resend-b` does. Appended at the end of the file on purpose: a
   media query adds NO specificity, so a rule for a component defined late has
   to live late. */
.refresh-offer-b { display:inline-flex; align-items:center; justify-content:center; }
@media (max-width:767.98px) {
    .refresh-offer-b { min-height:44px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   PHONE TAP TARGETS — the systemic pass (2026-08-20)
   ══════════════════════════════════════════════════════════════════════════
   ⚠ THIS BLOCK EXISTS TO STOP THE COUNTING. The note above `.refresh-offer-b`
   records "the SIXTH control in this app to arrive under the floor by
   inheriting a small-button class". A rendered-geometry audit at 360px and
   390px found the true number: 330 undersized targets across 18 pages, 55 of
   them on `/settings` alone. Patching a seventh, then an eighth, was never
   going to converge — `btn-sm` and the bare inline `<a>` are the two shapes
   that keep producing them, so they are fixed at the shape rather than at the
   instance.

   ⚠ APPENDED AT THE END OF THE FILE ON PURPOSE, for the reason the
   `.refresh-offer-b` note already gives: a media query adds NO specificity, so
   a mobile override for a component defined late in this file has to live
   later still. Everything here is phone-only and geometry-only — no colour is
   touched, so both themes are unaffected by construction.

   ⚠ TWO DIFFERENT FIXES, AND THE DIFFERENCE IS DELIBERATE:
     GROW      the control when its size is simply wrong (a primary action, a
               form field, a nav button).
     EXTEND    the touch area and leave the ink alone when the visual size is
               correct and only the target is short — every "×" here is 17-19px
               of glyph inside a chip, and a 44px "×" reads as a button bolted
               onto the chip. `::after { inset }` costs no layout.

   Verified with `.claude/skills/mobile-audit/mobile_audit.py`, which measures
   the rendered box rather than reading this stylesheet. */
@media (max-width:767.98px) {

    /* ── 1. The small-button class itself ─────────────────────────────────
       21 hits on /settings (role domains), 20 across five public pages.
       Centring is part of the fix: `min-height` alone on a `p-0` link button
       leaves the label pinned to the top of a 44px box. */
    .btn-sm,
    .btn-group-sm > .btn {
        min-height:44px;
        display:inline-flex;
        align-items:center;
        justify-content:center;
    }
    /* `w-100 d-flex` domain headers already lay out their own children; keep
       their justification and only raise the floor. */
    .role-domain .btn-sm { justify-content:space-between; }

    /* ── 2. Links that occupy their own line ──────────────────────────────
       ⚠ ONLY THESE. A link INSIDE a sentence is exempt under WCAG 2.2
       SC 2.5.8 — growing it either wrecks the paragraph's line spacing or
       silently overlaps the links above and below it, so a tap lands on
       whichever painted last. `.mkt-note a`, `.enpage-note a` and the consent
       line are inline and are deliberately absent from this list. */
    .jcard-title a,
    .bcard-title a,
    .rcount-note a,
    .mkt-crumb a,
    .lsec-sub a,
    .jcard-fact a,
    /* ⚠ `.auth-alt a` is the password-recovery link, and it only failed at
       360px — at 390 it shares its line with other text and is exempt as an
       inline link, at 360 it wraps onto a line of its own and becomes a
       standalone target. Worth remembering that the SAME element can be both,
       which is why the audit tests widths rather than trusting one. */
    .auth-alt a,
    .mkt-guide,
    .saved-exit,
    .rpager-b {
        min-height:44px;
        display:inline-flex;
        align-items:center;
    }
    /* A wrapped two-line title must still read as a block of text. */
    .jcard-title a, .bcard-title a { display:flex; }

    /* ── 3. Chip dismissers — keep the glyph, extend the target ───────────
       17px (settings), 19px (search chips) and 16px (the filter clear). The
       parent needs a positioning context; `.chip` and `.rfilter` already
       establish their own, so the pseudo-element is anchored on the control. */
    .kw-chip-x,
    .rfilter-x {
        position:relative;
    }
    .kw-chip-x::after,
    .rfilter-x::after {
        content:'';
        position:absolute;
        inset:-14px;
    }
    /* ⚠ AND NOT ON THE SETTINGS KEYWORD CHIPS, WHICH IS THE INTERESTING CASE.
       The same overlay was applied there first and the audit kept failing them
       at 17px — correctly. Those chips wrap into tight rows about 24px apart,
       so a ±14px overlay on one chip's "×" covers the "×" of the chip above and
       below it; `elementFromPoint` returns whichever painted last, and a tap
       near the edge removes the WRONG keyword. An overlay only works where
       there is empty space around the control to grow into. Here there is not,
       so the chip itself gets the height and the target comes with it. */
    .chip {
        min-height:44px;
        display:inline-flex;
        align-items:center;
    }
    .chip button { min-height:44px; min-width:44px; }

    /* ── 4. Form controls ─────────────────────────────────────────────────
       The 16px font floor two blocks up already stops iOS zooming on focus;
       this is the other half — the box itself. `.kw-entry` measured 32px and
       `.chip-entry` 29px, and both are the primary input on the page they
       appear on. */
    .kw-entry,
    .chips-control .chip-entry,
    .chip-entry,
    #filter-q,
    .bfilter .btn,
    .adopt-go {
        min-height:44px;
    }
    /* `.chips-control` is the box the entry sits in; it has to grow with it. */
    .chips-control { min-height:44px; align-items:center; }

    /* ── 5. The hamburger — 40px, on all 18 pages ─────────────────────────
       The single most-tapped control on a phone, and the last one anybody
       measured. */
    .navbar-toggler { min-height:44px; min-width:44px; }

    /* ── 6. The board card's own title button ─────────────────────────────
       22px, and it is the primary action on every card. */
    .bcard-title-btn, .job-open { min-height:44px; }

    /* ── 7. Dense multi-selects — 32px, and that is NOT a compromise ──────
       ⚠ THE 44px FLOOR IS THE WRONG TARGET HERE AND FORCING IT WOULD MAKE THE
       SCREEN WORSE. The role picker is ~160 checkboxes and the location picker
       is similar; at 44px a single expanded domain becomes a scroll marathon,
       which is a usability regression dressed as an accessibility fix. WCAG 2.2
       SC 2.5.8 asks for 24x24 CSS px at AA — 44px is the AAA/Apple-HIG figure
       this project adopts for STANDALONE controls. 32px clears the AA minimum
       with margin, keeps ten roles on a screen, and the row is the label, not
       the 18px box. The audit encodes this as a named exception rather than
       carrying 100+ permanent findings that nobody should act on. */
    .role-picker .form-check,
    .loc-picker .form-check,
    .clear-opt .form-check { min-height:32px; }
    .role-picker .form-check-label,
    .loc-picker .form-check-label,
    .clear-opt .form-check-label {
        min-height:32px;
        display:inline-flex;
        align-items:center;
    }
    /* The board's bulk-select box is standalone, so it keeps the full floor. */
    .row-check { min-height:24px; min-width:24px; }
    .bcard-check, .row-check-wrap { min-height:44px; display:inline-flex; align-items:center; }

    /* ── 8. Clearance for the fixed tab bar, on the one screen that needs
           more than the global reserve ────────────────────────────────────
       ⚠ MEASURED, AND THE FIRST DIAGNOSIS WAS WRONG. `body` and
       `.settings-wrap` both already reserve 60px for the 57px bar, which is
       enough for ordinary flow — so "settings has no clearance" was not the
       problem. `.role-picker` is its OWN scroll container (measured 1402px of
       content in a 382px box), and at full page scroll its BOX bottom landed
       at y=798 against a bar top of y=787. The last rows inside it were
       permanently under the bar and could not be scrolled into view, because
       the page had already stopped scrolling.
       A box the user scrolls INSIDE needs more reserve than one they scroll
       PAST: its bottom edge is a working surface, not a margin. Extra room
       here rather than in the global rule, which would add dead space to every
       other page. */
    .settings-wrap { padding-bottom:calc(96px + env(safe-area-inset-bottom)); }

    /* ── 9. The section links on the settings landing ─────────────────────
       16px, and they are how you reach every other tab. */
    .settings-acct-link a {
        min-height:44px;
        display:inline-flex;
        align-items:center;
    }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE 13px TEXT FLOOR — the cases the market pages missed (2026-08-20)
   ══════════════════════════════════════════════════════════════════════════
   `contrast-audit` sets the body floor at 13px and grants ONE exemption:
   "Small-caps section labels (text-transform:uppercase + letter-spacing):
   >= 11px and font-weight:700". A rendered audit of `/market` found ten
   sub-13px nodes, the most of any page — but they are not all the same case,
   and the difference decides which ones move:

     COMPLIANT, LEFT ALONE   `.jd-sech` and `.mkt-stat-k` are 11-11.5px
                             uppercase, bold and tracked. That is the exemption
                             working as written.
     ⚠ NOT COMPLIANT         `.mkt-fact-kind` is 11px, bold and tracked too —
                             but its text is HEBREW. `text-transform:uppercase`
                             does nothing to Hebrew: there are no capitals, so
                             none of the extra letterform height the exemption
                             assumes is there. It is simply an 11px label, on
                             the tags a reader scans first.

   Everything below is the second kind. Not scoped to a media query: a 12px
   Hebrew label is no more readable on a desktop, and the floor is not a
   phone rule. */
.mkt-fact-kind { font-size:13px; }
.mkt-fact-src  { font-size:13px; }
.mkt-guide-n   { font-size:13px; }
/* The counters on the settings pickers and the market guide cards. Bootstrap's
   `.badge` is `.75em`, which lands at 12px against a 16px body. Scoped to the
   badges the audit actually reached, not `.badge` globally. */
#match-jobs, #match-total,
.role-domain-total, .role-domain-count,
.loc-domain-total, .loc-domain-count { font-size:13px; }

/* ⚠ AND THE TAB BAR, WHICH IS A REVERSAL OF AN EARLIER DELIBERATE CHOICE.
   `.tabbar-i` carried a note: "12px, not 11: this is the app's permanent
   primary navigation, not a section label, so the small-caps exemption to the
   13px floor never applied to it." The reasoning is right and its conclusion
   was half-applied — it argues the exemption does NOT cover the tab bar, then
   stops at 12px anyway, one pixel under the floor it just invoked. It is on
   every page of the site, so it was 36 of the 78 findings in the audit.
   Measured at 13px: three labels still fit on one line at 360px, which was the
   original constraint. */
@media (max-width:768px) {
    .tabbar-i { font-size:13px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE MARKET TABLES ON A PHONE (2026-08-20)
   ══════════════════════════════════════════════════════════════════════════
   ⚠ EVERY AUTOMATED CHECK PASSED THIS TABLE AND IT WAS STILL WRONG. It fits
   368px with no overflow and no horizontal scroll, so `mobile-audit` is happy —
   it fits by wrapping "בינה מלאכותית ולמידת מכונה" over FOUR lines, which turns
   a 12-row table into 1,500px of scrolling, and by rendering the share column
   as a 4-10px stub of a bar carrying no number. Only the screenshot showed it.
   This is the case for "measured in a real browser" meaning *looked at*.

   Below 560px the rows stop being rows. Each becomes a small stacked card:
   the label on its own line, then one line per figure, each naming itself.

   ⚠ THE LABELS COME FROM `data-l` IN THE MARKUP, NOT FROM `content:"..."` HERE.
   Three templates use `.mkt-table` with three different column sets (Field /
   Open roles / Employers / Share, the same minus Share, and Skill / Ads /
   Share), so a `nth-child` mapping would be wrong on two of them — and a
   hard-coded string in CSS cannot be translated, on a site whose default
   language is Hebrew. `attr()` is the only version that is both correct per
   template and translatable.

   ⚠ `thead` IS HIDDEN VISUALLY, NOT REMOVED. `display:none` would take it from
   the accessibility tree too, and the per-cell labels below are what replace it
   for sighted users only. */
.mkt-share-n {
    font-variant-numeric:tabular-nums;
    /* 13px, not 12.5: this is new text and the floor applies to it like any
       other. It went in at 12.5 and the contrast audit's size check caught it
       on the desktop breakpoint, where the phone rule below does not reach. */
    font-size:13px;
    color:#4b5563;
    margin-inline-start:8px;
}
[data-bs-theme="dark"] .mkt-share-n { color:var(--muted); }

@media (max-width:559.98px) {
    .mkt-table, .mkt-table tbody, .mkt-table tr, .mkt-table td { display:block; }
    .mkt-table thead {
        position:absolute; width:1px; height:1px;
        overflow:hidden; clip-path:inset(50%); white-space:nowrap;
    }
    /* ⚠⚠ `overflow:hidden` ON A `thead` DOES NOT CLIP ITS CELLS, so the 1px
       box above was hiding nothing on its own. Table-internal boxes ignore
       `overflow`: each `th` kept its `white-space:nowrap` and its 14px padding
       and painted at full width, anchored off the start edge. Measured at
       360px before this rule: the four headers of the domain table paint 59,
       117, 81 and 54px wide — all of them outside the 1px `thead`, and all of
       them inside the viewport only by luck.
       ⚠ IT WENT UNSEEN BECAUSE EVERY HEADER WAS ONE SHORT WORD. The first one
       longer than that (2026-08-21, "Share of those stating" / "חלק מהמשרות
       שמציינות") painted 177px and hung 18px off the start edge, which is the
       `overflow` finding the mobile audit raised. The clip has to be on the
       CELL, which is the only box that honours it.
       ⚠ Higher specificity (0,1,2) than `.mkt-share`'s fixed 110px width and
       than the shared `.mkt-table th` padding, so it needs no `!important`;
       `html[lang="he"] .mkt-table thead th` outranks it but sets only type
       properties, which this does not touch. */
    .mkt-table thead th {
        /* ⚠ `display:block` IS WHAT MAKES `width` MEAN ANYTHING HERE. As a
           `table-cell` the box is sized by its COLUMN, so `width:1px` was
           advisory and the cell still laid out at 149px — inside the viewport
           by luck, exactly the state this rule exists to end. As a block it is
           genuinely 1px. It stays in the accessibility tree, which is the one
           thing `display:none` would have cost. */
        display:block; width:1px; height:1px; padding:0; border:none;
        overflow:hidden; clip-path:inset(50%);
    }
    .mkt-table tr {
        padding:11px 14px;
        border-bottom:1px solid var(--border);
    }
    .mkt-table tbody tr:last-child { border-bottom:none; }
    .mkt-table td { padding:0; border:none; }

    /* The row's subject: no longer squeezed into a quarter of the width, so it
       wraps at most once instead of four times. */
    .mkt-table td:first-child {
        font-size:15.5px; font-weight:700; line-height:1.35;
        margin-bottom:5px;
    }
    /* One figure per line, each naming itself. */
    .mkt-table td[data-l] {
        display:flex; align-items:center; gap:8px;
        font-size:13.5px; padding:1px 0;
    }
    .mkt-table td[data-l]::before {
        content:attr(data-l);
        color:#4b5563;
        flex:0 0 auto;
        min-width:10ch;
    }
    .mkt-table .mkt-num { text-align:start; }
    /* ⚠ AND THE BAR GOES AWAY ENTIRELY, which is the honest end of this fix.
       It was kept at first, sized to a 110px track — and it still rendered as a
       3-5px stub beside the number, because its width is a percentage of a cell
       that is now a flex line rather than a quarter-width column. A comparison
       bar you cannot compare with is decoration competing with the figure that
       replaced it. The percentage is readable, quotable and exact; the bar
       returns above 560px where it has the width to mean something. */
    .mkt-table .mkt-share .mkt-bar { display:none; }
    .mkt-share-n { margin-inline-start:0; font-size:13.5px; color:inherit; }
}
[data-bs-theme="dark"] .mkt-table td[data-l]::before { color:var(--muted); }

/* ══════════════════════════════════════════════════════════════════════════
   THE GUEST PAGES THE FIRST PASS NEVER LOADED (2026-08-20)
   ══════════════════════════════════════════════════════════════════════════
   ⚠ FOUND ONLY BY RUNNING THE AUDIT AGAINST PRODUCTION AS A SIGNED-OUT
   VISITOR. Every earlier run signed in first, so `/join`, `/login` and `/en`
   redirected to the board and were measured as the dashboard — twice — while
   reporting clean. These are the pages a stranger arriving from search or an
   ad actually sees, and their small print was 12-12.5px throughout.

   The audit now uses two browser contexts so this cannot recur; the fix for
   the pages themselves is below. Not scoped to a media query: 12px small print
   is no more readable on a desktop. */
.join-fine,
.auth-guest-note,
.auth-foot-note,
.asignup-fine,
.asignup-fine-tight { font-size:13px; }

/* ⚠ AND THE SKILL LINKS IN A GUIDE'S STACKED TABLE, WHICH ONLY EXIST WHEN
   THERE IS DATA. The dev database copy had no skill rows for that guide, so
   the table never rendered and the audit passed it locally — production has
   six of them per guide, each an 18px link. The stacked layout gives the first
   cell its own line, so there is room for the full target. */
@media (max-width:559.98px) {
    .mkt-table td:first-child a {
        min-height:44px;
        display:inline-flex;
        align-items:center;
    }
}

/* ══ THE PHONE RESULTS LANDING SHOWED NO JOBS AT ALL (2026-08-30) ═══════════
   Raz forwarded user feedback that the search landing "is not that clear".
   MEASURED, not reviewed, at /search?q=Product+Manager on a 390x844 phone:

       search form   top=  86  height= 280
       chip bar      top= 380  height=  44
       count <h1>    top= 434  height=  42
       coverage line top= 482  height=  39
       offer strip   top= 533  height= 144
       FIRST JOB     top= 693              <- consent banner covers 619-788,
                                              the tab bar owns the last 56px

   So a first-time visitor to a search URL saw the controls they had just used,
   the count, the offer — and NOT ONE JOB. Desktop is fine (first card y=445),
   which is why the report was phone-only. Two rules follow, and both are
   scoped to phones for that reason.

   ⚠⚠ BOTH BLOCKS MUST STAY AT THE END OF THIS FILE, BELOW .asignup-bar
   (~2773) AND .jsearch-bar (~1861). A media query adds NO specificity — this
   stylesheet's most-repeated trap, nine appearances — and placed above their
   own declarations either block would silently do nothing. */

/* 1. The offer leaves the phone's results page.
   ⚠ THIS NARROWS A PRIOR DECISION RATHER THAN REVERSING IT. The 2026-07-31
   pass compressed this strip 204px -> 144px and deliberately kept "all four
   placements". Kept on desktop; dropped on phones only, where the guest is
   still offered the same thing twice — the `card` at #results-end and the
   `modal` guest.js raises — so nothing is lost except the ask that arrives
   before any job does. `results.html`'s own comment at #results-end already
   states the principle this restores: the offer belongs where "is that all?"
   makes it welcome rather than interrupting. */
/* ⚠⚠ DELETED 2026-08-31e: `@media (max-width:767.98px) { .asignup-bar {
   display:none } }`. The rule was right about the SYMPTOM — the strip must not
   sit between the count and the first job — and wrong about the remedy, because
   the two placements it relied on are both far away on a phone: the `card` is
   20 cards down and the `modal` needs 3 opened jobs, 2 active minutes or a
   second search. Raz ran one search ("System Analyst" in Jerusalem, 53 results)
   and was offered the digest nowhere. The strip is now rendered INSIDE the list
   after the third card (`offer_after` in `_job_list.html`), which fixes the
   original complaint at the source instead of hiding the element, so no
   breakpoint rule is needed at all. */

/* 2. The form folds into one row on phones.
   It is navigation here, not the destination — the visitor has already
   searched and only needs it back if the results are wrong.

   ⚠⚠ THIS WAS A <details> FIRST, AND THAT SHIPPED A DESKTOP REGRESSION. The
   first cut paired <details> with `display:flex` on the content above 768px to
   defeat the UA's hiding of a closed disclosure. Chrome does not allow that —
   the content is hidden through the details content slot, which a `display` on
   the child cannot reach — so the search form disappeared from the desktop
   results page entirely. ⚠ Its bounding box still reported 94/139, so the
   scripted measurement beside it read as PRESENT; only the screenshot showed
   the form was gone. That is the house rule earning its keep: layout is
   measured in a real browser, and "measured" includes looking at it.
   `open` does not rescue it either — `open` is a DOM attribute, CSS cannot
   unset it, and one document cannot be open on desktop and closed on a phone.
   A label + checkbox can, so that is what this is.

   ⚠ Native <details> would still be the better SEMANTICS; it is given up for
   the one behaviour it cannot express. Stated rather than hidden. */
.jsearch-collapse { margin-bottom:14px; }
.jsearch-collapse > .jsearch-bar { margin-bottom:0; }
.jsearch-cosum {
    display:flex; align-items:center; gap:10px; cursor:pointer;
    min-height:48px; padding:10px 13px;
    background:var(--find-wash); border:2px solid var(--find);
    border-radius:var(--radius);
    box-shadow:0 4px 16px rgba(46,117,181,.18);
}
/* ⚠ The query can be long and arrives in either script, so it truncates rather
   than wrapping — a two-line summary gives back the height this block exists to
   reclaim. `dir="auto"` is on the span in the template so a Hebrew query reads
   correctly without turning the row around. */
.jsearch-cosum-what {
    flex:1 1 auto; min-width:0; font-size:15px; font-weight:700; color:var(--text);
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.jsearch-cosum-edit {
    flex:0 0 auto; font-size:13.5px; font-weight:600; color:var(--find);
    text-decoration:underline;
}
.jsearch-cosum .jsearch-caret { transition:transform .15s ease; }
/* ⚠ The checkbox is visually-hidden, not hidden, so it keeps its place in the
   tab order — this is the only focus indicator the control has. */
.jsearch-cotoggle:focus-visible + .jsearch-cosum {
    outline:2px solid var(--find); outline-offset:2px;
}
.jsearch-cotoggle:checked + .jsearch-cosum .jsearch-caret { transform:rotate(180deg); }

@media (max-width:767.98px) {
    .jsearch-collapse > .jsearch-bar { display:none; }
    /* `grid`, not `block`: that is what .jsearch-bar is given at this width. */
    .jsearch-cotoggle:checked ~ .jsearch-bar { display:grid; margin-top:10px; }
}
@media (min-width:768px) {
    /* Desktop is untouched: the summary goes and the form keeps the
       `display:flex` it already has from `.jsearch`. Nothing to override, which
       is the whole reason this shape works and the <details> one did not. */
    .jsearch-cosum { display:none; }
}

/* ── (removed 2026-08-31g) `.anew-addrow` / `.anew-add` ──────────────────────
   The alert flow's step 1 briefly grew a second submit ("Add") beside the
   primary, to feed a hand-rolled chip row. `[data-kw-autocomplete]` already
   provided the whole control — chips, removal and bilingual suggestions — so
   the button, its row and these rules had nothing left to style. Deleted
   rather than left behind: a dark-mode override that outlives its component is
   a trap this stylesheet has already logged. */

/* The "add this area" chip on the alert flow's step 2 (2026-08-31f). The
   OUTLINE twin of `.rfilter`: same rectangle, same radius, same type — so the
   chosen set and the available set read as one family and tapping one moves it
   from the second row to the first. It is an <a>, not a button: every action in
   this flow is a real URL.
   ⚠ 44px min-height — this is a primary tap target on a phone, and the admin
   console's 31px buttons are already an open item. */
.rfilter-add {
    display:inline-flex; align-items:center; gap:5px;
    min-height:44px; padding:6px 13px;
    border:1px dashed var(--border); border-radius:8px;
    background:var(--surface); color:var(--text);
    font-size:13.5px; font-weight:600; text-decoration:none;
}
.rfilter-add:hover { border-color:var(--find); border-style:solid; color:var(--find); }
.rfilter-plus { font-size:15px; font-weight:700; color:var(--find); line-height:1; }
.anew-locadd { margin-bottom:16px; }

/* ── /salaries (2026-09-10) ────────────────────────────────────────────────
   ⚠ TOKENS ONLY, NO LIGHT LITERALS. `--text` / `--muted` / `--border` /
   `--surface` flip under [data-bs-theme="dark"]; a hardcoded #fff here would
   stay white while the text around it went near-white, which is the invisible-
   text bug this project has shipped once already.
   ⚠ The one literal below (#4b5563) is the SAME one `.mkt-note` uses and it
   carries its own dark override on the next line, for the same reason: --muted
   is only 4.83:1 on light and this is small text. */
.sal-search { margin:18px 0 26px; }

.sal-faq { margin:0 0 24px; }

/* ⚠ A REAL <h3>, STYLED DOWN. The heading level is what lets an assistant lift
   the question and its answer as one unit — the entire reason this block
   exists — so it must not become a styled <div>. */
.sal-q {
    font-size:15px; font-weight:700; color:var(--text);
    margin:20px 0 6px; max-width:70ch;
}

.sal-a {
    font-size:14px; color:#4b5563; margin:0; max-width:70ch;
    line-height:1.65;
}
[data-bs-theme="dark"] .sal-a { color:var(--muted); }

.sal-next {
    margin:28px 0 8px; padding-top:18px;
    border-top:1px solid var(--border);
}

/* ⚠ THE CITATION LINK NEEDS A REAL TAP TARGET. Measured at 20px by the
   mobile-audit skill on the day /salaries shipped, against a 44px floor — and
   on this page the citation is the thing a reader actually reaches for, since
   it is what makes the figure checkable. `inline-flex` + `min-height` rather
   than padding alone so the hit area is the whole row height, not just the
   glyph box. */
.sal-src {
    display:inline-flex; align-items:center; min-height:44px;
    padding-inline-end:4px;
}

/* ── /salaries/<role> (2026-09-10b) ────────────────────────────────────────
   ⚠ Tokens only; the one literal (#4b5563) matches `.mkt-note` and carries its
   dark override below, for the same reason: --muted is 4.83:1 on light and
   this is small text. */
.sal-span {
    font-size:19px; margin:4px 0 10px; color:var(--text);
}
.sal-span strong { font-size:22px; font-weight:800; }

/* ⚠ "not the same job" — a WARNING chip, so it must not read as decoration.
   Stated per theme rather than taking var(--accent): this renders inside a
   table cell on --surface, and the accent is 4.48:1 on --bg, which is 0.02
   short of AA. The `contrast-audit` note in CLAUDE.md says to state the pair. */
.sal-proxy {
    display:inline-block; font-size:11px; font-weight:700;
    padding:1px 6px; border-radius:10px; white-space:nowrap;
    border:1px solid currentColor; color:#8a5a00; margin-inline-start:6px;
}
[data-bs-theme="dark"] .sal-proxy { color:#e0a63a; }


/* ── /contact honeypot (2026-09-11) ─────────────────────────────────────────
   The `website` field a bot fills and a person never sees. Moved off-canvas
   rather than `display:none`: some form-fillers skip hidden inputs, which is
   the one behaviour that would make the trap useless. `aria-hidden` on the
   wrapper and `tabindex="-1"` on the input keep it out of the accessibility
   tree and the tab order, so a screen-reader user never meets it either. */
.hp-wrap { position:absolute; width:1px; height:1px; overflow:hidden;
           clip:rect(0 0 0 0); opacity:0; pointer-events:none; }
.contact-form { max-width:640px; }
