/* ═══════════════════════════════════════════════════════════════════
   Mocca — AppShell chrome
   Forked from uploads/index.html. Size chips removed.
   Responds to the actual viewport (no P/T/H/V fake frames).
   ═══════════════════════════════════════════════════════════════════ */

*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;min-height:100%;background:var(--bg-1);color:var(--fg-1);font-family:var(--mk-font-ui)}
body{overflow-x:hidden}
/* B63-110 (Pablo: "the arrow and the hover highlighted area are missaligned...
   across the whole prototype") — root cause: the browser's UA stylesheet gives
   every <button> its own default padding (~1px 6px in Chromium). A fixed-size
   icon button that centers its `.mi` glyph with `display:grid/flex;place-items/
   align-items:center` and never explicitly zeroes padding gets that UA padding
   subtracted from ONE side of its content box unevenly relative to the glyph's
   own box, so the "centered" icon renders visibly off-centre (confirmed on
   `.gantt-proj-tw`: a 22px box whose 18px chevron rendered 4px right of centre
   and overflowing the box's right edge). Every component that already sets its
   own padding is unaffected (higher specificity beats this element selector);
   this only fixes the ones that were silently relying on the browser default. */
button{padding:0}

/* ── Keyboard focus baseline (a11y) ──────────────────────────────────
   A visible ring for keyboard navigation only (:focus-visible) — never on
   mouse/touch. Zero-specificity :where() so any component's own focus
   style still wins, and token-driven so it tracks the accent (incl. the
   staging-green shell override). Fills the gap where most controls had no
   visible focus indicator. */
:where(a, button, [role="button"], input, textarea, select,
       .cs, .cs-trigger, .cs-item, .sb-item, .tab, .nav-ic, .nav-pill,
       .btn, [tabindex]:not([tabindex="-1"])):focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
}
.mi{font-family:'Material Symbols Rounded';font-weight:400;font-size:20px;line-height:1;user-select:none;
    -webkit-font-feature-settings:'liga';font-feature-settings:'liga';display:inline-block;vertical-align:middle;
    /* hide literal ligature text until the icon font has loaded */
    font-size-adjust:none}
/* B62 UI-47 — the same rule for a font that never arrives: keep the box, hide
   the ligature name. Printing "add_location_alt" in the middle of a panel is the
   defect he reported twice; an empty slot is merely quiet. */
.fonts-loading .mi,.fonts-failed .mi{visibility:hidden}
/* B62 CX-53 — "This is supposed to be an icon but it's not showing up", on
   `.cn-swipe-meta … .mi` with the text "play_arrow". It was never the icon name
   (B61 already swapped play_circle → play_arrow): `.cn-swipe-meta span` is
   (0,1,1) and carries a `font:` SHORTHAND, which resets font-family — so every
   .mi that happens to be a <span> inside it renders its ligature as body text in
   Hanken Grotesk. Any `font:` shorthand on a descendant selector does this, so
   the guard is app-wide rather than per-surface: a (0,3,0) selector that pins
   ONLY the family and the ligature feature, leaving every existing size/colour
   override (`.wl-sech .mi{font-size:17px}` and its ~200 siblings) untouched. */
.mi.mi.mi{font-family:'Material Symbols Rounded';-webkit-font-feature-settings:'liga';font-feature-settings:'liga'}

/* ── Shell grid ── */
.shell{display:grid;
  grid-template-columns:var(--mk-sidebar-w) minmax(0,1fr);
  grid-template-rows:var(--mk-nav-h) 1fr auto;
  min-height:100dvh;position:relative;background:var(--bg-1)}
.shell > *{min-width:0}
/* HARDENING (Pablo 2026-06-22 — "the shell needs to be used almost everywhere"):
   the content track is minmax(0,1fr) so a wide page (e.g. the kanban board)
   can NEVER expand the grid past the viewport and push/clip the nav or
   sidebar. Wide content scrolls inside its own container instead. */
/* Signed-in: cap the shell to viewport height so body.page is the only
   vertical scroll surface. Sidebar then has its own independent scroll
   (via .sb-inner overflow-y:auto) and never tags along with the page. */
.shell.signed-in{height:100dvh;overflow:hidden;min-height:0}

/* Signed-out has no sidebar */
.shell.signed-out{grid-template-columns:minmax(0,1fr)}
.shell.signed-out .sb{display:none}
.shell.signed-out .body{grid-column:1/-1}
.shell.signed-out:not(.mobile) .nav-brand{min-width:var(--mk-sidebar-w);flex-shrink:0}

/* ── Viewport breakpoints (Uallet-style)
   Desktop  > 1024px  · full layout, nav row, hero row
   Tablet   721-1024  · nav row, every nav element stacks icon-above-label, hero column
   Mobile-V ≤720 portrait · floating glass nav, no wordmark, equalized icons, hero column
   Mobile-H ≤599 landscape · nav becomes left rail (flex-col), hero row
   ─────────────────────────────────────────────────────────── */

/* ── Navbar ── */
.nav{grid-column:1/-1;display:flex;align-items:center;padding:0 20px;gap:18px;background:var(--bg-1);
  border-bottom:1px solid var(--border);height:var(--mk-nav-h);position:sticky;top:0;z-index:40}
/* Signed-in DESKTOP only: brand fills the sidebar column so the search bar
   starts where the sidebar ends; the navbar/sidebar border vanishes so the
   chrome reads as a single tinted L-frame around the main content card.
   This rule must be confined to the desktop breakpoint — at tablet and
   below the brand is sized differently (64-px column, etc.) and the
   tablet/mobile media queries above already define those overrides.
   Without this MQ wrapper the base wins over the MQ rules due to source
   order (matching specificity). */
@media (min-width: 1025px) and (min-height: 600px){
  .shell.signed-in .nav{
    padding-inline-start:0;
    border-bottom:0;
    background:var(--bg-1);
  }
  /* The brand COLUMN is the sidebar width; the brand link + app-switch
     chevron both live inside it, so the chevron ends before the sidebar
     edge and never pushes into the search bar. Pablo 2026-06-01 #11. */
  .shell.signed-in .nav-brand-wrap{
    width:var(--mk-sidebar-w);
    box-sizing:border-box;
    flex-shrink:0;
  }
  .shell.signed-in .nav-brand{
    width:auto;
    height:var(--mk-nav-h);
    padding-inline-start:20px;
    flex-shrink:0;
    margin-inline-end:0;
  }
  .shell.signed-in .sb{border-inline-end:0}
  /* B63-035 (Pablo: "The search bar should be aligned to the left with the
     main content, it's a bit to the right currently") — the brand column
     ends exactly at the sidebar edge (above), but `.nav`'s flat 18px `gap`
     put the search bar 18px past it, while the page content starts 24px
     past the same edge (`.page > .page-h{padding-inline:24px}`, shell.css).
     Nudge the extra 6px onto the search bar specifically — the gap to the
     pills cluster on its other side is untouched. */
  /* B65 W2 (065-034, "The search bar starts a bit further to the right compared to the
     end of the sidebar … it should be aligned, currently it looks hacky") — aligning it
     with the TEXT inside the page (B63) still left it floating 24 px off the one edge you
     actually see: the main-content frame, which starts exactly where the sidebar ends.
     The bar now starts on that edge — `.nav`'s 18px gap cancelled — so the search box
     and the content frame share one vertical line. */
  .shell.signed-in .nav-search{margin-inline-start:-18px}
}
.nav-brand{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--fg-1);flex-shrink:0;cursor:pointer}
.nav-brand .icon{height:34px;width:34px;display:block}
.nav-brand .wordmark{height:20px;width:auto;display:block}
/* B39 C15/C33 — language-switcher flag SVGs */
.cs[data-cs="lang"] .cs-item{display:flex;align-items:center;gap:8px}
.cs[data-cs="lang"] .cs-val{display:inline-flex;align-items:center;gap:7px}
.mk-lang-flag{display:inline-flex;flex:0 0 auto;width:20px;height:14px;border-radius:3px;overflow:hidden;box-shadow:0 0 0 1px rgba(0,0,0,.08)}
.mk-lang-flag svg{width:100%;height:100%;display:block}
.mk-lang-t{min-width:0}
/* B40 C51 (Pablo) — number inputs NEVER show native spinner arrows, app-wide. */
input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
input[type="number"]{-moz-appearance:textfield;appearance:textfield}

/* ── Wordmark color tokens (Pablo 2026-05-31 — inline-SVG conversion)
   The wordmark is now an inline <svg><use href="#mocca-wordmark-{en,ar}"/></svg>
   referencing a sprite at the top of <body>. Text paths render in
   `currentColor`; the "o" highlight reads `var(--wordmark-accent)` via
   inline style. So a single CSS rule on `.wordmark` drives the whole
   theme/lang/staging treatment:
     • Theme → flips --wordmark-color (text)
     • Staging admin → flips --wordmark-accent (highlight)
     • Auth panel (always dark) → forces --wordmark-color locally
   The legacy `.wordmark-light`/`.wordmark-dark` classes are gone — display
   toggling for theme is no longer needed because color is the only diff. */
.wordmark{color:var(--wordmark-color, var(--mk-navy-700))}
/* dark-theme wordmark text now lives on [data-theme="dark"] in colors_and_type.css */

/* Brand IMAGE variant scoping — applies to icon-* (still <img>) only.
   Wordmark-{light,dark} are obsolete; this block now scopes only the icon
   variants used at every brand mark embed site (nav-brand, sb-brand, splash). */
html[data-theme="light"] .nav-brand .icon-dark,
html[data-theme="light"] .sb-brand  .icon-dark,
html[data-theme="light"] .splash    .icon-dark,
html[data-theme="light"] .close-mark .icon-dark{display:none !important}
html[data-theme="dark"]  .nav-brand .icon-light,
html[data-theme="dark"]  .sb-brand  .icon-light,
html[data-theme="dark"]  .splash    .icon-light,
html[data-theme="dark"]  .close-mark .icon-light{display:none !important}
html[lang="en"] .wordmark-ar,
html[lang="es"] .wordmark-ar{display:none !important}
html[lang="ar"] .wordmark-en{display:none !important}

/* Every app splashes with ITS OWN logo + name, never Mocca's (Pablo, 2026-09-29).
   html[data-app] is set pre-paint in landing_page.html, so the right pair is
   there from the first frame. Staff is a MODE of Jockey (data-app="jockey" +
   html[data-jk-staff]), so it gets its own pair. The Mocca pair (.icon-* +
   .splash-wordmark) shows only when no sub-app owns the page. */
.splash [data-splash-app]{display:none !important}
html[data-app="jockey"]:not([data-jk-staff]) .splash img[data-splash-app="jockey"],
html[data-jk-staff] .splash img[data-splash-app="staff"],
html[data-app="budget"]  .splash img[data-splash-app="budget"],
html[data-app="content"] .splash img[data-splash-app="content"],
html[data-app="melt"]    .splash img[data-splash-app="melt"],
html[data-app="buraco"]  .splash [data-splash-app="buraco"].splash-crest-buraco{display:grid !important}
html[data-app="jockey"]:not([data-jk-staff]) .splash span.splash-wordmark-app[data-splash-app="jockey"],
html[data-jk-staff] .splash span.splash-wordmark-app[data-splash-app="staff"],
html[data-app="budget"]  .splash span.splash-wordmark-app[data-splash-app="budget"],
html[data-app="content"] .splash span.splash-wordmark-app[data-splash-app="content"],
html[data-app="melt"]    .splash span.splash-wordmark-app[data-splash-app="melt"],
html[data-app="buraco"]  .splash span.splash-wordmark-app[data-splash-app="buraco"]{display:flex !important}
html[data-app="jockey"] .splash .icon-light, html[data-app="jockey"] .splash .icon-dark, html[data-app="jockey"] .splash .splash-wordmark,
html[data-app="budget"] .splash .icon-light, html[data-app="budget"] .splash .icon-dark, html[data-app="budget"] .splash .splash-wordmark,
html[data-app="melt"] .splash .icon-light, html[data-app="melt"] .splash .icon-dark, html[data-app="melt"] .splash .splash-wordmark,
html[data-app="content"] .splash .icon-light, html[data-app="content"] .splash .icon-dark, html[data-app="content"] .splash .splash-wordmark,
html[data-app="buraco"] .splash .icon-light, html[data-app="buraco"] .splash .icon-dark, html[data-app="buraco"] .splash .splash-wordmark{display:none !important}
/* The tile: each app's icon set is a full-bleed square (its OS-mask art), so the
   splash rounds it like an app icon (22.4% = the 112/512 corner of the favicons)
   and rings it with the app's own border so a navy/cream tile still reads on its own page. */
.splash .splash-app-ic{
  border-radius:22.4%;object-fit:cover;
  box-shadow:0 0 0 1px var(--border-strong), 0 10px 28px -14px rgba(0,0,0,.45);
}
/* Buraco: the in-app crest — felt tile + ivory spade, inverted in dark (buraco-app.css .bux-crest) */
.splash .splash-crest-buraco{place-items:center;background:#0f2e33;color:#f7f5ef;border-radius:22.4%}
.splash .splash-crest-buraco > svg{width:56%;height:56%;display:block}
html[data-theme="dark"] .splash .splash-crest-buraco{background:#f7f5ef;color:#0f2e33}
.splash .splash-wordmark-app{
  height:30px;align-items:center;flex-shrink:0;
  font:800 26px/1 var(--mk-font-ui,inherit);color:var(--fg-1,#1a1a1a);white-space:nowrap;
}
/* Jockey (member + Staff): the club's tracked caps */
.splash .splash-wordmark-app[data-splash-app="jockey"],
.splash .splash-wordmark-app[data-splash-app="staff"]{
  font-size:22px;letter-spacing:.08em;text-transform:uppercase;
}
/* Buraco: the Salón's serif; Content: Sora; Budget: Plus Jakarta (each via --mk-font-*) */
.splash .splash-wordmark-app[data-splash-app="buraco"]{font:600 32px/1 var(--mk-font-brand);letter-spacing:.005em}
.splash .splash-wordmark-app[data-splash-app="content"],
.splash .splash-wordmark-app[data-splash-app="melt"],
.splash .splash-wordmark-app[data-splash-app="budget"]{font-size:28px;letter-spacing:-.01em}
@media (max-width:600px){
  .splash .splash-wordmark-app{height:24px}
  .splash .splash-wordmark-app[data-splash-app="jockey"],
  .splash .splash-wordmark-app[data-splash-app="staff"]{font-size:17px}
  .splash .splash-wordmark-app[data-splash-app="buraco"]{font-size:26px}
  .splash .splash-wordmark-app[data-splash-app="content"],
  .splash .splash-wordmark-app[data-splash-app="melt"],
  .splash .splash-wordmark-app[data-splash-app="budget"]{font-size:23px}
}
/* Landing nav links */
.nav-sections{display:flex;gap:4px;align-items:center;flex-shrink:0}
.nav-link{display:inline-flex;align-items:center;padding:8px 12px;border-radius:8px;text-decoration:none;
  font:500 13px var(--mk-font-ui);color:var(--fg-2);white-space:nowrap}
.nav-link:hover{color:var(--fg-1);background:var(--bg-2)}
@media (max-width: 820px){ .nav-sections{display:none} }

/* The signed-in inline search bar grows to fill the navbar between the brand
   and the right cluster (no max-width cap on desktop or tablet). The
   .nav-spacer sibling collapses on signed-in via the rule below so all
   leftover space lands here. */
.nav-search{flex:1 1 auto;display:flex;align-items:center;gap:8px;background:var(--bg-input);
  border:1px solid var(--border);border-radius:9px;height:36px;padding:0 12px;min-width:0;cursor:pointer;
  transition:border-color var(--mk-t-fast),color var(--mk-t-fast)}
.nav-search:hover{border-color:var(--accent)}
.nav-search:hover .mi{color:var(--accent)}
.nav-search:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* B67 R21 (R67-089) — the bar's own focus treatment never showed: focus lands on its input, not the bar (keyboard: no
   indicator at all — only the caret). Same outline, while the input is focused. */
.nav-search:has(input:focus-visible){outline:2px solid var(--accent);outline-offset:2px}
.shell.signed-in .nav-spacer{display:none}
.nav-search input{background:none;border:none;outline:none;flex:1;font:inherit;color:var(--fg-1);min-width:0;cursor:inherit}
.nav-search input::placeholder{color:var(--fg-3)}
.nav-search .mi{font-size:18px;color:var(--fg-2);transition:color var(--mk-t-fast)}
.nav-search .kbd{font:600 10px var(--mk-font-mono);color:var(--fg-3);border:1px solid var(--border);border-radius:5px;padding:1px 5px}

.nav-spacer{flex:1}
.nav-right{display:flex;align-items:center;gap:6px;flex-shrink:0}
/* On signed-out desktop, the chip cluster reads as one tight unit hugging
   the sign-in CTA, with a clear gap separating it from the brand side. */
.nav-right.auth-out{gap:4px}

.icon-btn{width:36px;height:36px;border-radius:9px;border:1px solid var(--border);background:var(--bg-input);
  color:var(--fg-2);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;position:relative}
.icon-btn .mi{font-size:18px}
.icon-btn .dot{position:absolute;top:6px;right:6px;width:7px;height:7px;background:var(--accent);border:2px solid var(--bg-1);border-radius:999px}
html[dir="rtl"] .icon-btn .dot{right:auto;left:6px}
.icon-btn:hover{border-color:var(--accent);color:var(--accent)}

/* Default: search-icon-button is hidden on desktop and tablet — the inline
   search bar lives in the navbar at those widths. Mobile-V/H rules opt it
   back in (`!important` overrides the .nav-pill default `display:inline-flex`). */
.nav-search-btn{display:none !important}

/* B63-001 — dashboard-type routes have nothing to search; JS
   (sections.js syncCreateControls) toggles `.route-no-search` on #shell.
   Higher specificity than the plain `.shell.signed-in .nav-search(-btn)`
   rules below (which use !important for the opposite purpose at mobile
   widths) so this wins at every breakpoint. */
.shell.signed-in.route-no-search .nav-search{display:none !important}
.shell.signed-in.route-no-search .nav-search-btn{display:none !important}
/* B64-01 (Pablo: "When there is no search bar, the other nav bar elements
   (except the app logo and wordmark) should be aligned to the right") — the
   search bar was the flexible piece that pushed the cluster to the trailing
   edge; with it gone, the cluster itself takes the slack. Logical property,
   so RTL mirrors it to the left edge. */
.shell.signed-in.route-no-search .nav-right.auth-in{margin-inline-start:auto}

/* Signed-in nav-pill — mirrors the .cs-pill button so all navbar controls
   read as the same family. Desktop: row (icon + label); tablet: col (icon
   above label). Mobile-v: icon only. */
.nav-pill{
  display:inline-flex;align-items:center;gap:6px;
  height:36px;padding:0 12px;border-radius:9px;
  background:var(--bg-input);border:1px solid color-mix(in srgb, var(--fg-3) 26%, var(--border));
  color:var(--fg-1);font:600 12px var(--mk-font-ui);
  cursor:pointer;white-space:nowrap;position:relative;
  transition:border-color var(--mk-t-fast),color var(--mk-t-fast),background var(--mk-t-fast);
}
.nav-pill .mi{font-size:16px;color:var(--fg-2);transition:color var(--mk-t-fast)}
.nav-pill:hover,.nav-pill.active,.nav-pill[aria-expanded="true"]{border-color:var(--accent);color:var(--accent)}
.nav-pill:hover .mi,.nav-pill.active .mi{color:var(--accent)}
.nav-pill .np-dot{
  position:absolute;top:6px;inset-inline-end:6px;
  width:7px;height:7px;background:var(--accent);
  border:2px solid var(--bg-1);border-radius:999px;
}
/* Numeric badge on the pill — anchored to the pill's top-end corner so
   it overlaps the border the same way V7's filter-badge did. Sits above
   any icon/label content (z-index:2) and hides when [hidden] is set so
   we never paint a "0". */
.nav-pill .np-count{
  position:absolute;top:-5px;inset-inline-end:-5px;
  min-width:16px;height:16px;border-radius:999px;
  background:var(--accent);color:var(--accent-fg);
  font:800 9px var(--mk-font-mono);letter-spacing:0;
  display:inline-flex;align-items:center;justify-content:center;
  padding:0 4px;line-height:1;pointer-events:none;z-index:2;
}
.nav-pill .np-count[hidden]{display:none}

.avatar{width:36px;height:36px;border-radius:999px;background:linear-gradient(135deg,var(--accent),var(--mk-navy-700));
  display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:12px;
  cursor:pointer;border:1px solid var(--border);flex-shrink:0;
  background-size:cover;background-position:center center;background-repeat:no-repeat;
  overflow:hidden}
.avatar.has-photo{background-color:transparent;border-color:var(--border-strong)}

/* ── Profile box (V7 pattern) ───────────────────────────────────────
   Rounded pill matching the rest of the navbar's 36-px element family.
   On desktop, only the name is shown (so it lines up vertically with the
   other pills); the popover carries the full name + email pair. Email
   inline is suppressed by default and only re-shown when explicitly
   asked (e.g. wider-than-default desktop layouts in the future). */
.nav-profile{
  /* Box geometry mirrors `.nav-pill` (36 px tall on desktop). The
     tablet / mobile media queries below override with their own
     height so the profile keeps matching whichever pill family is
     active at each viewport. Box-sizing keeps the explicit height
     authoritative, so the avatar / two-line text inside can't push
     the box past the surrounding cluster. */
  position:relative;
  display:inline-flex;align-items:center;gap:8px;
  box-sizing:border-box;
  height:36px;
  padding:0 12px 0 4px;
  /* B63-128 (Pablo: "the profile button has a lighter border color than the
     other buttons") — `.nav-pill`'s border mixes in 26% `--fg-3` for more
     definition; this used the plain, lighter `--border` token instead, so the
     one navbar control NOT built from `.nav-pill` read as a step fainter. */
  border:1px solid color-mix(in srgb, var(--fg-3) 26%, var(--border));border-radius:9px;
  background:var(--bg-input);
  cursor:pointer;flex-shrink:0;
  transition:background var(--mk-t-fast),border-color var(--mk-t-fast),color var(--mk-t-fast);
}
html[dir="rtl"] .nav-profile{padding:0 4px 0 12px}
.nav-profile:hover{
  background:color-mix(in srgb, var(--accent) 10%, transparent);
  border-color:var(--accent);
  color:var(--accent);
}
.nav-profile:hover .avatar{border-color:var(--accent)}
.nav-profile .avatar{cursor:inherit;width:28px;height:28px}
.nav-profile .profile-info{
  display:flex;flex-direction:column;gap:0;min-width:0;
  line-height:1.15;justify-content:center;
}
.nav-profile .profile-name{
  font:600 13px var(--mk-font-ui);color:var(--fg-1);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px;
}
.nav-profile:hover .profile-name{color:var(--accent)}
/* Email is reserved for the popover — hidden inline on every breakpoint
   so the profile pill matches the 36-px height of the surrounding nav
   controls. */
.nav-profile .profile-email{display:none}

.btn-signin{display:inline-flex;align-items:center;gap:7px;height:36px;padding:0 14px;border-radius:9px;
  background:var(--accent);color:var(--accent-fg);border:1px solid var(--accent);font:600 13px var(--mk-font-ui);cursor:pointer;white-space:nowrap}
.btn-signin .mi{font-size:17px}
.btn-signin:hover{filter:brightness(1.05)}

/* Profile popover */
.pop{position:absolute;top:calc(100% + 8px);inset-inline-end:0;width:264px;z-index:210;background:var(--bg-1);
  border:1px solid var(--border);border-radius:12px;box-shadow:var(--shadow-lg);padding:10px;z-index:200;display:none}
.pop.open{display:block}
.pop .pu{display:flex;align-items:center;gap:10px;padding:8px;border-bottom:1px solid var(--border);margin-bottom:6px}
.pop .pu .av{width:36px;height:36px;border-radius:999px;background:linear-gradient(135deg,var(--accent),var(--mk-navy-700));
  color:#fff;font:700 12px var(--mk-font-ui);display:flex;align-items:center;justify-content:center;
  background-size:cover;background-position:center center;background-repeat:no-repeat;
  flex-shrink:0;overflow:hidden}
.pop .pu .av.has-photo{background-color:transparent}
.pop .pu .pu-info{min-width:0;flex:1}
.pop .pu .n{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pop .pu .e{font-size:11px;color:var(--fg-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* B62 wave 4 (W-07) — the trip-invitation alert. It sits directly under the
   identity block because it IS about you, and it carries both answers on the
   row: an alert you have to navigate away from to answer is a notification.
   Hidden entirely when empty (the [hidden] attribute), so the popover never
   grows a section with nothing in it. */
.pu-alerts{display:flex;flex-direction:column;gap:4px;padding:4px 2px 8px;margin-bottom:6px;border-bottom:1px solid var(--border)}
.pu-alerts[hidden]{display:none}
.pu-al-h{display:flex;align-items:center;gap:6px;font:600 10.5px/1 var(--mk-font-ui);letter-spacing:.06em;text-transform:uppercase;color:var(--fg-2);padding:2px 8px 4px}
.pu-al-h .mi{font-size:14px;color:var(--accent)}
.pu-al{display:flex;align-items:center;gap:6px;padding:4px 6px;border-radius:9px;background:color-mix(in srgb,var(--accent) 7%,transparent)}
.pu-al-main{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:1px;background:none;border:0;padding:4px 2px;cursor:pointer;text-align:start}
.pu-al-t{font:600 12px var(--mk-font-ui);color:var(--fg-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.pu-al-s{font:500 10.5px var(--mk-font-ui);color:var(--fg-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.pu-al-acts{display:flex;gap:4px;flex-shrink:0}
.pu-al-y,.pu-al-n{width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:8px;border:1px solid var(--border);background:var(--bg-1);color:var(--fg-1);cursor:pointer}
.pu-al-y{background:var(--accent);border-color:var(--accent);color:#fff}
.pu-al-y:hover{filter:brightness(1.06)}
.pu-al-n:hover{background:var(--bg-2)}
.pu-al-y .mi,.pu-al-n .mi{font-size:17px}
/* B63 W7 (026) — "the alert indicator on the profile box in the shell
   should be a bit more prominent and maybe show the number of elements
   pending actions." A real count badge replaces the old plain dot. */
.nav-profile{position:relative}
.nav-profile .np-count{position:absolute;top:-2px;inset-inline-end:-2px;min-width:17px;height:17px;border-radius:999px;
  background:var(--mk-warning);color:#1a1200;font:800 9.5px var(--mk-font-mono);letter-spacing:0;
  display:inline-flex;align-items:center;justify-content:center;padding:0 4px;line-height:1;
  pointer-events:none;z-index:2;border:2px solid var(--bg-1)}
.nav-profile .np-count[hidden]{display:none}

/* B63 W7 (027) — "I think this profile deserves it's own panel … there's a
   lot of controls." The dropdown grows into a proper, roomier panel: wider,
   its own scroll region, and a heavier shadow — short of a full migration
   to the shared MoccaPanel slide-in (see CHANGELOG for why). */
.pop.pop-panel{width:340px;max-height:calc(100vh - 96px);overflow:auto;box-shadow:var(--shadow-lg),0 0 0 1px color-mix(in srgb,var(--border) 60%,transparent)}
/* Currency/residency rows (027) can carry a long fallback string ("Add one
   under Contacts › Addresses") — truncate rather than force the panel to
   overflow horizontally, same as every other .cs-val in a tight row. */
.pop .row > .cs{min-width:0;max-width:60%}
.pop .row > .cs .cs-val{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:100%;vertical-align:bottom}

/* Requests — replaces the old .pu-alerts (trip invitations only) with a
   generalized list: any pending item with an approve/reject, ordered
   oldest-first, each carrying an urgency dot and how long it's been
   sitting. Pablo (026): "show … the time that it's been sitting there,
   maybe with a color indicator depending on how long has passed. And maybe
   with an urgency indicator." */
.pu-req{display:flex;flex-direction:column;gap:4px;padding:4px 2px 8px;margin-bottom:6px;border-bottom:1px solid var(--border)}
.pu-req[hidden]{display:none}
.pu-req-h{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:2px 8px 4px}
.pu-req-h-l{display:flex;align-items:center;gap:6px;font:600 10.5px/1 var(--mk-font-ui);letter-spacing:.06em;text-transform:uppercase;color:var(--fg-2)}
.pu-req-h-l .mi{font-size:14px;color:var(--accent)}
.pu-req-count{min-width:15px;height:15px;border-radius:999px;background:var(--accent);color:var(--accent-fg);
  font:800 9px var(--mk-font-mono);display:inline-flex;align-items:center;justify-content:center;padding:0 4px}
.pu-req-viewall{background:none;border:0;color:var(--accent);font:600 10.5px var(--mk-font-ui);cursor:pointer;padding:2px 4px}
.pu-req-row{display:flex;align-items:center;gap:6px;padding:4px 6px;border-radius:9px;background:color-mix(in srgb,var(--accent) 7%,transparent)}
.pu-req-urg{width:8px;height:8px;border-radius:999px;flex-shrink:0;margin-inline-start:2px}
.pu-req-urg.urg-4{background:var(--mk-danger,#e5484d)}
.pu-req-urg.urg-3{background:var(--mk-warning,#e5a000)}
.pu-req-urg.urg-2{background:var(--accent)}
.pu-req-urg.urg-1{background:var(--fg-3)}
.pu-req-main{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:1px;background:none;border:0;padding:4px 2px;cursor:pointer;text-align:start}
.pu-req-t{font:600 12px var(--mk-font-ui);color:var(--fg-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.pu-req-s{font:500 10.5px var(--mk-font-ui);color:var(--fg-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;display:flex;align-items:center;gap:4px}
.pu-req-age{font:700 9.5px var(--mk-font-mono)}
.pu-req-age.urg-4{color:var(--mk-danger,#e5484d)}
.pu-req-age.urg-3{color:var(--mk-warning,#e5a000)}
.pu-req-acts{display:flex;gap:4px;flex-shrink:0}
.pu-req-y,.pu-req-n{width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:8px;border:1px solid var(--border);background:var(--bg-1);color:var(--fg-1);cursor:pointer}
.pu-req-y{background:var(--accent);border-color:var(--accent);color:#fff}
.pu-req-y:hover{filter:brightness(1.06)}
.pu-req-n:hover{background:var(--bg-2)}
.pu-req-y .mi,.pu-req-n .mi{font-size:17px}
/* "View all" slide — a plain list, no truncation. */
.pu-req-all-list{display:flex;flex-direction:column;gap:6px;padding:8px}
.pu-req-all-empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:32px 16px;color:var(--fg-3);text-align:center}
.pu-req-all-empty .mi{font-size:28px}
.pop .row{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border-radius:8px}
.pop .row:hover{background:var(--bg-2)}
.pop .row > .k{display:flex;align-items:center;gap:10px;color:var(--fg-2);font-size:12px;font-weight:500}
/* B50-27 — the label keeps its intrinsic size; the profile switcher takes the
   remaining width and ellipses inside it instead of pushing the row wider. */
.pop .row > .k{flex:0 0 auto}
.pop .row > .cs-profile{flex:1 1 0;min-width:0}
.pop .row > .k .mi{font-size:18px}
.pop .divider{height:1px;background:var(--border);margin:6px 2px}
.pop .link{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;color:var(--fg-1);font-size:12px;cursor:pointer;text-decoration:none}
.pop .link:hover{background:var(--bg-2)}
.pop .link.danger{color:var(--mk-danger)}
.pop .link .mi{font-size:18px;color:var(--fg-2)}
.pop .link.danger .mi{color:var(--mk-danger)}

/* Theme chips */
.theme-chips{display:flex;gap:2px;background:var(--bg-input);border-radius:7px;padding:2px}
.theme-chips button{all:unset;width:28px;height:26px;display:inline-flex;align-items:center;justify-content:center;
  border-radius:5px;cursor:pointer;color:var(--fg-2)}
.theme-chips button .mi{font-size:16px}
.theme-chips button.on{background:var(--bg-1);color:var(--fg-1);box-shadow:var(--shadow-sm)}

/* Custom select */
.cs{position:relative;display:inline-block;user-select:none}
/* P4-MAP01 — the base `.cs{display:inline-block}` is an AUTHOR rule, so it beats
   the UA `[hidden]{display:none}` (author origin wins over UA regardless of
   specificity). Any `.cs` toggled via the `hidden` attribute (e.g. the map
   header layer selector, shown only on the Zones pane) therefore stayed visible.
   Restore the attribute's meaning for the whole family — a `.cs` is never meant
   to be visible while carrying `hidden`, so this only ever hides what should hide. */
.cs[hidden]{display:none}
/* VC2-T2 (Pablo m0111) — on touch (coarse pointer) lift the small icon controls
   to a ≥44px hit target. Scope = account avatar · gantt orientation toggle ·
   detail-panel close · Pay-panel copy row. The compact landscape strip + top-bar
   pills are intentionally left as-is (Pablo m0158); their avatars carry
   more-specific sizing inside their own media queries which keeps winning there,
   so this only lifts the default (roomy) contexts. */
@media (pointer:coarse){
  /* B62 § S-03 (the phone pass): a sidebar row was 39px — under the 44px floor
     that § M-43 makes non-negotiable, on EVERY route, and the sidebar is the
     only way to reach a section on a phone. Raised here (one place) rather
     than per route. */
  .sb-item,.sb-child{min-height:44px}
  .nav-profile .avatar{width:44px;height:44px;font-size:13px}
  .gantt-orient-seg button{min-width:44px;min-height:44px}
  .panel-close{width:44px;height:44px}
  .wl-pid-copy{min-height:44px}
}
.cs-btn{display:flex;align-items:center;gap:6px;padding:5px 10px;background:var(--bg-input);border:1px solid var(--border);
  border-radius:8px;font:600 11px var(--mk-font-ui);color:var(--fg-1);cursor:pointer;white-space:nowrap}
.cs-btn .mi{font-size:14px;color:var(--fg-2)}
.cs-btn .cs-arrow{transition:transform 200ms var(--mk-ease)}
.cs.open > .cs-btn{border-color:var(--accent)}
.cs.open > .cs-btn .cs-arrow{transform:rotate(180deg)}
.cs-menu{position:absolute;top:calc(100% + 4px);inset-inline-end:0;min-width:100%;background:var(--bg-1);
  border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-lg);padding:4px;z-index:500;
  opacity:0;pointer-events:none;transform:translateY(-4px);transition:opacity 150ms var(--mk-ease),transform 150ms var(--mk-ease),visibility 0s linear 150ms;
  /* B67 R21 (R67-082) — a closed menu only faded: Tab walked into its search box / button rows unseen (every app's dropdowns
     with search, the profile panel's lists, Staff's forms). Hidden once the fade ends, shown at once on open. The only other
     way a menu shows is Tickets' quick menu (.tk-qm.open, hung off <body>). */
  visibility:hidden}
.cs.open > .cs-menu, .cs-menu.tk-qm.open{opacity:1;pointer-events:auto;transform:none;visibility:visible;transition-delay:0s}
/* placeCsMenu() (shell.js) caps max-height and flips the menu upward via
   inline styles when there isn't room below, so the box never bleeds past
   its panel frame. Pablo 2026-06-01: "never bleed outside the frame". */
/* Searchable dropdown — a sticky filter at the top of long menus (auto for
   >7 items or [data-cs-search]); an admin-only "Add …" row when the search
   is empty + [data-cs-add]; a quiet no-results line otherwise. #7 / #10. */
.cs-search{
  position:sticky;top:-4px;z-index:2;
  display:flex;align-items:center;gap:6px;
  padding:4px;margin:-4px -4px 4px;
  background:var(--bg-1);border-bottom:1px solid var(--divider);
}
.cs-search .mi{font-size:16px;color:var(--fg-3);flex:none}
.cs-search input{
  flex:1;min-width:0;border:1px solid var(--border);border-radius:6px;
  background:var(--bg-input);color:var(--fg-1);
  font:500 12px var(--mk-font-ui);padding:6px 8px;outline:none;
}
.cs-search input:focus{border-color:var(--accent);box-shadow:0 0 0 2px var(--ring)}
.cs-add-new,.cs-no-results{
  display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:6px;
  font:500 12px var(--mk-font-ui);
}
.cs-add-new{color:var(--accent);cursor:pointer}
.cs-add-new .mi{font-size:16px}
.cs-add-new strong{font-weight:700}
.cs-add-new:hover{background:color-mix(in srgb, var(--accent) 10%, transparent)}
.cs-no-results{color:var(--fg-3);font-style:italic}
/* Create-entity slide panel (admin) — narrow .filter-panel with a name
   form. Pablo 2026-06-01 #7. */
.ce-panel{width:min(468px, 100vw);min-width:min(380px, 100vw)}
.ce-field{display:flex;flex-direction:column;gap:6px}
.ce-field label{font:600 11px var(--mk-font-ui);text-transform:none;letter-spacing:.04em;color:var(--fg-2)}
.ce-field input{padding:9px 11px;border:1px solid var(--border);border-radius:8px;background:var(--bg-1);color:var(--fg-1);font:500 13px var(--mk-font-ui);outline:none}
.ce-field input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--ring)}
.ce-hint{margin:4px 0 0;font:500 11px var(--mk-font-ui);color:var(--fg-3);line-height:1.5}
/* B54-02 — a shape/zone field's trigger. Same box as the date trigger; it just
   says what it opens, and shows the drawn shape's summary once there is one. */
.ce-shape-btn{justify-content:flex-start;gap:8px;text-align:start}
.ce-shape-btn .mi{font-size:17px;color:var(--fg-3)}
.ce-shape-btn.has-val{border-color:var(--accent)}
.ce-shape-btn.has-val .mi{color:var(--accent)}
.ce-shape-btn.has-val .ce-dt-l{color:var(--fg-1);font-weight:600}
/* B39 C34 — typed entity-record form controls */
.ce-form{display:flex;flex-direction:column;gap:15px}
.ce-field .ce-req{color:var(--mk-danger,#d64545);font-weight:700}
.ce-field.ce-invalid > label{color:var(--mk-danger,#d64545)}
.ce-field.ce-invalid input,.ce-field.ce-invalid .cs-btn{border-color:var(--mk-danger,#d64545)}
.ce-fhint{margin:1px 0 0;font:500 10.5px var(--mk-font-ui);color:var(--fg-3)}
.ce-field .cs.ce-cs{width:100%}
.ce-field .cs.ce-cs .cs-btn{width:100%;justify-content:space-between}
.ce-cur{display:flex;align-items:center;gap:0;position:relative}
.ce-cur-sym{position:absolute;inset-inline-start:11px;font:600 13px var(--mk-font-ui);color:var(--fg-3);pointer-events:none}
.ce-cur input{flex:1;padding-inline-start:24px}
.ce-dur{display:flex;gap:8px}.ce-dur input{flex:1}.ce-dur .cs.ce-cs{flex:0 0 110px;width:110px}
.ce-dt-btn{display:flex;align-items:center;gap:8px;width:100%;text-align:start;cursor:pointer}.ce-dt-btn .mi{font-size:16px;color:var(--fg-3)}.ce-dt-btn:hover{border-color:var(--accent)}
.ce-tog{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;height:30px;padding:0 11px 0 5px;border-radius:999px;border:1px solid var(--border);background:var(--bg-1);color:var(--fg-2);font:600 12px var(--mk-font-ui);cursor:pointer}
.ce-tog .ce-tog-dot{width:18px;height:18px;border-radius:50%;background:var(--fg-3);transition:background .15s,transform .15s}
.ce-tog[aria-pressed="true"]{border-color:color-mix(in srgb,var(--accent) 40%,transparent);background:var(--accent-soft,color-mix(in srgb,var(--accent) 12%,transparent));color:var(--accent-text,var(--accent))}
.ce-tog[aria-pressed="true"] .ce-tog-dot{background:var(--accent);transform:translateX(2px)}
.ce-tog.sm{height:26px}
.ce-chips{display:flex;flex-wrap:wrap;gap:6px;min-height:22px}
.ce-chip-empty{font:500 12px var(--mk-font-ui);color:var(--fg-3)}
.ce-chip{display:inline-flex;align-items:center;gap:3px;padding:3px 4px 3px 10px;border-radius:999px;background:var(--bg-2);border:1px solid var(--border);font:600 12px var(--mk-font-ui);color:var(--fg-1)}
.ce-chip-x{border:0;background:transparent;color:var(--fg-3);cursor:pointer;display:grid;place-items:center;padding:0;border-radius:50%}
.ce-chip-x .mi{font-size:14px}.ce-chip-x:hover{color:var(--mk-danger,#d64545)}
.ce-chip-add{display:flex;gap:6px;margin-top:6px}.ce-chip-add input{flex:1}
.ce-sub{display:flex;flex-direction:column;gap:8px}
.ce-sub-row{display:flex;gap:8px;align-items:flex-end;padding:9px 10px;border:1px solid var(--border);border-radius:10px;background:var(--bg-2)}
.ce-sub-f{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
.ce-sub-f > span{font:600 10px var(--mk-font-ui);color:var(--fg-3)}
.ce-sub-x,.ce-kv-x{flex:0 0 auto;width:30px;height:30px;border-radius:8px;border:1px solid var(--border);background:var(--bg-1);color:var(--fg-3);cursor:pointer;display:grid;place-items:center}
.ce-sub-x:hover,.ce-kv-x:hover{color:var(--mk-danger,#d64545);border-color:var(--mk-danger,#d64545)}
.ce-sub-add,.ce-kv-add{align-self:flex-start;margin-top:6px;display:inline-flex;align-items:center;gap:5px;padding:6px 12px;border-radius:8px;border:1px dashed var(--border);background:transparent;color:var(--accent-text,var(--accent));font:600 12px var(--mk-font-ui);cursor:pointer}
.ce-sub-add:hover,.ce-kv-add:hover{border-color:var(--accent);background:var(--accent-soft,color-mix(in srgb,var(--accent) 8%,transparent))}
.ce-sub-add .mi,.ce-kv-add .mi{font-size:16px}
.ce-kv{display:flex;flex-direction:column;gap:7px}
.ce-kv-row{display:flex;gap:7px;align-items:center}.ce-kv-row input{flex:1;min-width:0}
.cs-item{padding:7px 12px;border-radius:6px;font:500 12px var(--mk-font-ui);color:var(--fg-1);cursor:pointer;white-space:nowrap;
  display:flex;align-items:center;gap:8px}
.cs-item .mi{font-size:16px;color:var(--fg-2);transition:color 150ms var(--mk-ease)}
.cs-item:hover{background:var(--bg-2)}
/* B67 R30 (R67-107) — keyboard: the highlighted item reads like the hovered one; the trigger shows where focus is */
.cs-item.kb-hl{background:var(--bg-2);box-shadow:inset 2px 0 0 var(--accent)}
[dir="rtl"] .cs-item.kb-hl{box-shadow:inset -2px 0 0 var(--accent)}
.cs > .cs-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.cs-item.on{color:var(--accent);font-weight:600;background:color-mix(in srgb, var(--accent) 8%, transparent)}
.cs-item.on .mi{color:var(--accent)}
.cs.cs-pill > .cs-btn{height:36px;border-radius:9px;padding:0 10px;font:600 12px var(--mk-font-ui)}
.cs.cs-pill > .cs-btn .mi{font-size:16px}

/* Sidebar */
.sb{background:var(--bg-1);color:var(--fg-1);display:flex;flex-direction:column;overflow:hidden;
  grid-row:2/4;grid-column:1/2;border-inline-end:1px solid var(--border)}

/* Sidebar header — brand + close button. Hidden on desktop (the navbar
   brand is visible there); shown on tablet-wide overlay and on mobile-V/H. */
.sb-header{
  display:none;
  align-items:center;justify-content:space-between;
  padding:14px 14px 12px;flex-shrink:0;
  border-bottom:1px solid var(--border);
  gap:10px;
}
.sb-brand{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--fg-1);min-width:0}
.sb-brand .icon{height:32px;width:32px;display:block;flex-shrink:0}
.sb-brand .wordmark{height:18px;width:auto;display:block}
html[data-theme="light"] .sb-brand .icon-dark{display:none}
html[data-theme="dark"]  .sb-brand .icon-light{display:none}
/* wordmark-{light,dark} display rules removed — inline-SVG wordmark
   flips color via --wordmark-color instead of swapping elements. */
html[lang="en"] .sb-brand .wordmark-ar,
html[lang="es"] .sb-brand .wordmark-ar{display:none}
html[lang="ar"] .sb-brand .wordmark-en{display:none}
.sb-close{
  display:inline-flex;align-items:center;justify-content:center;
  width:32px;height:32px;border-radius:8px;flex-shrink:0;
  background:var(--bg-input);border:1px solid var(--border);color:var(--fg-2);
  cursor:pointer;
  transition:border-color var(--mk-t-fast),color var(--mk-t-fast);
}
.sb-close:hover{border-color:var(--accent);color:var(--accent)}
.sb-close .mi{font-size:18px}

.sb-inner{flex:1;padding:10px;overflow-y:auto;scrollbar-width:none}
.sb-inner::-webkit-scrollbar{display:none}
.sb-item{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:9px;color:var(--fg-2);
  font-size:13px;font-weight:500;margin-bottom:2px;cursor:pointer;border:1px solid transparent;
  transition:all var(--mk-t-fast);white-space:nowrap}
.sb-item .mi{font-size:19px;flex-shrink:0}
.sb-item:hover{background:var(--bg-2);color:var(--fg-1);border-color:color-mix(in srgb, var(--accent) 38%, var(--border))}
.sb-item.active{background:color-mix(in srgb, var(--accent) 12%, transparent);color:var(--accent-text);
  border-color:color-mix(in srgb, var(--accent) 28%, transparent)}
.sb-item.active .mi{color:var(--accent-text)}
.sb-item .lbl{flex:1;overflow:hidden;text-overflow:ellipsis}
.sb-item .badge{background:var(--accent);color:var(--accent-fg);font-size:10px;font-weight:700;padding:2px 7px;border-radius:999px}
.sb-item .sb-arrow{font-size:18px;transition:transform var(--mk-t-base);color:inherit;opacity:.7}
.sb-item.has-children.open .sb-arrow{transform:rotate(180deg)}

/* Children — collapse via max-height (smooth transition without measuring) */
.sb-children{
  max-height:0;overflow:hidden;
  transition:max-height var(--mk-t-base) var(--mk-ease);
  padding-inline-start:20px;
  margin-bottom:2px;
}
.sb-children.open{max-height:240px}
.sb-child{
  display:flex;align-items:center;gap:10px;position:relative;
  padding:7px 10px;border-radius:8px;
  color:var(--fg-2);font:500 12px var(--mk-font-ui);
  cursor:pointer;white-space:nowrap;
  transition:background var(--mk-t-fast),color var(--mk-t-fast);
}
.sb-child .mi{font-size:15px;flex-shrink:0;color:color-mix(in srgb, var(--accent) 55%, var(--fg-2))}
.sb-child .lbl{flex:1;overflow:hidden;text-overflow:ellipsis}
.sb-child:hover{background:var(--bg-2);color:var(--fg-1)}
.sb-child:hover .mi{color:var(--accent)}
/* Current screen path — clearly the strongest: full accent, bold,
   stronger fill + an accent indicator bar. (Pablo 2026-06-19)
   B62 § S-03 (the phone pass on Drive): the INK is `--accent-text`, not
   `--accent`. `--accent` is the FILL token (the bar below still uses it); as
   text on its own 12–15% tint it measured ~2:1 in light theme — the same
   accent-on-accent-tint defect B62 has now paid for four times, and it sat on
   the row that tells you where you are. */
.sb-child.active{color:var(--accent-text);font-weight:600;background:color-mix(in srgb, var(--accent) 15%, transparent)}
.sb-child.active .mi{color:var(--accent-text)}
.sb-child.active::before{
  content:"";position:absolute;inset-inline-start:0;top:50%;transform:translateY(-50%);
  width:3px;height:16px;border-radius:0 3px 3px 0;background:var(--accent);
}
html[dir="rtl"] .sb-child.active::before{border-radius:3px 0 0 3px}
/* The width band matters as much as the pointer type (B62 W-30's lesson: a
   390px browser window is where the phone reading actually happens, and
   `pointer:coarse` does not fire there) — and so does MOBILE LANDSCAPE, the
   short-viewport band this app already treats as mobile (it floats the FAB
   there). A sidebar row was 39px and a child 30px in that band. */
@media (max-width:600px),(max-height:599px) and (orientation:landscape){
  .sb-item,.sb-child{min-height:44px}
}

/* Sidebar bottom — only shown on tablet to expose the expand toggle.
   Two chevrons (one for each state); CSS swaps which is visible. */
.sb-bottom{display:none;padding:10px;border-top:1px solid var(--border);flex-shrink:0}
.sb-expand-btn{
  display:inline-flex;align-items:center;justify-content:center;
  width:100%;height:44px;border-radius:10px;
  background:var(--bg-input);border:1px solid var(--border);
  color:var(--fg-2);cursor:pointer;
  transition:background var(--mk-t-fast),border-color var(--mk-t-fast),color var(--mk-t-fast);
}
.sb-expand-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--bg-2)}
.sb-expand-btn .mi{font-size:26px}
/* Default (collapsed): show the right-pointing chevron. */
.sb-expand-btn .sb-expand-expanded{display:none}
/* When the shell is in expanded mode, swap to the left-pointing chevron.
   In RTL, mirror so the chevron always points "out" relative to the rail. */
.shell.sb-expanded .sb-expand-btn .sb-expand-collapsed{display:none}
.shell.sb-expanded .sb-expand-btn .sb-expand-expanded{display:inline-flex}
[dir="rtl"] .sb-expand-btn .mi{transform:scaleX(-1)}

.scrim{position:fixed;inset:0;background:var(--scrim);z-index:55;opacity:0;pointer-events:none;transition:opacity 200ms}
/* VC-R2 — a soft blur behind the open mobile drawer so content is clearly de-emphasized, not just dimmed. */
.sb-open .scrim{opacity:1;pointer-events:auto;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}

/* B63-010 (Pablo: "The navbar buttons and the create floating button in mobile
   should be behind the opaque layer when the sidebar is opened... based on the
   layering instruction I gave you before") — the floating mobile navbar
   (z:100) and the create FAB (z:880) both sat ABOVE the scrim (z:55), so the
   modal sidebar drawer left them fully lit and tappable through the dimmed
   layer. The scrim marks "nothing above me is interactive" while a panel-
   weight surface (the drawer) is open, so both drop below it for as long as
   the drawer is. The FAB/page-fab are siblings of #shell (body children,
   see sections.css § S-16), hence `body:has(...)`. */
.shell.sb-open .nav,
.shell.sb-expanded .nav{z-index:50}
body:has(.shell.sb-open) .route-fab,
body:has(.shell.sb-open) .page-fab,
body:has(.shell.sb-expanded) .route-fab,
body:has(.shell.sb-expanded) .page-fab{z-index:50}

/* Body (signed-in) — main content card lifted from the chrome. Use a 2-px
   border on the top + start edges and a wider radius so the rounded corner
   reads as a deliberate inset rather than a 1-px AA fuzz.
   The borders only apply to the signed-in body; the signed-out landing
   is a full-bleed marketing surface with no inset frame. */
.body{
  grid-column:2/3;grid-row:2/3;
  display:flex;flex-direction:column;overflow:hidden;
  background:var(--bg-2);
}
.shell.signed-in .body{
  border-start-start-radius:16px;
  border-top:2px solid var(--border);
  border-inline-start:2px solid var(--border);
}
.tabs{
  display:flex;align-items:stretch;gap:0;padding:0 16px;
  background:transparent;
  border-bottom:1px solid var(--border);
  border-start-start-radius:16px;
  height:52px;overflow-x:auto;scrollbar-width:none;flex-shrink:0;
}
.tabs::-webkit-scrollbar{display:none}
/* V7 desktop/tablet pattern: tabs spread evenly (flex:1), no fill on the
   active tab — instead a 2-px accent underline runs along the bottom edge.
   Keeps the chrome quiet so the page header is the load-bearing heading. */
.tab{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  flex:1;min-width:90px;padding:0 18px;
  font:500 13px var(--mk-font-ui);
  color:var(--fg-2);cursor:pointer;white-space:nowrap;
  border-radius:0;
  transition:background var(--mk-t-fast),color var(--mk-t-fast),box-shadow var(--mk-t-fast);
}
.tab .mi{font-size:17px}
.tab:hover{color:var(--fg-1);background:color-mix(in srgb, var(--fg-1) 4%, transparent)}
.tab.active{color:var(--accent);background:transparent;font-weight:600;
  box-shadow:inset 0 -2px 0 var(--accent);
}
.tab.active .mi{color:var(--accent)}
.page{flex:1;min-height:0;overflow-y:auto;padding:0;display:flex;flex-direction:column;gap:0}
/* Page header — pinned at the top of the body scroll container so it
   stays put while content scrolls underneath. Fully transparent
   background + backdrop-filter: at rest the body bg shows through
   unfiltered (visually identical to the body, no separate band of
   color); when content scrolls behind the header the blur softens
   it into a "liquid glass" surface that announces the scroll.
   Mobile-V/H fall back to non-sticky (header scrolls with content;
   the FAB carries the actions). Pablo iteration 2026-05-30b. */
.page-h{
  position:sticky;top:0;z-index:20;
  display:flex;justify-content:space-between;align-items:flex-end;
  gap:12px;flex-wrap:wrap;
  padding:18px 24px 14px;
  /* The header lives inside `.tab-view`, which carries 24px inline padding
     (from `.page > :not(.page-h)`). Pull the header back out with a
     matching negative inline margin so the liquid-glass band spans the
     FULL width of the main content; the 24px inner padding keeps the
     title/actions aligned with the body columns. (Pablo 2026-06-01 —
     "the blur should extend the whole width of the main content".) */
  margin-block:0;
  margin-inline:-24px;
  background:transparent;
  /* NO backdrop-filter on the element itself: an element-level blur is a
     hard rectangle with a SHARP bottom edge (the cut Pablo flagged), and
     it would also blur the title text. The blur now lives on the masked
     ::before pseudo below, exactly like the mobile-portrait floating nav
     (`.nav::before/::after` + gradient mask), so it dissolves gradually
     under scroll instead of cutting off. Pablo 2026-06-02 W1 #3. */
}
/* ── B60 — ONE glass, two surfaces (Pablo: the in-page tab strip "looks a bit
   different than the header (when it really shouldn't) and the blur is applied to a
   whole area it looks strange. We don't need to do something so complicated. Just
   make sure that the header and the tabs have the same style and behavior when
   content passes behind it").

   Before this, the header carried TWO stacked pseudo layers (a 26px overhang plus a
   64px trailing band centred on its bottom edge) and the tab strip mirrored them
   only once scrolled — so at rest the two surfaces did not match, and under scroll
   the blurred region reached ~52px past the strip. Both surfaces now paint the SAME
   single band: their own box, one wash, one blur, dissolving over the last 10px so
   the bottom edge is not a hard line. Nothing is gated on scroll — a backdrop blur
   with nothing behind it is invisible, so the state that used to be switched is not
   a state at all. The tab strip's own (superseded) copies of these rules used to
   live in sections.css and are gone; keep the two selectors together HERE so they
   can never drift apart again. */
.page-h::before,
.page > .sec-tabs.sec-tabs-inpage::before,
.page > [data-dr-switch].dr-switch-pinned::before{
  content:'';
  position:absolute;
  inset:0 0 -10px 0;
  pointer-events:none;
  z-index:-1;
  background:color-mix(in srgb,var(--bg-2) 60%,transparent);
  backdrop-filter:blur(24px) saturate(1.3);
  -webkit-backdrop-filter:blur(24px) saturate(1.3);
  mask-image:linear-gradient(to bottom, #000 0%, #000 84%, transparent 100%);
  -webkit-mask-image:linear-gradient(to bottom, #000 0%, #000 84%, transparent 100%);
}
/* The second layer is retired on both surfaces. */
/* ── B61 (Pablo: "Tabs on desktop look much better! There is a small strip between
   the header and the tabs that shows a gap and it looks bad, so try to fix that.
   But mark the improvement because this finally is very close to what I really
   wanted") ─────────────────────────────────────────────────────────────────────
   The gap was the two bands' own edges meeting. B60 gave BOTH surfaces the same
   band, including the same 10px overhang and the same dissolve over their bottom
   16% — so the header went translucent in exactly the strip of pixels where the
   strip's band starts at full strength, and the scrolling page showed through the
   seam between them. When a tab strip sits directly under the header they are ONE
   band, so the header runs flush and SQUARE (no overhang, no mask) and the strip
   alone owns the dissolve at the bottom of the pair. */
.page:has(> .sec-tabs.sec-tabs-inpage) > .page-h::before,
.page:has(> [data-dr-switch].dr-switch-pinned) > .page-h::before{
  inset:0;
  mask-image:none;
  -webkit-mask-image:none;
}
.page-h::after,
.page > .sec-tabs.sec-tabs-inpage::after,
.page > [data-dr-switch].dr-switch-pinned::after{ content:none; }
.page-h h2{font-size:24px;font-weight:700;letter-spacing:-.015em;margin:0;transition:color 160ms ease}
.page-h p{margin:4px 0 0;font-size:13px;color:var(--fg-2);transition:color 160ms ease}
/* When the sticky header title scrolls over a dark surface
   ([data-dark-surface]), shell.js syncHeaderContrast() adds `.over-dark`
   so the title stays readable against the dark background behind the
   header blur. */
.page-h.over-dark h2{color:#fff}
.page-h.over-dark p{color:rgba(255,255,255,.72)}
.page-h .acts{display:flex;gap:8px;align-items:center;flex-shrink:0}
/* The page scroll content needs lateral padding now that .page itself
   no longer pads — only the header was visually offset before. Apply
   to every direct child below the header. */
.page > :not(.page-h){padding-inline:24px}
.page > :first-child{padding-top:18px}
.page > :last-child{padding-bottom:24px}
.page{gap:18px}
/* The -24px bleed margin on .page-h assumes a 24px-padded wrapper (.tab-view
   on Payments) to cancel against. Section views (and Metrics) put .page-h as
   a DIRECT child of .page, which has no padding — so the margin shoved the
   title flush against the sidebar border. Reset it for direct-child headers
   and re-inset the content 24px so the title lines up with the body columns.
   Specificity (0,4,0) intentionally beats the mobile/tablet
   `.shell.signed-in .page-h{padding:0}` rules so the inset holds in every
   breakpoint (the glass band still spans the full content width). Pablo
   2026-06-18. */
.shell.signed-in .page > .page-h{margin-inline:0;padding-inline:24px}
.page > .page-h{margin-inline:0;padding-inline:24px}

/* Actions dropdown — replaces the lone Export button. Template-
   configured: production shows [Export]; staging+admin shows
   [Save, Cancel, Export]. Closer cousin to brand-app-menu than to
   the profile popover. */
.page-actions{position:relative;display:inline-block}
/* B37 C5/C6 — the dashboard Actions dropdown lives in the NAV BAR now
   (sections.js moves #pageActions there); size it like a nav pill and
   keep the FAB as the mobile surface. */
.nav-right > .page-actions .page-actions-btn{height:36px;padding:0 12px;border-radius:9px}
@media (max-width:600px){ .nav-right > .page-actions{display:none !important} }
@media (max-height:599px) and (orientation:landscape){ .nav-right > .page-actions{display:none !important} }
.page-actions-btn{
  display:inline-flex;align-items:center;gap:8px;
  padding:9px 14px;border-radius:9px;
  background:var(--bg-input);border:1px solid var(--border);
  color:var(--fg-1);font:600 13px var(--mk-font-ui);
  cursor:pointer;
  transition:border-color var(--mk-t-fast),color var(--mk-t-fast);
}
.page-actions-btn:hover{border-color:var(--accent);color:var(--accent)}
.page-actions-btn .mi{font-size:18px}
.page-actions-menu{
  position:absolute;top:calc(100% + 6px);inset-inline-end:0;z-index:200;
  min-width:200px;
  background:var(--bg-1);border:1px solid var(--border);border-radius:10px;
  box-shadow:var(--shadow-pop);
  padding:6px;display:none;flex-direction:column;gap:2px;
}
.page-actions-menu.open{display:flex}
.page-actions-item{
  display:flex;align-items:center;gap:10px;
  padding:8px 10px;border-radius:8px;border:0;background:transparent;
  cursor:pointer;text-align:start;color:var(--fg-1);
  font:500 13px var(--mk-font-ui);
  transition:background var(--mk-t-fast);
}
.page-actions-item:hover{background:color-mix(in srgb, var(--accent) 10%, transparent);color:var(--accent)}
.page-actions-item .mi{font-size:16px;color:inherit}
.page-actions-item.danger:hover{
  background:color-mix(in srgb, var(--mk-danger) 12%, transparent);color:var(--mk-danger);
}
.page-actions-divider{height:1px;background:var(--border);margin:4px 0}

/* Floating action FAB — primary entry point for template actions on
   mobile-V and mobile-H. The button label collapses to "Actions" with
   an `add` icon and a chevron that toggles the menu above it. Hidden
   by default; mobile media queries surface it via `display:flex`. */
.page-fab{
  display:none;
  position:fixed;inset-inline-end:16px;
  bottom:calc(var(--mk-tabbar-h) + 16px);
  z-index:110;
  /* Bar at the bottom, menu (absolutely positioned) floats above. The
     container doesn't need flex-direction since the menu is absolute. */
  align-items:flex-end;
}
/* B58 (Pablo, mobile portrait: "When there's no tabs the floating button can be
   further down in the screen") — the base `bottom` lifts the FAB clear of the
   bottom tab BAR, so on a route that has no tabs it was floating over nothing.
   `#shell.has-page-tabs` is the same signal the route-fab uses (sections.css). */
@media (max-width:600px) and (orientation:portrait){
  .shell.signed-in:not(.has-page-tabs) .page-fab{bottom:calc(16px + env(safe-area-inset-bottom,0px))}
}
/* The page FAB (Add transaction + Export) is DASHBOARD-ONLY — it must never
   appear on any other route, at any breakpoint. This global rule overrides
   the mobile-V/H `display:flex` whenever the active route isn't payments.
   (Pablo 2026-06-22 — "this button shouldn't show up outside of Dashboard".) */
.shell.signed-in:has(.body-in:not([data-page="payments"])) .page-fab{display:none !important}
/* Round corner FAB (Pablo 2026-07-12): the bar is just a transparent
   wrapper; the toggle IS the round accent circle (same visual language as
   .route-fab). Tapping it opens the action menu above. */
.page-fab-bar{
  display:inline-flex;align-items:center;
  background:transparent;box-shadow:none;border-radius:0;
}
.page-fab-toggle{
  display:inline-flex;align-items:center;justify-content:center;
  width:52px;height:52px;border-radius:50%;
  background:var(--accent);color:var(--accent-fg,#04282b);border:0;cursor:pointer;
  box-shadow:0 10px 26px color-mix(in srgb, var(--accent) 32%, transparent), 0 4px 12px rgba(0,0,0,.20);
  transition:transform .18s var(--mk-ease),box-shadow .18s var(--mk-ease);
}
.page-fab-toggle:hover{transform:translateY(-2px)}
.page-fab-toggle::before{content:none}
.page-fab-chev{font-size:26px !important;transition:transform .18s var(--mk-ease)}
.page-fab.open .page-fab-chev{transform:rotate(45deg)}
.page-fab-menu{
  display:none;flex-direction:column;gap:2px;
  /* Floats ABOVE the bar. position:absolute keeps the bar layout stable
     while the menu expands upward (Pablo 2026-05-30 spec — expand up). */
  position:absolute;bottom:calc(100% + 8px);inset-inline-end:0;
  min-width:240px;padding:6px;border-radius:12px;
  background:var(--bg-1);border:1px solid var(--border);
  box-shadow:0 12px 32px rgba(0,0,0,.28);
  /* Reverse the visual stacking so the bottom item is closest to the
     FAB — feels natural with an upward expand. */
  flex-direction:column-reverse;
}
.page-fab.open .page-fab-menu{display:flex}
/* B46 P6-ML02 — `.page-fab-item{display:flex}` / `.page-actions-item{
   display:flex}` both OUT-RANK the `hidden` attribute (same specificity,
   later in source), so every `el.hidden = !editing` in
   dashboard-editor.js was a no-op: Save + Cancel showed permanently in
   BOTH page menus, on every size. Same family as P4-MAP01's `.cs[hidden]`.
   Any new item class needs its own guard here. */
.page-fab-item[hidden],
.page-actions-item[hidden]{display:none}
/* Short landscape phones: the menu expands UP from the FAB and at ~240px
   of height a full edit-mode menu ran off the top of the screen. Capped
   below (after the item rules, so the compact padding actually wins). */
.page-fab-item{
  display:flex;align-items:center;gap:10px;
  padding:10px 12px;border-radius:8px;border:0;background:transparent;
  cursor:pointer;text-align:start;color:var(--fg-1);
  font:600 14px var(--mk-font-ui);
  transition:background var(--mk-t-fast);
}
.page-fab-item:hover{background:color-mix(in srgb, var(--accent) 12%, transparent);color:var(--accent)}
.page-fab-item .mi{font-size:18px;color:inherit}
.page-fab-item.danger:hover{
  background:color-mix(in srgb, var(--mk-danger) 12%, transparent);color:var(--mk-danger);
}
/* Cap the menu to the room above the FAB and compact the rows, so the
   four items that matter in edit mode (Add · Add widget · Save · Cancel)
   all clear the fold on a ~240px-tall landscape phone; Export scrolls. */
@media (max-height: 599px){
  .page-fab-menu{max-height:calc(100dvh - 80px);overflow-y:auto;overscroll-behavior:contain}
}
/* Below ~420px there is no arrangement of 5 items at full size that
   clears the fold, so the rows compact rather than scroll out of sight. */
@media (max-height: 420px){
  .page-fab-menu{padding:5px;gap:1px}
  .page-fab-item{padding:5px 12px;font:600 13px var(--mk-font-ui)}
  .page-fab-item .mi{font-size:16px}
}

/* Buttons */
.btn{display:inline-flex;align-items:center;gap:6px;padding:9px 14px;border-radius:10px;font:600 13px var(--mk-font-ui);
  cursor:pointer;border:1px solid var(--border);background:var(--bg-input);color:var(--fg-1);
  transition:all var(--mk-t-fast);white-space:nowrap}
.btn .mi{font-size:16px}
.btn:hover{border-color:var(--accent);color:var(--accent)}
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--accent-fg);
  box-shadow:0 1px 2px rgba(13,37,48,.10);transition:transform var(--mk-t-fast),box-shadow var(--mk-t-fast),background var(--mk-t-fast),border-color var(--mk-t-fast)}
.btn.primary:hover{color:var(--accent-fg);border-color:transparent;
  background:color-mix(in srgb, var(--accent) 90%, #000);
  transform:translateY(-1px);
  box-shadow:0 7px 20px color-mix(in srgb, var(--accent) 42%, transparent)}
.btn.primary:active{transform:translateY(0);
  box-shadow:0 2px 8px color-mix(in srgb, var(--accent) 34%, transparent)}
.btn.lg{height:46px;padding:0 18px;font-size:14px;border-radius:11px}
.btn.lg .mi{font-size:18px}

/* Stats */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:12px}
.stat{background:var(--bg-1);border:1px solid var(--border);border-radius:var(--mk-radius-lg);padding:16px}
.stat .l{font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:none;color:var(--fg-2);margin-bottom:8px}
.stat .v{font-size:26px;font-weight:800;letter-spacing:-.02em}
.stat .v.p{color:var(--accent)}
.stat .v.s{color:var(--accent-2)}
.stat .d{font-size:11px;color:var(--fg-2);margin-top:4px}

/* Mobile tab bar — V7 pattern: glass surface with backdrop blur, items
   stack icon-above-label, active item carries a soft accent fill pill. */
.tb{
  grid-column:1/-1;grid-row:3/4;
  background:color-mix(in srgb, var(--bg-1) 92%, transparent);
  backdrop-filter:blur(12px);
  -webkit-backdrop-filter:blur(12px);
  border-top:1px solid var(--border);
  display:none;
  align-items:center;justify-content:space-around;
  height:var(--mk-tabbar-h);
  padding:6px 8px;gap:2px;
  z-index:100;
}
/* No primary tab/seg on this route → collapse the bar so the shell doesn't
   show an empty ghost strip (Pablo 2026-06-22). Beats the mobile-V/H
   display:flex rules. */
.tb.tb-empty{display:none !important}
.tb .ti{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:3px;padding:6px 4px;
  color:var(--fg-2);
  font:600 10px var(--mk-font-ui);
  text-align:center;
  cursor:pointer;
  border-radius:8px;
  flex:1;max-width:72px;min-width:36px;
  transition:color var(--mk-t-fast),background var(--mk-t-fast);
}
.tb .ti .mi{font-size:22px;color:inherit}
.tb .ti:hover{color:var(--fg-1)}
.tb .ti.active{
  color:var(--accent);
  background:color-mix(in srgb, var(--accent) 13%, transparent);
}
.tb .ti.active .mi{color:var(--accent)}



/* TABLET 721-1024 (portrait or any min-height ≥600) — V7 pattern:
   narrow rail with stacked items by default; expand button (or tapping a
   parent with children) opens a WIDE overlay sidebar that floats above
   content with a scrim. Click outside → collapse back to narrow. */
@media (min-width: 601px) and (max-width: 1024px) and (min-height: 600px){
  /* Row 1 must auto-size to the navbar (which is min-height:64px at this
     breakpoint — taller than the default 58px --mk-nav-h). Without the
     override the nav overflows down past its grid row and paints over
     the body's top border, making the 16-px rounded inset disappear. */
  .shell:not(.signed-out){grid-template-columns:80px 1fr;grid-template-rows:auto 1fr auto}
  .shell:not(.signed-out) .sb{overflow:hidden}

  /* Narrow items: stacked icon-above-label like V7, with a small chevron
     under the label for items that have children (Tickets/Schedule/
     Users/Services). The chevron sits in flex-col so the column reads
     icon → label → arrow consistently across every parent item. */
  .shell:not(.signed-out) .sb .sb-item{
    flex-direction:column;align-items:center;justify-content:center;
    gap:4px;padding:8px 4px 6px;border-radius:10px;min-height:44px;
    text-align:center;
    font-size:9px;font-weight:600;letter-spacing:.2px;
    position:relative;
  }
  .shell:not(.signed-out) .sb .sb-item .mi{font-size:21px}
  /* P4-TAB-R01 (Pablo m0111): icon-only rail — drop the always-visible 9px
     label, which truncated at 80px ("Users & R…"/"Marketpla…"). The full
     route name is revealed through the ONE shared tooltip (tooltip.js /
     MoccaTip): hover on a fine pointer, sustained long-press on a coarse
     one. shell.js mirrors each item's LIVE (i18n) label into data-tip only
     while this rail is active, so desktop/mobile (labels visible) get no
     redundant tooltip. min-height:44px above keeps the now-iconless items a
     valid touch target (no VC2-T2 regression). */
  /* B53-11 (Pablo: "On tablet, the menu items should also have the name
     displayed in flex-col as well, truncated obviously but still displayed") —
     the label is BACK on the rail. It was dropped in P4-TAB-R01 because it
     truncated; truncation is what Pablo wants, so it is now explicit: one line,
     ellipsis, full width of the 80px column. The shared tooltip still carries
     the full name, which is what makes a truncated label safe. */
  .shell:not(.signed-out) .sb .sb-item .lbl{
    display:block;flex:0 0 auto;width:100%;max-width:100%;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    text-align:center;line-height:1.25;
  }
  /* Badge floats top-right of the narrow item */
  .shell:not(.signed-out) .sb .sb-item .badge{
    position:absolute;top:4px;inset-inline-end:4px;
    padding:1px 5px;font-size:9px;
  }
  /* Stacked chevron for parent items — render below the label, not
     hidden. Active/open parents flip 180deg via the .open class on
     .sb-item.has-children (set by shell.js). */
  .shell:not(.signed-out) .sb .sb-item.has-children .sb-arrow{
    display:inline-flex;font-size:14px;color:var(--fg-3);opacity:.85;
    margin-top:-1px;
  }
  .shell:not(.signed-out) .sb .sb-item:not(.has-children) .sb-arrow{display:none}
  .shell:not(.signed-out) .sb .sb-children{display:none}

  /* Brand stacks logo over a small wordmark — V7 tablet pattern. Pin the
     brand to the sidebar column width (80 px) so the icon sits centered
     directly above the sidebar items below — the navbar reads as a
     continuation of the rail. Hide the dark/light variants we don't need
     so the four wordmark SVGs don't stack on top of each other. */
  .shell.signed-in .nav-brand{
    width:80px;height:auto;padding:6px 0;
    flex-direction:column;gap:4px;
    align-items:center;justify-content:center;
    flex-shrink:0;
  }
  .shell.signed-in .nav-brand .icon{height:30px;width:30px}
  .shell.signed-in .nav-brand .wordmark{height:12px;width:auto;display:block;max-width:72px}
  /* On tablet the brand sits in the sidebar column with the rail items
     below it; surface a stacked chevron under the brand (admin only) so
     the app-switcher affordance reads inline with how parent sidebar
     items advertise their children. */
  .shell.signed-in .nav-brand-wrap{
    flex-direction:column;gap:2px;align-items:center;padding:4px 4px 2px;
  }
  body[data-role="admin"] .shell.signed-in .nav-brand-wrap .brand-app-chevron{
    font-size:14px;color:var(--fg-3);opacity:.85;
  }
  /* Theme/lang scoping for the brand wordmarks (need higher specificity
     than the base 0,3,1 rules since this lives inside the tablet MQ) */
  html[data-theme="light"] .shell.signed-in .nav-brand .icon-dark{display:none !important}
  html[data-theme="dark"] .shell.signed-in .nav-brand .icon-light{display:none !important}
  /* wordmark-{light,dark} display rules removed — inline-SVG wordmark
     flips color via --wordmark-color instead of swapping elements. */
  html[lang="en"] .shell.signed-in .nav-brand .wordmark-ar,
  html[lang="es"] .shell.signed-in .nav-brand .wordmark-ar{display:none !important}
  html[lang="ar"] .shell.signed-in .nav-brand .wordmark-en{display:none !important}

  /* Inline search input height matches navbar pill rhythm (48 px) so the
     search field doesn't read as a smaller, lower-priority control. The
     search has no max-width cap so it claims all remaining horizontal
     room between the brand column and the right cluster. */
  .shell.signed-in .nav-search{height:48px;border-radius:11px;padding:0 14px;flex:1 1 auto}

  /* Show sidebar-bottom (expand toggle) on tablet only */
  .sb-bottom{display:flex !important;justify-content:center}

  /* WIDE overlay — sidebar floats above content with scrim, V7-style.
     Triggered by `.sb-expanded` class on the shell (set by the bottom
     button OR by tapping a parent with children). */
  .shell.sb-expanded:not(.signed-out) .sb{
    position:fixed;top:0;bottom:0;inset-inline-start:0;
    width:var(--mk-sidebar-w);
    z-index:120;
    box-shadow:var(--shadow-lg);
    border-inline-end:1px solid var(--border);
    transition:transform var(--mk-t-base);
  }
  .shell.sb-expanded:not(.signed-out) .sb-header{display:flex}
  .shell.sb-expanded:not(.signed-out) .sb .sb-item{
    flex-direction:row;align-items:center;justify-content:flex-start;
    gap:10px;padding:9px 10px;border-radius:9px;
    text-align:start;font-size:13px;font-weight:500;letter-spacing:0;
  }
  .shell.sb-expanded:not(.signed-out) .sb .sb-item .mi{font-size:19px}
  .shell.sb-expanded:not(.signed-out) .sb .sb-item .lbl{
    flex:1;font-size:13px;text-align:start;
  }
  .shell.sb-expanded:not(.signed-out) .sb .sb-item .badge{
    position:static;padding:2px 7px;font-size:10px;margin-inline-start:auto;
  }
  .shell.sb-expanded:not(.signed-out) .sb .sb-item .sb-arrow{display:inline-flex}
  .shell.sb-expanded:not(.signed-out) .sb .sb-children{display:block}
  .shell.sb-expanded:not(.signed-out) .sb-bottom{display:none !important}
  /* Scrim shows behind the wide overlay */
  .shell.sb-expanded .scrim{opacity:1;pointer-events:auto}

  /* Signed-in nav pills stack icon-above-label, matching the signed-out cs-pills */
  .nav-right.auth-in .nav-pill{
    flex-direction:column;gap:1px;
    width:auto;min-width:48px;height:48px;padding:4px 8px;
    justify-content:center;align-items:center;font-size:9px;font-weight:600;
  }
  .nav-right.auth-in .nav-pill .mi{font-size:16px;margin:0}
  .nav-right.auth-in .nav-pill .np-l{font-size:9px;line-height:1}
  .nav-right.auth-in{gap:6px}

  /* Profile box matches the same pill family on tablet: icon-above-label
     with no email row. The avatar shrinks to 30px so the box reads
     compact alongside the other 48-px pills. */
  .nav-profile{
    flex-direction:column;gap:2px;
    width:auto;min-width:48px;height:48px;padding:4px 8px;
    justify-content:center;align-items:center;
    border-radius:9px;
  }
  html[dir="rtl"] .nav-profile{padding:4px 8px}
  .nav-profile .avatar{width:22px;height:22px;font-size:9px;border-radius:7px}
  .nav-profile .profile-info{align-items:center}
  .nav-profile .profile-name{
    font-size:9px;font-weight:600;line-height:1;
    max-width:60px;
  }
  .nav-profile .profile-email{display:none}

  .nav-sections{display:none}

  /* Tall navbar — every cluster stacks icon-above-label */
  .nav{height:auto;min-height:64px;padding:8px 14px;gap:8px;align-items:center}

  /* Signed-in: brand fills the 64-px sidebar column, so navbar starts at
     x=0 (no inline padding). The chrome is borderless on tablet — the
     only border is the rounded-corner inset on .body, so the dashboard
     card reads as the single bordered surface. */
  .shell.signed-in .nav{
    padding-inline-start:0;
    padding-inline-end:14px;
    border-bottom:0;
    background:var(--bg-1);
  }
  /* Same: drop the sidebar's end border on tablet — the body's start
     border handles the visual separation between rail and content. */
  .shell.signed-in .sb{border-inline-end:0}
  /* Wide-overlay sidebar header on tablet has no bottom border — the
     menu items below sit directly under the brand without a divider. */
  .shell.sb-expanded:not(.signed-out) .sb-header{border-bottom:0}

  .shell.signed-out:not(.mobile) .nav-brand{min-width:0}
  .nav-brand{flex-direction:column;gap:3px !important;padding:2px 4px;align-items:center}
  .nav-brand .icon{height:28px !important;width:28px !important}
  .nav-brand .wordmark{height:13px !important}

  .nav-right{gap:6px;align-self:center}
  .nav-right.auth-out .cs-pill > .cs-btn{
    flex-direction:column;gap:1px;
    width:auto;min-width:48px;height:48px;padding:4px 8px;
    justify-content:center;align-items:center;
    font-size:9px;
  }
  .nav-right.auth-out .cs-pill > .cs-btn .mi{font-size:16px;margin:0}
  .nav-right.auth-out .cs-pill > .cs-btn .cs-val{font-size:9px;line-height:1;font-weight:600}
  .nav-right.auth-out .cs-pill > .cs-btn .cs-arrow{display:none}

  .nav-right.auth-out .btn-signin{
    flex-direction:column;gap:1px;
    width:auto;min-width:48px;height:48px;padding:4px 8px;
    justify-content:center;
    font-size:9px;line-height:1;
  }
  .nav-right.auth-out .btn-signin .mi{font-size:16px;margin:0}
}

/* MOBILE-V (portrait ≤720) — floating glass nav, no wordmark */
@media (max-width: 600px) and (orientation: portrait){
  .shell:not(.signed-out){grid-template-columns:1fr;grid-template-rows:var(--mk-nav-h) 1fr var(--mk-tabbar-h)}
  .shell:not(.signed-out) .sb{
    grid-column:unset;grid-row:unset;
    position:fixed;top:0;bottom:0;inset-inline-start:0;width:280px;
    z-index:120;transform:translateX(-110%);transition:transform var(--mk-t-base);
    border-inline-end:1px solid var(--border);box-shadow:var(--shadow-lg)}
  html[dir="rtl"] .shell:not(.signed-out) .sb{transform:translateX(110%)}
  .shell:not(.signed-out).sb-open .sb{transform:translateX(0) !important}
  /* B67 R67-034 — the closed drawer's buttons stayed in the Tab order on phones */
  .shell:not(.signed-out):not(.sb-open) .sb{visibility:hidden;transition:transform var(--mk-t-base), visibility 0s linear .3s}
  .shell:not(.signed-out).sb-open .sb{visibility:visible;transition:transform var(--mk-t-base), visibility 0s}
  /* Sidebar header (with brand + close) is shown in the modal sidebar */
  .shell:not(.signed-out) .sb-header{display:flex}
  .shell:not(.signed-out) .tb{display:flex}
  /* B67 R67-006 — as specific as the desktop rule it resets (`.shell.signed-out:not(.mobile) .nav-brand{min-width:sidebar}`
     out-ranked the plain selector: the logo kept 232 px and pushed Sign in 5 px off a 375 px phone) */
  .shell.signed-out .nav-brand, .shell.signed-out:not(.mobile) .nav-brand{min-width:0}

  /* Signed-in: brand isn't pinned to the sidebar column on mobile-v
     (sidebar is off-screen until you open it). Reset desktop-only widths
     so the navbar packs left → right like the signed-off layout. */
  .shell.signed-in .nav{padding-inline-start:12px}
  .shell.signed-in .nav-brand{width:auto;padding:0;height:auto}
  /* Re-use the floating-glass nav from signed-off: collapse the nav row so
     the body extends to the viewport top (y=0). Content scrolls behind the
     nav, the blur picks it up — same effect as signed-off. */
  .shell.signed-in{grid-template-rows:1fr}
  .shell.signed-in .body{
    grid-column:1/-1;
    grid-row:1/2;
    border:0;border-radius:0;
    background:var(--bg-1); /* same surface as the nav blur source */
  }
  /* On mobile-V the Payments .tabs strip is hidden; the section .sec-tabs
     strip stays visible as the in-page tab nav (Pablo B11-12/13 removed the
     bottom .tb bar — the strip + sidebar are the only navigation now). */
  .shell.signed-in .tabs{display:none}
  /* B62 UI-79 (Pablo: "I feel like this header should be fixed in mobile portrait as
     well. I know I said it shouldn't be but I feel it's always relevant to see it")
     — this REVERSES the 2026-05-30 decision, and his newer word wins. The header is
     sticky inside `.page` (the app's one scroll surface) at exactly the offset the
     floating navbar already leaves it at, so it never jumps on the first scroll: it
     simply stays, and content passes UNDER it. The glass band comes back with it —
     a header pinned over moving content has to stay legible, which is the whole
     reason the band exists on desktop. `.acts` stays hidden (the FAB carries the
     actions on compact; the dashboard timeframe chip keeps its own exception). */
  .shell.signed-in .page-h{
    position:sticky;
    top:var(--mk-nav-clear);
    z-index:20;
    background:transparent;
    border-bottom:0;
    padding-block:6px 10px;
    /* Base .page-h pulls itself out of .tab-view's 24px inline padding
       with margin-inline:-24px (for the full-bleed glass band). With the
       zero-padding mobile header that left the title flush against
       the screen edge — reset so it aligns with the body columns.
       Pablo 2026-06-11. */
    margin-inline:0;
  }
  /* B63-131 (Pablo: "Why does the header only blur until it's margins in
     mobile portrait? It should take the whole width of the screen") — the
     `margin-inline:0` reset above is right for a SECTION header (already a
     direct `.page` child with no wrapper padding to cancel, via the
     `.page > .page-h{margin-inline:0}` rule higher up), but the Dashboard's
     header lives one level deeper inside `.tab-view` (its own 24px padding),
     so zeroing the margin there left the glass band inset by that same
     24px on both sides instead of spanning edge-to-edge. Restore the bleed
     for that one wrapped case. */
  .shell.signed-in .tab-view > .page-h{margin-inline:-24px}
  /* …and per the same comment, no tint when nothing is behind it: the
     mobile navbar dropped its background tint for exactly this reason
     (B59) and kept only the blur, which "blends in" instead of reading as
     a separate coloured band. The header matches that now. */
  .shell.signed-in .page-h::before{background:transparent}
  /* B65 W2 (065-097, "there's still a gap between the header and the nav bar. Please don't
     leave any gaps … This is general for the shell") — the header pins at
     --mk-nav-clear (60px) but the floating nav ends at 58px, so a 2px strip of page
     scrolled by un-blurred between them. The header's glass now reaches up under the
     nav to the top of the screen: whatever the two heights are, nothing shows through. */
  .shell.signed-in .page-h::before,
  .shell.signed-in .page:has(> .sec-tabs.sec-tabs-inpage) > .page-h::before,
  .shell.signed-in .page:has(> [data-dr-switch].dr-switch-pinned) > .page-h::before{top:calc(-1 * var(--mk-nav-clear, 60px))}
  /* A sticky header must REST where it PINS, or it jumps 18px the first time you
     scroll. `.page > :first-child{padding-top:18px}` (shell.css) gives the tab-view
     that gap, so the header started 18px below the offset it sticks at. Zeroed
     only where that first child really holds the header — the header's own
     `padding-block` is the breathing room now, and the page gains 18px of content
     height, which is UI-78's other half.
     B62, THIRD round, and this time MEASURED (scraps/b62-w3-diag.html on a real
     390×844 boot): the band was 91px of blank between the nav's ink and the title,
     and neither earlier fix could have touched it, because it was never the 18px.
     The header sat at y=140 — `.page{padding-top:70px}` (the nav clearance) PLUS
     `.page-h{top:70px}` (the pin offset), because a sticky offset inside a padded
     scroller is resolved from the scroller's CONTENT edge: the two offsets ADD.
     So the clearance now has exactly ONE origin — the scroller's padding is zero
     and the first child carries the clearance as a MARGIN, which is also the one
     arrangement where padding edge and content edge coincide, so rest == pin in
     any engine and not just this one. `--mk-nav-clear` is that single value; the
     header's `top` reads the same var so the two can never drift again. */
  .shell.signed-in{--mk-nav-clear:calc(var(--mk-nav-h) + 2px)}
  .shell.signed-in .page > :first-child{padding-top:0;margin-top:var(--mk-nav-clear)}
  .shell.signed-in .page-h::before{display:block}
  .shell.signed-in .page-h::after{display:none}
  .shell.signed-in .page-h .acts{display:none}
  /* P5-ML03 (Pablo 2026-08-04) — "everything I do on one size I should be able
     to do on all." The Dashboard timeframe selector (#dashTruncCs) is the one
     header control the page-fab does NOT mirror, so surface the SAME element
     (same wiring, no parallel control) as an inline chip on compact. Mirrors
     the existing .sec-view .page-h .acts{display:flex} precedent. #pageActions
     stays hidden — its items already live in the page-fab. */
  .shell.signed-in .page-h .acts:has(#dashTruncCs){display:flex}
  .shell.signed-in .page-h .acts:has(#dashTruncCs) #pageActions{display:none}
  .shell.signed-in .page-fab{display:flex}
  /* Make the body card reachable: nav floats, tab bar floats, and the
     page is the single scroll surface in between. `min-height:0` is
     critical for nested-flex scrolling to actually engage. */
  .shell.signed-in .body{min-height:0;overflow:hidden}
  .shell.signed-in .page-view{min-height:0}
  .shell.signed-in .page{
    background:var(--bg-1);
    padding-top:0;
    padding-bottom:24px;
    min-height:0;flex:1;
    overflow-y:auto;-webkit-overflow-scrolling:touch;
  }
  /* The base shell grid puts .tb at row 3/4, but mobile-V signed-in only
     declares 2 rows (1fr / tabbar-h). Pin .tb to the second row so it
     hugs the body and doesn't leave a phantom row of empty bg-1 between
     the page content and the tab bar. */
  .shell.signed-in .tb{grid-row:2/3;grid-column:1/-1}
  /* The bottom tab bar serves as the page's tab navigation on mobile-V;
     items match the in-page tabs (Balance/Expenses/Income) when the
     payments page is active. The active item carries an accent fill
     pill. */
  .shell.signed-in .tb .ti.active{
    color:var(--accent);
    background:color-mix(in srgb, var(--accent) 13%, transparent);
  }
  .shell.signed-in .tb .ti.active .mi{color:var(--accent)}
  /* Mobile-V signed-in nav: brand on the far left, profile on the far
     right, the four pills (search/filter/sort/alerts) evenly distributed
     between. Done by flattening `.nav-right.auth-in` with `display:contents`
     so all rail items become direct flex children of `.nav`; then the
     nav itself uses `space-between` to push brand and profile to the
     edges and `gap` does the rest. */
  .shell.signed-in .nav{
    justify-content:space-between;
    padding-inline-start:12px;
    padding-inline-end:12px;
    gap:6px;
  }
  .nav-right.auth-in{display:contents}
  /* Brand and pills share the same 36-px square footprint so the row
     reads as one even sequence — the icon doesn't visually outsize
     the other items anymore. */
  .shell.signed-in .nav-brand{
    width:36px;height:36px;padding:0;
    align-items:center;justify-content:center;
    flex-shrink:0;
  }
  /* B63-002 (Pablo: "the app icon is smaller than all the other elements on
     the nav bar") — same fix B59 already made for mobile-LANDSCAPE (line
     ~1597): the mark fills the same 36px optical box as its neighbours'
     glyph-in-pill instead of sitting 4px smaller inside it. Portrait just
     never got the matching update. */
  .shell.signed-in .nav-brand .icon{width:36px;height:36px}
  /* On mobile-V the app-switcher affordance lives in the sidebar's
     sb-header brand; the navbar brand is icon-only here. */
  .shell.signed-in .nav-brand-wrap .brand-app-chevron{display:none !important}
  .shell.signed-in .nav-brand-wrap{padding:0;background:transparent !important;cursor:default}
  .shell.signed-in .avatar{flex-shrink:0}
  .nav-right.auth-in .nav-pill{
    width:36px;height:36px;padding:0;gap:0;
    border-radius:9px;justify-content:center;align-items:center;
    flex-direction:row;flex:0 0 auto;
    background:color-mix(in srgb, var(--bg-input) 80%, transparent);
    backdrop-filter:blur(8px);
  }
  .nav-right.auth-in .nav-pill .np-l{display:none}
  .nav-right.auth-in .nav-pill .mi{font-size:18px;margin:0}
  .nav-right.auth-in .nav-pill .np-dot{top:5px;inset-inline-end:5px}
  /* Hide the inline desktop search; show the search icon button instead */
  .shell.signed-in .nav-search{display:none !important}
  .shell.signed-in .nav-search-btn{display:inline-flex !important}
  /* Avatar gets the same glass treatment so it's visually part of the cluster */
  .shell.signed-in .avatar{
    background-color:color-mix(in srgb, var(--bg-input) 80%, transparent);
    backdrop-filter:blur(8px);
  }
  /* Profile box collapses to an icon-only button on mobile-V. The popover
     still anchors off the wrapper. */
  .nav-profile{
    padding:0;border:0;background:transparent;
    width:36px;height:36px;border-radius:9px;
    align-items:center;justify-content:center;
  }
  .nav-profile:hover{background:transparent;border-color:transparent}
  .nav-profile .profile-info{display:none}
  /* The profile button is a 36px rounded square — crop the photo to that
     shape directly instead of floating a circle inside a square box
     (Pablo 2026-06-11). */
  .nav-profile .avatar{width:36px;height:36px;border-radius:10px}

  /* Signed-out: collapse the grid row reserved for the nav so the hero
     extends behind the floating nav (progressive blur reveals content). */
  .shell.signed-out{grid-template-rows: 1fr auto}

  /* Floating nav with progressive glass blur */
  .nav{
    position:fixed;top:0;left:0;right:0;
    height:var(--mk-nav-h);
    background:transparent;border-bottom:none;
    padding:8px 12px;gap:8px;
    z-index:100;
  }
  /* B59 (Pablo: "Only on mobile, the navbar shouldn't have a background color
     (both in portrait and landscape) It should feel like it's floating on top of
     the content (but make sure you keep the blur and everything else")

     This SUPERSEDES B51-13, which is worth reading before touching it again: back
     then the bar drew `--bg-1 / 80%` on a page that is also `--bg-1`, so it read as
     GONE — and the fix was to paint the chrome surface (`--bg-2`) plus a hairline.
     Pablo now wants the floating-glass reading instead, so the TINT and the hairline
     go and the BLUR stays: what makes the bar present is no longer a band of colour
     but the way content visibly softens as it passes underneath, plus the trailing
     progressive fade below. The failure mode B51-13 hit does not return, because a
     tint of 0 with `backdrop-filter` still refracts — the bar is only invisible where
     there is nothing behind it to blur, which at the top of a page is correct. */
  .shell.signed-in .nav{border-bottom:none}
  .shell.signed-in .nav::before{ background:transparent; }
  /* Hero gets a small top pad so the headline isn't hidden under the nav */
  .body-out .hero{padding-top:calc(var(--mk-nav-h) + 16px)}
  /* Layer 1: tinted blur covering ONLY the nav surface (no trailing fade
     by default so the page title sits clear at scroll=0). The trailing
     soft fade is added below via body.scrolled. */
  /* B61 (Pablo: "The navbar in mobile (both portrait and landscape) has a different
     background color again. Why do you fix something and mess it up some other way?")
     B59 removed the TINT and left `saturate(1.5)` on the backdrop-filter. A
     saturation filter is not neutral: it repaints everything under the bar at 150%
     chroma, so the band read as a different colour than the page even though its own
     background is `transparent`. That is the same complaint in a new place. The BLUR
     stays (B59's explicit ask, now 20px) and the colour maths goes -- a pure blur
     cannot shift a hue, so the bar can only read as the page, softened. Applies to
     both mobile bars: this one and the landscape rail's `::before` below. */
  .nav::before{
    content:"";position:absolute;inset:0;
    /* B59 — no tint; the blur IS the surface. Slightly stronger than before so
       the glass still reads once the colour is gone. */
    background:transparent;
    backdrop-filter:blur(20px);
    -webkit-backdrop-filter:blur(20px);
    pointer-events:none;z-index:-1;
    transition:inset 200ms var(--mk-ease);
  }
  /* Layer 2: progressive trailing blur, ONLY engaged when the page has
     scrolled. shell.js toggles `body.scrolled` based on .page scrollTop. */
  body.scrolled .nav::before{
    inset:0 0 -28px 0;
    mask-image:linear-gradient(to bottom, #000 0%, #000 60%, transparent 100%);
    -webkit-mask-image:linear-gradient(to bottom, #000 0%, #000 60%, transparent 100%);
  }
  body.scrolled .nav::after{
    content:"";position:absolute;inset:auto 0 -56px 0;height:84px;
    backdrop-filter:blur(6px);
    -webkit-backdrop-filter:blur(6px);
    mask-image:linear-gradient(to bottom, transparent 0%, #000 30%, transparent 100%);
    -webkit-mask-image:linear-gradient(to bottom, transparent 0%, #000 30%, transparent 100%);
    pointer-events:none;z-index:-1;
  }
  /* …but NOT when signed in, and that is the other half of the blank-band fix.
     Those two layers reach 28px and 56px BELOW the nav box, and the nav sits above
     the page (z 100 vs 20), so anything in that band gets blurred — including the
     pinned header. Keeping the title clear of it is what had pushed the header 91px
     down the page. Since UI-79 the header is ALWAYS pinned directly under the nav
     and carries its own glass band, so the trailing fade has nothing left to soften:
     one glass surface under the nav, not two overlapping ones. */
  body.scrolled .shell.signed-in .nav::before{
    inset:0;
    mask-image:none;-webkit-mask-image:none;
  }
  body.scrolled .shell.signed-in .nav::after{display:none}

  /* Hide wordmark; equalize logo with other navbar elements */
  .nav-brand{padding:0}
  .nav-brand .icon{width:36px;height:36px}
  .nav-brand .wordmark{display:none !important}

  /* All nav-right buttons become 36px squares, icon-only */
  .nav-right.auth-out{gap:6px}
  .nav-right.auth-out .cs-pill > .cs-btn{
    width:36px;height:36px;padding:0;gap:0;
    border-radius:9px;justify-content:center;align-items:center;
  }
  .nav-right.auth-out .cs-pill > .cs-btn .cs-val,
  .nav-right.auth-out .cs-pill > .cs-btn .cs-arrow{display:none}
  .nav-right.auth-out .cs-pill > .cs-btn .mi{font-size:18px;margin:0}

  .nav-right.auth-out .btn-signin{
    width:36px;height:36px;padding:0;gap:0;
    border-radius:9px;justify-content:center;
  }
  .nav-right.auth-out .btn-signin [data-i18n]{display:none}
  .nav-right.auth-out .btn-signin .mi{font-size:18px;margin:0}

}

/* MOBILE-H (landscape with short height) — Uallet pattern: nav becomes left rail, body row */
@media (max-height: 599px) and (orientation: landscape){
  /* B67 R67-007 — the rail is 56 px: the signed-out logo's 232 px desktop minimum hung 88 px past the screen edge */
  .shell.signed-out .nav-brand, .shell.signed-out:not(.mobile) .nav-brand{min-width:0}
  .shell.signed-out,
  .shell.signed-in{
    grid-template-columns:64px 1fr !important;
    grid-template-rows:1fr;
    min-height:100dvh;
  }

  /* Pin nav to column 1 (override base .nav { grid-column:1/-1 }) and
     turn it into a vertical rail. `space-between` on the nav itself
     puts brand at the top, profile (last child) at the bottom, and
     evenly distributes everything in between — no clumping. To make
     that work, `.nav-right.auth-in` uses `display:contents` so its
     children (search/filter/sort/alerts/profile) become direct flex
     items of the rail, not nested inside another container. */
  .shell.signed-out .nav,
  .shell.signed-in .nav{
    grid-column:1/2 !important;grid-row:1/2;
    flex-direction:column;
    width:56px;height:100dvh;
    padding:4px 0;gap:0;
    align-items:center;justify-content:space-between;
    border-bottom:none;border-inline-end:1px solid var(--border);
    background:var(--bg-1);
    position:sticky;top:0;
    /* overflow:visible so the profile .pop popover can escape the rail
       to the right (it's positioned `inset-inline-start: 100%+8px`). The
       rail items are sized to the fixed 44-px cell so nothing else
       needs the overflow clip. */
    overflow:visible;
    /* B52-15 (Pablo: "The navbar on mobile landscape should be a single column
       but the items between the app icon and the avatar are layed out on a
       square grid. This was working before stop breaking things that are
       working") — SINGLE COLUMN, reverting B46 P6-ML11's two-column rail.
       That change spent width to keep a 44px target, arguing 7 items × 44px
       could not fit; the rail actually carries FIVE cells (brand + search +
       filter + alerts + profile) = 5×44 + 4×4 = 236px, which fits every real
       landscape height (≥320px) in one column. The rail narrows to match one
       44px track, so the page gets the 44px back. */
    display:grid;
    grid-template-columns:44px;
    grid-auto-rows:44px;
    justify-content:center;align-content:space-between;
    gap:4px;
  }
  .shell.signed-out .nav-brand-wrap,
  .shell.signed-in .nav-brand-wrap{
    grid-column:1/-1;justify-self:stretch;
    /* The wrap is the GRID ITEM (not .nav-brand), so it is what has to be
       constrained to the rail — it otherwise keeps its 232px desktop width
       and overflows the 92px track in both directions. */
    width:100%;min-width:0;max-width:100%;
    min-height:44px;padding:0;
    display:flex;align-items:center;justify-content:center;
  }
  /* Tighten the grid column to match the rail (one 44px track + gutters). */
  .shell.signed-out,
  .shell.signed-in{
    grid-template-columns:56px 1fr !important;
  }
  /* Mobile-H: the nav rail and the main content share ONE background so
     the rail reads as part of the surface, not a separate white chrome
     column. Match the body's --bg-2 (with no end-border) — white cards
     still pop against it. (Pablo 2026-06-02 W1 extra.) */
  /* B51-14 (Pablo 2026-08-19: "What happened to the navbar in mobile
     landscape?? We can't lose that part of the shell ever") — ROOT CAUSE: the
     rail was deliberately painted `--bg-2`, the SAME token as the body, with
     `border-inline-end:0` (2026-06-02: "the rail reads as part of the surface").
     That is exactly what made it disappear. The rail is chrome again: its own
     `--bg-1` surface + the hairline that separates it from the content. */
  /* B59 (same comment as portrait: "both in portrait and landscape") — the rail
     drops its `--bg-1` surface and its hairline and keeps the glass. The B52-16 note
     just above explains why the surface was added; the answer is the same as in
     portrait — the blur is what makes it present, and it survives. */
  .shell.signed-in .nav{
    background:transparent;border-inline-end:none;
  }
  .shell.signed-in .nav::before{
    content:"";position:absolute;inset:0;
    background:transparent;
    backdrop-filter:blur(20px);
    -webkit-backdrop-filter:blur(20px);
    pointer-events:none;z-index:-1;
  }
  .nav-spacer{display:none}
  /* Brand: spans the rail's full width as the top row (B46 P6-ML11), so its
     tap target is 92×44 — comfortably over the floor, which matters because
     this is the drawer trigger and the most-tapped control on the rail. */
  .shell.signed-out .nav-brand,
  .shell.signed-in .nav-brand{
    flex:0 0 auto;
    width:100%;height:44px;
    padding:0;margin:0;
    flex-direction:row;
    align-items:center;justify-content:center;
  }
  .shell.signed-out .nav-brand .icon,
  .shell.signed-in .nav-brand .icon{width:36px;height:36px;margin:0}   /* B59 — see below */

  .nav-brand{flex-direction:column;gap:0;padding:2px}
  .nav-brand .icon{width:30px;height:30px}
  .nav-brand .wordmark{display:none !important}

  /* Signed-in brand: fill the rail width and center the icon inside. The
     auto-width version was leaving asymmetric whitespace that read as
     "not centered". Width:100% guarantees a single centering anchor. */
  .shell.signed-in .nav-brand{
    flex-direction:row;gap:0;padding:4px 0;
    width:100%;height:auto;
    align-items:center;justify-content:center;
  }
  /* B59 (Pablo: "On Mobile landscape (for some reason) the app logo is smaller than
     all the other elements in the navbar. Please fix it") — every other cell on this
     rail is a 44px pill, and a 30px mark inside one reads as the runt of the column.
     The mark now fills the same optical box as its neighbours' glyph-in-pill. */
  .shell.signed-in .nav-brand .icon{width:36px;height:36px;margin:0 auto}
  .shell.signed-in .nav-brand .wordmark{display:none !important}
  /* Mobile-H — app-switcher chevron lives in the sidebar's sb-header
     brand (admin only). Keep the navbar brand chevron-free. */
  .shell.signed-in .nav-brand-wrap .brand-app-chevron,
  .shell.signed-out .nav-brand-wrap .brand-app-chevron{display:none !important}
  .shell.signed-in .nav-brand-wrap,
  .shell.signed-out .nav-brand-wrap{padding:0;background:transparent !important;cursor:default}
  .shell.signed-in .sb{
    grid-column:unset;grid-row:unset;
    position:fixed;top:0;bottom:0;inset-inline-start:0;width:280px;
    z-index:120;
    transform:translateX(-110%);transition:transform var(--mk-t-base);
    box-shadow:var(--shadow-lg);border-inline-end:1px solid var(--border);
  }
  html[dir="rtl"] .shell.signed-in .sb{transform:translateX(110%)}
  .shell.signed-in.sb-open .sb{transform:translateX(0) !important}
  .shell.signed-in.sb-open .scrim{opacity:1;pointer-events:auto}
  /* B67 R21 (R67-084) — as R67-034 in portrait: the closed landscape drawer's buttons stayed in the Tab order */
  .shell.signed-in:not(.sb-open) .sb{visibility:hidden;transition:transform var(--mk-t-base), visibility 0s linear .3s}
  .shell.signed-in.sb-open .sb{visibility:visible;transition:transform var(--mk-t-base), visibility 0s}
  .shell.signed-in .sb-header{display:flex}

  /* B62 UI-78 (Pablo: "On mobile landscape we should also see the header fixed but
     it would be nice to have it take as little height space as possible") — in
     landscape the navbar is the LEADING-edge rail, so nothing overlays the top and
     the header can pin at `top:0`. And it is as short as it can honestly be: the
     title one step down, the subtitle dropped, 4/5px of breathing room — ~34px
     instead of ~58px on a 667×375 phone. The FAB keeps the actions. */
  .shell.signed-in .page-h{
    position:sticky;
    top:0;
    z-index:20;
    background:transparent;
    border-bottom:0;
    padding-block:4px 5px;
    align-items:center;
    margin-inline:0; /* same flush-edge fix as mobile-V above */
  }
  /* rest offset == pinned offset (see the mobile-V note): no jump on first scroll */
  .shell.signed-in .page > :first-child:is(.page-h, :has(> .page-h)){padding-top:0}
  /* B65 W2 (065-033, "could have a slightly bigger font. Let's not go overboard") — +2 px, every page */
  .shell.signed-in .page-h h2{font-size:17px}
  .shell.signed-in .page-h p{display:none}
  .shell.signed-in .page-h::before{display:block}
  .shell.signed-in .page-h::after{display:none}
  .shell.signed-in .page-h .acts{display:none}
  /* P5-ML03 (mobile-H) — surface the Dashboard timeframe chip here too. */
  .shell.signed-in .page-h .acts:has(#dashTruncCs){display:flex}
  .shell.signed-in .page-h .acts:has(#dashTruncCs) #pageActions{display:none}
  .shell.signed-in .page-fab{
    display:flex;
    bottom:16px;
    inset-inline-end:80px;
  }
  /* B60 (Pablo: "The floating button on mobile landscape (when there's no tabs)
     could be positioned a bit further to the right") — the 80px inset exists to
     clear the 64px tab RAIL on the trailing edge. With no tabs there is no rail to
     clear, so the button sits in the corner like every other floating action. */
  .shell.signed-in:not(.has-page-tabs) .page-fab{ inset-inline-end:16px; }

  /* Body fills column 2; no rounded card here — viewport is short. Mirror
     the V7 mobile-H pattern: stack the body horizontally so the tab strip
     sits as a vertical rail on the OPPOSITE edge from the nav (right
     side), with the page area between them. */
  .shell.signed-in .body{
    grid-column:2/3;grid-row:1/2;
    border:0;border-radius:0;
    flex-direction:row-reverse;
  }
  .shell.signed-in .tabs{
    flex-direction:column;
    width:64px;height:100%;
    padding:6px;gap:3px;
    border-radius:0;
    border-bottom:none;
    border-inline-start:1px solid var(--border);
    background:var(--bg-1);
    overflow-x:hidden;overflow-y:auto;
    align-items:stretch;justify-content:space-evenly;
  }
  .shell.signed-in .tab{
    flex:0;flex-direction:column;
    gap:3px;padding:8px 4px;
    width:100%;min-width:unset;
    font-size:9px;text-align:center;
    border-radius:8px;
  }
  .shell.signed-in .tab .mi{font-size:20px}
  /* Restore the pill-fill active style for the vertical rail (the
     desktop underline indicator reads as an arbitrary edge-line when
     the tab itself is column-stacked). */
  .shell.signed-in .tab.active{
    background:color-mix(in srgb, var(--accent) 13%, transparent);
    color:var(--accent);
    box-shadow:none;
  }
  .shell.signed-in .tab.active .mi{color:var(--accent)}

  /* `display:contents` flattens the cluster so its children become direct
     flex items of the rail. Combined with the rail's `space-between`,
     this gives brand-top, profile-bottom, and 4 pills (search/filter/
     sort/alerts) evenly distributed in between — no clumping. */
  .nav-right.auth-in{display:contents}
  /* B46 P6-ML11 — items FILL their 44px grid cell instead of floating a
     32px pill inside it: visual == hit area, so the target is exactly the
     44px floor with a 4px gutter between neighbours (no overlap). */
  .nav-right.auth-in .nav-pill{
    width:44px;height:44px;padding:0;gap:0;
    border-radius:11px;justify-content:center;align-items:center;
    flex-direction:row;flex:0 0 auto;position:relative;
  }
  .nav-right.auth-in .nav-pill .np-l{display:none}
  .nav-right.auth-in .nav-pill .mi{font-size:19px;margin:0}
  /* Profile matches the same 44px cell footprint as the pills. */
  .shell.signed-in .nav .nav-profile{
    width:44px;height:44px;padding:0;
    border:0;background:transparent;border-radius:11px;
    justify-content:center;align-items:center;flex:0 0 auto;
  }
  /* Avatar crops to the rail's rounded-square shape (no circle-in-square). */
  .shell.signed-in .nav .nav-profile .avatar{width:34px;height:34px;font-size:12px;border-radius:9px}
  .shell.signed-in .nav .nav-profile:hover{background:transparent;border-color:transparent}
  .shell.signed-in .nav .nav-profile .profile-info{display:none}

  /* Inline search hidden on the rail; tap the avatar / use the search pill instead */
  .shell.signed-in .nav-search{display:none !important}
  .shell.signed-in .nav-search-btn{display:inline-flex !important}

  /* Profile popover — default position (top:100%; inset-inline-end:0) puts
     it BELOW the avatar, off-screen on the bottom of the rail. Reposition
     to flow OUT from the rail (to the inline-end side so it auto-flips
     for RTL) and shrink so it fits inside a short landscape viewport. */
  .shell.signed-in .nav .nav-profile .pop{
    top:auto;bottom:0;
    /* inset-inline-start auto-flips to "right of avatar" in LTR and
       "left of avatar" in RTL because the rail itself flips sides. */
    inset-inline-start:calc(100% + 8px);
    inset-inline-end:auto;
    /* No max-height + no overflow on mobile-H — overflow:auto was
       clipping the language .cs-menu when it opened (the menu is
       position:absolute and was getting cut off by .pop's scroll box).
       The pop's natural content height fits inside a 320-px landscape
       viewport once the compact-row rules below kick in. */
    width:220px;
    font-size:11px;
    flex-direction:column;
  }
  /* Display must be gated on .open or the popover ignores the
     close click — the default `.pop{display:none}` becomes
     `.pop.open{display:block}`, and the mobile-H override needs to
     keep the same .open dependency. */
  .shell.signed-in .nav .nav-profile .pop.open{display:flex}
  .shell.signed-in .nav .nav-profile .pop .pu{padding:6px;margin-bottom:4px}
  .shell.signed-in .nav .nav-profile .pop .pu .av{width:26px;height:26px;font-size:10px}
  .shell.signed-in .nav .nav-profile .pop .pu .n{font-size:11px}
  .shell.signed-in .nav .nav-profile .pop .pu .e{font-size:9px}
  /* Stack each row vertically — label on top, value on bottom — so the
     value column has the full popup width instead of fighting the label
     for space at the right edge. Fixes "options show to the side" feel. */
  .shell.signed-in .nav .nav-profile .pop .row{
    flex-direction:column;align-items:stretch;gap:4px;
    padding:6px 8px;
  }
  .shell.signed-in .nav .nav-profile .pop .row > .k{font-size:10px;gap:6px;color:var(--fg-3);text-transform:none;letter-spacing:.06em}
  .shell.signed-in .nav .nav-profile .pop .row > .k .mi{font-size:12px}
  .shell.signed-in .nav .nav-profile .pop .link{padding:5px 8px;font-size:11px;gap:8px}
  .shell.signed-in .nav .nav-profile .pop .link .mi{font-size:13px}
  .shell.signed-in .nav .nav-profile .pop .theme-chips{align-self:flex-start}
  .shell.signed-in .nav .nav-profile .pop .theme-chips button{width:26px;height:22px}
  .shell.signed-in .nav .nav-profile .pop .theme-chips button .mi{font-size:13px}
  /* Language .cs takes the full row width when stacked under its label. */
  .shell.signed-in .nav .nav-profile .pop .row .cs{display:block;width:100%}
  .shell.signed-in .nav .nav-profile .pop .row .cs > .cs-btn{width:100%;justify-content:space-between}
  /* B10-06 (Pablo): but the LANGUAGE selector should stay inline (label left,
     value right) like desktop — he prefers that consistency over the stacked
     block. Scope the override to its own row so the theme row is untouched. */
  .shell.signed-in .nav .nav-profile .pop .row:has(.cs[data-cs="lang"]){
    flex-direction:row;align-items:center;justify-content:space-between;gap:8px;
  }
  .shell.signed-in .nav .nav-profile .pop .row:has(.cs[data-cs="lang"]) > .k{
    text-transform:none;letter-spacing:0;font-size:11px;color:var(--fg-2);flex:none;
  }
  .shell.signed-in .nav .nav-profile .pop .row .cs[data-cs="lang"]{display:inline-flex;width:auto;min-width:0}
  .shell.signed-in .nav .nav-profile .pop .row .cs[data-cs="lang"] > .cs-btn{width:auto;justify-content:space-between}
  /* Keep the whole popover on-screen — scroll if the rows (plus an open
     dropdown) exceed the short landscape height. The inner dropdowns
     expand INLINE (position:static) so they ride the popover's scroll
     instead of opening as an absolute overlay off to the side. Pablo
     2026-06-01 #2: "I don't want the profile modal to bleed off-screen". */
  .shell.signed-in .nav .nav-profile .pop{
    max-height:calc(100dvh - 16px);
    overflow-y:auto;scrollbar-width:thin;
  }
  .shell.signed-in .nav .nav-profile .pop .cs.open > .cs-menu{
    position:static;inset:auto;transform:none;
    box-shadow:none;margin-top:4px;max-height:none;
  }
  /* Persona-style menus (Switch Profile / Viewing as) declare a 230px
     min-width for their floating treatment — wider than the 220px
     mobile-H popover, so the inline-static expansion above blew the
     popover open horizontally and broke the layout (Pablo 2026-06-11:
     "in mobile landscape I get the dropdown within the box and
     everything breaks"). Inline menus must take the popover's width:
     the rows already ellipsise their nick/email so nothing clips. */
  .shell.signed-in .nav .nav-profile .pop .cs.open > .cs-menu.persona-menu{
    min-width:0;width:100%;max-width:100%;
  }

  /* Signed-out rail: same flattening — children become direct GRID items
     of the nav rail (brand spans the top row; theme, language and sign-in
     flow 2-up beneath it).
     B46 P6-ML11 — these were left at 32px when the signed-in pills were
     re-pitched to 44, which meant the state the app actually BOOTS in
     (`.nav-right.auth-in` ships `display:none`; JS flips it) still failed
     the touch floor — including the sign-in CTA, the most important target
     on the screen in that state.
     NB the GRID ITEM is `.cs-pill`, not the `.cs-btn` inside it — sizing
     only the button leaves a 32px control floating in a 44px cell (same
     trap as `.nav-brand-wrap`), so the wrapper is sized and the button
     fills it. */
  .nav-right.auth-out{display:contents}
  .nav-right.auth-out .cs-pill{
    width:44px;height:44px;
    display:flex;align-items:center;justify-content:center;
  }
  .nav-right.auth-out .cs-pill > .cs-btn{
    width:100%;height:100%;padding:0;gap:0;
    border-radius:11px;justify-content:center;align-items:center;
  }
  .nav-right.auth-out .cs-pill > .cs-btn .cs-val,
  .nav-right.auth-out .cs-pill > .cs-btn .cs-arrow{display:none}
  .nav-right.auth-out .cs-pill > .cs-btn .mi{font-size:19px;margin:0}

  .nav-right.auth-out .btn-signin{
    width:44px;height:44px;padding:0;gap:0;
    border-radius:11px;justify-content:center;
  }
  .nav-right.auth-out .btn-signin [data-i18n]{display:none}
  .nav-right.auth-out .btn-signin .mi{font-size:19px;margin:0}

  /* Body fills column 2 with internal scroll. Reset the desktop
     rounded-corner inset — on a short landscape viewport that radius
     reads as a stray corner, not a deliberate frame. */
  .shell.signed-out .body-out{
    grid-column:2/3;grid-row:1/2;
    overflow-y:auto;max-height:100dvh;
    padding-top:0;
    border:0;border-radius:0;
  }

  /* Open cs-menu opens to the side, not over the nav */
  .nav .cs-menu{
    inset-inline-end:auto;inset-inline-start:calc(100% + 6px);
    top:0;
  }
}
/* ── Splash · multi-phase hydration narrative ───────────────
   A: particles orbit as a spinner (grays)         0 → 1800ms
   B: logo fades in beside cluster, ~30% recolor   1800 → 2600ms
   C: backdrop fades, body shows skeleton-only     2600 → 4100ms
   D: real content cross-fades over skeleton       4100 → 5000ms
   ─────────────────────────────────────────────────────────── */
.splash{
  position:fixed;inset:0;z-index:80;background:var(--bg-1);
  display:flex;flex-direction:row;
  align-items:center;justify-content:center;gap:20px;
  pointer-events:none;
  transition:opacity 500ms var(--mk-ease), background 400ms var(--mk-ease);
}
.splash.gone{display:none}
.splash.phase-3{opacity:0}
/* Tablet (any orientation) and Mobile-V stack vertically so the cluster
   and logo fill the screen's width. Desktop and Mobile-H stay horizontal. */
@media (min-width: 601px) and (max-width: 1024px) and (min-height: 600px),
       (max-width: 600px) and (orientation: portrait){
  .splash{flex-direction:column;gap:36px}
}

/* Non-routable items — childless leaves and bottom-tab-bar slots that
   exist for shape only in this prototype. shell.js stamps `data-noop`
   on them at boot. Keep them visually identical (so the menu reads as
   complete) but signal they don't navigate via the cursor. */
[data-noop]{cursor:default}
[data-noop]:hover{background:transparent !important;color:inherit !important}
.sb-item[data-noop]:hover{background:var(--bg-2);color:var(--fg-1)}
/* Sub-items WITHOUT a route stay decorative — children of expanded
   parents, but not actual destinations in this prototype. */
.sb-child:not([data-route]){cursor:default;opacity:.8}
.sb-child:not([data-route]) .mi{color:color-mix(in srgb, var(--accent) 35%, var(--fg-3))}
.sb-child:not([data-route]):hover{background:transparent;color:var(--fg-2)}

/* Register animatable custom props so phase B can smoothly converge particles */
@property --r-chaos {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}
@property --jitter {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}

/* Particle cluster.
   Each particle gets its OWN orbit duration (--od), target radius (--rb),
   and convergence duration (--cd). Same chaos→converge story, but the
   per-particle variation makes the swarm feel organic, not mechanical. */
.particles{
  position:relative;
  width:280px;height:280px;
  flex-shrink:0;
  overflow:visible;
  /* Cluster-level breathing runs the whole time the splash is up. */
  animation: cluster-breathe 3.6s ease-in-out infinite;
}
@keyframes cluster-breathe{
  0%, 100% { transform: scale(1) rotate(0deg); }
  50%      { transform: scale(1.05) rotate(3deg); }
}

.particles i{
  position:absolute;
  top:50%;left:50%;
  width:var(--sz, 3px);height:var(--sz, 3px);
  margin:calc(var(--sz, 3px) * -0.5) 0 0 calc(var(--sz, 3px) * -0.5);
  border-radius:50%;
  background:var(--border-strong);
  transform-origin:center center;
  /* From frame 1: orbit + twinkle. Each particle has its own period so the
     cluster shimmers continuously without any synced "spinner" feel. */
  animation: orbit   var(--od, 1.4s) linear              infinite,
             twinkle var(--tk, 2.4s) ease-in-out         infinite;
  animation-delay: calc(var(--ph, 0) * var(--od, 1.4s) * -1),
                   calc(var(--ph, 0) * var(--tk, 2.4s) * -1);
  /* Soft per-particle halo — luminous dots, not flat dust. */
  box-shadow: 0 0 6px var(--border-strong);
  /* Only the color crossfades to brand. Nothing else moves. */
  transition: background 900ms cubic-bezier(.22,.61,.36,1) var(--td, 0ms);
}
@keyframes orbit{
  from{ transform: rotate(0deg)   translateX(calc(var(--rb, 92px) + var(--rd, 0px) + var(--jd, 0px))) rotate(0deg); }
  to  { transform: rotate(360deg) translateX(calc(var(--rb, 92px) + var(--rd, 0px) + var(--jd, 0px))) rotate(-360deg); }
}
@keyframes twinkle{
  0%, 100% { opacity: .55; }
  50%      { opacity: 1; }
}
/* Mirror the orbit + cluster-breathe direction in RTL so particles
   spin counter-clockwise — matches Arabic's right-to-left reading
   direction. Both individual orbits and the cluster's wobble flip. */
html[dir="rtl"] .particles{animation-direction:reverse}
html[dir="rtl"] .particles i{animation-direction:reverse, reverse}

/* Phase B: logo appears, particles recolor to brand. (Colors are set
   directly per class — no intermediate custom property — so brand-a/b
   tint both the dot and its halo; the `background` transition above still
   drives the crossfade.) */
.splash.phase-2 .particles i.brand-a{ background:var(--accent);   box-shadow:0 0 6px var(--accent) }
.splash.phase-2 .particles i.brand-b{ background:var(--accent-2); box-shadow:0 0 6px var(--accent-2) }
/* "head" particles — leading edge of each orbit. --sz comes from the
   per-particle inline style (which wins over a rule here), so we only
   tune the halo: gray heads have no extra glow; brand heads glow larger. */
.splash.phase-2 .particles i.head{ box-shadow:none }
.splash.phase-2 .particles i.head.brand-a{ box-shadow:0 0 10px var(--accent) }
.splash.phase-2 .particles i.head.brand-b{ box-shadow:0 0 10px var(--accent-2) }

/* Logo lockup (icon + wordmark) appears beside the cluster in phase B.
   The brand-image scoping rules above already pick the correct icon and
   wordmark for the current theme + language; everything else is hidden.
   `.splash-icon` sizes the icon; `.splash-wordmark` sizes the wordmark.
   Direction follows the splash root: row on desktop+landscape, column on
   portrait+tablet (set in the matching MQ further up). */
.splash .logo-s{
  display:flex;align-items:center;justify-content:center;
  flex-direction:row;gap:14px;
  opacity:0;transform:translateX(-10px) scale(.94);
  transition:opacity 600ms var(--mk-ease), transform 600ms var(--mk-ease);
}
.splash .splash-icon{width:82px;height:82px;display:block;flex-shrink:0}
.splash .splash-wordmark{height:30px;width:auto;display:block;flex-shrink:0}
.splash.phase-2 .logo-s{ opacity:1; transform:none }
/* Tablet (any orientation) and Mobile-V stack the logo lockup vertically too,
   matching the splash root's flex direction so the cluster, icon, and
   wordmark all line up on a single column. */
@media (min-width: 601px) and (max-width: 1024px) and (min-height: 600px),
       (max-width: 600px) and (orientation: portrait){
  .splash .logo-s{flex-direction:column;gap:10px}
}
@media (max-width: 720px){
  .particles{width:220px;height:220px}
  .splash .splash-icon{width:64px;height:64px}
  .splash .splash-wordmark{height:24px}
}

/* Phase C — particles + logo fade as backdrop releases */
.splash.phase-3 .particles i,
.splash.phase-3 .logo-s{ opacity:0 }

/* ── body.hydrating · whole app reads as skeleton ─── */
body.hydrating .hero-h,
body.hydrating .hero-sub,
body.hydrating .hero-cta,
body.hydrating .ticker,
body.hydrating .nav-right.auth-out > *,
body.hydrating .runtime,
body.hydrating .phases,
body.hydrating .close{
  opacity:0;
  pointer-events:none;
  transition:opacity 400ms var(--mk-ease);
}
body:not(.hydrating) .hero-h,
body:not(.hydrating) .hero-sub,
body:not(.hydrating) .hero-cta,
body:not(.hydrating) .ticker,
body:not(.hydrating) .nav-right.auth-out > *,
body:not(.hydrating) .runtime,
body:not(.hydrating) .phases,
body:not(.hydrating) .close{
  opacity:1;
  transition:opacity 500ms var(--mk-ease);
}

/* ── Signed-in skeleton ──
   On reload with persisted auth='in', the splash fades to reveal the
   shell + dashboard mid-hydrate: card backgrounds pulse while their
   text/numbers/SVG stay hidden, then everything fades in once the
   `hydrating` class is dropped. Mirrors the signed-out hydrate above
   but targets dashboard surfaces instead of marketing sections. */
@keyframes skel-pulse{
  0%, 100% { background-color: var(--bg-2); }
  50%       { background-color: color-mix(in srgb, var(--fg-3) 16%, var(--bg-3)); }
}
/* The dashboard widget frames (cards + balance banner) NO LONGER pulse —
   they're "known" shell, so they show their resting surface while the
   pulsing MOCK skeleton (`.widget-ghost`, injected by dashboard-editor.js)
   sits over them with a typed mock chart + label bars. Only the remaining
   shell chrome (tabs, nav pills, sidebar rows) keeps the background pulse.
   Pablo 2026-06-02 W1 #1. */
body.hydrating .body-in .tab,
body.hydrating .body-in .tabs,
body.hydrating .nav-right.auth-in .nav-pill,
body.hydrating .nav-right.auth-in .nav-profile,
body.hydrating .sb .sb-item,
body.hydrating .nav-search{
  animation: skel-pulse 1.6s ease-in-out infinite;
  /* B9-02 (Pablo 2026-06-22): do NOT force the border transparent during
     hydrate — the border-color transition out of `transparent` was getting
     stuck, leaving nav pills + sidebar rows permanently borderless post-load.
     The skeleton pulse is background-only; the resting neutral border can stay
     visible the whole time. */
}
/* Banner uses a layered gradient + decorative ::after blob in its
   resting state. During hydrating, flatten both to a single pulsing
   surface so the skeleton reads cleanly. */
body.hydrating .body-in .bal-banner{
  background:var(--bg-2) !important;
  box-shadow:none;
}
body.hydrating .body-in .bal-banner::after{display:none}
/* Count badges (sidebar item counts + Filter/Sort/Alerts bubbles) are REAL
   DATA, not skeleton — they must not pulse and must not appear until load
   finishes. Pulled out entirely during hydrate (display:none, so no layout
   and no inherited pulse), then they POP IN once hydrating drops. Pablo
   2026-06-02 W1 #1. */
body.hydrating .sb .sb-item .badge,
body.hydrating .nav-right.auth-in .nav-pill .np-count{
  display:none !important;
}
@keyframes badge-pop{
  0%   { transform:scale(0);   opacity:0 }
  60%  { transform:scale(1.18); opacity:1 }
  100% { transform:scale(1);    opacity:1 }
}
body:not(.hydrating) .sb .sb-item .badge,
body:not(.hydrating) .nav-right.auth-in .nav-pill .np-count:not([hidden]){
  animation: badge-pop 300ms var(--mk-ease) 220ms both;
}
/* Hide the content INSIDE those surfaces so only pulsing shapes show. */
body.hydrating .body-in h2,
body.hydrating .body-in h3,
body.hydrating .body-in h4,
body.hydrating .body-in p,
body.hydrating .body-in .btn,
body.hydrating .body-in .btn-ghost,
body.hydrating .body-in svg,
body.hydrating .body-in .bb-l,
body.hydrating .body-in .bb-v,
body.hydrating .body-in .bb-d,
body.hydrating .body-in .card-legend,
body.hydrating .body-in .txn-list,
body.hydrating .body-in .tab > [data-i18n],
body.hydrating .body-in .tab .mi,
body.hydrating .nav-search .mi,
body.hydrating .nav-search input,
body.hydrating .nav-search .kbd,
body.hydrating .nav-right.auth-in .nav-pill > *,
body.hydrating .nav-right.auth-in .nav-profile > *,
body.hydrating .sb .sb-item > *,
body.hydrating .sb .sb-children{
  opacity:0;
  pointer-events:none;
  transition:opacity 400ms var(--mk-ease);
}
body:not(.hydrating) .body-in h2,
body:not(.hydrating) .body-in h3,
body:not(.hydrating) .body-in h4,
body:not(.hydrating) .body-in p,
body:not(.hydrating) .body-in .btn,
body:not(.hydrating) .body-in .btn-ghost,
body:not(.hydrating) .body-in svg,
body:not(.hydrating) .body-in .bb-l,
body:not(.hydrating) .body-in .bb-v,
body:not(.hydrating) .body-in .bb-d,
body:not(.hydrating) .body-in .card-legend,
body:not(.hydrating) .body-in .txn-list,
body:not(.hydrating) .body-in .tab > [data-i18n],
body:not(.hydrating) .body-in .tab .mi,
body:not(.hydrating) .nav-search .mi,
body:not(.hydrating) .nav-search input,
body:not(.hydrating) .nav-search .kbd,
body:not(.hydrating) .nav-right.auth-in .nav-pill > *,
body:not(.hydrating) .nav-right.auth-in .nav-profile > *,
body:not(.hydrating) .sb .sb-item > *,
body:not(.hydrating) .sb .sb-children{
  opacity:1;
  transition:opacity 500ms var(--mk-ease);
}
/* The entrance-fade rule above pins in-body buttons to an opacity-only
   transition, which made hover lifts snap instantly. Re-add the
   interactive properties (keeping the entrance opacity fade) so the
   hover animates. Same selector specificity, defined later → wins.
   Pablo 2026-06-01 item 11. */
body:not(.hydrating) .body-in .btn{
  transition:transform var(--mk-t-fast), box-shadow var(--mk-t-fast),
             background var(--mk-t-fast), border-color var(--mk-t-fast),
             color var(--mk-t-fast), opacity 500ms var(--mk-ease);
}

/* ── Skeleton waterfall (F4) ────────────────────────
   When `body.hydrating` drops, each shell surface fades in on its
   own delay so the load reads brand → sidebar → navbar → tabs →
   body, instead of the whole shell appearing in one beat. Each
   transition-delay extends the `body:not(.hydrating)` opacity:1
   transition above; ordering keeps the bespoke chart SVGs and the
   balance banner numbers as the LAST things to land. */
body:not(.hydrating) .sb .sb-item:nth-child(1) > * { transition-delay: 60ms }
body:not(.hydrating) .sb .sb-item:nth-child(2) > * { transition-delay: 100ms }
body:not(.hydrating) .sb .sb-item:nth-child(3) > * { transition-delay: 140ms }
body:not(.hydrating) .sb .sb-item:nth-child(4) > * { transition-delay: 180ms }
body:not(.hydrating) .sb .sb-item:nth-child(5) > * { transition-delay: 220ms }
body:not(.hydrating) .sb .sb-item:nth-child(6) > * { transition-delay: 260ms }
body:not(.hydrating) .sb .sb-item:nth-child(7) > * { transition-delay: 300ms }
body:not(.hydrating) .sb .sb-item:nth-child(8) > * { transition-delay: 340ms }
body:not(.hydrating) .sb .sb-children{ transition-delay: 380ms }
body:not(.hydrating) .nav-search .mi,
body:not(.hydrating) .nav-search input,
body:not(.hydrating) .nav-search .kbd{ transition-delay: 380ms }
body:not(.hydrating) .nav-right.auth-in .nav-pill:nth-child(1) > * { transition-delay: 420ms }
body:not(.hydrating) .nav-right.auth-in .nav-pill:nth-child(2) > * { transition-delay: 460ms }
body:not(.hydrating) .nav-right.auth-in .nav-pill:nth-child(3) > * { transition-delay: 500ms }
body:not(.hydrating) .nav-right.auth-in .nav-pill:nth-child(4) > * { transition-delay: 540ms }
body:not(.hydrating) .nav-right.auth-in .nav-profile > * { transition-delay: 580ms }
body:not(.hydrating) .body-in .tab:nth-child(1) > * { transition-delay: 620ms }
body:not(.hydrating) .body-in .tab:nth-child(2) > * { transition-delay: 660ms }
body:not(.hydrating) .body-in .tab:nth-child(3) > * { transition-delay: 700ms }
/* Body widget content now cross-fades in as the mock skeleton (ghost)
   fades OUT (~420ms) — short uniform delays instead of a long 720→900ms
   waterfall, so there's no empty-card gap between the two and the
   skeleton clearly READS as the held reload state. The sidebar / nav /
   tab chrome above keeps its quick reveal cascade. Pablo 2026-06-02 W1 #1. */
body:not(.hydrating) .body-in h2,
body:not(.hydrating) .body-in p{ transition-delay: 90ms }
body:not(.hydrating) .body-in .bb-l,
body:not(.hydrating) .body-in .bb-v,
body:not(.hydrating) .body-in .bb-d{ transition-delay: 110ms }
body:not(.hydrating) .body-in .card-legend{ transition-delay: 130ms }
body:not(.hydrating) .body-in svg,
body:not(.hydrating) .body-in .txn-list{ transition-delay: 140ms }
/* Pulse animation also gets a matched fade-out cascade so the
   skeleton stops pulsing on each surface in the same order.

   Pablo 2026-05-31 (cascade-bleed sweep fix): the prior approach used
   `transition: background-color 200ms ease N00ms` here, which won the
   cascade over each element's own `transition: all var(--mk-t-fast)`
   rule and replaced it — silently breaking hover behavior across
   .tab (820ms bg lag), .nav-profile (780ms), .nav-pill (border+color
   hover stripped → instant flash), .sb-item (color hover stripped).

   Same family of bug as WD2.3 (chart chrome opacity). Fix is the same
   shape: a one-shot @keyframes animation handles the cascade fade-in,
   then the element's own transition rule reasserts for hover. Each
   element keeps its INTENDED resting bg via the --rest-bg custom
   property (declared per element below). */
@keyframes mocca-bg-fade-in {
  from { background-color: var(--bg-2); }
  to   { background-color: var(--rest-bg, transparent); }
}
body:not(.hydrating) .sb .sb-item               { animation: mocca-bg-fade-in 200ms var(--mk-ease)  60ms backwards; }
body:not(.hydrating) .nav-right.auth-in .nav-pill    { animation: mocca-bg-fade-in 200ms var(--mk-ease) 420ms backwards; }
body:not(.hydrating) .nav-right.auth-in .nav-profile { animation: mocca-bg-fade-in 200ms var(--mk-ease) 580ms backwards; }
body:not(.hydrating) .body-in .tab              { animation: mocca-bg-fade-in 200ms var(--mk-ease) 620ms backwards; }
/* fill-mode is `backwards`, NOT `both`: `both` holds the final keyframe
   (`--rest-bg`/transparent) on the element FOREVER, out-prioritizing the
   normal cascade — which silently suppressed every background-driven
   hover/.active state on these elements (e.g. the mobile-H rail's active
   tab fill — Pablo 2026-06-11: "on the current tab we should see some
   outline or background change like V"). `backwards` covers the delay
   window, then releases the element back to its stylesheet background. */
/* Cards + balance banner no longer pulse their bg during hydrate (the
   ghost is the skeleton), so the bg-fade-in here would just hold them
   grey via backwards-fill for its delay then flash to resting — drop it.
   They show their resting surface straight away, under the fading ghost.
   Pablo 2026-06-02 W1 #1. */
body:not(.hydrating) .body-in .bal-banner,
body:not(.hydrating) .body-in .card             { animation: none; }
/* Per-element resting bg tokens — only declare non-default values
   (defaults to `transparent` via the @keyframes fallback). */
.nav-pill, .nav-profile { --rest-bg: var(--bg-input); }
.card { --rest-bg: var(--bg-1); }
/* Active states land on their accent fill at the end of the fade (instead
   of snapping transparent → fill when the animation releases). */
.sb-item.active { --rest-bg: color-mix(in srgb, var(--accent) 12%, transparent); }
.tab.active { --rest-bg: transparent; }
@media (max-height: 599px) and (orientation: landscape){
  /* Plain .tab.active (a registered token scope) — the mobile-H rail is
     the only place tabs exist signed-out… they don't; tabs are signed-in
     only, so the .shell.signed-in qualifier was redundant. */
  .tab.active { --rest-bg: color-mix(in srgb, var(--accent) 13%, transparent); }
}
/* .sb-item, .tab, .bal-banner default to transparent (the fallback) —
   their resting state shows the parent surface; hover/.active states
   paint via higher-specificity rules on each element. */

/* ── Hydrate gap-fillers (Pablo 2026-05-30) ───────────────────────
   The original hydrate list missed several surfaces that ship live
   content from JS — they were rendering pre-loaded over the pulsing
   skeleton. The selectors below silence them until hydrate flips.

   • `.page-actions-btn` — top-of-template Actions dropdown chrome.
   • `.bb-active::before` / `.bb-passive::before` — the colored dots
     on the KPI tiles flanking the Net balance.
   • `[data-edit-actions]` / `[data-resize-handle]` / `[data-trunc-ctl]`
     — staging-mode chrome (pencil/X buttons, corner resize handle,
     Truncation pill) injected by dashboard-editor.js.
   • `.chart-tooltip` / `.chart-lock` — overlay layers anchored to
     chart cards; they should sit behind the pulsing surface, not on
     top of it. */
body.hydrating .body-in .page-actions-btn > *,
body.hydrating .body-in [data-edit-actions],
body.hydrating .body-in [data-resize-handle],
body.hydrating .body-in [data-trunc-ctl],
body.hydrating .body-in .chart-tooltip,
body.hydrating .body-in .chart-lock{
  opacity:0;
  pointer-events:none;
  /* No transition here — the post-hydrate cascade uses @keyframes
     below instead of a CSS transition. The hydrate side just snaps
     to 0 so it can fade IN cleanly when hydrating flips. */
}
/* Post-hydrate waterfall — one-shot @keyframes per element group so
   the cascade in cannot bleed into subsequent opacity transitions
   (hover, focus, etc). Pablo 2026-05-30c (WD2.3 fix): the previous
   `transition: opacity 500ms 940ms` approach left a 1.44s hover-out
   lag on `.card-edit-actions` and `.card-resize-handle` because the
   transition rule won the cascade over each element's resting 140ms
   transition. @keyframes animations are one-shot and don't reappear
   in the transition cascade once they end.

   Each element keeps its INTENDED resting opacity via the
   `--rest-opacity` custom property declared further below; the
   animation interpolates 0 → var(--rest-opacity, 1). After the
   animation ends (`fill-mode: both` keeps the to-state), normal
   hover/idle CSS transitions take over. */
@keyframes mocca-chart-chrome-in {
  from { opacity: 0; }
  to   { opacity: var(--rest-opacity, 1); }
}
body:not(.hydrating) .body-in .page-actions-btn > *,
body:not(.hydrating) .body-in [data-edit-actions],
body:not(.hydrating) .body-in [data-resize-handle],
body:not(.hydrating) .body-in [data-trunc-ctl],
body:not(.hydrating) .body-in .chart-lock{
  animation: mocca-chart-chrome-in 500ms var(--mk-ease) both;
}
/* B52-14 (Pablo: "I used to have tooltips when hovering on charts, now none of
   the charts have the tooltips") — ROOT CAUSE, and it was RIGHT HERE:
   `.chart-tooltip` was in the waterfall list above with `--rest-opacity:0` and
   `fill-mode:both`, so the animation's forwards-fill pinned opacity to 0
   FOREVER — and an animation beats a normal author declaration in the cascade,
   so `.chart-tooltip.on{opacity:1}` could never win. Every chart tooltip was
   painting at opacity 0. A tooltip is hidden at rest and only ever shown on
   hover, so it has nothing to fade in on hydrate: it is out of the waterfall. */
/* Resting opacities per element type. Defaults to 1 via the
   @keyframes fallback. Only declare the non-default cases. */
.card-edit-actions  { --rest-opacity: 0; /* @kind other */ } /* hover-revealed */
.card-resize-handle { --rest-opacity: 0; /* @kind other */ } /* hover-revealed (Pablo 2026-05-31) */
/* KPI dots — hide via animation override since they're pseudo-
   elements; pulsing their background to transparent reads cleanly
   against the parent banner pulse. */
body.hydrating .body-in .bb-active::before,
body.hydrating .body-in .bb-passive::before{
  background:transparent !important;
  box-shadow:none !important;
}
/* The Actions dropdown chrome stays VISIBLE (just like other
   chrome surfaces — it's a control affordance, not content), but
   its inner glyphs/labels are masked above and the chevron is
   replaced with a centered spinner so users read it as "loading
   options" rather than "click me now". Pablo 2026-05-30 spec:
   "dropdowns should show as disabled with a spinner where the
   arrow usually goes". */
@keyframes skel-spin{
  to { transform: rotate(360deg); }
}
body.hydrating .body-in .page-actions-btn{
  pointer-events:none;
  position:relative;
}
body.hydrating .body-in .page-actions-btn::after{
  content:'';
  position:absolute;
  inset-block:0;
  inset-inline-end:10px;
  inline-size:14px;
  block-size:14px;
  margin-block:auto;
  border:2px solid var(--bg-3);
  border-top-color:var(--accent);
  border-radius:50%;
  animation: skel-spin 700ms linear infinite;
}
body:not(.hydrating) .body-in .page-actions-btn::after{
  display:none;
}

/* Staging chart controls cascade — match the body waterfall delays
   so they fade in after the chart card chrome. These are
   `animation-delay` (not `transition-delay`) now that the cascade
   uses @keyframes — see comment above the @keyframes rule for the
   WD2.3 fix rationale. */
body:not(.hydrating) .body-in [data-edit-actions],
body:not(.hydrating) .body-in [data-resize-handle],
body:not(.hydrating) .body-in [data-trunc-ctl]{ animation-delay: 940ms }
body:not(.hydrating) .body-in .chart-lock{ animation-delay: 900ms }

/* ═════════════════════════════════════════════════════════════════
   WAVE B — Per-role shell gating
   `body[data-role="…"]` is set by shell.js applyRoleGating(). Every
   role-specific rule in this section hangs off that attribute so a
   single sign-in flow drives the entire shell shape. Admin-only DOM
   pieces carry `.data-role-admin` so they collapse for non-admins
   without per-element JS.
   ═════════════════════════════════════════════════════════════════ */

/* Admin-only DOM (env switcher, persona switcher, app switcher entry,
   "Open my CV" link). Hidden by default — only `body[data-role="admin"]`
   reveals them. Uses `!important` because per-element rules in the
   profile popover (`.pop .row` etc.) have higher specificity than a
   single-class selector. */
.data-role-admin{display:none !important}
body[data-role="admin"] .data-role-admin{display:flex !important}
body[data-role="admin"] a.data-role-admin{display:inline-flex !important}

/* Env switcher (Production / Staging) — admin-only, available on EVERY
   viewport (Pablo 2026-06-01, reversing the earlier desktop-only gate).
   On touch the per-widget edit chrome reveals via touch-and-hold instead
   of hover. The `+ .divider` is the separator that follows the env row in
   the profile popover. */
body[data-role="admin"] [data-env-toggle]{display:flex !important}
body[data-role="admin"] [data-env-toggle] + .divider{display:block !important}
/* C2 (Pablo 2026-07-01) — the default signed-in owner persona (Pablo Osorio,
   role unset) also gets the environment selector in the profile popover. It had
   vanished because the whole env row was gated to [data-role="admin"] only.
   Mirror the brand-app-switch pattern, which is likewise enabled for the
   empty-role signed-in persona. */
body[data-role=""] .shell.signed-in [data-env-toggle],
body:not([data-role]) .shell.signed-in [data-env-toggle]{display:flex !important}
body[data-role=""] .shell.signed-in [data-env-toggle] + .divider,
body:not([data-role]) .shell.signed-in [data-env-toggle] + .divider{display:block !important}
/* Staging recolor for the empty-role owner persona (core accent + wordmark) so
   picking Staging gives visible feedback, mirroring the admin recolor. */
body[data-env="staging"][data-role=""],
body[data-env="staging"]:not([data-role]){
  --accent:var(--mk-staging);
  --ring:color-mix(in srgb, var(--mk-staging) 30%, transparent);
  --wordmark-accent:var(--mk-staging);
  /* B39 C2 — the nav-brand logo icon's accent path uses --brand-accent; the
     admin staging block flips it but the default owner persona (role="") did
     not, so the logo stayed cyan in staging. Flip it here too. */
  --brand-accent:var(--mk-staging);
}

/* Salome — `member-no-filter`. Filter pill + tabs + dashboard editor
   affordance off. Sort + Search visible. Sidebar + body unchanged.
   `!important` is needed throughout this block because the navbar's
   mobile-V/H media queries use `!important` to keep nav-pills visible,
   and tab styling for `.tabs` has higher base specificity. */
body[data-role="member-no-filter"] .nav-pill[title="Filter"]{display:none !important}
body[data-role="member-no-filter"] .tabs{display:none !important}
body[data-role="member-no-filter"] .dashboard-edit-toggle{display:none !important}

/* Carlos — `member-locked-charts`. Filter + Sort pills off (Search stays).
   Sidebar column removed; brand sits flush in the navbar's left edge
   and the body extends to the viewport edge (no rounded inset). */
body[data-role="member-locked-charts"] .nav-pill[title="Filter"],
body[data-role="member-locked-charts"] .nav-pill[title="Sort"]{display:none !important}
body[data-role="member-locked-charts"] .shell.signed-in{
  grid-template-columns:1fr;
}
body[data-role="member-locked-charts"] .shell.signed-in .sb{display:none !important}
body[data-role="member-locked-charts"] .shell.signed-in .body{
  grid-column:1/-1;
  border-start-start-radius:0;
  border-inline-start:0;
}
body[data-role="member-locked-charts"] .shell.signed-in .nav-brand{
  min-width:0;
  padding-inline-start:8px;
}

/* OTP-new — every navbar action goes silent. The body content is
   replaced by JS with #otpNoAccess; the existing payments page
   collapses behind a `.has-no-access` class on `main.body-in`. */
body[data-role="otp-new"] .nav-pill[title="Filter"],
body[data-role="otp-new"] .nav-pill[title="Sort"],
body[data-role="otp-new"] .nav-pill[title="Search"],
body[data-role="otp-new"] .nav-search{display:none !important}
main.body-in.has-no-access > :not(#otpNoAccess){display:none !important}
.otp-no-access{
  flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;padding:60px 24px;text-align:center;
  background:var(--bg-2);
}
.otp-no-access .otp-na-icon{
  font-size:64px;color:var(--fg-3);
  background:color-mix(in srgb, var(--accent) 8%, transparent);
  border-radius:50%;padding:18px;
}
.otp-no-access .otp-na-h{
  font:700 22px/1.25 var(--mk-font-ui);color:var(--fg-1);margin:0;
}
.otp-no-access .otp-na-p{
  font:400 14px/1.5 var(--mk-font-ui);color:var(--fg-2);
  max-width:420px;margin:0 0 6px 0;
}

/* ── Brand wrap (B.1.3) ─────────────────────────────
   The whole .nav-brand-wrap is the app-switcher hit target on admin.
   Non-admin roles still see the brand link but no chevron + no
   pointer cursor — the wrap reads as a plain logo. */
.nav-brand-wrap{
  display:inline-flex;align-items:center;gap:4px;position:relative;
}
body[data-role="admin"] .nav-brand-wrap{
  cursor:pointer;border-radius:9px;padding:2px 4px;
  transition:background var(--mk-t-fast);
}
body[data-role="admin"] .nav-brand-wrap:hover{
  background:color-mix(in srgb, var(--accent) 8%, transparent);
}
.brand-app-chevron{
  display:none;font-size:18px;color:var(--fg-3);
  transition:transform 150ms ease;
}
body[data-role="admin"] .brand-app-chevron{display:inline-flex}
/* Salome (member-no-filter) can app-switch too — it takes her to the CV.
   Pablo 2026-06-18. */
body[data-role="member-no-filter"] .nav-brand-wrap,
body[data-role="member-no-filter"] .sb-brand-wrap{
  cursor:pointer;border-radius:9px;padding:2px 4px;
  transition:background var(--mk-t-fast);
}
body[data-role="member-no-filter"] .nav-brand-wrap:hover,
body[data-role="member-no-filter"] .sb-brand-wrap:hover{
  background:color-mix(in srgb, var(--accent) 8%, transparent);
}
body[data-role="member-no-filter"] .brand-app-chevron{display:inline-flex}
/* Default / cold-load persona (Pablo Osorio, role unset) can also app-switch
   in place — Pablo 2026-06-21: "I can switch apps but I don't see any
   identifier that I can." Show the same chevron + hit-target affordance for
   the empty-role signed-in state (scoped to signed-in so the signed-out logo
   stays a plain link). */
body[data-role=""] .shell.signed-in .nav-brand-wrap,
body:not([data-role]) .shell.signed-in .nav-brand-wrap,
body[data-role=""] .shell.signed-in .sb-brand-wrap,
body:not([data-role]) .shell.signed-in .sb-brand-wrap{
  cursor:pointer;border-radius:9px;padding:2px 4px;
  transition:background var(--mk-t-fast);
}
body[data-role=""] .shell.signed-in .nav-brand-wrap:hover,
body:not([data-role]) .shell.signed-in .nav-brand-wrap:hover{
  background:color-mix(in srgb, var(--accent) 8%, transparent);
}
body[data-role=""] .shell.signed-in .brand-app-chevron,
body:not([data-role]) .shell.signed-in .brand-app-chevron{display:inline-flex}
body[data-role="admin"] .nav-brand-wrap.has-open .brand-app-chevron,
body[data-role="admin"] .nav-brand-wrap:has(.brand-app-menu.open) .brand-app-chevron,
body[data-role="admin"] .sb-brand-wrap.has-open .brand-app-chevron,
body[data-role="admin"] .sb-brand-wrap:has(.sb-brand-app-menu.open) .brand-app-chevron{
  transform:rotate(180deg);
}
/* ── Sidebar brand-app wrap ─────────────────────────
   Mirror of the nav-brand wrap (admin-only app switcher), used inside
   sb-header on T/V/H — the navbar chevron is hidden on those sizes
   so the affordance moves to the sidebar where the brand actually
   lives at those breakpoints. */
.sb-brand-wrap{
  display:inline-flex;align-items:center;gap:4px;position:relative;min-width:0;
}
body[data-role="admin"] .sb-brand-wrap{
  cursor:pointer;border-radius:9px;padding:2px 4px;
  transition:background var(--mk-t-fast);
}
body[data-role="admin"] .sb-brand-wrap:hover{
  background:color-mix(in srgb, var(--accent) 8%, transparent);
}
.sb-brand-app-menu{
  position:absolute;top:calc(100% + 6px);inset-inline-start:0;
}
/* `[hidden]` attribute needs to win over forced display flex below. */
.ar-success[hidden]{display:none !important}

/* ── Staging accent (B.1.1, revised) ────────────────
   Staging recolors the SHELL chrome to green so the user instantly
   knows they're not in production. The accent override deliberately
   does NOT bleed into the body's page content — `.page-view` and its
   descendants reset the accent back to the production value so the
   dashboard cards/charts read the same as in prod. */
body[data-env="staging"][data-role="admin"]{
  --accent:var(--mk-staging);
  --ring:color-mix(in srgb, var(--mk-staging) 30%, transparent);
}
/* Staging/preview PAGE-CONTENT accent resets (… .page-view …) live in
   env-theater.css — app-level overrides, deliberately outside the
   design-system closure. */
/* Brand mark color tokens — drive the inline-SVG icon's fills via CSS
   variables so staging can swap the whole palette without a second
   asset. Production defaults set on :root; the staging-admin selector
   overrides to the staging green (#1AB394) family. Pablo 2026-05-30. */
:root{
  --brand-bg:#22526A;
  --brand-accent:#03C9CB;
  --brand-fg:#ECEEEE;
}
/* Dark theme inverts the brand-mark tile + inner fill so the inline-SVG icon
   matches the DESIGNED dark logo (mirrors the mocca_icon_dark.svg <img> used on
   landing/splash): light tile, navy inner mark, cyan outline unchanged. The
   inline icon was the only brand surface that wasn't theme-differentiated, so
   it read as the same navy-tile logo in both modes. Pablo 2026-06-09. */
html[data-theme="dark"]{
  --brand-bg:#F3F4F6;
  --brand-fg:#22526A;
}
body[data-env="staging"][data-role="admin"]{
  /* Logo TILE background stays production navy in staging — only the
     accent flips green (Pablo 2026-06-01: "the background color of the
     logo should stay the same as production, only change the accent").
     Pinned explicitly so the new dark-theme tile flip above can't override
     it in staging+dark (this selector out-specifies html[data-theme]). */
  --brand-bg:#22526A;
  --brand-accent:var(--mk-staging);
  --brand-fg:#E8F5F1;
}
/* Dark+staging brand-mark flip (html[data-theme="dark"] body[…]) lives in
   env-theater.css — app-level override, outside the DS closure. */
/* Staging-mode brand accent — wordmark is now inline SVG, so we set
   --wordmark-accent directly (replaces the prior hue-rotate filter that
   approximated the staging green). The .brand-app-ic menu icons are still
   <img> and still need the filter. */
body[data-env="staging"][data-role="admin"]{
  --wordmark-accent: var(--mk-staging);
}
/* App-switcher menu icons (.brand-app-ic) deliberately carry NO env
   filter — the menu lists the APPS, and an app's logo is its production
   identity regardless of which environment the shell is showing.
   (Pablo 2026-06-11: "the app logos look weird depending on the
   environment — these should always be the production logos". Replaces
   the old hue-rotate approximation.) */

/* ── Preview accent (W3 #5) ─────────────────────────
   Preview = "viewing as another user, as of a point in time" (read-only).
   Mirrors the staging recolor but in PURPLE so the mode is unmistakable —
   no persistent banner; the purple chrome + the viewing-as avatar are the
   signal. The accent override stays on the SHELL chrome and the page header;
   body page content resets to the production cyan so the data reads true to
   what that user would actually see. Gated only on `data-env="preview"` —
   shell.js only sets that when the signed-in user has preview access, so no
   role selector is needed here. Pablo 2026-06-02. */
body[data-env="preview"]{
  --accent:var(--mk-preview);
  --ring:color-mix(in srgb, var(--mk-preview) 30%, transparent);
  --brand-accent:var(--mk-preview);
  --wordmark-accent:var(--mk-preview);
}
/* (No .brand-app-ic recolor in preview either — see the staging note
   above: app logos always read as production.) */
/* Preview-only profile-pop rows (Viewing as / As of) — collapsed unless
   the shell is actually in preview. Mirrors the .data-role-admin gate. */
[data-preview-only]{display:none !important}
body[data-env="preview"] [data-preview-only]{display:flex !important}
/* Rows that make no sense while previewing as another user — e.g. the
   Switch Profile row (the profile is irrelevant mid-preview; Pablo
   2026-06-11). Declared after the admin show rule so it wins at equal
   specificity. */
body[data-env="preview"] [data-hide-in-preview]{display:none !important}

/* ── As-of picker v2 (Pablo 2026-06-11) ─────────────
   Three modes: Now · Set date & time (snapshot, to the millisecond) ·
   Replay a range (plays the stored event log back as a session
   approximation). The two non-trivial picks expand an inline form
   inside the same .cs-menu. */
.cs-asof > .cs-menu{min-width:320px}
/* The As-of trigger label can get long ("Replay · Jun 12 14:00 → Jun 12 15:00")
   and was overflowing the profile pop. Truncate it. Pablo 2026-06-18. */
.cs-asof{min-width:0}
.cs-asof > .cs-btn{max-width:100%}
.cs-asof .cs-val{display:inline-block;max-width:190px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}
.shell.signed-in .nav .nav-profile .pop .row .cs-asof > .cs-btn .cs-val{flex:1;max-width:100%}
.cs-asof .cs-item .mi{font-size:16px;color:var(--fg-3);flex:none}
.cs-asof .cs-item.on .mi{color:var(--accent)}
.asof-form{display:flex;flex-direction:column;gap:10px;padding:8px}
.asof-hd{display:flex;align-items:center;gap:8px;font:700 12px var(--mk-font-ui);color:var(--fg-1)}
.asof-hd .mi{font-size:16px;color:var(--accent)}
.asof-note{font:500 11px/1.5 var(--mk-font-ui);color:var(--fg-3);text-wrap:pretty}
.asof-row{display:flex;flex-direction:column;gap:4px}
.asof-row > label{font:600 10px var(--mk-font-ui);color:var(--fg-2);text-transform:none;letter-spacing:.05em}
.asof-inputs{display:flex;align-items:center;gap:6px}
.asof-inputs input{
  border:1px solid var(--border);border-radius:8px;background:var(--bg-input);
  color:var(--fg-1);font:500 12px var(--mk-font-mono);padding:6px 8px;min-width:0;
}
.asof-inputs input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px var(--ring)}
.asof-inputs [data-asof-d]{flex:1.1}
.asof-inputs [data-asof-t]{flex:1.1}
.asof-inputs [data-asof-ms]{width:56px;flex:none}
.asof-ms-l{font:600 10px var(--mk-font-ui);color:var(--fg-3)}
.asof-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:2px}
.asof-btn{
  display:inline-flex;align-items:center;gap:4px;
  border:1px solid var(--border);border-radius:8px;background:transparent;
  color:var(--fg-2);font:600 12px var(--mk-font-ui);padding:7px 12px;cursor:pointer;
  transition:border-color var(--mk-t-fast),color var(--mk-t-fast);
}
.asof-btn:hover{border-color:var(--accent);color:var(--accent)}
.asof-btn .mi{font-size:15px}
.asof-btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}
.asof-btn-primary:hover{color:#fff;filter:brightness(1.06)}

/* ── Session-replay bar (As-of range) ───────────────
   Fixed playback chrome while a range replay runs: play/pause, a
   scrubbable timeline with event markers, the mocked timestamp (ms
   precision), speed, and stop. Purple-trimmed so it reads as preview
   machinery, not page content. */
.replay-bar{
  position:fixed;bottom:16px;left:50%;transform:translateX(-50%);
  z-index:760;
  display:flex;align-items:center;gap:12px;
  width:min(760px, calc(100vw - 24px));
  padding:10px 12px;border-radius:14px;
  background:var(--bg-1);
  border:1px solid color-mix(in srgb, var(--mk-preview) 50%, var(--border));
  box-shadow:var(--shadow-lg), 0 0 0 4px color-mix(in srgb, var(--mk-preview) 12%, transparent);
}
.rb-btn{
  display:inline-flex;align-items:center;justify-content:center;
  width:32px;height:32px;flex:none;
  border:1px solid var(--border);border-radius:9px;background:transparent;
  color:var(--fg-2);cursor:pointer;
  transition:border-color var(--mk-t-fast),color var(--mk-t-fast);
}
.rb-btn:hover{border-color:var(--mk-preview);color:var(--mk-preview)}
.rb-btn .mi{font-size:18px}
.rb-play{background:var(--mk-preview);border-color:var(--mk-preview);color:#fff}
.rb-play:hover{color:#fff;filter:brightness(1.05)}
.rb-speed{width:auto;padding:0 10px;font:700 11px var(--mk-font-mono)}
.rb-mid{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
.rb-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.rb-now{font:600 11px var(--mk-font-mono);color:var(--fg-1);white-space:nowrap}
.rb-event{font:600 11px var(--mk-font-ui);color:var(--mk-preview);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rb-track{position:relative;height:6px;border-radius:999px;background:color-mix(in srgb, var(--mk-preview) 16%, var(--bg-2));cursor:pointer}
.rb-fill{position:absolute;inset:0 auto 0 0;width:0;border-radius:999px;background:var(--mk-preview);pointer-events:none}
.rb-mark{
  position:absolute;top:50%;width:4px;height:4px;border-radius:50%;
  background:var(--bg-1);transform:translate(-50%,-50%);pointer-events:none;
  box-shadow:0 0 0 1px color-mix(in srgb, var(--mk-preview) 60%, transparent);
}
.rb-head{
  position:absolute;top:50%;width:12px;height:12px;border-radius:50%;
  background:var(--bg-1);border:3px solid var(--mk-preview);
  transform:translate(-50%,-50%);pointer-events:none;
}
@keyframes replayPulse{
  0%{box-shadow:0 0 0 0 color-mix(in srgb, var(--mk-preview) 60%, transparent)}
  70%{box-shadow:0 0 0 14px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}
.replay-pulse{animation:replayPulse 1.4s var(--mk-ease)}
@media (max-width:720px){ .rb-now{display:none} }

/* ── Brand app dropdown (B.1.3) ────────────────────
   The whole .nav-brand-wrap is the hit target (admin-only). The
   chevron + popover live inside the wrap (.nav-brand-wrap is
   position:relative). */
.brand-app-menu{
  position:absolute;top:calc(100% + 6px);inset-inline-start:0;
  z-index:300;min-width:260px;
  background:var(--bg-1);border:1px solid var(--border);border-radius:12px;
  box-shadow:var(--shadow-pop);
  padding:6px;display:none;flex-direction:column;gap:2px;
}
.brand-app-menu.open{display:flex}
.brand-app-item{
  display:flex;align-items:center;gap:10px;
  padding:8px 10px;border-radius:8px;border:0;background:transparent;
  cursor:pointer;text-align:start;color:var(--fg-1);
  transition:background var(--mk-t-fast);
}
.brand-app-item:hover{background:color-mix(in srgb, var(--accent) 10%, transparent)}
.brand-app-ic{width:26px;height:26px}
.brand-app-ic.dark{display:none}
html[data-theme="dark"] .brand-app-ic.light{display:none}
html[data-theme="dark"] .brand-app-ic.dark{display:block}
.brand-app-ic-mi{
  font-size:22px;width:26px;height:26px;
  display:inline-flex;align-items:center;justify-content:center;
  color:var(--accent);
}
.brand-app-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.brand-app-l{font:600 13px var(--mk-font-ui);color:var(--fg-1)}
.brand-app-s{font:400 11px var(--mk-font-ui);color:var(--fg-3)}
.brand-app-check{
  font-size:16px;color:var(--accent);margin-inline-start:auto;
  display:none;
}
.brand-app-item.on .brand-app-check{display:inline-flex}

/* B63 W9 (148) — the sub-domain section appended below the three apps.
   Same row grammar as .brand-app-item; the dot replaces the app icon since a
   sub-domain has no logo of its own, only a colour override. */
.brand-app-sep{height:1px;background:var(--border);margin:4px 2px}
.brand-app-sub-dot{
  width:14px;height:14px;border-radius:50%;flex:0 0 auto;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.08);
}
.brand-app-sub-dot-base{
  width:26px;height:26px;border-radius:0;box-shadow:none;
  display:inline-flex;align-items:center;justify-content:center;
  font-size:18px;color:var(--fg-3);background:transparent;
}

/* ── Env segmented (B.1.1) inside the profile pop ──
   Same footprint as the theme chips (Pablo: "the selector is too big —
   compare it to the theme selector"): 28×26 icon-only chips, 2px gutter,
   no outer border — the .on state carries the accent tint. */
.env-seg{
  display:inline-flex;gap:2px;background:var(--bg-input);
  border-radius:7px;padding:2px;
}
.env-seg button{
  all:unset;width:28px;height:26px;
  display:inline-flex;align-items:center;justify-content:center;
  border-radius:5px;cursor:pointer;color:var(--fg-2);
}
.env-seg button .mi{font-size:16px}
.env-seg button.on{background:color-mix(in srgb, var(--accent) 16%, transparent);color:var(--accent)}

/* ── Profile switcher (B.1.2) inside the profile pop ──
   A floating .cs dropdown labeled "Switch Profile" (behaves like the
   Language selector); the active persona shows as avatar + nickname on
   the button, and the menu lists the avatar rows. placeCsMenu's
   horizontal clamp keeps the wide menu on-screen so it never clips.
   Pablo 2026-06-01 #2 round 2. */
/* B50-27 (Pablo: "This box overflows, let's truncate the names if they exceed a
   particular width") — `.cs-val{flex:1}` resolves to `flex:1 1 0%`, but a flex
   item's default `min-width:auto` still floors it at its min-content width, so a
   long display name pushed the whole switcher past the popover edge instead of
   ellipsing. Zero the floor at every level of the chain. */
.cs-profile{min-width:0;max-width:100%}
.cs-profile > .cs-btn{gap:8px;min-width:0;max-width:100%}
.cs-av-btn{
  width:20px;height:20px;border-radius:999px;flex:none;
  object-fit:cover;background:var(--bg-3);
}
.cs-profile .cs-val{
  flex:1 1 0;min-width:0;text-align:start;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-weight:600;
}
/* The persona rows inside the menu truncate the same way. */
.persona-menu .persona-row{min-width:0}
.persona-menu .persona-info{min-width:0;flex:1 1 0}
.persona-menu .persona-nick,.persona-menu .persona-email{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
/* Menu wide enough for an avatar + nick + email row, but capped so it
   stays inside a narrow viewport (placeCsMenu also clamps horizontally). */
/* Menu wide enough for an avatar + nick + email row, but capped so it
   stays inside a narrow viewport (placeCsMenu also clamps horizontally).
   Height-capped like the Filters-panel dropdowns — long rosters scroll
   inside the box and the Wave-A fit/`data-cs-search` rule supplies the
   in-menu search (Pablo 2026-06-12). */
.persona-menu{
  min-width:230px;max-width:min(280px, calc(100vw - 24px));
  max-height:min(320px, calc(100dvh - 120px));
  overflow-y:auto;scrollbar-width:thin;
}
.persona-row{
  display:flex;align-items:center;gap:10px;
  padding:6px 8px;border-radius:9px;
  background:transparent;border:0;cursor:pointer;
  text-align:start;transition:background var(--mk-t-fast);
  width:100%;
}
.persona-row:hover{background:var(--bg-2)}
.persona-row.on{background:color-mix(in srgb, var(--accent) 10%, transparent)}
.persona-av{
  width:30px;height:30px;border-radius:999px;flex:none;
  background-size:cover;background-position:center;
  border:2px solid transparent;
}
.persona-row.on .persona-av{border-color:var(--accent)}
.persona-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.persona-nick{font:600 12px var(--mk-font-ui);color:var(--fg-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.persona-email{font:400 10px var(--mk-font-ui);color:var(--fg-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.persona-on{font-size:16px;color:var(--accent);margin-inline-start:auto}

/* ── Chart lock overlay (B.4) ──────────────────────
   Sits on every chart-card; visible only when `body[data-role="member-
   locked-charts"]`. Semi-opaque scrim + centered lock icon + Request
   access CTA. */
.chart-card.chart-host{position:relative}
.chart-lock{
  position:absolute;inset:0;
  display:none;flex-direction:column;align-items:center;justify-content:center;gap:12px;
  background:color-mix(in srgb, var(--bg-1) 78%, transparent);
  backdrop-filter:blur(2px);
  border-radius:inherit;z-index:5;
}
body[data-role="member-locked-charts"] .chart-lock{display:flex}
.chart-lock-icon{
  font-size:36px;color:var(--fg-3);
  background:color-mix(in srgb, var(--accent) 10%, transparent);
  border-radius:50%;padding:14px;
}
.chart-lock-cta{
  display:inline-flex;align-items:center;gap:6px;
  padding:8px 14px;border-radius:8px;
  background:var(--accent);color:var(--accent-fg);border:0;cursor:pointer;
  font:600 12px var(--mk-font-ui);
}
.chart-lock-cta:hover{filter:brightness(1.06)}

/* ── Access request modal (B.4 / B.5) ──────────────── */
.ar-hint{font:500 13px/1.5 var(--mk-font-ui);color:var(--fg-2);margin:0 0 10px 0}
.ar-msg{
  width:100%;padding:10px 12px;
  background:var(--bg-1);border:1px solid var(--border);border-radius:9px;
  color:var(--fg-1);font:500 13px var(--mk-font-ui);
  resize:vertical;min-height:90px;
}
.ar-msg:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--ring)}
.ar-msg.is-error{border-color:var(--mk-danger);box-shadow:0 0 0 3px rgba(255,90,90,.18)}
.ar-success{
  display:flex;flex-direction:column;align-items:center;gap:8px;
  padding:24px 16px;color:var(--accent);
}
.ar-success .mi{font-size:44px}
.ar-success-l{font:600 14px var(--mk-font-ui);color:var(--fg-1)}

/* ── Profile editor PANEL (B.2) — slide-in, Pablo 2026-06-01 #3 ── */
.pe-panel{width:min(480px, 100vw);min-width:0}
.panel-card.panel-card-wide{width:min(720px, calc(100vw - 32px));max-height:calc(100dvh - 32px)}
/* A .cs rendered as a full-width form control. */
.pe-cs{display:block;width:100%}
.pe-cs > .cs-btn{width:100%;height:38px;justify-content:space-between;border-radius:8px;background:var(--bg-1);font:500 13px var(--mk-font-ui);padding:0 11px}
.pe-cs .cs-val{flex:1;text-align:start;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pe-cs .cs-val.empty{color:var(--fg-3)}
.cs-flag{font-size:16px;line-height:1;flex:none}
/* Round-cropped flag thumbnails (flagcdn) — mirrors panels.css .cb-opt-flag
   so the profile form matches the Filter condition inputs' image flags
   (Pablo 2026-06-11: emoji glyphs never cropped). */
img.cs-flag{
  width:18px;height:18px;border-radius:50%;object-fit:cover;
  background:var(--bg-2);
  box-shadow:0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent) inset;
}
.cs-item-l{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Composite phone — flag picker · dial code · number. */
.pe-phone{display:flex;align-items:stretch;border:1px solid var(--border);border-radius:8px;background:var(--bg-1);overflow:hidden;height:38px}
.pe-phone:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--ring)}
.pe-phone-cc{flex:none;width:auto}
.pe-phone-cc > .cs-btn{height:36px;border:0;border-radius:0;background:var(--bg-2);padding:0 8px;gap:3px}
.pe-phone-dial{display:inline-flex;align-items:center;padding:0 8px;color:var(--fg-2);font:600 13px var(--mk-font-mono);background:var(--bg-2);border-inline-end:1px solid var(--border)}
.pe-phone-num{flex:1;min-width:0;border:0;outline:none;background:transparent;padding:0 10px;color:var(--fg-1);font:500 13px var(--mk-font-ui)}
/* Tag fields (skills / languages). */
.pe-tags{display:flex;flex-direction:column;gap:10px}
.pe-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:22px}
.pe-chips-empty{color:var(--fg-3);font:italic 500 12px var(--mk-font-ui)}
.pe-chip{display:inline-flex;align-items:center;gap:2px;padding:4px 4px 4px 11px;border-radius:999px;background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);font:600 12px var(--mk-font-ui)}
.pe-chip button{display:inline-flex;align-items:center;justify-content:center;width:17px;height:17px;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer;padding:0}
.pe-chip button:hover{background:color-mix(in srgb,var(--accent) 24%,transparent)}
.pe-chip button .mi{font-size:13px}
.pe-tag-cs{display:inline-block;align-self:flex-start}
.pe-tag-cs > .cs-btn.pe-tag-add{height:32px;border:1px dashed color-mix(in srgb,var(--accent) 40%,var(--border));color:var(--accent);background:transparent;gap:4px;border-radius:8px}
.pe-tag-cs > .cs-btn.pe-tag-add .mi{font-size:16px;color:var(--accent)}
.pe-tag-cs > .cs-btn.pe-tag-add:hover{background:color-mix(in srgb,var(--accent) 8%,transparent)}
/* Collection card-lists (social / experience / education). */
.pe-cards{display:flex;flex-direction:column;gap:6px}
.pe-cards-empty{padding:10px 12px;color:var(--fg-3);font:500 12px var(--mk-font-ui);border:1px dashed var(--border);border-radius:9px;text-align:center}
.pe-card{display:flex;align-items:center;gap:10px;width:100%;text-align:start;padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg-1);cursor:pointer;transition:border-color var(--mk-t-fast),background var(--mk-t-fast)}
.pe-card:hover{border-color:var(--accent);background:var(--bg-2)}
.pe-card-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.pe-card-title{font:600 13px var(--mk-font-ui);color:var(--fg-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pe-card-sub{font:500 11px var(--mk-font-ui);color:var(--fg-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pe-card-rm{flex:none;width:24px;height:24px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--fg-3);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
.pe-card-rm:hover{color:var(--mk-danger);border-color:var(--mk-danger)}
/* 44px hit-target floor (Pablo, 2026-08-26). The card itself is clickable, so
   this expansion is VERTICAL ONLY — a horizontal one would overlap the card and
   turn an edge tap into a delete. 34 + 5px top/bottom = 44 tall, 40 wide. */
.pe-card-rm{position:relative;width:40px;height:34px}
.pe-card-rm::after{content:'';position:absolute;inset:-5px 0}
.pe-card-rm .mi{font-size:14px}
.pe-card-chev{color:var(--fg-3);font-size:18px;flex:none}
/* B63 W7 (066) — company logo on an Experience card. Same fallback
   convention as company-panel.js's mark(): a real square mark when the
   company has one, else a generic icon — never an invented monogram. */
.pe-card-logo{flex:none;width:28px;height:28px;border-radius:7px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:var(--bg-2);border:1px solid var(--border)}
.pe-card-logo img{width:100%;height:100%;object-fit:contain}
.pe-card-logo.is-fb .mi{font-size:16px;color:var(--fg-3)}
html[dir="rtl"] .pe-card-chev::before{content:"chevron_left"}
.pe-subsection{margin-top:8px;padding-top:14px;border-top:1px solid var(--divider)}
/* Per-item sub-sheet — modal (desktop) / bottom sheet (mobile-V) / side panel (mobile-H). */
.pe-sheet-overlay{position:fixed;inset:0;z-index:600;display:flex;align-items:center;justify-content:center;padding:16px;background:var(--scrim);opacity:0;pointer-events:none;transition:opacity 200ms var(--mk-ease)}
.pe-sheet-overlay.open{opacity:1;pointer-events:auto}
.pe-sheet{display:flex;flex-direction:column;width:min(460px,100%);max-height:calc(100dvh - 32px);background:var(--bg-1);border:1px solid var(--border);border-radius:14px;box-shadow:var(--shadow-lg);transform:translateY(8px) scale(.98);transition:transform 200ms var(--mk-ease)}
.pe-sheet-overlay.open .pe-sheet{transform:none}
.pe-sheet-body{flex:1;min-height:0;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px}
.pe-sheet-rm{color:var(--mk-danger);border-color:transparent;margin-inline-end:auto;gap:4px}
.pe-sheet-rm:hover{border-color:var(--mk-danger)}
.pe-sheet-rm .mi{font-size:16px}
@media (max-width:600px) and (orientation:portrait){
  .pe-sheet-overlay{align-items:flex-end;padding:0}
  .pe-sheet{width:100%;max-height:88dvh;border-radius:18px 18px 0 0;transform:translateY(100%)}
  .pe-sheet-overlay.open .pe-sheet{transform:none}
}
@media (max-height:599px) and (orientation:landscape){
  .pe-sheet-overlay{align-items:stretch;justify-content:flex-end;padding:0}
  .pe-sheet{height:100%;max-height:100%;width:min(440px,92vw);border-radius:0;transform:translateX(100%)}
  .pe-sheet-overlay.open .pe-sheet{transform:none}
  html[dir="rtl"] .pe-sheet{transform:translateX(-100%)}
}
#profileEditModal .panel-body{
  /* No fixed max-height — flex:1 + min-height:0 (from .panel-body) lets
     the body shrink to whatever the card allows and scroll internally.
     The old `max-height:60vh` over-grew the body in short landscape
     viewports so the footer + content spilled past the card with no
     scroll. Pablo 2026-06-01. */
  flex:1;min-height:0;overflow-y:auto;
  display:flex;flex-direction:column;gap:18px;
}
.pe-section{display:flex;flex-direction:column;gap:8px}
/* Section header — accent icon chip + sentence-case label + trailing
   hairline (Pablo 2026-06-11: the all-caps gray row read too plain). */
.pe-section-hd{
  display:flex;align-items:center;gap:10px;
  font:700 13px var(--mk-font-ui);letter-spacing:.01em;color:var(--fg-1);
}
.pe-section-hd .mi{
  font-size:15px;color:var(--accent);
  width:26px;height:26px;flex:none;border-radius:8px;
  display:inline-flex;align-items:center;justify-content:center;
  background:color-mix(in srgb, var(--accent) 11%, transparent);
}
.pe-section-hd::after{content:"";flex:1;height:1px;background:var(--divider);margin-inline-start:2px}
.pe-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
/* B51-03 — `pe-field-full` was emitted by peTextField/sfText from the start but
   never had a rule, so the Bio and every "full" textarea sat in one column. */
.pe-field-full{grid-column:1 / -1}
@media (max-width: 720px){ .pe-grid{grid-template-columns:1fr} }
.pe-field{display:flex;flex-direction:column;gap:4px}
.pe-field > label{
  font:600 11px var(--mk-font-ui);color:var(--fg-2);text-transform:none;letter-spacing:.04em;
  display:flex;align-items:center;justify-content:space-between;gap:6px;
}
/* B63 W7 (057) — the per-field translate button that replaces the old
   page-level EN/ES toggle (see cv-editor.js openTranslateModal). */
.pe-translate-btn{flex:none;width:20px;height:20px;border-radius:6px;border:0;background:none;color:var(--fg-3);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
.pe-translate-btn:hover{color:var(--accent);background:var(--bg-2)}
.pe-translate-btn .mi{font-size:15px}
.pe-field input, .pe-field textarea{
  width:100%;padding:8px 10px;background:var(--bg-1);border:1px solid var(--border);
  border-radius:8px;color:var(--fg-1);font:500 13px var(--mk-font-ui);
}
.pe-field input:focus, .pe-field textarea:focus{
  outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--ring);
}
.pe-field textarea{min-height:64px;resize:vertical}
/* B63 W7 (065) — "I don't like the 'blank = Present' placeholder. Maybe just
   a checkbox or toggle that I currently work here." A plain labeled
   checkbox, matching the form's field chrome. */
.pe-check{display:flex;align-items:center;gap:8px;cursor:pointer;padding:6px 2px;font:500 13px var(--mk-font-ui);color:var(--fg-1)}
.pe-check input{width:16px;height:16px;accent-color:var(--accent);cursor:pointer}
/* B63 W7 (063) — "Direct reports should be a number selector … we already
   have" — the −/{n}/+ stepper, lifted from the cond-builder's WF-offset
   control (panels.js cb-wf-offset-step) so Direct reports uses the same
   pattern instead of a bare number input. */
.pe-stepper{display:flex;align-items:center;gap:8px}
.pe-stepper button{width:32px;height:32px;border-radius:8px;border:1px solid var(--border);background:var(--bg-1);color:var(--fg-1);font:700 16px var(--mk-font-ui);cursor:pointer;display:flex;align-items:center;justify-content:center}
.pe-stepper button:hover{background:var(--bg-2)}
.pe-stepper-val{min-width:28px;text-align:center;font:700 14px var(--mk-font-mono);cursor:pointer;padding:4px 2px;border-radius:6px}
.pe-stepper-val:hover{background:var(--bg-2)}
.pe-stepper-inp{width:60px;padding:6px 8px;background:var(--bg-1);border:1px solid var(--accent);border-radius:8px;color:var(--fg-1);font:700 14px var(--mk-font-mono);text-align:center}
/* Month-field trigger — opens the reused date picker. Matches the form's
   text inputs (bg/border/radius) with a leading calendar glyph + chevron. */
.pe-date-btn{
  width:100%;display:flex;align-items:center;gap:8px;
  padding:8px 10px;background:var(--bg-1);border:1px solid var(--border);
  border-radius:8px;color:var(--fg-1);font:500 13px var(--mk-font-ui);
  cursor:pointer;text-align:start;
  transition:border-color var(--mk-t-fast),box-shadow var(--mk-t-fast);
}
.pe-date-btn:hover{border-color:color-mix(in srgb, var(--accent) 35%, var(--border))}
.pe-date-btn:focus-visible{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--ring)}
.pe-date-btn .pe-date-ic{color:var(--accent);font-size:18px;flex-shrink:0}
.pe-date-btn .pe-date-val{flex:1;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.pe-date-btn .pe-date-val.empty{color:var(--fg-3);font-style:italic}
.pe-date-btn .cs-arrow{color:var(--fg-3);font-size:18px;flex-shrink:0}
.pe-list{display:flex;flex-direction:column;gap:6px}
.pe-item{
  display:grid;grid-template-columns:1fr auto;gap:8px;
  padding:8px 10px;border:1px solid var(--border);border-radius:9px;background:var(--bg-1);
}
.pe-item-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.pe-item-grid input{
  padding:6px 8px;background:var(--bg-2);border:1px solid transparent;border-radius:6px;
  color:var(--fg-1);font:500 12px var(--mk-font-ui);
}
.pe-item-grid input:focus{outline:none;border-color:var(--accent);background:var(--bg-1)}
.pe-item-rm{
  width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:transparent;
  color:var(--fg-3);cursor:pointer;align-self:start;
}
.pe-item-rm:hover{color:var(--mk-danger);border-color:var(--mk-danger)}
.pe-item-rm{position:relative;width:40px;height:34px}
.pe-item-rm::after{content:'';position:absolute;inset:-5px 0}
.pe-item-rm .mi{font-size:14px}
.pe-add{
  align-self:flex-start;display:inline-flex;align-items:center;gap:6px;
  padding:6px 10px;border-radius:7px;
  background:transparent;border:1px dashed color-mix(in srgb, var(--accent) 55%, var(--border));
  color:var(--accent);cursor:pointer;font:600 11px var(--mk-font-ui);
}
.pe-add:hover{background:color-mix(in srgb, var(--accent) 8%, transparent)}
/* 44px hit-target floor (Pablo, 2026-08-26) — the profile editor's own controls.
   Inputs take the floor for real (44px is the standard field height and reads
   correctly); the add button keeps its size and grows its target. */
.pe-add{position:relative;min-height:40px}
.pe-add::after{content:'';position:absolute;inset:-2px 0}
.pe-sheet input:not([type="checkbox"]):not([type="radio"]),
.pe-sheet select,.pe-sheet textarea,
.pe-field input,.pe-field select,.pe-field textarea,
.pe-inp,.pe-body .ent-input{min-height:44px}


/* ═══ B51-03 · My account › CV ══════════════════════════════════════════════
   The CV section inside the profile editor. It reuses .pe-section / .pe-card /
   .pe-grid wholesale; these rules only cover what is new: the language switch,
   the "what this is" note, the per-section language tag and the stat strip. */
.pe-cv .pe-cv-hd-r{display:flex;align-items:center;gap:8px;flex:none}
.pe-cv-lang{display:inline-flex;gap:2px;padding:2px;border:1px solid var(--border);border-radius:999px;background:var(--bg-2)}
.pe-cv-lang button{min-width:38px;height:24px;border:0;border-radius:999px;background:transparent;cursor:pointer;
  color:var(--fg-3);font:700 10.5px/1 var(--mk-font-ui);letter-spacing:.04em}
.pe-cv-lang button:hover{color:var(--fg-1)}
.pe-cv-lang button.on{background:var(--accent);color:var(--accent-contrast,#fff)}
.pe-cv-open{display:inline-flex;align-items:center;gap:5px;height:26px;padding:0 10px;border:1px solid var(--border);
  border-radius:999px;background:var(--bg-1);color:var(--fg-2);font:600 11px/1 var(--mk-font-ui);text-decoration:none}
.pe-cv-open:hover{border-color:var(--accent);color:var(--accent)}
.pe-cv-open .mi{font-size:14px}
.pe-cv-note{margin:10px 0 0;font:500 11.5px/1.55 var(--mk-font-ui);color:var(--fg-3);max-width:76ch;text-wrap:pretty}
.pe-cv-note b{color:var(--fg-2);font-weight:700}
.pe-cv-reset{border:0;background:transparent;padding:0;cursor:pointer;color:var(--accent);
  font:600 11.5px/1.55 var(--mk-font-ui);text-decoration:underline;text-underline-offset:2px}
.pe-cv-stats{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0 14px}
.pe-cv-stat{display:inline-flex;align-items:baseline;gap:5px;padding:4px 10px;border-radius:999px;background:var(--bg-3);
  color:var(--fg-3);font:600 10.5px/1.3 var(--mk-font-ui)}
.pe-cv-stat b{color:var(--fg-1);font:800 12px/1 var(--mk-font-ui)}
.pe-cv-tag{flex:none;padding:2px 8px;border-radius:999px;background:color-mix(in srgb,var(--accent) 14%,transparent);
  color:var(--accent);font:700 9.5px/1.6 var(--mk-font-ui);letter-spacing:.06em;text-transform:uppercase}
/* ═══ B53-11 — VERY SHORT MOBILE LANDSCAPE ════════════════════════════════
   Pablo: "On Mobile landscape, there could be very short heights, I'd rather
   have very small icons in the column than having the profile button missing
   or cropped."

   The rail is a fixed 44px grid (5 cells = 236px + 16px of gutters + 8px of
   padding = 260px). On a 320px-tall landscape phone that fits; on the 240–280px
   viewports a browser with visible chrome actually gives you, the last cell —
   the profile button — falls off the bottom, because `align-content:space-between`
   on an overflowing grid clips instead of shrinking.

   So the CELL shrinks. Two tiers, both keeping every control present and
   centred: nothing is ever dropped, hidden or cropped. This deliberately goes
   under the 44px floor — Pablo's explicit instruction, and the alternative is a
   control the user cannot reach at all. */
@media (max-height:439px) and (orientation:landscape){
  .shell.signed-out .nav,
  .shell.signed-in .nav{ grid-auto-rows:38px;gap:3px;padding:3px 0; }
  .shell.signed-out .nav-brand,
  .shell.signed-in .nav-brand{ height:38px; }
  .shell.signed-out .nav-brand-wrap,
  .shell.signed-in .nav-brand-wrap{ min-height:38px; }
  /* B59 — the mark tracks the pill size in the short bands too, so it never
     becomes the smallest thing on the rail again. */
  .shell.signed-out .nav-brand .icon,
  .shell.signed-in .nav-brand .icon{ width:31px;height:31px; }
  .nav-right.auth-in .nav-pill{ width:38px;height:38px;border-radius:10px; }
  .nav-right.auth-in .nav-pill .mi{ font-size:17px; }
  .shell.signed-in .nav .nav-profile{ width:38px;height:38px;border-radius:10px; }
  .shell.signed-in .nav .nav-profile .av,
  .shell.signed-in .nav .nav-profile img{ width:28px;height:28px;font-size:10px; }
  .nav-right.auth-in .nav-pill .badge{ font-size:8.5px;min-width:14px;height:14px; }
}
@media (max-height:359px) and (orientation:landscape){
  .shell.signed-out .nav,
  .shell.signed-in .nav{ grid-auto-rows:32px;gap:2px;padding:2px 0; }
  .shell.signed-out,
  .shell.signed-in{ grid-template-columns:44px 1fr !important; }
  .shell.signed-out .nav,
  .shell.signed-in .nav{ width:44px;grid-template-columns:32px; }
  .shell.signed-out .nav-brand,
  .shell.signed-in .nav-brand{ height:32px; }
  .shell.signed-out .nav-brand-wrap,
  .shell.signed-in .nav-brand-wrap{ min-height:32px; }
  .shell.signed-out .nav-brand .icon,
  .shell.signed-in .nav-brand .icon{ width:26px;height:26px; }
  .nav-right.auth-in .nav-pill{ width:32px;height:32px;border-radius:9px; }
  .nav-right.auth-in .nav-pill .mi{ font-size:15px; }
  .shell.signed-in .nav .nav-profile{ width:32px;height:32px;border-radius:9px; }
  .shell.signed-in .nav .nav-profile .av,
  .shell.signed-in .nav .nav-profile img{ width:24px;height:24px;font-size:9px; }
  /* The right-hand tab rail gives up its label at this height so the page keeps
     usable width; the icon alone still identifies each tab, and the tooltip
     carries the name. */
  .shell.signed-in .tabs{ width:44px;padding:4px; }
  .shell.signed-in .tab{ font-size:0;padding:6px 2px;gap:0; }
  .shell.signed-in .tab .mi{ font-size:18px; }
}
.pe-cv-tag.shared{background:var(--bg-3);color:var(--fg-3)}
.pe-cv-sub{margin-top:18px}
.pe-cv-adopt{margin-top:6px;border:1px dashed var(--border);background:var(--bg-2);border-radius:8px;cursor:pointer;
  padding:6px 10px;color:var(--fg-2);font:600 11px/1.35 var(--mk-font-ui);text-align:start;width:100%}
.pe-cv-adopt:hover{border-color:var(--accent);color:var(--accent)}
.pe-bullets{width:100%;min-height:132px;resize:vertical;font:500 12.5px/1.6 var(--mk-font-ui)}
@media (max-width:720px){
  .pe-cv .pe-section-hd{flex-wrap:wrap}
  .pe-cv .pe-cv-hd-r{width:100%;justify-content:flex-start;order:3}
  .pe-cv-lang{padding:3px}
  .pe-cv-lang button{min-width:48px;height:38px}
  .pe-cv-open{height:44px;padding:0 14px}
}

/* ══ B61 — the profile is ONE form ═══════════════════════════════════════════
   Pablo: "The CV shouldn't be a section on the profile. You're repeating fields.
   Everything (contact info, user info, CV info) should be on the same form in the
   profile." The .pe-cv wrapper section is gone (cv-editor.js), so its inner
   .pe-cv-sub / .pe-cv-tag decorations have nothing left to decorate; what remains
   here is the small set of new controls that replaced the repeats. */
/* email is the sign-in key and cannot be typed over (Pablo) */
.pe-field-locked label{display:flex;align-items:center;gap:6px}
.pe-lock-mi{font-size:14px;color:var(--fg-3)}
.pe-locked{background:var(--bg-2)!important;color:var(--fg-2)!important;cursor:not-allowed;
  -webkit-text-fill-color:var(--fg-2)}
.pe-lock-why{margin-top:5px;font:500 10.5px/1.4 var(--mk-font-ui);color:var(--fg-3)}
/* residency: one pick, and the country + city READ off it */
.pe-derived{gap:0}
.pe-derived-v{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:38px;
  padding:8px 10px;border:1px dashed var(--border);border-radius:8px;background:var(--bg-2);
  font:600 12.5px/1.4 var(--mk-font-ui);color:var(--fg-1)}
.pe-derived-v .cs-flag{width:18px;height:auto;border-radius:2px;flex:none}
.pe-derived-line{font:500 11px/1.4 var(--mk-font-ui);color:var(--fg-3)}
.pe-derived-empty{color:var(--fg-3);font-weight:500}
.pe-derived-empty .mi{font-size:16px}
/* B61 (Pablo: "On the phone number input, the country code doesn't need to have
   it's own box. It can show up within the same box as the number input but not
   editable. The only way to change a country code of a phone number is by
   selecting the country on the dropdown") — the flag+dial trigger is now flush
   inside the one field: no fill of its own, no divider, and the dial is a plain
   mark rather than a second boxed control. */
.pe-phone-cc > .cs-btn{background:none;padding:0 4px 0 10px;gap:5px}
.pe-phone-cc > .cs-btn .cs-arrow{font-size:15px;color:var(--fg-3)}
.pe-phone-cc .pe-phone-dial{background:none;border:0;padding:0;color:var(--fg-2)}
.pe-phone-num{padding-inline-start:4px}
/* B61 — a searchable menu that opened UPWARD puts its search at the bottom, so
   the field the user just left is the nearest thing to it (Pablo).
   B62 W-70 — and it STAYS there. `margin-top:auto` on the last flex item pushes it
   to the box's bottom edge whenever there is slack (a filtered list shorter than the
   box), and auto margins resolve to zero when there is none, so a long list still
   scrolls from its first row. Measured: 6px from the selector unfiltered, 6px with
   two rows left — it was drifting to 14.5px. */
.cs.cs-up > .cs-menu{padding-bottom:0}
.cs.cs-up > .cs-menu > .cs-search{top:auto;bottom:0;margin-top:auto;
  border-bottom:0;border-top:1px solid var(--divider)}

/* ═══ B64-03 — the profile is a PANEL ══════════════════════════════════
   Pablo: "I want a panel for the profile because there's a lot of configs
   right now, a dropdown modal is not going to cut it". Same contents as the
   old dropdown, grouped under section heads, following the Mocca panel
   grammar: side panel (desktop) · slide-up bottom sheet with rounded top
   corners (tablet) · full screen (phones, both orientations). */
#profilePop.profile-panel{
  position:fixed;top:0;bottom:0;inset-inline-end:0;inset-inline-start:auto;z-index:1400;
  width:min(440px,100vw);max-height:none;height:100dvh;margin:0;padding:0;border-radius:0;
  display:flex;flex-direction:column;overflow:hidden;visibility:hidden;
  background:var(--bg-1);border:0;border-inline-start:1px solid var(--border);
  box-shadow:-24px 0 64px rgba(13,37,48,.20);
  transform:translateX(110%);transition:transform .3s cubic-bezier(.32,.72,.28,1),visibility 0s linear .3s;
}
html[dir="rtl"] #profilePop.profile-panel{ transform:translateX(-110%); }
#profilePop.profile-panel.open{ visibility:visible;transform:none;transition:transform .3s cubic-bezier(.32,.72,.28,1); }
/* Audit A11 — (1,2,1) beat (1,2,0): in Arabic the profile panel never slid in, at any size. */
html[dir="rtl"] #profilePop.profile-panel.open{ transform:none; }
.pp-hd{ display:flex;align-items:center;gap:10px;padding:12px 14px;flex:0 0 auto;border-bottom:1px solid var(--border);background:var(--bg-2);position:relative; }
.pp-hd > .mi{ font-size:22px;color:var(--accent-text); }
.pp-hd b{ flex:1;font:700 15px var(--mk-font-ui);color:var(--fg-1); }
.pp-body{ flex:1;min-height:0;overflow:auto;padding:10px 12px 18px; }
.pp-sec-h{ margin:14px 10px 4px;font:700 10.5px var(--mk-font-ui);letter-spacing:.08em;text-transform:uppercase;color:var(--fg-3); }
#profilePop.profile-panel .row{ min-height:44px; }
#profilePop.profile-panel .row > .cs{ max-width:62%; }
.pp-scrim{ position:fixed;inset:0;z-index:1399;background:var(--scrim);opacity:0;pointer-events:none;
  transition:opacity 200ms var(--mk-ease);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px); }
.pp-scrim.open{ opacity:1;pointer-events:auto; }
.pv-app-av{ display:inline-flex !important;align-items:center;justify-content:center;background:var(--accent-soft) !important;color:var(--accent-text); }
.pv-app-av .mi{ font-size:16px; }
.cs-pvapp-ic{ font-size:16px;color:var(--accent-text);margin-inline-end:6px; }
/* tablet: a big slide-up bottom sheet */
@media (max-width:1024px) and (min-width:601px) and (min-height:600px){
  #profilePop.profile-panel{ inset-inline:0;top:auto;bottom:0;width:100%;height:min(92dvh, calc(100dvh - 44px));
    border-inline-start:0;border-top:1px solid var(--border);border-radius:20px 20px 0 0;transform:translateY(102%);
    box-shadow:0 -18px 48px color-mix(in srgb, var(--scrim, #0d2530) 22%, transparent); }
  html[dir="rtl"] #profilePop.profile-panel{ transform:translateY(102%); }
  .pp-hd{ padding-top:18px; }
  .pp-hd::after{ content:'';position:absolute;top:7px;inset-inline-start:50%;transform:translateX(-50%);width:38px;height:4px;border-radius:999px;background:var(--border); }
}
/* phones, both orientations: full screen */
@media (max-width:600px), (max-height:599px) and (orientation:landscape){
  #profilePop.profile-panel{ inset-inline:0;width:100%;height:100dvh;border:0; }
}

/* B64-04 — sub-domain rows in the brand switcher open their own app */
a.brand-app-item.brand-app-sub{ text-decoration:none;color:inherit; }
.brand-app-sub-ic{ width:30px;height:30px;border-radius:9px;display:grid;place-items:center;flex:0 0 auto; }
.brand-app-sub-ic .mi{ font-size:17px; }
.brand-app-sub-logo{ object-fit:cover; }
.brand-app-go{ margin-inline-start:auto;font-size:16px;color:var(--fg-3); }

/* ── B65 W2 — ONE count-bubble style everywhere (065-031 · 075 · 076 · 169) ──────
   Pablo: "We have good bubbles on the navbar buttons … these styles got damaged".
   Two sizes, one look (sample b65/components.html §5):
   · floating bubbles (nav pills + the profile box) — 18 px, 800 weight, a 2 px ring
     in the bar's colour, same top-end corner (-7 px) on every one of them;
   · inline counts (sidebar items, list/tree rows) — 20 px pills, bold, accent fill +
     accent-fg (white); the soft variant (accent-soft + accent-text) in lists/trees.
   UI face, tabular figures — never the mono face that read "thin". */
.nav-pill .np-count,
.nav-profile .np-count{
  top:-7px;inset-inline-end:-7px;box-sizing:content-box;
  min-width:18px;height:18px;padding:0 5px;border-radius:999px;
  background:var(--accent);color:var(--accent-fg);border:2px solid var(--bg-1);
  font:800 10.5px/18px var(--mk-font-ui);font-variant-numeric:tabular-nums;letter-spacing:0;
  display:inline-flex;align-items:center;justify-content:center;
}
.nav-pill .np-count{min-width:8px}
.nav-profile .np-count{min-width:8px}
.sb-item .badge,
.shell.sb-expanded:not(.signed-out) .sb .sb-item .badge,
.ct-row-n,
.pu-req-count{
  box-sizing:border-box;min-width:22px;height:20px;padding:0 7px;border-radius:999px;
  background:var(--accent);color:var(--accent-fg);
  font:800 11px/20px var(--mk-font-ui);font-variant-numeric:tabular-nums;letter-spacing:0;
  display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
}
.ct-row-n{background:var(--accent-soft);color:var(--accent-text);margin-inline-start:auto}
.pu-req-count{min-width:18px;height:18px;padding:0 5px;font-size:10.5px;line-height:18px}
/* the icon-rail (tablet) floats the count on the item's corner, like the nav bubbles */
@media (min-width: 601px) and (max-width: 1024px) and (min-height: 600px){
  .shell:not(.signed-out):not(.sb-expanded) .sb .sb-item .badge{
    position:absolute;top:2px;inset-inline-end:2px;min-width:18px;height:18px;padding:0 5px;
    font-size:10px;line-height:18px;border:2px solid var(--sidebar,var(--bg-1));box-sizing:content-box;
  }
}
@media (max-height:439px) and (orientation:landscape){
  .nav-right.auth-in .nav-pill .np-count,
  .shell.signed-in .nav .nav-profile .np-count{min-width:8px;height:15px;line-height:15px;padding:0 4px;font-size:9.5px;top:-6px;inset-inline-end:-6px}
}

/* B65 W2 (065-129) — press-and-hold Sign out: a fill sweeps the row for 0.8 s,
   then the session "expires" in place (auth.js session gate). */
.so-hold-host{position:relative;overflow:hidden;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none}
.so-hold-host > :not(.so-hold){position:relative}
.so-hold{position:absolute;inset:0;background:var(--mk-danger);opacity:.16;transform-origin:left center;transform:scaleX(0);pointer-events:none}
[dir="rtl"] .so-hold{transform-origin:right center}
.so-hold-host.hold .so-hold{transition:transform .8s linear;transform:scaleX(1)}

/* ── B65 W2 (065-166 · 171 · 099) — bar selectors live IN the page header on desktop.
   header-dock.js moves the page's in-page tab strip, Drive's kind switch and any
   `[data-hdr-dock]` page filter into `.ph-dock`, right after the title. The dock
   takes the free width, never wraps to a second row, and scrolls sideways once it
   runs out. Tablet/phone keep their own placement (the element is moved back). */
@media (min-width:1025px){
  .page-h.has-dock{flex-wrap:nowrap;align-items:center}
  .page-h.has-dock > :first-child{flex:0 0 auto;min-width:0}
  .page-h.has-dock .acts{margin-inline-start:auto}
  .page-h > .ph-dock{
    flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:10px;
    overflow-x:auto;overflow-y:hidden;scrollbar-width:none;overscroll-behavior-x:contain;
    margin-inline-start:12px;padding-block:2px;
    mask-image:linear-gradient(to right,#000 calc(100% - 18px),transparent);
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 18px),transparent);
  }
  [dir="rtl"] .page-h > .ph-dock{mask-image:linear-gradient(to left,#000 calc(100% - 18px),transparent);-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 18px),transparent)}
  .page-h > .ph-dock::-webkit-scrollbar{display:none}
  .ph-dock > .is-docked{flex:0 0 auto;position:static !important;top:auto !important;margin:0 !important;padding:0 !important;width:auto;max-width:none;min-width:0}
  .ph-dock > .is-docked::before,.ph-dock > .is-docked::after{content:none !important;display:none !important}
  /* the in-page tab strip, compact: its own tabs, no full-width spread, no rule */
  .ph-dock > .sec-tabs{height:40px;border-bottom:0;gap:2px;background:none}
  .ph-dock > .sec-tabs .tab{flex:0 0 auto;min-width:0;height:40px;padding:0 12px;white-space:nowrap}
  .ph-dock .seg{flex-wrap:nowrap;flex:0 0 auto;max-width:none;overflow:visible}
  .ph-dock .seg button{flex:0 0 auto;white-space:nowrap}
  .ph-dock .dr-seg{overflow:visible}
  /* Calendar's Day / Week / Month sits in the centre of the whole header (099) */
  .page-view[data-view^="schedule-"] .page-h .acts > .seg{position:absolute;left:50%;transform:translateX(-50%)}
}

/* ── B66 W1 sweeps ─────────────────────────────────────────────────────────
   066-088 · 105 — "I prefer having 'Transact...' than the text wrapping on the box":
   a tab label truncates with an ellipsis, it never wraps (every tab strip). */
.tab{ min-width:0; overflow:hidden; }
.tab > span:not(.mi), .tab > [data-i18n]{ min-width:0; max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* 066-087 — phone landscape: the page header gets room above it and a slightly
   larger title ("this looks very very small and it gets lost"). */
@media (max-height:599px) and (orientation:landscape){
  .shell.signed-in .page-h{ padding-block:12px 8px; }
  .shell.signed-in .page-h h2{ font-size:20px; }
}
/* 066-031 — "they look too small in height compared to the boxes above … fix it in
   all the forms. I want consistency in styles": ONE control height for every text
   box and dropdown in a form (the condition builder's inline chips keep theirs). */
:root{ --mk-control-h:38px; }
:is(.panel-body, .wl-frow, .wl-modal-bd, .ent-field, .pe-field, .pp-body) .cs-btn:not(:is([class*="cb-"] *)):not(.cs-sm *){
  min-height:var(--mk-control-h); padding-block:0; padding-inline:11px 8px; font:500 13px var(--mk-font-ui); border-radius:9px; }
:is(.panel-body, .wl-frow, .wl-modal-bd, .ent-field, .pe-field, .pp-body) .cs-btn:not(:is([class*="cb-"] *)) .cs-arrow{ font-size:18px; margin-inline-start:auto; }
.ent-input:not(.sm):not(textarea){ min-height:var(--mk-control-h); }
/* 066-058 · 086 — the main-content slide (main-slide.js): while one is open the
   section's own header, tab strip and docked bar step away; the slide owns the page. */
.page.mc-has-slide > :not(.mc-slide){ display:none !important; }
.page-view.mc-slide-open > .page > .page-h, .page-view.mc-slide-open > .page > .sec-tabs, .page-view.mc-slide-open > .page > .ph-dock,
.page-view.mc-slide-open [data-dr-switch], .page-view.mc-slide-open > .sec-tabs{ display:none !important; }
.mc-slide{ display:flex; flex-direction:column; min-height:0; min-width:0; opacity:0; transform:translateX(28px); transition:opacity .22s, transform .28s cubic-bezier(.2,.8,.2,1); }
html[dir="rtl"] .mc-slide{ transform:translateX(-28px); }
.mc-slide.open, html[dir="rtl"] .mc-slide.open{ opacity:1; transform:none; }   /* Audit A — the RTL start offset out-ranked .open: Arabic slides stayed 28px off */
.mc-slide.closing{ opacity:0; transform:translateX(28px); }
.mc-slide-h{ position:sticky; top:0; z-index:20; align-items:center !important; }
.mc-slide-t{ display:flex; align-items:center; gap:10px; min-width:0; }
.mc-slide-t h2{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mc-slide-ic{ color:var(--fg-3); font-size:22px; }
.mc-slide-acts{ display:flex; align-items:center; gap:8px; margin-inline-start:auto; }
.mc-slide-acts:empty{ display:none; }
.mc-slide-b{ flex:1; min-height:0; display:flex; flex-direction:column; }
.mc-slide.is-fill .mc-slide-b{ border-radius:16px; overflow:hidden; border:1px solid var(--border); background:var(--bg-1); }
/* B66 W2 (066-036…042 · 080 · 082) — sections that left the Mocca domain (or moved into
   Library) keep their DOM and templates; they are just not Mocca's navigation any more. */
.sb-inner .sb-left{ display:none !important; }
/* B66 W2 (066-043 · 048) — the profile panel: the user block is the header; Preview is a slide */
.pp-hd.pp-hd-user{ background:var(--bg-1); }
.pp-hd-user .pu{ flex:1; min-width:0; display:flex; align-items:center; gap:10px; padding:0; margin:0; border:0; }
.pp-hd-user .pu .av{ width:40px; height:40px; flex:0 0 auto; }
.pp-hd-user .pu-info{ min-width:0; }
.pp-hd-user .pu .n{ font:700 15px var(--mk-font-ui); color:var(--fg-1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pp-hd-user .pu .e{ font:500 12.5px var(--mk-font-ui); color:var(--fg-3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pp-stack{ position:relative; flex:1; min-height:0; overflow:hidden; }
.pp-stack > .pp-body{ position:absolute; inset:0; transition:transform .28s cubic-bezier(.2,.8,.2,1), opacity .2s; }
.pp-stack > .pp-pv{ transform:translateX(104%); opacity:0; pointer-events:none; background:var(--bg-1); }
html[dir="rtl"] .pp-stack > .pp-pv{ transform:translateX(-104%); }
#profilePop.pp-pushed .pp-main{ transform:translateX(-22%); opacity:0; pointer-events:none; }
html[dir="rtl"] #profilePop.pp-pushed .pp-main{ transform:translateX(22%); }
#profilePop.pp-pushed .pp-pv{ transform:none; opacity:1; pointer-events:auto; }
.pp-pv-hd{ display:flex; align-items:center; gap:10px; padding:2px 0 12px; }
.pp-pv-hd b{ font:700 15px var(--mk-font-ui); }
.pp-pv-go{ display:none; align-items:center; gap:10px; width:100%; min-height:46px; padding:6px 8px; border:1px solid var(--border); border-radius:12px; background:var(--accent-soft); color:var(--fg-1); cursor:pointer; text-align:start; margin:4px 0; }
body[data-env="preview"] .pp-pv-go{ display:flex; }
.pp-pv-go > .mi{ color:var(--accent-text); }
.pp-pv-go-t{ flex:1; min-width:0; display:flex; flex-direction:column; }
.pp-pv-go-t b{ font:650 13.5px var(--mk-font-ui); }
.pp-pv-go-t small{ font:500 12px var(--mk-font-ui); color:var(--fg-3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* 066-045 — text in these forms wraps on words, never mid-word */
#profilePop :is(input, textarea), #profileEditModal :is(input, textarea, .pe-card-title, .pe-card-sub){ word-break:normal; overflow-wrap:break-word; }
/* 066-050 — the phone number fills its wrapper's height */
.pe-phone{ align-items:stretch; }
.pe-phone .pe-phone-num{ height:auto; min-height:100%; align-self:stretch; box-sizing:border-box; }
.pe-resid{ display:flex; align-items:center; gap:8px; width:100%; min-height:var(--mk-control-h,38px); padding:0 10px; border:1px solid var(--border); border-radius:9px; background:var(--bg-input); color:var(--fg-1); font:500 13.5px var(--mk-font-ui); cursor:pointer; text-align:start; }
.pe-resid .pe-resid-v{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pe-resid .mi{ color:var(--fg-3); font-size:19px; }
.pe-resid:hover{ border-color:var(--accent); }
/* B66 W7 (066-065 — "Why is this bar selector not aligned to the right?") — a docked bar selector sits at the END of the
   page header (the standing rule: right in LTR); the spacer keeps the dock scrollable when it runs out of room. */
@media (min-width:1025px){
  .page-h > .ph-dock::before{content:"";flex:1 1 auto;min-width:0}
  .page-h > .ph-dock{mask-image:none;-webkit-mask-image:none}
  [dir="rtl"] .page-h > .ph-dock{mask-image:none;-webkit-mask-image:none}
}
/* B66 W7 (066-068) — an optional field says so quietly */
.ce-field .ce-opt{color:var(--fg-3);font-weight:500}
/* B66 W3 (066-027) — a page that filters itself (MoccaPageSearch) takes the navbar's search; on phones the search pill
   opens the bar over the page, with a close button, instead of the full-screen modal. */
.nav-search .ps-x{display:none;width:32px;height:32px;border:0;background:none;border-radius:8px;cursor:pointer;color:var(--fg-2);place-items:center;flex:0 0 auto}
.nav-search .ps-x .mi{font-size:18px}
.nav-search .ps-x:hover{background:var(--bg-3,var(--bg-input));color:var(--fg-1)}
.shell.signed-in.ps-open .nav-search{display:flex !important;position:fixed;top:calc(8px + env(safe-area-inset-top,0px));inset-inline:8px;height:48px;margin:0;z-index:450;
  background:var(--bg-1);border:1px solid var(--border);border-radius:12px;box-shadow:var(--shadow-lg,0 8px 30px rgba(0,0,0,.18));padding:0 6px 0 14px}
.shell.signed-in.ps-open .nav-search .kbd{display:none}
.shell.signed-in.ps-open .nav-search .ps-x{display:grid}

/* date-field.js — Mint's / Buraco's date and time fields open Mocca's own picker; the button wears its app's input classes */
.mdf-btn{ display:flex !important; align-items:center; justify-content:space-between; gap:8px; text-align:start; cursor:pointer; }
.mdf-btn .mdf-v{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mdf-btn.is-empty .mdf-v{ opacity:.6; }
.mdf-btn > .mi{ font-size:18px; opacity:.7; flex:none; }
.mdf-bare{ border:0; background:none; padding:0; font:inherit; color:inherit; }
.mdf-bare > .mi{ display:none; }
.mdf-btn:focus-visible{ outline:2px solid var(--accent, currentColor); outline-offset:2px; }


/* B67 R67-014 — in Arabic a Latin name truncated at its START ("…s-Cola Company"): the box takes its direction from
   its own text, so Latin truncates at its end and Arabic still at its start; alignment stays on the right. */
html[dir="rtl"] .nav-profile .profile-name{ unicode-bidi:plaintext; text-align:right; }

/* B67 R21 (R67-079) — directional icons follow the reading direction in Arabic, as the rest of the app already does (Audit A:
   "forward points to the reading direction"; .ent-open · .pe-card-chev · .cal-period .gantt-step · .fp-back-btn …). Each was
   the only one of its kind still pointing the LTR way: "go" chevrons, forward/back arrows, prev/next steppers, crumbs, twists. */
html[dir="rtl"] .brand-app-go{ transform:scaleX(-1); }

/* B67 R21 (R67-081) — the profile panel's parked page and its closed lists leave the Tab order (as R67-034): the Preview slide
   (or the main page behind it) only fades/slides away, and the app / "viewing as" lists (closed menus — R67-082, shared) —
   Tab walked into all of them unseen. Hidden once the slide has finished, so the motion is unchanged. */
.pp-stack > .pp-body{ transition:transform .28s cubic-bezier(.2,.8,.2,1), opacity .2s, visibility 0s; }
.pp-stack > .pp-pv,
#profilePop.pp-pushed .pp-main{ visibility:hidden; transition:transform .28s cubic-bezier(.2,.8,.2,1), opacity .2s, visibility 0s linear .28s; }
#profilePop.pp-pushed .pp-pv{ visibility:visible; transition:transform .28s cubic-bezier(.2,.8,.2,1), opacity .2s, visibility 0s; }
