:root{
  /* ---- surfaces ---- */
  --bg:#0a0a0c; --panel:#111116; --panel-2:#16161d;
  --hair:rgba(255,255,255,.10); --hair-2:rgba(255,255,255,.06); --scrim:rgba(0,0,0,.58);
  /* ---- ink ---- */
  --ink:#fff; --ink-2:#d1d5db; --ink-3:#9ca3af; --ink-4:#6b7280;
  /* ---- brand ---- */
  --purple:#9685CA; --pink:#EE8C9D; --teal:#71BFCC;
  --grad:linear-gradient(120deg,#72BECE,#5D7FB7,#9685CA,#EE8C9D,#A78CA7,#71BFCC);
  /* ---- lanes: colour as a scale, one hue per state of the journey ---- */
  --lane-draft:#71BFCC; --lane-live:#7F9FD4; --lane-sent:#9685CA;
  --lane-opened:#EE8C9D; --lane-replied:#7EE0B8; --lane-closed:#6b7280;
  --lane-draft-t:rgba(113,191,204,.12); --lane-live-t:rgba(127,159,212,.12);
  --lane-sent-t:rgba(150,133,202,.12); --lane-opened-t:rgba(238,140,157,.12);
  --lane-replied-t:rgba(126,224,184,.12);
  /* ---- Thrive Operations status bar: the one dusty rose accent (Direction A, approved) ---- */
  --ops-rose:#C98B8B; --ops-rose-fill:rgba(201,139,139,.16); --ops-rose-bd:rgba(201,139,139,.34);
  /* ---- states ---- */
  --state-stall:#F6CF5B; --state-stall-t:rgba(246,207,91,.11); --state-stall-b:rgba(246,207,91,.55);
  /* failed: a delivered send whose row was never recorded. A warmer red than the pink lane accent, so a real
     failure never reads as a routine stall or an Opened-lane tint. */
  --state-err:#E0736F; --state-err-t:rgba(224,115,111,.20); --state-err-b:rgba(224,115,111,.55);
  --state-good:#7EE0B8; --state-bad:#EE8C9D;
  /* ---- T4 glow: one calm accent for the value that earned attention ---- */
  --glow:#7EE0B8; --glow-1:rgba(126,224,184,.42); --glow-0:rgba(126,224,184,0); --glow-t:rgba(126,224,184,.10);
  --t-glow:1600ms;   /* one slow cycle: deliberately outside the interaction scale, a glow is not a transition */
  /* ---- radii: four values, nothing else ---- */
  --r-sm:8px; --r-md:12px; --r-lg:16px; --r-xl:18px; --r-pill:999px;
  /* ---- space: a 4px base scale ---- */
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px; --s-5:20px; --s-6:26px; --s-7:34px; --s-8:48px;
  /* ---- type ---- */
  --font:"Lato","Alyamama",sans-serif;
  /* One Latin face and one Arabic face, and nothing between them. A stack resolves per
     character, not per element, so Latin letters find Lato and Arabic letters, which Lato
     does not carry, fall through to Alyamama. The six system families that used to sit
     between Lato and Alyamama here (Segoe UI, Roboto, -apple-system, BlinkMacSystemFont,
     Helvetica Neue, Arial) were the defect: on a device that has them, an Arabic glyph
     resolved to a system Arabic face long before it reached Alyamama, so the bold headline
     and every Arabic word in the English chrome came out in a system font. Removing them
     is the root fix. Alyamama is self hosted in fonts.css, so the trailing sans-serif is
     only a last resort if it ever fails to load. Lato stays first, not Alyamama, because
     Alyamama first would draw Latin business names in Alyamama's own serif Latin. */
  --font-ar:"Lato","Alyamama",sans-serif;
  --fs-micro:10.5px; --fs-xs:11.5px; --fs-sm:12.5px; --fs-base:13.5px;
  --fs-md:15px; --fs-lg:17px; --fs-xl:22px; --fs-2xl:30px;
  --lh-tight:1.25; --lh-base:1.55; --lh-read:1.85;
  /* ---- elevation: objects only, never surfaces ---- */
  --el-raised:0 6px 18px rgba(0,0,0,.40); --el-modal:0 0 60px rgba(0,0,0,.60);
  --el-accent:0 8px 24px rgba(150,133,202,.28);
  /* ---- motion: five durations, three easings, nothing outside the scale ---- */
  --t-instant:90ms; --t-quick:160ms; --t-base:240ms; --t-slow:320ms; --t-page:420ms; --t-settle:480ms;
  --e-standard:cubic-bezier(.2,.7,.2,1); --e-enter:cubic-bezier(.16,1,.3,1); --e-exit:cubic-bezier(.4,0,1,1);
  --ease:var(--e-standard);                 /* the name the existing rules already use */
  /* Direction sign, set once so no component branches on dir. */
  --dir:1;
  /* ---- weights: Alyamama reads heavier than Lato at the same nominal weight,
         so every Latin display weight steps down by one in Arabic ---- */
  --w-body:400; --w-strong:700; --w-head:800; --w-max:900;
  --font-lat:var(--font);
  /* Syne is the brand display face, but it is not embedded here and one more base64 family
     on every page is not worth one heading. Lato at its heaviest carries the moment instead. */
  --font-display:var(--font);
  /* ---- board tuning ---- */
  --stall-days:10; --lane-min:132px;
  color-scheme:dark; /* dark native date-picker, selects, scrollbars */
}
html[dir="rtl"]{ --dir:-1; --w-body:400; --w-strong:500; --w-head:600; --w-max:700; --font-lat:var(--font-ar) }
*{box-sizing:border-box}
:focus-visible{outline:2px solid var(--purple);outline-offset:2px;border-radius:6px}
/* The containment model, stated once. The viewport is the hard right and left edge: nothing paints
   past it. overflow-x:clip bounds the page without creating a scroll box, so the sticky header still
   sticks and vertical scroll is untouched (unlike overflow:hidden, which would break both). Inside
   this, .wrap sets the equal margins and the max width, and .board is the ONE element allowed to
   scroll horizontally, and it scrolls WITHIN its own box, so a lane that overflows is reachable by
   scrolling the board, never pushed off the screen. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%;overflow-x:clip}
body{overflow-x:clip}
body{margin:0;background:var(--bg);color:var(--ink-2);font-family:var(--font);font-size:15px;line-height:1.55;-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent}
a{color:inherit}
img{max-width:100%;height:auto;border:0}
.gt{background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent}
[hidden]{display:none!important}

/* header */
.top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:14px;padding:14px 20px;background:rgba(10,10,12,.85);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border-bottom:1px solid var(--hair)}
.brand{display:flex;align-items:center;gap:10px;text-decoration:none}
.brand img{width:26px;height:26px;display:block;animation:spin 22s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.brand b{font-size:12px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-2)}
.nav{display:flex;gap:6px;margin-inline-start:auto;align-items:center;flex-wrap:wrap}
.nav a{padding:8px 13px;border-radius:9px;font-size:13px;font-weight:700;color:var(--ink-3);text-decoration:none;transition:.2s}
.nav a:hover{color:var(--ink);background:var(--hair-2)}
.nav a.active{color:var(--ink);background:rgba(150,133,202,.16)}
/* One height for everything in the bar. Arabic sets a taller line box than Latin, so the
   language switch stood 36px tall next to a 33px lock: two identical controls, different
   sizes, decided by which script happened to be inside them. */
.nav a,.langbtn{min-height:36px;display:inline-flex;align-items:center;justify-content:center}
.langbtn{padding:8px 12px;border-radius:9px;border:1px solid var(--hair);background:transparent;color:var(--ink-2);font-weight:800;font-size:12px;cursor:pointer;font-family:inherit}
.langbtn:hover{border-color:var(--purple)}

/* The controls that wait. Real, reachable in one tap, and out of the way of the one thing the
   screen is for. The button carries a count when something inside is on, so folding a filter
   away never hides that it is filtering. */
.more-bar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:-8px 0 18px;
  padding:14px 16px;border:1px solid var(--hair);border-radius:var(--r-lg);background:var(--panel)}
.more-bar select{max-width:200px}
.more-bar .field{flex:1 1 220px;margin:0}
.more-bar .chk{flex:1 1 100%}
#libMoreBtn.has-on,#cmpMoreBtn.has-on{border-color:var(--purple);color:var(--ink)}
.wrap{max-width:1080px;margin:0 auto;padding:26px 20px 60px}
/* Heading and its status badge on one anchor. align-items:center puts the badge on the heading's
   vertical centerline (flex-end sat it 7px low, off the line, on iPad and desktop). Below 640, where
   a long sync line cannot share the row with the heading, the badge holds ONE steady position on its
   own line under the heading (flex-basis:100%), leading-aligned, instead of drifting between inline
   and wrapped as the sync text changes. So it is centered when it is beside the heading and steady
   when it is below, never moving with the state. RTL inherits this: the leading edge is the right. */
.page-h{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:6px}
@media(max-width:640px){
  .page-h{ align-items:flex-start; row-gap:6px }
  .page-h > .pill{ flex-basis:100% }
}
h1.title{font-size:26px;font-weight:800;color:var(--ink);margin:0;letter-spacing:-.01em}
.sub{color:var(--ink-3);font-size:14px;margin:4px 0 22px}

/* toolbar */
.toolbar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:18px}
.search{flex:1 1 220px;min-width:180px;position:relative}
/* The magnifier is a field icon: it belongs inside the input, centered, not floating above it. It had
   no rule at all, so it sat in normal flow before the block input and appeared out of place. Position
   it inside the (now relative) container and reserve inline-start room on the input so text clears it.
   inset-inline-start keeps it correct in both directions. */
.search .fieldic{position:absolute;inset-inline-start:12px;inset-block-start:50%;transform:translateY(-50%);
  display:inline-flex;align-items:center;color:var(--ink-3);pointer-events:none}
.input,.search input,select,textarea{width:100%;background:var(--panel);border:1px solid var(--hair);border-radius:10px;color:var(--ink);padding:11px 13px;font-size:14px;font-family:inherit;outline:none;transition:border-color .2s}
.search input{padding-inline-start:38px}   /* after the base rule, so the shorthand does not reset it: room for the icon */
.input:focus,.search input:focus,select:focus,textarea:focus{border-color:var(--purple)}
select{cursor:pointer}
.btn{display:inline-flex;align-items:center;gap:8px;padding:11px 18px;border-radius:10px;font-weight:800;font-size:13.5px;border:0;cursor:pointer;text-decoration:none;font-family:inherit;color:#15121b;background:var(--grad);box-shadow:0 8px 24px rgba(150,133,202,.28);transition:transform .2s}
.btn:hover{transform:translateY(-1px)}
.btn:active{transform:translateY(0) scale(.98)}
/* A blocked or in-flight button says so at a glance: dimmed, no hover lift, and the cursor refuses.
   A send that is not allowed is never lit-but-inert. */
.btn:disabled,.btn.is-blocked{opacity:.45;cursor:not-allowed;box-shadow:none}
.btn:disabled:hover,.btn.is-blocked:hover{transform:none;border-color:var(--hair);color:inherit}
.btn.ghost{background:transparent;color:var(--ink-2);border:1px solid var(--hair);box-shadow:none}
.btn.ghost:hover{border-color:var(--purple);color:var(--ink)}
.btn.ghost.danger:hover{border-color:var(--pink);color:var(--pink)}
.btn.sm{padding:8px 13px;font-size:12.5px}

/* cards / table */
/* Wider tracks so the cards are not cramped, especially at iPad-landscape where 300px forced three
   tight columns. min(100%,340px) keeps a single column from ever overflowing a narrow phone. */
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,340px),1fr));gap:16px}
.card{background:var(--panel);border:1px solid var(--hair);border-radius:16px;padding:18px;transition:border-color .2s,transform .2s;display:flex;flex-direction:column}
.grid .card{padding:20px 22px}   /* more room for the title and body; the #48 skeleton is unchanged */
.card:hover{border-color:rgba(150,133,202,.5);transform:translateY(-2px)}
.card.is-arch{opacity:.62}
.card.is-arch:hover{opacity:1}
.card-top{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}
.card-id{min-width:0}
.card .biz{font-size:17px;font-weight:800;color:var(--ink);margin:0 0 4px;overflow-wrap:anywhere}
.card .link{display:inline-block;max-width:100%;font-size:12.5px;color:var(--teal);overflow-wrap:anywhere;word-break:break-word;text-decoration:none;font-weight:700}
.card .link:hover{text-decoration:underline}
.meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.chip{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:700;color:var(--ink-2);background:var(--panel-2);border:1px solid var(--hair);border-radius:999px;padding:5px 10px}
.chip.tmpl{color:#15121b;background:linear-gradient(120deg,#72BECE,#EE8C9D);border:0}
.badge{flex:0 0 auto;white-space:nowrap;font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;padding:4px 9px;border-radius:6px}
.badge.sent{color:#7ee0b8;background:rgba(126,224,184,.12)}
.badge.draft{color:#F6CF5B;background:rgba(246,207,91,.12)}
.badge.edit{color:#9fb7ff;background:rgba(159,183,255,.12)}
.badge.arch{color:var(--ink-3);background:rgba(255,255,255,.06)}
.card .row{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:14px}
.card .actions{display:flex;gap:8px}

/* --- Library card, one shared structure --------------------------------------
   Three tiers on one skeleton, so every card is the same shape and the grid stays
   even. Primary: name, state, the one primary action. Secondary: the url and the
   supporting facts, present but quiet, one line of muted text rather than a wall of
   bright chips. Tertiary: the rest of the actions, reachable behind a native
   disclosure so the face is not a row of equal buttons. Nothing is removed, only
   re-tiered. The foot pins to the bottom (flex-column card + margin-top:auto) so
   every card's action row lines up, whatever facts it carries. */
.card-state{display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex:0 0 auto}
.card-state .flag{white-space:nowrap;font-size:var(--fs-micro);font-weight:800;padding:3px 8px;border-radius:var(--r-pill)}
.card-state .flag-fu{color:var(--state-stall);background:var(--state-stall-t);border:1px solid var(--state-stall-b)}
.card-state .flag-half{color:var(--lane-live);background:var(--lane-live-t);border:1px solid var(--hair)}
.card-facts{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 0;margin:12px 0 0;font-size:var(--fs-xs);color:var(--ink-4);font-weight:600;line-height:1.6}
.card-facts .fact{white-space:nowrap}
.card-facts .fact-tmpl{color:var(--ink-3);font-weight:700}
.card-facts .fact:not(:last-child)::after{content:"\00B7";margin:0 8px;opacity:.5}
.card-foot{margin-top:auto;padding-top:14px}
.card-act{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.card-act .btn.sm{flex:0 0 auto}
.card-act .stage-sel{margin-inline-start:auto}
.card-more{margin-top:10px}
.card-more-s{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-xs);font-weight:700;color:var(--ink-3);user-select:none;width:max-content}
.card-more-s::-webkit-details-marker{display:none}
.card-more-s .ic{transition:transform var(--t-quick) var(--ease)}
.card-more[open] .card-more-s .ic{transform:rotate(180deg)}
.card-more-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:8px;margin-top:10px}
.card-more-list>*{justify-content:center;text-align:center;white-space:nowrap}
/* The state badge is a Latin abbreviation styled with tracking and caps; neither may touch
   Arabic, where tracking breaks the joins and caps do nothing. Cleared in RTL. */
[dir="rtl"] .badge{letter-spacing:normal;text-transform:none}
@media (pointer:coarse){ .card-more-s{min-height:44px} }

.empty{text-align:center;color:var(--ink-3);padding:60px 20px;border:1px dashed var(--hair);border-radius:16px}

/* R16 (P25): the Library organized by mission. Each shelf is a bounded section leading with its base
   template (the source of truth) and its requirements manifest, then the filed pages in the card grid.
   The card grid inside a shelf reuses .grid, so the cards themselves are unchanged. */
.shelves{display:grid;gap:26px}
.shelf{background:var(--panel);border:1px solid var(--hair);border-radius:var(--r-xl);padding:20px 22px}
.shelf-h{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}
.shelf-id{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;min-width:0}
.shelf-t{font-family:var(--font-display);font-size:var(--fs-lg);font-weight:800;color:var(--ink);margin:0;line-height:1.2}
.shelf-count{font-size:var(--fs-xs);font-weight:700;color:var(--ink-3);white-space:nowrap}
.shelf-count .n{font-weight:800;color:var(--ink-2)}
.shelf-del{color:var(--state-err-t)}
.shelf-tag{margin:6px 0 0;font-size:var(--fs-sm);color:var(--ink-3);line-height:var(--lh-read);max-width:70ch}
.shelf-base{margin:16px 0 0;padding:14px 16px;background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-lg)}
.shelf-base-tag{display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-micro);font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-3)}
[dir="rtl"] .shelf-base-tag{letter-spacing:normal;text-transform:none}
.shelf-base-body{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px}
.shelf-base-note{font-size:var(--fs-sm);color:var(--ink-3);line-height:var(--lh-read);flex:1 1 100%}
.shelf-manifest{margin:12px 0 0}
.shelf-manifest-s,.ed-manifest-s{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-xs);font-weight:800;color:var(--ink-2);user-select:none}
.shelf-manifest-s::-webkit-details-marker,.ed-manifest-s::-webkit-details-marker{display:none}
.shelf-manifest-p{margin:8px 0 0;font-size:var(--fs-xs);color:var(--ink-3);line-height:var(--lh-read)}
.mf-list{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:8px}
.mf-item{display:flex;align-items:flex-start;gap:8px;font-size:var(--fs-sm);color:var(--ink-2);line-height:var(--lh-read)}
.mf-item .ic{flex:0 0 auto;margin-top:3px;color:var(--ink-4)}
.shelf-cards{margin-top:18px}
.shelf-empty{grid-column:1/-1;text-align:center;color:var(--ink-3);padding:28px 16px;border:1px dashed var(--hair);border-radius:var(--r-lg);font-size:var(--fs-sm)}
.ed-manifest{margin:0 0 15px;padding:12px 14px;background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-md)}
.ed-manifest[hidden]{display:none}

/* R17 (P26): the Batches view. Each batch is a bounded section: its documents (each a disclosure rendering
   the md read-only on demand) and the opportunities it produced. And the card Overview's quiet "from batch"
   chip. */
#batchList{display:grid;gap:22px;margin-top:6px}
.batch{background:var(--panel);border:1px solid var(--hair);border-radius:var(--r-xl);padding:18px 20px}
.batch-h{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}
.batch-id{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;min-width:0}
.batch-t{font-family:var(--font-display);font-size:var(--fs-lg);font-weight:800;color:var(--ink);margin:0;line-height:1.2}
.batch-t .n{font-weight:800}
.batch-date{font-size:var(--fs-xs);font-weight:700;color:var(--ink-3);white-space:nowrap}
.batch-counts{display:flex;gap:8px;flex-wrap:wrap}
.batch-count{font-size:var(--fs-xs);font-weight:700;color:var(--ink-3);white-space:nowrap;background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-pill);padding:3px 10px}
.batch-count .n{font-weight:800;color:var(--ink-2)}
.batch-tag{margin:8px 0 0;font-size:var(--fs-sm);color:var(--ink-3);line-height:var(--lh-read);max-width:75ch}
.batch-docs{margin:14px 0 0;display:grid;gap:8px}
.bdoc{background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-md);overflow:hidden}
.bdoc-s{list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;padding:10px 14px;font-weight:700;user-select:none}
.bdoc-s::-webkit-details-marker{display:none}
.bdoc-type{font-size:var(--fs-xs);font-weight:800;color:var(--ink-2)}
.bdoc-name{font-size:var(--fs-xs);color:var(--ink-4);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bdoc-body{padding:4px 16px 14px;border-top:1px solid var(--hair);max-height:520px;overflow:auto}
.doc-h{margin:12px 0 4px;font-weight:800;color:var(--ink);line-height:var(--lh-tight)}
.doc-h1{font-size:var(--fs-md)}.doc-h2{font-size:var(--fs-base);font-weight:800}.doc-h3,.doc-h4,.doc-h5,.doc-h6{font-size:var(--fs-sm);color:var(--ink-2)}
.doc-p{margin:4px 0;font-size:var(--fs-sm);color:var(--ink-2);line-height:var(--lh-read)}
.doc-li{margin:3px 0 3px 4px;padding-inline-start:14px;position:relative;font-size:var(--fs-sm);color:var(--ink-2);line-height:var(--lh-read)}
.doc-li::before{content:"\2022";position:absolute;inset-inline-start:0;color:var(--ink-4)}
.doc-gap{height:6px;margin:0}
.doc-code{margin:8px 0;padding:10px 12px;background:var(--bg);border:1px solid var(--hair);border-radius:var(--r-sm);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:var(--fs-xs);color:var(--ink-2);white-space:pre-wrap;overflow-x:auto}
.batch-opps{margin:14px 0 0}
.batch-opps-h{font-size:var(--fs-micro);font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-3)}
[dir="rtl"] .batch-opps-h{letter-spacing:normal;text-transform:none}
.bopp-list{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
.bopp-chip{background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-pill);padding:5px 12px;font-size:var(--fs-xs);font-weight:700;color:var(--ink-2);cursor:pointer}
.bopp-chip:hover{border-color:var(--hair-2);color:var(--ink)}
.mw-batch-chip{display:inline-flex;align-items:center;gap:6px;background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-pill);padding:3px 10px;font-size:var(--fs-xs);font-weight:700;color:var(--ink-2);cursor:pointer}
.mw-batch-chip:hover{border-color:var(--hair-2);color:var(--ink)}
.mw-batch-chip .ic{color:var(--ink-4)}
.mw-batch-d{font-weight:800}

/* P27: the oversight room + the shared member performance panel (also used in profile via #pfWindows). */
#ovRoom{display:grid;gap:20px;margin-top:6px}
.ov-member{background:var(--panel);border:1px solid var(--hair);border-radius:var(--r-xl);padding:18px 20px}
.ov-member.is-own{margin-top:14px}
.ov-mh{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}
.ov-mid{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
.ov-mn{font-family:var(--font-display);font-size:var(--fs-md);font-weight:800;color:var(--ink);margin:0;line-height:1.2}
.ov-role{font-size:var(--fs-micro)}
.ov-trend{color:var(--teal);flex:0 0 auto}
.spark{display:block;overflow:visible}
.ov-wins{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:12px;margin-top:14px}
.ov-win{background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-lg);padding:12px 14px}
.ov-win-h{font-size:var(--fs-micro);font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-3);margin-bottom:10px}
[dir="rtl"] .ov-win-h{letter-spacing:normal;text-transform:none}
.ov-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:10px 8px}
.ov-cell{display:flex;flex-direction:column;gap:2px;min-width:0}
.ov-cv{font-size:var(--fs-lg);font-weight:800;color:var(--ink);line-height:1.1}
.ov-cv .n{font-weight:800}
.ov-ck{display:inline-flex;align-items:center;gap:4px;font-size:var(--fs-micro);color:var(--ink-4);font-weight:600}
.ov-own-note{margin:10px 0 0;font-size:var(--fs-xs);color:var(--ink-3);line-height:var(--lh-read)}
.ov-trail{margin-top:14px}
.ov-trail-s{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-xs);font-weight:800;color:var(--ink-2);user-select:none}
.ov-trail-s::-webkit-details-marker{display:none}
.ov-tl-n{font-weight:800;color:var(--ink-4)}
.ov-tl-list{margin-top:10px;display:grid;gap:6px}
.ov-tl-row{display:flex;align-items:baseline;gap:10px;font-size:var(--fs-xs)}
.ov-tl-k{flex:0 0 auto;font-size:var(--fs-micro)}
.ov-tl-d{flex:1 1 auto;color:var(--ink-2);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ov-tl-t{flex:0 0 auto;color:var(--ink-4)}
.ov-roster{background:var(--panel);border:1px solid var(--hair);border-radius:var(--r-xl);padding:18px 20px}
.ov-roster-h{font-size:var(--fs-md);font-weight:800;color:var(--ink);margin:0 0 12px}
.ov-roster-list{display:grid;gap:8px}
.ov-roster-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:8px 12px;background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-md)}
.ov-rn{font-weight:700;color:var(--ink)}
.ov-re{font-size:var(--fs-xs);color:var(--ink-4)}
.ov-rr,.ov-ri{font-size:var(--fs-micro);margin-inline-start:auto}
.ov-roster-note{margin:12px 0 0;font-size:var(--fs-xs);color:var(--ink-3);line-height:var(--lh-read)}
.ov-hist{background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-lg);padding:14px 16px}
.ov-hist-h{font-size:var(--fs-sm);font-weight:800;color:var(--ink-2);margin:0 0 4px}
.ov-hist-line{margin:0;font-size:var(--fs-xs);color:var(--ink-3)}
.pf-windows{margin-top:16px}
.pf-windows .ov-member{padding:0;border:0;background:none}

/* editor */
.editor{display:grid;grid-template-columns:minmax(0,420px) 1fr;gap:22px;align-items:start}
@media(max-width:860px){.editor{grid-template-columns:1fr}}
.panel{background:var(--panel);border:1px solid var(--hair);border-radius:16px;padding:20px}
.field{margin-bottom:15px}
.field label{display:block;font-size:12.5px;font-weight:800;color:var(--ink-2);margin-bottom:6px}
.field .hint{font-size:11.5px;color:var(--ink-3);margin-top:5px}
.field.req label::after{content:" *";color:var(--pink)}
.seg{display:flex;gap:8px;margin-bottom:16px}
.seg button{flex:1;padding:10px;border-radius:10px;border:1px solid var(--hair);background:transparent;color:var(--ink-3);font-weight:800;font-size:13px;cursor:pointer;font-family:inherit}
.seg button.on{color:var(--ink);background:rgba(150,133,202,.16);border-color:var(--purple)}
/* Undo and redo for the outreach message text sit in the composer toolbar. Redo is the undo glyph
   mirrored, so no new symbol is added. Greyed and inert when the history has nothing to step to. */
.etoolbar .etb-hist{line-height:0}
.etoolbar .etb-hist.ed-redo .ic{transform:scaleX(-1)}
.etoolbar .etb-hist:disabled{opacity:.4;cursor:not-allowed}
.etoolbar .etb-hist:disabled:hover{border-color:var(--hair);color:var(--ink-2)}
.preview{position:sticky;top:76px}
/* WO-023 button geometry: one row model for every button bar in the console. A .bar is a flex row,
   vertically centered, wrapping, with ONE gap token (--s-3) used everywhere, so no two controls touch
   and none is cramped. Buttons in a row share one height: line-height:1 so a label control matches a
   button (a label otherwise inherits the body line-height and runs taller), and a transparent 1px
   border on the primary so its box matches the ghost's bordered box. No explicit height, so the mobile
   44px touch target is untouched. */
.bar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--s-3)}
.bar .btn{line-height:1}
.bar .btn:not(.ghost){border:1px solid transparent}
.preview .bar{margin-bottom:10px}
.urlpill{flex:1 1 auto;min-width:0;font-size:12.5px;font-weight:700;color:var(--teal);background:var(--panel);border:1px solid var(--hair);border-radius:8px;padding:9px 12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.frame{width:100%;height:70vh;min-height:460px;border:1px solid var(--hair);border-radius:14px;background:#0a0a0c}
.dz{border:1.5px dashed var(--hair);border-radius:12px;padding:26px;text-align:center;color:var(--ink-3);font-size:13px;cursor:pointer;transition:.2s}
.dz.over{border-color:var(--purple);background:rgba(150,133,202,.08);color:var(--ink)}
/* One block shell for every notice: a bordered panel, one radius, one padding rhythm. The quiet note
   and the louder amber band (.mw-band) share this shell and differ only by the state colour they carry,
   so a notice reads the same on every page rather than each its own shape. */
.note{font-size:12.5px;color:var(--ink-3);background:var(--panel-2);border:1px solid var(--hair);border-radius:var(--r-md);padding:12px 14px;line-height:1.6;margin-top:14px}
.note code{color:var(--teal);font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
.toast{position:fixed;inset-inline-end:20px;bottom:20px;z-index:200;background:#16161d;border:1px solid var(--purple);color:var(--ink);padding:13px 18px;border-radius:12px;font-size:13.5px;font-weight:700;box-shadow:0 10px 30px rgba(0,0,0,.5);opacity:0;transform:translateY(10px);transition:.25s;pointer-events:none}
/* The action runner's outcome surface. It sits above every overlay (the modal is 81, the confirm
   dialog 120), pinned to the top of the current view, so an action's working, confirmed, or error
   state is never hidden behind a panel and never off screen. An error persists and carries the real
   text; it wraps rather than truncates, so the reason is readable in full. */
.act-status{position:fixed;inset-block-start:0;inset-inline:0;z-index:300;display:none;
  align-items:flex-start;gap:var(--s-2);padding:12px 16px;font-size:var(--fs-sm);font-weight:var(--w-head);
  color:var(--ink);background:#16161d;border-block-end:1px solid var(--hair);
  box-shadow:0 8px 24px rgba(0,0,0,.5)}
.act-status.show{display:flex}
.act-status .act-ic{flex:none;display:inline-flex;margin-block-start:1px}
.act-status .act-msg{flex:1 1 auto;min-width:0;overflow-wrap:anywhere;line-height:var(--lh-base)}
.act-status .act-x{flex:none;background:transparent;border:0;color:var(--ink-3);font-size:20px;line-height:1;
  cursor:pointer;padding:0 4px}
.act-work{border-block-end-color:var(--purple)} .act-work .act-ic{color:var(--purple)}
.act-ok{border-block-end-color:var(--lane-replied)} .act-ok .act-ic{color:var(--lane-replied)}
.act-err{border-block-end-color:var(--state-bad);background:#1c1418} .act-err .act-ic{color:var(--state-bad)}
[dir="rtl"] .act-status{letter-spacing:normal}
.btn.is-running{opacity:.75;cursor:progress}
/* The activation crowning moment. The full brand gradient is spent here and only here: a rose, teal
   and gold ring that sweeps around once on appear, then settles to a calm static gradient. It lives in
   the always-visible runner layer (z-index 300), pinned to the top, non-blocking, no scrim. The card
   persists until dismissed. prefers-reduced-motion gets the static gradient with no sweep. */
@property --ac-ang{syntax:"<angle>";inherits:false;initial-value:0deg}
.act-status.act-card{padding:0;background:transparent;border:0;box-shadow:none;display:none}
.act-status.act-card.show{display:block}
.act-card .act-card-in{position:relative;max-width:540px;margin-inline:auto;padding:16px 18px;
  border-radius:0 0 16px 16px;background:#16161d;box-shadow:0 12px 34px rgba(0,0,0,.55);overflow:hidden}
.act-card .act-card-in::before{content:"";position:absolute;inset:0;border-radius:inherit;padding:2px;
  background:conic-gradient(from var(--ac-ang,0deg),#C98B8B,#5CB5A8,#D4A574,#C98B8B);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;pointer-events:none}
.act-card.sweep .act-card-in::before{animation:ac-sweep .9s ease-out 1 both}
@keyframes ac-sweep{from{--ac-ang:0deg}to{--ac-ang:360deg}}
.act-card .act-card-h{font-size:var(--fs-md);font-weight:var(--w-head);color:var(--ink);padding-inline-end:20px}
.act-card .act-card-url{display:block;margin:6px 0 12px;font-size:12.5px;font-weight:700;color:var(--teal);
  overflow-wrap:anywhere;text-decoration:none}
.act-card .act-card-acts{display:flex;gap:8px;flex-wrap:wrap}
.act-card .act-x{position:absolute;inset-block-start:8px;inset-inline-end:10px;background:transparent;
  border:0;color:var(--ink-3);font-size:20px;line-height:1;cursor:pointer;padding:0 4px}
/* The permanent home in Overview: the same ring, one gentle sweep the first time the record shows the
   activated state, then static. */
.ov-live{position:relative;border-radius:12px;padding:2px;display:inline-block;max-width:100%}
.ov-live::before{content:"";position:absolute;inset:0;border-radius:inherit;
  background:conic-gradient(from var(--ac-ang,0deg),#C98B8B,#5CB5A8,#D4A574,#C98B8B);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;padding:2px;pointer-events:none}
.ov-live.sweep::before{animation:ac-sweep .9s ease-out 1 both}
.ov-live .ov-live-in{position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:8px;
  border-radius:10px;background:var(--panel);padding:9px 11px}
.ov-live-tag{font-size:11px;font-weight:var(--w-head);color:var(--ink-2);background:var(--panel-2);
  border-radius:999px;padding:2px 9px}
.ov-live-url{font-size:12.5px;font-weight:700;color:var(--teal);text-decoration:none;overflow-wrap:anywhere}
.ov-live-acts{display:inline-flex;gap:6px;margin-inline-start:auto}
@media (prefers-reduced-motion:reduce){
  .act-card.sweep .act-card-in::before{animation:none}
  .ov-live.sweep::before{animation:none}
}
.toast.show{opacity:1;transform:none;pointer-events:auto}
/* An undo you have to catch is not an undo, so the toast that carries one is a control and
   accepts a pointer. The plain toast stays inert so it never blocks what is under it. */
.toast:not(.has-act){pointer-events:none}
.toast.has-act{display:flex;align-items:center;gap:var(--s-3)}
.toast-act{background:transparent;border:1px solid var(--purple);border-radius:var(--r-sm);
  color:var(--ink);font:inherit;font-size:var(--fs-sm);font-weight:var(--w-head);
  padding:6px 12px;cursor:pointer;flex:0 0 auto}
.toast-act:hover{background:rgba(150,133,202,.18)}

/* P21: the send moment. A calm, centred arrival card shown once an outreach send is confirmed: the
   in-repo asterisk signature mark with a gentle radiate, and one line. Tap anywhere or wait to return to
   the board. Above everything (z above the modal). The radiate is CSS only and stops under reduced motion. */
.sm-scrim{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;
  padding:var(--s-4);background:rgba(0,0,0,.62);backdrop-filter:blur(6px);
  opacity:0;transition:opacity .24s ease;cursor:pointer;outline:none}
.sm-scrim.on{opacity:1}
.sm-card{inline-size:min(360px,90vw);background:var(--panel);border:1px solid var(--hair);
  border-radius:var(--r-xl,20px);padding:var(--s-6,32px) var(--s-5,24px);text-align:center;
  box-shadow:var(--el-raised,0 30px 80px rgba(0,0,0,.55));
  transform:translateY(8px) scale(.98);transition:transform .28s cubic-bezier(.2,.7,.2,1)}
.sm-scrim.on .sm-card{transform:none}
.sm-mark-wrap{display:flex;align-items:center;justify-content:center;margin-block-end:var(--s-4,16px)}
.sm-mark{color:var(--purple,#9685ca);display:block}
.sm-mark .sm-rays{opacity:.95}
.sm-mark .sm-core{opacity:.95}
/* the two rings breathe outward and fade, staggered, so the mark reads as radiating not spinning */
.sm-mark .sm-ring{transform-box:fill-box;transform-origin:center;opacity:0;
  animation:sm-radiate 2.6s ease-out infinite}
.sm-mark .sm-ring-2{animation-delay:1.3s}
@keyframes sm-radiate{0%{transform:scale(.55);opacity:.55}70%{opacity:.12}100%{transform:scale(1.65);opacity:0}}
.sm-line{margin:0;font-size:var(--fs-md,16px);line-height:var(--lh-base,1.55);color:var(--ink,#fff);font-weight:var(--w-head,600)}
[dir="rtl"] .sm-line{letter-spacing:normal}
@media (prefers-reduced-motion:reduce){
  .sm-scrim{transition:none}
  .sm-scrim.on .sm-card{transition:none}
  .sm-mark .sm-ring{animation:none;opacity:.2;transform:scale(1.2)}   /* a still ring, no radiate */
}

/* templates gallery */
.tpl{display:grid;grid-template-columns:1fr;gap:16px}
.tpl .item{display:grid;grid-template-columns:220px 1fr;gap:18px;background:var(--panel);border:1px solid var(--hair);border-radius:16px;padding:16px}
@media(max-width:640px){.tpl .item{grid-template-columns:1fr}}
/* A message template has no page to show a picture of, so it must not be squeezed into the
   column that was reserved for one. */
.tpl .item.no-thumb{grid-template-columns:1fr}
/* Subject and preview are quoted content: an Arabic message inside an English console, or the
   reverse, keeps its own reading order. */
.tpl .meta-line{unicode-bidi:plaintext}
.tpl .thumb{border:1px solid var(--hair);border-radius:12px;overflow:hidden;background:#0a0a0c;aspect-ratio:16/11}
.tpl .thumb iframe{width:250%;height:250%;transform:scale(.4);transform-origin:top left;border:0;pointer-events:none}
.tpl h3{margin:0 0 4px;font-size:18px;color:var(--ink);font-weight:800}
.tpl .id{font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--teal)}
.tpl p{font-size:13.5px;color:var(--ink-2);margin:8px 0 14px}
.tpl .actions{display:flex;gap:10px;flex-wrap:wrap}
/* How a message has actually done, beside the message. Numbers first, label after, so the
   comparison between two templates is a glance down one column. */
.tpl .et-perf{display:flex;gap:16px;flex-wrap:wrap;margin:10px 0 14px;font-size:12px;color:var(--ink-3)}
.tpl .et-perf b{color:var(--ink);font-weight:800;margin-inline-end:3px;font-family:var(--font-lat);direction:ltr;unicode-bidi:isolate}
.tpl .et-perf .ok-n b{color:var(--state-good)}
.tpl .et-perf.muted{font-style:normal;color:var(--ink-4)}

.field .hint.warn{color:var(--pink);font-weight:700}
.empty-wrap{display:block}
.card .row{flex-wrap:wrap}
.card .actions{flex-wrap:wrap}

/* toast: keep on-screen on small viewports */
.toast{max-width:min(360px,calc(100vw - 40px));overflow-wrap:anywhere}

/* activity log */
.logwrap{overflow-x:auto;border:1px solid var(--hair);border-radius:14px}
.logtable{width:100%;border-collapse:collapse;font-size:13px;min-width:520px}
.logtable th,.logtable td{text-align:start;padding:11px 14px;border-bottom:1px solid var(--hair-2);vertical-align:top}
.logtable thead th{position:sticky;top:0;background:var(--panel-2);color:var(--ink-3);font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;z-index:1}
.logtable tbody tr:hover{background:rgba(255,255,255,.03)}
.logtable td.mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;color:var(--ink-2);white-space:nowrap}
.tag{display:inline-block;font-size:11px;font-weight:800;padding:3px 9px;border-radius:999px;background:rgba(150,133,202,.16);color:#cbb8ff;white-space:nowrap}
.tag-remove{background:rgba(238,140,157,.14);color:var(--pink)}
.tag-archive{background:rgba(255,255,255,.06);color:var(--ink-3)}
.tag-login,.tag-create{background:rgba(126,224,184,.12);color:#7ee0b8}

/* access gate */
html.gate-locked .top,html.gate-locked .wrap{display:none!important}
#thriveGate{position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;padding:22px;
  background:radial-gradient(1200px 600px at 50% -10%,rgba(150,133,202,.18),transparent 60%),var(--bg)}
.gate-card{width:100%;max-width:360px;background:var(--panel);border:1px solid var(--hair);border-radius:18px;padding:30px 26px;text-align:center;box-shadow:0 30px 80px rgba(0,0,0,.55)}
.gate-logo{width:40px;height:40px;display:block;margin:0 auto 14px;animation:spin 22s linear infinite}
.gate-title{font-size:20px;font-weight:800;color:var(--ink);margin:0 0 6px}
.gate-sub{font-size:13px;color:var(--ink-3);margin:0 0 18px}
.gate-input{width:100%;background:var(--panel-2);border:1px solid var(--hair);border-radius:10px;color:var(--ink);padding:12px 14px;font-size:15px;font-family:inherit;outline:none;text-align:center;letter-spacing:.02em;transition:border-color .2s}
.gate-input:focus{border-color:var(--purple)}
.gate-input.shake{animation:shake .4s}
.gate-btn{width:100%;margin-top:12px;padding:12px 18px;border:0;border-radius:10px;font-weight:800;font-size:14px;cursor:pointer;color:#15121b;background:var(--grad);box-shadow:0 8px 24px rgba(150,133,202,.28);font-family:inherit}
.gate-btn:active{transform:scale(.99)}
/* P31: the one-tap Retry after a sign-in timeout. A quiet secondary button (no gradient), so it reads as
   a recovery action, not the primary sign-in. It opens a fresh connection on tap. */
.gate-btn.ghost{color:var(--ink-1);background:transparent;box-shadow:none;border:1px solid var(--line);margin-top:8px}
.gate-err{color:var(--pink);font-size:12.5px;font-weight:700;margin:12px 0 0}
/* P29 diagnostic: the raw error text under the human message, so a non-timeout reason is never hidden by a
   silent hang. Muted, monospace, wraps, and stays LTR so an ASCII error is not reordered in an RTL card. */
.gate-diag{color:var(--ink-3);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;margin:6px 0 0;line-height:1.4;word-break:break-word;direction:ltr;text-align:start}
.gate-note{color:var(--ink-3);font-size:11.5px;margin:16px 0 0;line-height:1.5}
.gate-alt{display:block;margin:12px 0 0;color:var(--accent);font-size:12.5px;text-decoration:underline;text-underline-offset:2px}
.gate-build{color:var(--muted);font-size:11px;margin:12px 0 0;opacity:.75;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
/* P39: the ephemeral-session notice. Pinned to the top over the live console, outside the gate, so it
   survives reveal() and stays until dismissed. It says plainly that the session lives in this tab only. */
.gate-ephemeral{position:fixed;top:0;left:0;right:0;z-index:9000;display:flex;align-items:center;gap:12px;
  padding:10px 16px;background:var(--panel);border-bottom:1px solid var(--hair);
  box-shadow:0 8px 24px rgba(0,0,0,.35);color:var(--ink-1);font-size:12.5px;line-height:1.45}
.gate-ephemeral span{flex:1}
.gate-ephemeral-x{flex:none;width:26px;height:26px;border:0;border-radius:8px;background:transparent;
  color:var(--ink-3);font-size:18px;line-height:1;cursor:pointer}
.gate-ephemeral-x:hover{background:var(--hair);color:var(--ink-1)}
.gate-build bdi{unicode-bidi:isolate}
@keyframes shake{10%,90%{transform:translateX(-1px)}20%,80%{transform:translateX(2px)}30%,50%,70%{transform:translateX(-5px)}40%,60%{transform:translateX(5px)}}

/* section headings inside panels */
.sec-h{font-size:11px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin:20px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--hair-2)}
.sec-h:first-child{margin-top:0}
.block-h{font-size:16px;font-weight:800;color:var(--ink);margin:26px 0 12px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:0 14px}
.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:0 14px}
/* grid children must be allowed to shrink, or intrinsically-wide controls (Safari's date
   input especially) overflow their cell and spill across the next field */
.grid2>*,.grid3>*{min-width:0}
.grid2 .input,.grid3 .input,.grid2 select,.grid3 select{max-width:100%}
input[type="date"]{-webkit-appearance:none;appearance:none;min-width:0;width:100%;text-align:start}
@media(max-width:560px){.grid2,.grid3{grid-template-columns:1fr}}
@media(max-width:560px){.grid2,.grid3{grid-template-columns:1fr}}

/* editor preview */
.editor .preview .sec-h{margin-top:0}
.devtoggle{display:inline-flex;border:1px solid var(--hair);border-radius:9px;overflow:hidden;margin-inline-end:auto}
.devtoggle button{padding:7px 12px;border:0;background:transparent;color:var(--ink-3);font-weight:800;font-size:12px;cursor:pointer;font-family:inherit}
.devtoggle button.on{background:rgba(150,133,202,.16);color:var(--ink)}
.framewrap{display:flex;justify-content:center;background:#0a0a0c;border:1px solid var(--hair);border-radius:14px;padding:0;overflow:hidden}
/* The device toggle used to animate `width`, which reflows every frame and makes
   the previewed text unreadable while it moves. WO-012 phase 7 located it and
   §2.4 required leaving it; VISUAL_MEMORY Law 5 now forbids it outright, so the
   width changes at once and only the opacity is animated. */
.frame{width:100%;height:70vh;min-height:460px;border:0;border-radius:14px;background:#0a0a0c;transition:opacity .25s var(--ease)}
.frame.phone{width:390px;max-width:100%}
.bar-actions{margin-top:12px}

/* draft vs live cards */
.card.is-draft{border-style:dashed}
.card.needs-fu{border-color:rgba(246,207,91,.45)}
.link.muted{color:var(--ink-3);font-weight:600;cursor:default}
.fu-chip{color:#F6CF5B!important;background:rgba(246,207,91,.12)!important;border-color:rgba(246,207,91,.3)!important}
/* Every action the same size, laid on a grid that fills, so a card never ends with one lonely
   button on a line of its own in one language and a tidy row in the other. */
.actions-wrap{display:grid;grid-template-columns:repeat(auto-fit,minmax(86px,1fr));gap:8px;margin-top:10px}
.actions-wrap>*{justify-content:center;text-align:center;white-space:nowrap}
/* The stage select carries a sentence ("Derived: Ready to send"), which in Arabic is longer
   than the button beside it, so the two sat unbalanced and pushed the actions into a ragged
   extra row. The select takes the width it needs and the row wraps as a unit. */
.stage-sel{width:auto;min-width:120px;max-width:100%;padding:8px 10px;font-size:12.5px;font-weight:700;
  text-overflow:ellipsis}
.card .row{flex-wrap:wrap}
.card .row>*{flex:0 1 auto;min-width:0}

/* pipeline summary */
.pipeline{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 16px}
.pl-pill{font-size:12px;font-weight:700;color:var(--ink-2);background:var(--panel);border:1px solid var(--hair);border-radius:999px;padding:6px 12px;cursor:pointer;font-family:inherit;transition:.15s}
.pl-pill b{color:var(--ink);margin-inline-start:2px}
.pl-pill:hover{border-color:var(--purple);color:var(--ink)}
.pl-pill.on{background:rgba(150,133,202,.18);border-color:var(--purple);color:var(--ink)}
/* The pill and the lane are the same state, so they are the same colour. A reader who learns
   the board reads the library without learning it twice. */
.pl-draft b{color:var(--lane-draft)} .pl-live b{color:var(--lane-live)}
.pl-sent b{color:var(--lane-sent)} .pl-opened b{color:var(--lane-opened)}
.pl-replied b{color:var(--lane-replied)}
.pl-won b{color:#7ee0b8} .pl-lost b{color:var(--pink)}
.pl-fu{border-color:rgba(246,207,91,.4);color:#F6CF5B}
.pl-fu:hover{background:rgba(246,207,91,.1);border-color:#F6CF5B}
.pl-fu.on{background:rgba(246,207,91,.16);border-color:#F6CF5B;color:#F6CF5B}
.pl-clear{color:var(--ink-3)}

/* email composer */
.compose-panel{max-width:820px}
/* A reply rides the ONE calm chrome (Aa reveals formatting, More reveals the overflow, Preview stays
   first-class); it needs no bespoke disclosure. reply-lean only tucks the campaign-only affordances a
   one-address reply never uses: the personalize chip and the campaign send, plus the outreach send
   conveniences (send-to-myself / copy / mail) that live in the overflow. */
.reply-lean #tbPersonalize,
.reply-lean #eCampaign{ display:none !important }
.reply-lean .ovf-actions{ display:none !important }      /* a reply is one Send; the outreach conveniences stay out */
.etpl-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:16px;padding-bottom:14px;border-bottom:1px solid var(--hair-2)}
.etpl-label{font-size:12.5px;font-weight:800;color:var(--ink-2)}
.etpl-bar select{max-width:240px}
/* One tap to a starting message, offered only while the message is still empty. */
.etpl-quick{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:-4px 0 16px}
.etpl-quick-h{font-size:12px;font-weight:800;color:var(--ink-3)}
.etpl-quick .lp{font-size:12px;font-weight:700;padding:7px 12px;border-radius:999px;border:1px solid var(--hair);background:transparent;color:var(--ink-2);cursor:pointer;font-family:inherit;transition:border-color .15s,color .15s}
.etpl-quick .lp:hover{border-color:var(--purple);color:var(--ink)}
/* Two kinds of template, and the tabs that keep them apart. */
.tpl-tabs{max-width:520px;margin:0 0 18px}
.tile-sub{font-size:13px;font-weight:600;color:var(--ink-3)}
.tag-cat-emails{background:rgba(150,133,202,.16);color:#cbb8ff}
.tag-cat-pages{background:rgba(113,191,204,.14);color:#8fd3de}
.tag-cat-templates{background:rgba(238,140,157,.14);color:#f3a9b6}
.tag-cat-system{background:rgba(255,255,255,.06);color:var(--ink-3)}
.tag-email,.tag-reply{background:rgba(150,133,202,.16);color:#cbb8ff}
.tag-publish,.tag-unpublish{background:rgba(126,224,184,.12);color:#7ee0b8}
.etoolbar{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.etoolbar button{padding:7px 11px;border-radius:8px;border:1px solid var(--hair);background:transparent;color:var(--ink-2);font-weight:800;font-size:13px;cursor:pointer;font-family:inherit;min-width:34px}
.etoolbar button:hover{border-color:var(--purple);color:var(--ink)}
.etoolbar .tb-accent{margin-inline-start:auto;color:#15121b;background:var(--grad);border:0}
.ebody{min-height:240px;background:var(--panel);border:1px solid var(--hair);border-radius:12px;padding:16px 18px;color:var(--ink);font-size:15px;line-height:1.7;outline:none}
.ebody:focus{border-color:var(--purple)}
.ebody a{color:var(--teal);text-decoration:underline}
.ebody:empty::before{content:attr(data-ph);color:var(--ink-3)}

/* One message body, one frame. Every surface that renders a message body shares ONE set of wrapping rules,
   so a long line or an unbreakable URL wraps INSIDE the frame instead of running past its edge. The earlier
   frame fix reached the compose-preview iframe and the editable .ebody, but not the <pre class="mw-pitch">
   of the modal text tab and the overview pitch: a bare <pre> keeps the UA default white-space:pre (no wrap)
   and clipped every long line. Routing every body surface through this one rule closes that gap and leaves
   no surface with its own divergent wrapping. CSS only; the type is never shrunk, the text wraps. Surfaces:
   the modal text tab and overview pitch (.mw-pitch), the editable compose body (.ebody), the thread reply
   body and its blocks (.rp-snip/.rp-msg/.rp-line/.rp-quoted-body), and the message preview (.mprev). The
   compose preview is its own iframe document and mirrors these values in its srcdoc body style. */
.msg-body,
.mw-pitch,
.ebody,
.rp-snip, .rp-msg, .rp-line, .rp-quoted-body,
.mprev{
  white-space:pre-wrap;
  overflow-wrap:anywhere;
  word-break:break-word;
  max-width:100%;
}
.etoolbar button u{text-decoration:underline}

/* ── P9 / D8 the calm composer chrome ─────────────────────────────────────────────────────────────
   At rest: To, Subject, body, Send. Formatting hides behind Aa and appears as a floating bar on a text
   selection; the rest waits behind one overflow. Existing tokens only; reduced motion respected; no
   letter-spacing added anywhere (Arabic inherits the global normal). */
.compose-panel.calm{ position:relative }
.ebody-rail{ display:flex; align-items:center; gap:6px; margin-bottom:8px }
.ebody-rail .rail-spacer{ flex:1 1 auto }
.rail-btn{ padding:6px 11px; border-radius:8px; border:1px solid var(--hair); background:transparent;
  color:var(--ink-2); font-weight:800; font-size:13px; cursor:pointer; font-family:inherit; line-height:1;
  min-width:34px }
.rail-btn:hover{ border-color:var(--purple); color:var(--ink) }
.rail-btn.has-on,[aria-expanded="true"].rail-btn{ border-color:var(--purple); color:var(--ink) }
.ebody-rail .etb-hist{ line-height:0; min-width:34px }
.ebody-rail .etb-hist.ed-redo .ic{ transform:scaleX(-1) }
.ebody-rail .etb-hist:disabled{ opacity:.4; cursor:not-allowed }
.ebody-rail .etb-hist:disabled:hover{ border-color:var(--hair); color:var(--ink-2) }
/* the floating format bar: absolute within the panel, above the selection, kept inside the panel by JS */
.efloat{ position:absolute; z-index:40; top:0; inset-inline-start:0; display:flex; gap:6px; padding:6px;
  border:1px solid var(--hair-strong,var(--hair)); border-radius:10px; background:var(--panel);
  box-shadow:0 8px 24px rgba(0,0,0,.30); animation:efloat-in .12s var(--e-enter,ease) }
.efloat[hidden]{ display:none }
.efloat button{ padding:7px 11px; border-radius:8px; border:1px solid var(--hair); background:transparent;
  color:var(--ink-2); font-weight:800; font-size:13px; cursor:pointer; font-family:inherit; min-width:34px }
.efloat button:hover{ border-color:var(--purple); color:var(--ink) }
.efloat button u{ text-decoration:underline }
.efloat .tb-accent{ color:#15121b; background:var(--grad); border:0 }
@keyframes efloat-in{ from{ opacity:0; transform:translateY(4px) } to{ opacity:1; transform:none } }
@media (prefers-reduced-motion:reduce){ .efloat{ animation:none } }
/* one overflow menu: everything real but rarely touched */
.cmp-overflow{ margin:2px 0 16px; padding:14px; border:1px dashed var(--hair); border-radius:12px;
  background:var(--panel-2,rgba(255,255,255,.02)); display:flex; flex-direction:column; gap:12px }
.cmp-overflow[hidden]{ display:none }
.cmp-overflow .etpl-bar{ display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin:0 }
.cmp-overflow .more-bar{ margin:0 }
.cmp-overflow .mw-more{ margin:0 }
.ovf-actions{ display:flex; gap:var(--s-3); flex-wrap:wrap }
/* the base toggle look for the personalize chip, now living above Send (was in the toolbar before) */
.tb-toggle{ padding:7px 12px; border-radius:8px; border:1px solid var(--hair); background:transparent;
  color:var(--ink-2); font-weight:800; font-size:13px; cursor:pointer; font-family:inherit; line-height:1 }
.tb-toggle:hover{ border-color:var(--purple); color:var(--ink) }
/* campaign controls: the personalize chip and Preview, side by side, above Send */
.cmp-campaign-controls{ display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap; margin:8px 0 12px }
.cmp-campaign-controls .chip-ctl{ min-height:34px }
.cmp-prev-wrap{ margin-top:0 }
/* the Preview control (above Send) owns the disclosure now, so the details' own summary is redundant */
.cmp-prev-wrap > summary{ display:none }
/* reply mode: the same fields, prefilled; this banner names the thread being answered */
.reply-banner{ display:flex; align-items:center; gap:8px; margin:0 0 14px; padding:9px 12px;
  border:1px solid var(--hair); border-inline-start:3px solid var(--purple); border-radius:10px;
  background:var(--panel-2,rgba(150,133,202,.06)); color:var(--ink-2); font-size:12.5px; font-weight:700 }
.reply-banner[hidden]{ display:none }
.elinkbar{display:flex;flex-direction:column;gap:8px;margin:0 0 10px;padding:12px;border:1px solid var(--hair);border-radius:12px;background:var(--panel-2,rgba(255,255,255,.02))}
.elinkrow{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.elinkrow .input{flex:1 1 200px;min-width:140px}
.elinkbar #elinkurl{direction:ltr}
.elinkrow-actions{justify-content:flex-end}
.elinkpresets{display:flex;gap:6px;flex-wrap:wrap}
.elinkpresets .lp{font-size:12px;font-weight:600;padding:5px 10px;border-radius:999px;border:1px solid var(--hair);background:transparent;color:var(--ink-2);cursor:pointer}
.elinkpresets .lp:hover{border-color:var(--purple);color:var(--ink)}
/* links manager: every link in the message, editable & removable */
.elinks{margin:10px 0 4px;border:1px solid var(--hair);border-radius:12px;padding:10px 12px;background:var(--panel)}
.elinks-h{font-weight:700;color:var(--ink);font-size:13px;margin-bottom:8px;display:flex;align-items:center;gap:8px}
.elink-item{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 0;border-top:1px dashed var(--hair);flex-wrap:wrap}
.elink-item:first-of-type{border-top:0}
.elink-info{min-width:0;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.elink-text{font-weight:600;color:var(--ink);overflow-wrap:anywhere}
.elink-url{font-size:11.5px;color:var(--muted);overflow-wrap:anywhere;direction:ltr}
.elink-acts{display:flex;gap:6px;flex-shrink:0}
.btn.sm.danger,.elink-acts .danger{color:#e88}
html[dir="rtl"] .elink-url{text-align:start}
/* P23 rich-link kind badge, next to the origin tag */
.tag-kind{background:rgba(90,150,220,.16);color:#8fb6e6}
.tag-kind.lk-drive{background:rgba(240,190,90,.16);color:#e6c07a}
.tag-kind.lk-youtube{background:rgba(230,110,110,.16);color:#e69090}
/* P23 attachment strip: every image, its filename, how it will land, and remove */
.eattach{margin:10px 0 4px;border:1px solid var(--hair);border-radius:12px;padding:10px 12px;background:var(--panel)}
.eattach-h{font-weight:700;color:var(--ink);font-size:13px;margin-bottom:8px;display:flex;align-items:center;gap:8px}
.eatt-item{display:flex;align-items:center;gap:10px;padding:8px 0;border-top:1px dashed var(--hair)}
.eatt-item:first-of-type{border-top:0}
.eatt-thumb{width:44px;height:44px;flex:none;border-radius:8px;object-fit:cover;border:1px solid var(--hair);background:var(--bg)}
.eatt-info{min-width:0;flex:1 1 auto;display:flex;flex-direction:column;gap:3px}
.eatt-name{font-weight:600;color:var(--ink);overflow-wrap:anywhere}
.eatt-meta{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--muted)}
.eatt-rm{flex:none}
/* P23 Drive-sharing reminder chip, sender-only */
.edrivechip{display:flex;align-items:flex-start;gap:8px;margin:8px 0 4px;padding:9px 12px;border-radius:12px;
  background:rgba(240,190,90,.10);border:1px solid rgba(240,190,90,.30);color:var(--ink-2);font-size:12.5px;line-height:1.45}
.edrivechip .edrive-ic{flex:none;color:#e6c07a;display:inline-flex;margin-top:1px}
.edrivechip .edrive-msg{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
/* P23 / Condition 2: the always-visible relay version + capability matrix */
.relay-caps{margin:6px 0 12px;border:1px solid var(--hair);border-radius:12px;padding:10px 12px;background:var(--panel)}
.relay-caps .rc-head{font-size:13px;color:var(--ink-2);margin-bottom:8px}
.relay-caps .rc-ver b{color:var(--ink)}
.relay-caps .rc-ver.rc-unknown{color:var(--muted)}
.relay-caps .rc-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.relay-caps .rc-cap{display:flex;align-items:center;gap:8px;font-size:12.5px}
.relay-caps .rc-dot{flex:none;display:inline-flex}
.relay-caps .rc-cap.on .rc-dot{color:var(--ops-green,#7cc38f)}
.relay-caps .rc-cap.off .rc-dot{color:var(--muted)}
.relay-caps .rc-cap.off{color:var(--muted)}
.relay-caps .rc-name{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.relay-caps .rc-need{flex:none;font-size:11.5px;color:var(--muted)}
.relay-caps .rc-cap.on .rc-need{color:var(--ops-green,#7cc38f)}
.chk{display:inline-flex;align-items:center;gap:8px;margin:0 0 16px;font-size:13px;color:var(--ink-2);cursor:pointer;font-weight:600}
.chk input{width:16px;height:16px;accent-color:var(--purple);cursor:pointer}

/* QR modal */
.qr-modal{position:fixed;inset:0;z-index:9998;display:flex;align-items:center;justify-content:center;padding:20px;background:rgba(0,0,0,.6)}
.qr-card{background:var(--panel);border:1px solid var(--hair);border-radius:18px;padding:22px;text-align:center;max-width:340px;width:100%}
.qr-label{font-weight:800;color:var(--ink);margin-bottom:12px;font-size:16px}
.qr-img canvas{width:100%;max-width:260px;height:auto;border-radius:10px;display:block;margin:0 auto;background:#fff}
.qr-url{direction:ltr;font-size:12px;color:var(--teal);word-break:break-all;margin:12px 0 4px}
.qr-card .bar{justify-content:center;margin-top:12px}

/* upload panel + template management + settings */
.upload-panel{margin-bottom:24px}
.settings-panel{margin-bottom:20px;max-width:820px}
.settings-panel .bar{margin-top:var(--s-2)}
/* WO-022: Settings in five sections, one card each. Structure and spacing only, no behaviour change.
   A section is one .set-group card holding labelled .set-block sub-blocks, hairline separated. Every
   button row is a real flex row: one gap, one baseline, never touching, one gradient (the primary). */
.set-group{margin-bottom:22px;max-width:820px}
.set-group-h{display:flex;align-items:center;gap:.5em;font-size:19px;font-weight:800;color:var(--ink);margin:0 0 4px}
.set-group > .sub{margin:0 0 20px}
.set-block{margin-top:24px}
.set-block:first-of-type{margin-top:6px}
.set-block + .set-block{border-top:1px solid var(--hair);padding-top:22px}
.set-block .block-h{margin:0 0 8px}
.set-block > .sub{margin:0 0 14px}
.set-group .bar{margin-top:14px}   /* the flex row, one gap token and equal heights come from .bar above */
/* WO-028 Settings v2: the one advanced disclosure. Same card as a section, closed by default; the
   summary reads like a section title with a chevron, and the blocks inside reuse the .set-block model. */
.set-advanced{margin-bottom:22px;max-width:820px}
.set-adv-summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:.5em}
.set-adv-summary::-webkit-details-marker{display:none}
.set-adv-summary::after{content:"";width:.6em;height:.6em;margin-inline-start:auto;border-right:2px solid var(--ink-3);border-bottom:2px solid var(--ink-3);transform:rotate(-45deg);transition:transform .15s}
.set-advanced[open] .set-adv-summary::after{transform:rotate(45deg)}
.set-advanced > .sub{margin:6px 0 0}
.set-advanced[open] > .sub{margin:6px 0 20px}
.set-advanced .set-block:first-of-type{margin-top:20px}
.gh-result{margin-top:12px;font-size:13px;font-weight:700;padding:10px 14px;border-radius:10px;border:1px solid var(--hair);color:var(--ink-2);word-break:break-word}
.gh-result.ok{color:#7ee0b8;border-color:rgba(126,224,184,.4);background:rgba(126,224,184,.08)}
.gh-result.warn{color:#F6CF5B;border-color:rgba(246,207,91,.35);background:rgba(246,207,91,.08)}
.item-body{min-width:0}
.item-body h3{overflow-wrap:anywhere}
.meta-line{font-size:12px;color:var(--ink-3);margin:6px 0 12px}

/* insights tiles */
/* A fixed column count, so a group of six reads as two rows of three rather than as however
   many happen to fit. auto-fit gave six across at this width, and a group of seven then left
   one tile stranded on a line of its own, which reads as a mistake because it is one. */
.tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:6px 0 20px}
@media(max-width:820px){ .tiles{grid-template-columns:repeat(2,1fr)} }
@media(max-width:430px){ .tiles{grid-template-columns:1fr} }
/* WO-023: one aligned unit per tile. Icon, number and label stack on one axis and hug the reading
   edge (flex-start mirrors by direction: left in English, right in Arabic), never icon-centered while
   the number and label sit to the side. */
.tile{background:var(--panel);border:1px solid var(--hair);border-radius:14px;padding:16px 18px;display:flex;flex-direction:column;align-items:flex-start}
/* "15 / 3000" wrapped onto two lines while its neighbours stayed on one, which made a row of
   tiles look broken over one long number. */
.tile-v{font-size:26px;font-weight:800;color:var(--ink);line-height:1.1;white-space:nowrap}
.tile-k{font-size:12px;color:var(--ink-3);margin-top:4px;font-weight:700}
.pill{font-size:11px;font-weight:800;letter-spacing:.04em;padding:4px 10px;border-radius:999px;border:1px solid var(--hair);color:var(--ink-3)}
.pill.ok{color:#7ee0b8;border-color:rgba(126,224,184,.4);background:rgba(126,224,184,.08)}
.pill.warn{color:#F6CF5B;border-color:rgba(246,207,91,.35);background:rgba(246,207,91,.08)}
.pill.drift{color:#F6CF5B;border-color:rgba(246,207,91,.5);background:rgba(246,207,91,.12);display:inline-flex;align-items:center;gap:5px}
.pill.drift svg{width:13px;height:13px;flex:none}
.pill.drift[hidden]{display:none}
/* P22: the inbound heartbeat, on the board. A quiet amber clock for a stale sweep, a louder rose alert for
   a backlog the sweep could not clear, both beside the sync pill so silence is never silent. */
.pill.inbound-delay{color:var(--ink-3,#9aa);border-color:var(--hair);background:transparent;display:inline-flex;align-items:center;gap:5px}
.pill.inbound-gap{color:#E38B8B;border-color:rgba(227,139,139,.5);background:rgba(227,139,139,.12);display:inline-flex;align-items:center;gap:5px}
.pill.inbound-delay svg,.pill.inbound-gap svg{width:13px;height:13px;flex:none}
.pill.inbound-delay[hidden],.pill.inbound-gap[hidden]{display:none}
/* The week, as days. One line each, the day on one side and what happened on the other, so a
   glance down the left tells you when and a glance down the right tells you what. */
.story-days{list-style:none;margin:0 0 26px;padding:0;border:1px solid var(--hair);border-radius:var(--r-lg);overflow:hidden}
.story-days li{display:flex;gap:16px;align-items:baseline;padding:12px 16px;border-bottom:1px solid var(--hair-2);flex-wrap:wrap}
.story-days li:last-child{border-bottom:0}
.story-when{flex:0 0 auto;min-width:120px;font-size:12px;font-weight:800;color:var(--ink-3)}
.story-said{flex:1 1 220px;color:var(--ink-2);font-size:14px}
.story-said b{color:var(--ink);font-weight:800;font-family:var(--font-lat);direction:ltr;unicode-bidi:isolate}
.spark{display:inline-flex;align-items:flex-end;gap:2px;height:24px}
.spark i{display:inline-block;width:5px;border-radius:2px;background:linear-gradient(180deg,#9685CA,#71BFCC)}

/* mobile header wrapping */
@media(max-width:560px){
  .top{flex-wrap:wrap;gap:8px 10px}
  .nav{width:100%;margin-inline-start:0;gap:4px}
  .nav a{padding:7px 10px;font-size:12.5px}
  .langbtn{padding:7px 10px}
}
/* A fourth destination costs width, and the bar is the one thing on screen that must not grow
   a row on a phone: every row it takes is a row the work does not get. Tighter horizontal
   padding on the narrowest screens, and nothing else changes. Heights are untouched, so the
   40px touch floor below still holds. */
@media(max-width:430px){
  .nav{gap:3px}
  .nav a{padding:7px 7px}
  .langbtn{padding:7px 8px}
}
/* The smallest phones still in use. One more notch, so the bar is one row there too. */
@media(max-width:380px){
  .nav{gap:2px}
  .nav a{padding:7px 5px;font-size:12px}
  .langbtn{padding:7px 6px;font-size:11.5px}
}

/* RTL */
html[dir="rtl"] body,html[dir="rtl"] input,html[dir="rtl"] textarea,html[dir="rtl"] select,html[dir="rtl"] button{font-family:var(--font-ar)}

/* Arabic typography: no letter-spacing (breaks joined letters), no uppercase, lighter weights */
html[dir="rtl"] body{font-weight:400}
html[dir="rtl"] .brand b,html[dir="rtl"] .nav a,html[dir="rtl"] .badge,html[dir="rtl"] .sec-h,
html[dir="rtl"] .id,html[dir="rtl"] .tag,html[dir="rtl"] .pl-pill,html[dir="rtl"] .chip,
html[dir="rtl"] .block-h,html[dir="rtl"] h1.title,html[dir="rtl"] .tile-k,html[dir="rtl"] .pill,
html[dir="rtl"] .brand b,html[dir="rtl"] label,html[dir="rtl"] .logtable thead th{letter-spacing:normal;text-transform:none}
html[dir="rtl"] h1.title{font-weight:700}
html[dir="rtl"] .block-h,html[dir="rtl"] .card .biz,html[dir="rtl"] .tpl h3,html[dir="rtl"] .tile-v,
html[dir="rtl"] .gate-title{font-weight:600}
html[dir="rtl"] .brand b,html[dir="rtl"] .nav a,html[dir="rtl"] .btn,html[dir="rtl"] .badge,
html[dir="rtl"] .chip,html[dir="rtl"] .seg button,html[dir="rtl"] .langbtn,html[dir="rtl"] .pl-pill,
html[dir="rtl"] .tag,html[dir="rtl"] .id,html[dir="rtl"] .sec-h,html[dir="rtl"] label,
html[dir="rtl"] .field label,html[dir="rtl"] .etpl-label,html[dir="rtl"] .devtoggle button{font-weight:500}
html[dir="rtl"] .sub,html[dir="rtl"] .hint,html[dir="rtl"] .note,html[dir="rtl"] .meta-line{font-weight:400}
html[dir="rtl"] .card .link,html[dir="rtl"] .urlpill{direction:ltr;text-align:start}
html[dir="rtl"] .tpl .thumb iframe{transform-origin:top right}
/* The gate input tracks its Latin placeholder, and that tracking was reaching the Arabic
   one, where letter-spacing breaks the joins. Latin only. */
html[dir="rtl"] #thriveGate .gate-input{text-align:center;letter-spacing:normal}
html[dir="rtl"] .logtable td.mono{direction:ltr;text-align:start}

/* ---- Performance: off-screen cards skip layout/paint; respect reduced-motion; pause logo spin on hidden tab ---- */
.card{content-visibility:auto;contain-intrinsic-size:0 240px}

/* ===========================================================================
   Motion layer. Two properties move: transform and opacity. Plus border-color,
   background-color and box-shadow on small objects, at --t-instant only.
   Never width, height, top, left, margin, padding or font-size: they lay out
   on every frame, drop frames on an iPad, and reflow text under a reader.
   Exit is always shorter than enter, because waiting for something to finish
   leaving is the most common way an interface feels slow.
   =========================================================================== */
.rv{opacity:0;transform:translateY(8px);transition:opacity var(--t-base) var(--e-enter),transform var(--t-base) var(--e-enter)}
.rv.in{opacity:1;transform:none}
.press{transition:transform var(--t-instant) var(--e-standard)}
.press:active{transform:scale(.98)}
/* An object that arrives, capped at 8px of travel: more reads as theatre. */
/* The board settles calmly: a card fades in where it belongs, never rising or flying from off-axis.
   Opacity only, no transform, so a re-render (a hydrate, a sign-in, a refresh) paints in place. */
@keyframes rise-in{from{opacity:0}to{opacity:1}}
.enter{animation:rise-in var(--t-base) var(--e-enter) both}
.enter-1{animation-delay:0ms} .enter-2{animation-delay:40ms} .enter-3{animation-delay:80ms}

/* The warm arrival. When the board first paints for real - a cold open resolves, a sign-in lands, the
   hydrate returns the live board - the lanes settle from a soft blur into focus instead of snapping into a
   dead gap. Opacity and filter only, no transform and no layout, so it is GPU-cheap and never reflows. It
   plays ONCE per arrival (app.js gates it on the not-live to live transition), never on a background poll
   or a badge repaint. The count chips tick up to their values over the same beat, in JS. Reduced motion
   gets the board in focus at once and the numbers at their final value, with no settle and no count. */
@keyframes board-settle{ from{ opacity:.001; filter:blur(7px) } to{ opacity:1; filter:blur(0) } }
.board.board-settle{ animation:board-settle var(--t-settle) var(--e-enter) both; will-change:opacity, filter }
@media (prefers-reduced-motion:reduce){ .board.board-settle{ animation:none } }

/* T4: the quiet glow. It marks one thing per column or section, the value that
   earned attention, and a card in a state to act on. .is-glow is the resting
   accent that holds while the fact holds; .is-glow-new plays ONE slow cycle when
   the fact becomes true, gated in app.js (glowChanged) so a re-render never
   replays it. One accent, never the gradient, never a field of pulses. */
@keyframes glow-swell{ 0%{box-shadow:0 0 0 0 var(--glow-0)} 35%{box-shadow:0 0 0 4px var(--glow-1)} 100%{box-shadow:0 0 0 0 var(--glow-0)} }
@keyframes glow-fill{ 0%{background-color:var(--glow-0)} 35%{background-color:var(--glow-1)} 100%{background-color:var(--glow-t)} }
.is-glow-new{ animation:glow-swell var(--t-glow) var(--e-standard) 1 both }
/* (Part 2) the board-token accent ring (.tok.is-glow) is retired: card emphasis is the one visual-state law
   (cardState -> data-state) now, not a lane-literal ring. The .is-glow-new swell and the .logtable td.is-glow
   wash below remain, used only by the Insights column-max highlight, which is a table cell, not a card. */
/* the top cell of a column rests with a faint accent wash and its figure in accent */
.logtable td.is-glow{ background:var(--glow-t) }
.logtable td.is-glow b,.logtable td.is-glow .ok-n{ color:var(--glow) }
.logtable td.is-glow-new{ animation:glow-fill var(--t-glow) var(--e-standard) 1 both }

@media (prefers-reduced-motion:reduce){
  .brand img,.gate-logo,.card,.card:hover{animation:none!important;transition:none!important}
  .rv{opacity:1;transform:none;transition:opacity var(--t-instant) linear}
  .enter{animation:none}
  .card:hover{transform:none}
  /* the one slow cycle is removed; the resting accent (.is-glow) stays as a static emphasis, and a
     card that just changed shows the same static ring in place of the cycle it would have played */
  .is-glow-new,.logtable td.is-glow-new{ animation:none!important }
}
html.tab-hidden .brand img,html.tab-hidden .gate-logo{animation-play-state:paused}

/* ---------- Email conversation threads (activity ledger) ---------- */
.threads-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin:8px 0 10px}
.threads-head .block-h{margin:0}
.input.sm{max-width:320px;padding:8px 12px;font-size:13px}
.tag-templates{background:rgba(150,133,202,.16);color:#b9a9e6}
.tag-plain{background:rgba(150,160,170,.14);color:#aab2bd}
.threads{display:flex;flex-direction:column;gap:8px;margin-bottom:18px}
.thread{border:1px solid var(--hair);border-radius:12px;background:var(--panel);overflow:hidden}
.thread>summary{list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px}
.thread>summary::-webkit-details-marker{display:none}
.thread[open]>summary{border-bottom:1px solid var(--hair)}
.th-main{min-width:0;flex:1 1 220px;display:flex;flex-direction:column;gap:6px}
.th-who{font-weight:700;color:var(--ink);overflow-wrap:anywhere;unicode-bidi:plaintext}
/* On a phone the summary is a column, not a row: an address squeezed into a third of 390px
   broke into "a@b.e xample", which is not an address any more. */
@media(max-width:560px){
  .thread>summary{flex-direction:column;align-items:stretch;gap:8px}
  .th-side{justify-content:flex-start}
}
.th-meta{display:flex;gap:6px;flex-wrap:wrap}
.th-side{display:flex;align-items:center;gap:10px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}
.th-counts{font-size:12.5px;color:var(--teal);font-weight:600;white-space:nowrap}
.th-last{font-size:11.5px;color:var(--muted);white-space:nowrap}
.thread-body{padding:6px 14px 12px}
/* Correcting which opportunity a conversation belongs to. A decision you make, never one the
   console guesses, so it sits with the conversation rather than in a settings page. */
/* Which opportunity a conversation counts towards, on the row itself. It used to live inside
   the conversation, which meant three taps to reach a correction the console had told the
   reader to make. A correction you are asked for has to be where you are looking. */
.th-opp{max-width:220px;font-size:12px;padding:6px 10px;border-radius:var(--r-pill)}
.msg{padding:10px 0;border-top:1px dashed var(--hair)}
.msg:first-child{border-top:0}
.msg-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px}
.msg-time{font-size:11.5px;color:var(--muted)}
.mdir{font-weight:800;font-size:14px}
.mdir.out{color:#8fd3de}
.mdir.in{color:#8ecf9a}
.msg-subj{font-size:14px;color:var(--ink);font-weight:600;overflow-wrap:anywhere}
.mprev{font-size:12.5px;color:var(--muted);margin-top:3px;line-height:1.5}
html[dir="rtl"] .th-side{justify-content:flex-start}
html[dir="rtl"] .mdir.out,html[dir="rtl"] .mdir.in{transform:scaleX(-1);display:inline-block}

/* ---------- Resend free-tier send meter ---------- */
.quota-meter{font-size:12.5px;font-weight:600;padding:6px 12px;border-radius:999px;white-space:nowrap;align-self:center}
/* A quiet confirmation that the working draft is safe in the durable record, never a claim of a send. */
.draft-saved{display:inline-flex;align-items:center;gap:.4em;align-self:center;
  font-size:var(--fs-xs);font-weight:var(--w-head);color:var(--lane-replied)}
.draft-saved .ic{color:var(--lane-replied)}
.quota-meter:empty{display:none}
.quota-meter.ok{background:rgba(113,191,204,.12);color:#8fd3de}
.quota-meter.near{background:rgba(230,180,90,.15);color:#e6b45a}
.quota-meter.full{background:rgba(224,110,110,.16);color:#e88}
.quota-readout{font-size:14px;color:var(--ink);padding:12px 14px;border:1px solid var(--hair);border-radius:12px;margin:6px 0 14px;line-height:1.6}
.quota-readout.near{border-color:rgba(230,180,90,.5)}
.quota-readout.full{border-color:rgba(224,110,110,.5)}
.quota-readout b{font-weight:800}

/* ---------- guided "Link to opportunity" panel + status ---------- */
.eoppbar{gap:10px}
.eopp-l{font-size:12.5px;font-weight:600;color:var(--ink-2)}
.eopp-preview{display:flex;gap:12px;align-items:flex-start}
.eopp-thumb{width:132px;height:92px;border:1px solid var(--hair);border-radius:8px;background:#fff;overflow:hidden;flex-shrink:0;pointer-events:none;transform:scale(1);transform-origin:top left}
.eopp-info{min-width:0;display:flex;flex-direction:column;gap:4px}
.eopp-title{font-weight:700;color:var(--ink);overflow-wrap:anywhere}
.eopp-tag{font-size:12.5px;color:var(--ink-2);overflow-wrap:anywhere}
.eopp-open{font-size:11.5px;color:var(--teal);overflow-wrap:anywhere;direction:ltr;text-decoration:none}
.eopp-open:hover{text-decoration:underline}
.eopp-note{font-size:12.5px;color:var(--muted)}
.cmp-belowbody{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.eoppstatus{font-size:12.5px;font-weight:700;padding:5px 12px;border-radius:999px;white-space:nowrap}
.eoppstatus.ok{background:rgba(142,207,154,.16);color:#8ecf9a}
.eoppstatus.todo{background:rgba(150,160,170,.14);color:#aab2bd}
html[dir="rtl"] .eopp-thumb{transform-origin:top right}

/* ---------- live merge fields (compose) ---------- */
.month-input{max-width:240px}
.ebody [data-m]{border-bottom:1px dashed rgba(150,133,202,.55)}  /* editor-only cue; sent HTML has no spans */
/* P6/D4: the {{NAME}} merge token as a soft pill (never raw braces). contenteditable=false on the
   span makes it delete whole. Editor-only: htmlOut strips it before send. */
.ebody [data-m="name"]{border-bottom:0;background:rgba(150,133,202,.16);border:1px solid rgba(150,133,202,.42);border-radius:6px;padding:0 5px;white-space:nowrap}
[dir="rtl"] .ebody [data-m="name"]{letter-spacing:normal}
/* the Personalize-names toggle chip: neutral off, purple accent on */
#tbPersonalize.tb-toggle.on{background:rgba(150,133,202,.18);border-color:var(--purple);color:var(--ink)}
/* the pre-send merge roster: every recipient with the exact merge name and greeting */
.merge-roster{margin:.5rem 0}
.mr-h{margin:0 0 var(--s-2);font-size:var(--fs-micro);color:var(--ink-3);font-weight:700}
.mr-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--s-2)}
.mr-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-2)}
.mr-name{font-weight:600;flex:1 1 8rem}
.mr-addr{flex:1 1 10rem;min-width:0;overflow:hidden;text-overflow:ellipsis;color:var(--ink-4);font-size:var(--fs-micro)}
.mr-greet{flex:1 1 100%;color:var(--ink-3);font-size:var(--fs-micro)}
.mr-miss{color:var(--warn,#b26a00);font-size:var(--fs-micro)}
[dir="rtl"] .mr-row,[dir="rtl"] .mr-name,[dir="rtl"] .mr-greet{letter-spacing:normal}

/* ---------- Overview home + email-template editor ---------- */
.tile.t-warn{border-color:rgba(230,180,90,.45)}
.tile.t-good{border-color:rgba(142,207,154,.45)}
.ok-n{color:#8ecf9a}
.et-body{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:13px;line-height:1.6;resize:vertical;min-height:180px}
#etEditor{border-color:rgba(150,133,202,.45)}

/* ---------- metric explanations (hover on desktop, tap on touch) ---------- */
.tile-k{display:flex;align-items:center;gap:6px;justify-content:center;flex-wrap:wrap}
.info{width:16px;height:16px;flex:0 0 16px;border-radius:50%;border:1px solid var(--hair);
  background:transparent;color:var(--ink-3);font-size:10px;font-weight:800;line-height:1;
  cursor:pointer;font-family:inherit;padding:0;display:inline-flex;align-items:center;justify-content:center}
.info:hover,.info:focus-visible{border-color:var(--purple);color:var(--ink)}
.tipbubble{position:absolute;z-index:60;max-width:min(300px,84vw);background:#16161d;
  border:1px solid var(--purple);color:var(--ink);padding:10px 13px;border-radius:10px;
  font-size:12.5px;line-height:1.6;font-weight:600;box-shadow:0 10px 30px rgba(0,0,0,.55);
  opacity:0;transition:opacity .15s;pointer-events:none}
.tipbubble.show{opacity:1}
.warn-note{border-color:rgba(230,180,90,.45);color:var(--ink-2)}

/* ---------- RTL numeric integrity ----------
   Ratios, counters and timestamps are LTR sequences. Without isolation the bidi algorithm
   reorders "15 / 100" into "100 / 15" and mangles dates, and the numbers then read as wrong. */
.tile-v,.th-counts,.quota-meter,.quota-readout,.elink-url,.msg-time,.th-last,
.logtable td.mono,.mono,.spark,.urlpill{unicode-bidi:isolate}
html[dir="rtl"] .tile-v,html[dir="rtl"] .quota-meter,html[dir="rtl"] .quota-readout,
html[dir="rtl"] .th-counts,html[dir="rtl"] .logtable td.mono,html[dir="rtl"] .msg-time,
html[dir="rtl"] .th-last{direction:ltr}
html[dir="rtl"] .tile-v{text-align:start}
html[dir="rtl"] .logtable td.mono,html[dir="rtl"] .th-last,html[dir="rtl"] .msg-time{text-align:start}
/* numeric cells in tables stay LTR so counts never flip */
html[dir="rtl"] .logtable td b,html[dir="rtl"] .logtable td .ok-n{unicode-bidi:isolate;direction:ltr}
/* read-only identity fields (email addresses, URLs) are LTR regardless of UI language */
html[dir="rtl"] #efrom,html[dir="rtl"] #em_from,html[dir="rtl"] #eto,
html[dir="rtl"] #sy_ep,html[dir="rtl"] #em_ep,html[dir="rtl"] #ep2,html[dir="rtl"] #et_id{direction:ltr;text-align:start}

/* ---------- tile info badge: pinned to the trailing top corner ----------
   Top-right in LTR, top-left in RTL. Absolutely positioned so it can never reflow the
   label onto a second line, and the tile reserves room for it. */
.tile{position:relative}
.tile-info{position:absolute;top:10px;inset-inline-end:10px;opacity:.55;transition:opacity .15s}
.tile:hover .tile-info,.tile-info:focus-visible{opacity:1}
.tile-v{padding-inline-end:22px}                 /* keep big numbers clear of the badge */
.tile-k{display:block;text-align:inherit}        /* label is plain again, the badge lives in the corner */
.zero{color:var(--ink-3);font-weight:600}
/* Pages & visitors tiles carry their own icon and accent so the row reads at a glance rather than as
   one grey block. Scoped to #tilesPages so the Outreach tiles above are untouched. The accent is on
   the icon chip and a top edge; the number stays white for readability. */
#tilesPages .tile{ border-block-start:2px solid var(--tile-acc,var(--hair)) }
#tilesPages .tile-ic{ display:flex; width:34px; height:34px; margin:0 0 8px; align-items:center;
  justify-content:center; border-radius:10px; color:var(--tile-acc); background:var(--tile-acc-fill) }
#tilesPages .tile.acc-teal{   --tile-acc:#71BFCC; --tile-acc-fill:rgba(113,191,204,.14) }
#tilesPages .tile.acc-purple{ --tile-acc:#9685CA; --tile-acc-fill:rgba(150,133,202,.14) }
#tilesPages .tile.acc-blue{   --tile-acc:#7F9FD4; --tile-acc-fill:rgba(127,159,212,.14) }
#tilesPages .tile.acc-gold{   --tile-acc:#F6CF5B; --tile-acc-fill:rgba(246,207,91,.14) }
#tilesPages .tile.acc-rose{   --tile-acc:#C98B8B; --tile-acc-fill:rgba(201,139,139,.14) }
#tilesPages .tile.acc-green{  --tile-acc:#7EE0B8; --tile-acc-fill:rgba(126,224,184,.14) }
/* header badges stay inline but never wrap away from their label */
.logtable th .info{margin-inline-start:6px;vertical-align:middle}
.logtable th{white-space:nowrap}
.relay-err{display:inline-block;margin-inline-start:6px;color:#e6b45a;font-size:11.5px}
#homeProbe .mono{color:var(--teal)}

/* Boot failsafe: shown only when the console scripts never ran (no JS, a failed load, a
   parse error). Content trapped behind a gate that never rendered is the worst failure a
   private page can have, so it says what happened instead of showing a blank screen. */
.bootfail{max-width:520px;margin:80px auto;padding:16px 18px;border:1px solid var(--hair);
  border-radius:12px;background:var(--panel);color:var(--ink-2);font-size:14px;line-height:1.6;
  text-align:center;font-family:var(--font)}
.ok-line{color:#7fd1a3;font-weight:700}
.warn-line{color:#e0b24a;font-weight:700}

/* Connection health: one ordered list that names the broken link, so a failure is never
   diagnosed from a symptom three screens away. */
.conn-list{list-style:none;margin:6px 0 14px;padding:0;display:flex;flex-direction:column;gap:8px}
.conn-row{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;font-size:13.5px;color:var(--ink-2);
  background:var(--panel-2);border:1px solid var(--hair);border-radius:10px;padding:10px 12px}
.conn-row.bad{border-color:rgba(224,178,74,.5)}
.conn-i{flex:0 0 auto;font-weight:800;width:14px;text-align:center}
.conn-ok{color:#7fd1a3}
.conn-bad{color:#e0b24a}
.conn-wait{color:var(--ink-3)}
.conn-t{font-weight:700;color:var(--ink)}
.conn-d{font-size:12px;color:var(--ink-3);font-family:ui-monospace,Menlo,Consolas,monospace;
  unicode-bidi:isolate;direction:ltr;overflow-wrap:anywhere}
a.conn-d{color:var(--teal);text-decoration:underline;text-underline-offset:3px}
.conn-fix{flex:1 1 100%;font-size:12.5px;color:var(--ink-3);line-height:1.6}

/* Where a person stands: a state you can act on, coloured by temperature not by decoration. */
.tag-st-replied{background:rgba(127,209,163,.14);color:#7fd1a3;border-color:rgba(127,209,163,.35)}
.tag-st-warm{background:rgba(224,178,74,.14);color:#e0b24a;border-color:rgba(224,178,74,.35)}
.tag-st-warm_cold{background:rgba(238,140,157,.14);color:#EE8C9D;border-color:rgba(238,140,157,.35)}
.tag-st-sent{background:var(--panel-2);color:var(--ink-3)}
.tag-st-cold{background:var(--panel-2);color:var(--ink-3);opacity:.75}
.tag-st-pretrack{background:var(--panel-2);color:var(--ink-3);opacity:.7;font-style:italic}
.tag-warn{background:rgba(224,178,74,.14);color:#e0b24a;border-color:rgba(224,178,74,.35)}

/* The one sentence that reads the numbers back to you. Warmth is a sentence, not a colour. */
.lede{font-size:16.5px;line-height:1.7;color:var(--ink);margin:14px 0 18px;max-width:70ch;font-weight:600}
.lede .q{color:var(--ink-3);font-weight:400}
html[dir="rtl"] .lede{font-weight:500;line-height:1.8}

/* --- page header state strip -------------------------------------------------
   The summary paragraph, redesigned. A calm row of the header's real numbers, each a compact cell:
   a warm icon in one accent, the number, a quiet label. It echoes the status component (#52): the
   icon and the figure carry the meaning, the cell stays quiet, and there is no field of bright
   colours. The figures are isolated LTR so Arabic keeps them upright with the icon on the leading
   (right) side. This is the header language that will later reach the other page tops. */
.statstrip{ display:flex; flex-wrap:wrap; gap:var(--s-2); margin:var(--s-3) 0 var(--s-5) }
.statcell{
  display:inline-flex; align-items:center; gap:var(--s-2);
  padding:var(--s-2) var(--s-3); border-radius:var(--r-md);
  background:var(--panel); border:1px solid var(--hair);
}
.statcell-ic{ display:inline-flex; flex:0 0 auto; color:var(--teal) }
.statcell-n{
  font-family:var(--font-lat); font-size:var(--fs-lg); font-weight:900; color:var(--ink);
  font-variant-numeric:tabular-nums; line-height:1; direction:ltr; unicode-bidi:isolate;
}
.statcell-l{ font-size:var(--fs-xs); font-weight:700; color:var(--ink-4) }
@media(pointer:coarse){ .statcell{ min-height:44px } }

/* ============================================================================
   Thrive Console · board view
   Depends on tokens.css and motion.css.
   Every horizontal offset uses logical properties so RTL needs no overrides.
   ============================================================================ */

/* --- numeric isolation ----------------------------------------------------
   Every digit rendered inside a sentence goes in this class. Without it the
   bidi algorithm reorders sequences like "15 / 100" inside Arabic text, and the
   number then reads as wrong rather than as mirrored. The console already
   learned this on its tiles. IDENTITY §6.3.                                  */

.n{
  font-family:var(--font-lat);
  direction:ltr; unicode-bidi:isolate;
  display:inline-block;
  font-variant-numeric:tabular-nums;
}

/* --- verdict line: the one Syne moment on this screen --------------------- */

.board-head{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:var(--s-4); flex-wrap:wrap; margin-bottom:var(--s-5);
}
.verdict{
  display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
  font-family:var(--font-display);
  font-weight:var(--w-max);
  font-size:clamp(20px,3.2vw,var(--fs-2xl));
  line-height:var(--lh-tight);
  color:var(--ink);
  letter-spacing:-.01em;
}
/* T4 counter: the verdict number shown as a figure you read at a glance, with
   the sentence as its label. It is the same value ThriveBoard.verdict derives;
   there is no second count. aria-hidden on the figure so a reader hears the
   sentence once, not the number and then the number spelled out. */
.vcount{
  display:inline-block; border-radius:var(--r-md);
  font-size:clamp(34px,6vw,58px); line-height:.9; color:var(--lane-opened);
  font-variant-numeric:tabular-nums; unicode-bidi:isolate;
}
.vtext{ max-width:34ch }
[dir="rtl"] .verdict{ letter-spacing:normal; text-transform:none }
.verdict em{ font-style:normal; color:var(--ops-rose) }
.verdict-sub{
  font-size:var(--fs-sm); color:var(--ink-4);
  font-weight:var(--w-strong); margin-top:var(--s-1);
}

/* Direction A: an icon in a soft rose badge stands where the giant number was, and the one
   accented number is read once inside the sentence. One accent, the dusty rose, nowhere twice. */
.ops-status{ display:flex; align-items:center; gap:var(--s-3); min-width:0 }
.ops-badge{ flex:none; width:44px; height:44px; border-radius:var(--r-md);
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--ops-rose-fill); border:1px solid var(--ops-rose-bd); color:var(--ops-rose) }
.ops-badge .ic{ display:block }
.ops-lines{ min-width:0 }
.verdict .n{ color:var(--ops-rose); font-weight:var(--w-max);
  font-variant-numeric:tabular-nums; unicode-bidi:isolate }

/* The slim pipeline strip: five real stage counts, the live stage lit in the rose accent, the
   rest quiet. A read only summary that reads as one family with the status component. */
.ops-pipeline{ display:flex; align-items:center; flex-wrap:wrap; gap:var(--s-2);
  margin:0 0 var(--s-5) }
.plchip{ display:inline-flex; align-items:baseline; gap:var(--s-2);
  padding:5px 11px; border-radius:var(--r-pill);
  border:1px solid var(--hair); background:var(--panel);
  font-size:var(--fs-xs); font-weight:var(--w-strong); color:var(--ink-4) }
.plchip .pl-n{ font-family:var(--font-lat); font-weight:var(--w-head); color:var(--ink-3);
  font-variant-numeric:tabular-nums; unicode-bidi:isolate }
.plchip.is-live{ border-color:var(--ops-rose-bd); background:var(--ops-rose-fill); color:var(--ops-rose) }
.plchip.is-live .pl-n{ color:var(--ops-rose) }
[dir="rtl"] .plchip{ letter-spacing:normal }

/* --- board: two layouts, one board ------------------------------------------
   One breakpoint at 1024 forks the board, because horizontal scroll hides the
   columns behind a swipe and that is the same frustration as a card off the edge.

   At and above 1024 (iPad landscape and desktop): a CONDENSED five-column board.
   repeat(5, minmax(0,1fr)) makes the five columns share the width and SHRINK to
   fit, so the row is always inside the viewport and never overflows and never needs
   a sideways swipe. min-width:0 on the lane lets a column shrink and its card wrap.

   Below 1024 (iPad portrait and phone): five 236px columns cannot sit readably
   inside the screen (768 portrait leaves each column ~139px, below the touch card),
   so instead of hiding them behind a swipe the board becomes a status TAB BAR: one
   column shown in full at a time, stacked, all reachable, nothing hidden off a swipe.

   One card component and one lane content feed both layouts; nothing is rendered twice. */
.board{
  display:grid;
  grid-template-columns:repeat(5, minmax(0,1fr));
  gap:var(--s-2);
  min-width:0;
}
.board-tabs{ display:none }   /* the tab bar belongs to the narrow layout only */

@media(min-width:1024px){
  /* condensed: a tighter gap and a tighter card so five columns read calmly at ~190 to 230px */
  .board{ gap:var(--s-1) }
  .board .tok{ --tok-pad:var(--s-2) }   /* condensed columns: one uniform inset, still equal on every side */
}

@media(max-width:1023px){
  .board-tabs{
    display:flex; gap:var(--s-1); margin-bottom:var(--s-4);
  }
  /* one lane at a time, stacked, full width, nothing hidden off a swipe */
  .board{ display:block }
  .board .lane{ display:none }
  .board[data-active-lane="draft"]   .lane[data-lane="draft"],
  .board[data-active-lane="live"]    .lane[data-lane="live"],
  .board[data-active-lane="sent"]    .lane[data-lane="sent"],
  .board[data-active-lane="opened"]  .lane[data-lane="opened"],
  .board[data-active-lane="replied"] .lane[data-lane="replied"]{ display:flex }
  .board .lane.tab-in{ animation:tab-in var(--t-base) var(--e-enter) both }
  /* De-duplicate the count: the tab pill owns it in this layout, so the column header no longer
     repeats the number above the column. The header keeps its icon and word as the column's title. */
  .lane-head .lane-count{ display:none }
}
/* the soft switch between columns, removed under reduced motion */
@keyframes tab-in{ from{ opacity:0; transform:translateY(6px) } to{ opacity:1; transform:none } }
@media(prefers-reduced-motion:reduce){ .board .lane.tab-in{ animation:none } }

/* the tab: its lane icon, its label, its count; the active one carries one quiet accent. The five
   tabs share the width so all five, and all five counts, are always in view without a swipe, which
   is the point of the strip. The label rides in when there is room (iPad portrait, large phone) and
   steps aside on the narrowest phones, where the icon and the count carry the state. */
/* ===========================================================================
   The status component. One visual language, two densities. A pill in the state's
   own colour: the warm state icon leads, an integrated count sits inside as a
   small quiet chip (shown once, never a bare digit), and the current state carries
   one calm accent, its own lane hue, so the bar reads warm without a field of
   bright pills. Below the breakpoint (phone, iPad portrait) it is the tab bar
   pill: icon plus count, no visible word. At and above (iPad landscape, desktop)
   it is the column header: the same icon and the same count chip, with the word.
   =========================================================================== */
.btab{
  flex:1 1 0; min-width:0; display:inline-flex; align-items:center; justify-content:center; gap:6px;
  padding:8px 10px; border-radius:var(--r-pill);
  background:var(--panel); border:1px solid var(--hair);
  color:var(--ink-3); font:inherit; font-size:var(--fs-xs); font-weight:700;
  cursor:pointer; white-space:nowrap;
  transition:color var(--t-quick) var(--ease), border-color var(--t-quick) var(--ease), background var(--t-quick) var(--ease);
}
.btab .btab-ic{ color:var(--ink-4); display:inline-flex }
/* the pill shows no word; its label stays for assistive tech so the tab keeps a real name */
.btab-l{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0 }
.btab.is-on{ color:var(--ink); border-color:var(--lane-c, var(--purple)); background:var(--panel-2) }
.btab.is-on .btab-ic{ color:var(--lane-c, var(--purple)) }
@media(pointer:coarse){ .btab{ min-height:44px } }

/* the integrated count: one designed chip, shared by the pill and the column header */
.btab-n, .lane-count{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:18px; height:18px; padding:0 5px; border-radius:var(--r-pill);
  font-family:var(--font-lat); font-size:var(--fs-micro); font-weight:800;
  font-variant-numeric:tabular-nums; line-height:1;
  background:var(--hair-2); color:var(--ink-3);
  direction:ltr; unicode-bidi:isolate;
}
.btab.is-on .btab-n{ background:var(--lane-t, rgba(150,133,202,.16)); color:var(--lane-c, var(--purple)) }
/* each pill's state hue, applied only when it is the current one, so one accent lights at a time */
.btab[data-tab="draft"]{   --lane-c:var(--lane-draft);   --lane-t:var(--lane-draft-t) }
.btab[data-tab="live"]{    --lane-c:var(--lane-live);    --lane-t:var(--lane-live-t) }
.btab[data-tab="sent"]{    --lane-c:var(--lane-sent);    --lane-t:var(--lane-sent-t) }
.btab[data-tab="opened"]{  --lane-c:var(--lane-opened);  --lane-t:var(--lane-opened-t) }
.btab[data-tab="replied"]{ --lane-c:var(--lane-replied); --lane-t:var(--lane-replied-t) }

/* --- lane ----------------------------------------------------------------- */

.lane{ min-width:0; display:flex; flex-direction:column }
.lane-head{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-1);
  padding-bottom:var(--s-2); margin-bottom:var(--s-3);
  border-bottom:2px solid var(--lane-c);
  position:sticky; top:0; z-index:2;
  background:var(--bg);
}
.lane-title{
  font-family:var(--font-lat);
  font-size:var(--fs-micro); font-weight:900;
  letter-spacing:.08em; text-transform:uppercase;
  color:var(--lane-c);
}
[dir="rtl"] .lane-title{
  font-family:var(--font-ar);
  font-weight:var(--w-head);
  letter-spacing:normal; text-transform:none;
  font-size:var(--fs-xs);
}
/* the column header's count is the same chip, worn in its lane hue quietly */
.lane-count{ background:var(--lane-t); color:var(--lane-c) }

.lane[data-lane="draft"]{   --lane-c:var(--lane-draft);   --lane-t:var(--lane-draft-t) }
.lane[data-lane="live"]{    --lane-c:var(--lane-live);    --lane-t:var(--lane-live-t) }
.lane[data-lane="sent"]{    --lane-c:var(--lane-sent);    --lane-t:var(--lane-sent-t) }
.lane[data-lane="opened"]{  --lane-c:var(--lane-opened);  --lane-t:var(--lane-opened-t) }
.lane[data-lane="replied"]{ --lane-c:var(--lane-replied); --lane-t:var(--lane-replied-t) }

.lane-body{ display:flex; flex-direction:column; gap:var(--s-2); min-height:60px }

/* --- WO-023: the Replied lane speaks quietly ------------------------------------
   Two elements, one visual language, reusing the Replied green (no new colour). */

/* 1. A card that carries a conversation breathes a calm green glow behind it, the Replied lane's own hue.
   Slow and quiet, never a blink. Derivation-driven: .has-reply is set from the reply derivation (#82/#108),
   never per card. GPU-cheap: a layered pseudo-element behind the card animates OPACITY only, so nothing
   reflows and the halo is never clipped (the lanes set no overflow). The card keeps its own border; this
   sits behind it. */
.tok.has-reply{ position:relative; }
.tok.has-reply::after{
  content:""; position:absolute; inset:0; border-radius:inherit; z-index:-1;
  box-shadow:0 0 10px 1px var(--glow-1);            /* --glow-1 is the Replied green at low alpha, no new token */
  opacity:.42; animation:reply-breathe 3.6s ease-in-out infinite;
  pointer-events:none;
}
@keyframes reply-breathe{ 0%,100%{ opacity:.30 } 50%{ opacity:.64 } }
/* Reduced motion: a static soft glow, no breathing. */
@media(prefers-reduced-motion:reduce){ .tok.has-reply::after{ animation:none; opacity:.46 } }

/* 2. The Replied column is set apart by one very thin dashed rule, the ONLY separator on the board. Logical
   inline-start, so it sits before the Replied column in reading order and mirrors in Arabic with no second
   rule, full column height (the lanes stretch to the grid row, so the rule spans it even when Replied holds
   fewer cards). It reads as a clear, calm boundary: a touch more contrast than --hair (.10 was too faint to
   the eye against the near-black board, so .16 white, still quiet, still no new colour), and one spacing
   token on BOTH sides of it (margin before it, padding after it) so the Replied column is a distinct region
   with room to breathe, not a line crammed against its neighbour. The wider inset also clears the reply
   glow, so the two never collide. Still not a wall. */
@media(min-width:1024px){
  .lane[data-lane="replied"]{
    border-inline-start:1px dashed rgba(255,255,255,.16);
    margin-inline-start:var(--s-3);
    padding-inline-start:var(--s-3);
  }
}
/* Phone / iPad portrait (one lane at a time): the same language as a fine dashed TOP rule on the Replied
   lane section, so the distinction survives without columns, with the same contrast and the same spacing
   token above and below it. The glow is identical in both layouts. */
@media(max-width:1023px){
  .board .lane[data-lane="replied"]{
    border-top:1px dashed rgba(255,255,255,.16);
    margin-top:var(--s-3);
    padding-top:var(--s-3);
  }
}

/* Part 4: the Replied column reads as its own kind, using the board's own lane tokens (no new chrome). A
   faint replied-green wash on the column body and the lane's own accent on its title and tab tell a reply
   apart from a first-contact opportunity; the label already reads "Replied". The dashed boundary above stays
   the separator, so this only tints and accents, never a wall. */
.lane[data-lane="replied"] .lane-title{ color:var(--lane-replied); }
.lane[data-lane="replied"] .lane-body{ background:var(--lane-replied-t); border-radius:var(--r-md); padding:var(--s-1); }
.btab[data-tab="replied"] .btab-l{ color:var(--lane-replied); }
/* The reply-count badge on a card (Part 3): a compact replied-green marker, the same token the lane uses,
   so the number on the card matches the inbox and the card's reply list. */
.tok-replies{ display:inline-flex; align-items:center; gap:3px; margin-inline-start:var(--s-1);
  font-size:var(--fs-micro); font-weight:var(--w-head); color:var(--lane-replied); letter-spacing:0; }
.tok-replies svg{ width:11px; height:11px; flex:none; }

/* Part 1: the reply card. A distinct card placed under its parent opportunity in the Replied lane: the
   reply itself surfaced with its sender and a snippet, inset and quieter than the opportunity token so the
   operator reads two things, the opportunity and the reply tied to it. The lane's own replied-green accent
   and existing tokens, no new colour and no new chrome. */
/* Finding 2: one bounded grouped reply card per parent. Inset under its parent and CONTAINED inside the
   column: as a flex item in the lane-body column it stretches to the column width MINUS its own inline
   inset, so it never bleeds past the lane edge. Its own children stretch to it in turn, so nothing spills. */
.reply-group{
  margin-inline-start:var(--s-3);                       /* nested under the parent, still inside the column */
  min-width:0; display:flex; flex-direction:column; gap:var(--s-1);
}
.reply-group-head{ display:flex; align-items:center; gap:4px; padding-inline:var(--s-1);
  font-size:var(--fs-micro); font-weight:var(--w-head); color:var(--lane-replied); }
.reply-group-head svg{ width:12px; height:12px; flex:none; }
/* The expander: a single native <details>, opens in place and collapses; the earlier list is bounded
   (max-height + scroll) so the lane height stays bounded no matter how many replies there are. */
.reply-group-more > summary{ cursor:pointer; list-style:none; font-size:var(--fs-xs); color:var(--ink-3);
  padding:var(--s-1) var(--s-2); border-radius:var(--r-sm); }
.reply-group-more > summary::-webkit-details-marker{ display:none; }
.reply-group-more > summary:hover,.reply-group-more[open] > summary{ color:var(--lane-replied); }
.reply-group-list{ display:flex; flex-direction:column; gap:var(--s-1); margin-block-start:var(--s-1);
  max-height:196px; overflow-y:auto; }
/* One reply row. No own width and no own inset: it stretches inside .reply-group, so it is always contained. */
.reply-card{
  display:flex; align-items:flex-start; gap:var(--s-2); text-align:start; min-width:0;
  padding:var(--s-2) var(--s-3);
  background:var(--panel); border:1px solid var(--lane-replied-t);
  border-inline-start:2px solid var(--lane-replied);
  border-radius:var(--r-md); cursor:pointer; color:var(--ink-2); font:inherit;
}
.reply-card:hover,.reply-card:focus-visible{ border-color:var(--lane-replied); outline:none;
  box-shadow:0 0 0 1px var(--lane-replied); }
.reply-card-mark{ flex:none; color:var(--lane-replied); margin-block-start:2px; display:inline-flex; }
.reply-card-mark svg{ width:13px; height:13px; }
.reply-card-body{ display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 auto; }
.reply-card-top{ display:flex; align-items:baseline; gap:var(--s-2); flex-wrap:wrap; }
.reply-card-num{ flex:none; font-size:var(--fs-micro); font-weight:var(--w-head);
  color:var(--lane-replied); unicode-bidi:isolate; }
.reply-card-latest{ flex:none; font-size:var(--fs-micro); font-weight:var(--w-head); color:var(--lane-replied);
  background:var(--lane-replied-t); border-radius:var(--r-sm); padding:0 var(--s-2); unicode-bidi:isolate; }
.reply-card-who{ font-weight:600; unicode-bidi:isolate; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.reply-card-snip{ color:var(--ink-3); font-size:var(--fs-sm); unicode-bidi:isolate;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.lane-empty{
  border:1px dashed var(--hair); border-radius:var(--r-md);
  padding:var(--s-4) var(--s-3); text-align:center;
  font-size:var(--fs-xs); color:var(--ink-4); line-height:var(--lh-base);
}

/* --- token ---------------------------------------------------------------- */

.tok{
  background:var(--panel);
  border:1px solid var(--hair);
  border-radius:var(--r-md);
  padding:var(--s-2) var(--s-3);
  text-align:start;
  cursor:pointer;
  font:inherit; color:inherit;
  display:block; width:100%;
}
.tok:hover{ border-color:var(--lane-c) }
.tok:focus-visible{ outline:2px solid var(--purple); outline-offset:2px }

/* A long business name wraps to two lines instead of truncating: a clipped
   "Ludic..." is a name you cannot read, and the lane is now sized to hold two
   lines. The box still clips at the second line so one very long name cannot
   push the card taller than its neighbours, and overflow-wrap breaks a single
   unspaced token rather than letting it widen the lane. */
.tok-name{
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2;
  font-family:var(--font-lat);
  font-size:var(--fs-xs); font-weight:var(--w-head); color:var(--ink);
  line-height:var(--lh-tight);
  overflow:hidden; overflow-wrap:anywhere;
}
[dir="rtl"] .tok-name{ font-family:var(--font-ar) }
/* A business name is a name, not a sentence, and it keeps its own reading order. In an Arabic
   page "2 Faces" was being reordered into "Faces 2", which is the wrong name. plaintext lets
   each name set its own direction from its first letter; the explicit alignment keeps every
   token, card and tray chip lined up on the same edge regardless. */
[dir="rtl"] .tok-name,
[dir="rtl"] .card .biz,
[dir="rtl"] .tray-item,
[dir="rtl"] .modal-title,
[dir="rtl"] .logtable td:first-child{ unicode-bidi:plaintext; text-align:right }

/* The meta line is mixed content: an Arabic phrase around Latin digits. It stays
   in the paragraph direction so the words read correctly, and the digits are
   isolated by the .n class inside it, not by forcing the whole line to LTR. */
.tok-meta{
  display:block;
  font-size:var(--fs-micro); color:var(--ink-4);
  font-weight:var(--w-strong);                 /* spaced from the title by the tok-open gap, one gap token */
  text-align:start;
  overflow-wrap:anywhere;
}
/* WO-015 §6: the chapter marker. One quiet word, never a badge. The offer chapter
   carries the spark accent so a live offer reads at a glance; the first contact is
   muted, because it is the baseline. No letter-spacing: Arabic joins must not break. */
.tok-chapter{
  display:inline-block; margin-top:var(--s-1);
  font-size:var(--fs-micro); font-weight:var(--w-head); color:var(--ink-4);
  letter-spacing:0;
}
.tok-chapter.is-offer{ color:var(--spark, var(--teal)) }

/* The visible outbox: a send is out but the server ledger has not confirmed the row yet. An amber pill on the
   card, the same amber the drift badge uses, so an unconfirmed send is never mistaken for a settled Sent. */
.tok-sending{
  display:inline-flex; align-items:center; gap:5px; margin-top:var(--s-1); margin-inline-start:var(--s-1);
  font-size:var(--fs-micro); font-weight:var(--w-head); letter-spacing:0;
  color:#F6CF5B;
}
.tok-sending::before{
  content:""; width:6px; height:6px; border-radius:50%; background:#F6CF5B; flex:none;
  animation:tok-send-pulse 1.6s ease-in-out infinite;
}
@keyframes tok-send-pulse{ 0%,100%{ opacity:.4 } 50%{ opacity:1 } }
@media(prefers-reduced-motion:reduce){ .tok-sending::before{ animation:none; opacity:.8 } }

/* THE VISUAL-STATE LAW (Part 2). Each treatment below is the ONE emphasis for exactly one card state, set
   by cardState in app.js as data-state and the matching class. awaiting-action -> is-stalled (amber ring);
   failed -> is-failed (red ring); new-activity -> has-reply (green breathe, defined near the top); in-flight
   -> the .tok-sending pill. settled has no emphasis. The retired treatments (is-hot lane-fill, is-provisional
   dashed-dim, the board is-glow lane-literal ring) are gone: a card wears emphasis only through a state. */

/* awaiting-action: a contacted card idle past the stall clock. Amber ring, and always an age in text beside
   it. IDENTITY §4.4. A nudge is due; the treatment is calm, not alarming. */
.tok.is-stalled{
  border-color:var(--state-stall-b);
  box-shadow:0 0 0 3px var(--state-stall-t);
}
.tok.is-stalled .tok-meta{ color:var(--state-stall) }

/* failed: a delivered send whose row was never recorded (the write-gap state). A red warning ring, static
   (no animation, so it needs no reduced-motion variant), distinct from the amber nudge so a real failure is
   never mistaken for a routine stall. */
.tok.is-failed{
  border-color:var(--state-err-b);
  box-shadow:0 0 0 3px var(--state-err-t);
}
.tok.is-failed .tok-meta{ color:var(--state-err) }

/* --- summary chips under the board ---------------------------------------- */

.board-note{
  margin-top:var(--s-5); padding-top:var(--s-4);
  border-top:1px solid var(--hair-2);
  display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap;
}
.chip{
  font-size:var(--fs-xs); font-weight:var(--w-head);
  /* Each chip is a filter, so it is a control before it is a label and needs to be worth
     aiming at with a mouse as well as a finger. */
  padding:7px 13px; min-height:30px; display:inline-flex; align-items:center; border-radius:var(--r-pill);
  border:1px solid var(--hair); background:var(--panel); color:var(--ink-3);
  cursor:pointer;
}
.chip b{ color:var(--ink); margin-inline-start:2px;
  direction:ltr; unicode-bidi:isolate; display:inline-block }
.chip.warn{
  color:var(--state-stall); background:var(--state-stall-t);
  border-color:rgba(246,207,91,.30);
}
.chip.warn b{ color:var(--state-stall) }

/* --- closed tray ---------------------------------------------------------- */

.tray{ margin-top:var(--s-5); border-top:1px solid var(--hair-2); padding-top:var(--s-4) }
.tray-toggle{
  display:flex; align-items:center; gap:var(--s-2);
  background:transparent; border:0; padding:0; cursor:pointer;
  font:inherit; color:var(--ink-3);
  font-size:var(--fs-sm); font-weight:var(--w-head);
}
.tray-chev{ width:10px; height:10px; flex:0 0 10px; opacity:.6 }
.tray-body{ padding-top:var(--s-3) }
.tray-list{ display:flex; flex-wrap:wrap; gap:var(--s-2) }
/* A tray item is a real control now: a closed card opens its window from here, so a finished
   opportunity stays retrievable rather than being a dead label. It keeps the same quiet chip look. */
.tray-item{
  display:inline-flex; align-items:center; gap:6px;
  background:transparent; cursor:pointer; font:inherit;
  border:1px solid var(--hair); border-radius:var(--r-sm);
  padding:6px 11px; font-size:var(--fs-xs); color:var(--ink-4);
  font-weight:var(--w-strong);
}
.tray-item:hover{ border-color:var(--purple); color:var(--ink) }
.tray-item:focus-visible{ outline:2px solid var(--purple); outline-offset:2px }
.tray-item.won{ color:var(--state-good); border-color:rgba(126,224,184,.30) }
.tray-item.lost{ color:var(--ink-4) }
.tray-item.dropped{ color:var(--ink-4) }
/* A bounced or failed delivery wears the warning rose, so a non-delivery reads as one, never as a
   quiet Sent. Placement (this tray, a lane, or a card chip) is raised in the PR as a product-voice call. */
.tray-item.bounced{ color:var(--state-bad); border-color:rgba(238,140,157,.34) }
.tray-item.failed{ color:var(--state-stall); border-color:rgba(246,207,91,.34) }
/* A reply that arrived after the card closed: a quiet green dot on the tray item, so it surfaces on
   the board without reopening the card. The card's window carries the one-tap reopen. */
.tray-item.has-reply{ border-color:rgba(126,224,184,.45) }
.tray-dot{ width:7px; height:7px; border-radius:50%; background:var(--state-good); flex:0 0 auto }


/* --- the lifecycle: moves, notes, and the standing prohibition -------------
   The band is the one thing in this window that is not grey, because it has to
   be read before the message goes out rather than after. It cannot be dismissed
   and it is not a toast: a toast is something you missed, and this is something
   you must not miss.                                                        */

.mw-band{
  display:flex; align-items:center; gap:var(--s-3);
  margin-block-end:var(--s-4); padding:var(--s-3) var(--s-4);
  border:1px solid rgba(246,207,91,.34); border-radius:var(--r-md);
  background:var(--state-stall-t);
}
.mw-band-i{
  flex:0 0 auto; width:20px; height:20px; border-radius:var(--r-pill);
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--state-stall); color:#15121b;
  font-size:var(--fs-xs); font-weight:900;
}
.mw-band b{ display:block; font-size:var(--fs-xs); color:var(--state-stall) }
.mw-band span{ display:block; margin-block-start:2px; font-size:var(--fs-xs);
  color:var(--ink-2); line-height:var(--lh-base) }

.mw-notes{ display:grid; gap:var(--s-2); margin-block-end:var(--s-4) }
.mw-note-row{
  font-size:var(--fs-xs); color:var(--ink-3);
  padding:var(--s-2) var(--s-3);
  border:1px solid var(--hair-2); border-radius:var(--r-sm);
}
.mw-note-row b{ color:var(--ink-2) }
.mw-warn-line{ margin:var(--s-2) 0 0; font-size:var(--fs-xs); color:var(--state-stall) }

/* A reply that landed on a closed card, surfaced in its window with a one-tap reopen. It reads as
   a live note (the good green) rather than an error, because a reply is good news. */
.mw-reopen{
  display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
  margin-block-end:var(--s-4); padding:var(--s-2) var(--s-3);
  border:1px solid rgba(126,224,184,.34); border-radius:var(--r-sm);
  background:rgba(126,224,184,.06);
}
.mw-reopen-txt{ flex:1 1 auto; min-width:0; font-size:var(--fs-xs); color:var(--ink-2) }

/* The 'failed' banner in the card: a delivered send whose record never landed, with the one retry. Uses the
   failed accent so it reads as the same state the board token wears. */
.mw-unrec{
  margin-block-end:var(--s-4); padding:var(--s-2) var(--s-3);
  border:1px solid var(--state-err-b); border-radius:var(--r-sm);
  background:var(--state-err-t);
}
.mw-unrec-h{ font-weight:600; color:var(--state-err) }
.mw-unrec-b{ font-size:var(--fs-xs); color:var(--ink-2); margin-block:var(--s-1) var(--s-2) }

/* Only the legal moves, and they are buttons rather than a menu of greyed rules. */
.mw-moves{ display:flex; flex-wrap:wrap; gap:var(--s-2) }

/* One outcome control. The terminal choices (Won, Lost, No-fit, Archived, page-gone) read as one designed
   set: icon-led rows, one height, one gap, each with a quiet one-line description of its value in the flow.
   The promotion (convert) and the recover moves sit apart and above, so closing and advancing never read as
   siblings. One gap token throughout keeps the campaign chip above from being crowded. RTL mirrors for free:
   logical properties and text-align:start, and the icon leads in the reading direction. */
.mw-outcome{ margin-block-start:var(--s-4); display:flex; flex-direction:column; gap:var(--s-4) }
.mw-advance{ display:flex; flex-wrap:wrap; gap:var(--s-2) }
.mw-close .mw-h{ margin-block-end:var(--s-3) }
.close-set{ display:grid; gap:var(--s-2) }
.close-opt{ display:flex; align-items:flex-start; gap:var(--s-3); inline-size:100%; text-align:start;
  padding:12px 14px; border:1px solid var(--hair); border-radius:12px; background:var(--panel);
  color:var(--ink); cursor:pointer; font-family:inherit; transition:border-color var(--t-quick) var(--ease) }
.close-opt:hover{ border-color:var(--purple); background:var(--panel-2,rgba(255,255,255,.02)) }
.close-opt:focus-visible{ outline:2px solid var(--purple); outline-offset:2px }
.close-ic{ flex:0 0 auto; display:inline-flex; margin-block-start:1px; color:var(--ink-3) }
.close-won .close-ic{ color:#7ee0b8 }
.close-mark_lost .close-ic{ color:var(--ops-rose) }
.close-txt{ display:flex; flex-direction:column; gap:2px; min-inline-size:0 }
.close-label{ font-weight:700; font-size:14px; color:var(--ink) }
.close-desc{ font-size:12.5px; color:var(--ink-4); line-height:1.5; overflow-wrap:anywhere }
/* --- R12 (P19): delete, set apart from the close set. A draft's delete carries the one warmer accent so it
   never reads as a routine archive; a card with history shows only the quiet, locked reason line. --------- */
.mw-del{ margin-block-start:var(--s-3) }
.close-delete{ border-color:var(--state-err-b,rgba(224,115,111,.4)) }
.close-delete .close-ic{ color:var(--state-err,#E0736F) }
.close-delete:hover{ border-color:var(--state-err,#E0736F); background:var(--state-err-t,rgba(224,115,111,.14)) }
.mw-del-why{ display:flex; align-items:flex-start; gap:var(--s-2); font-size:12.5px; color:var(--ink-4);
  line-height:1.5; padding:10px 12px; border:1px dashed var(--hair); border-radius:12px }
.mw-del-why .ic{ flex:none; color:var(--ink-4); margin-block-start:1px }
[dir="rtl"] .mw-del-why,[dir="rtl"] .close-delete .close-label,[dir="rtl"] .close-delete .close-desc{ letter-spacing:normal }

/* --- the off channel send, three numbered steps --------------------------- */

.oc-step .mw-h{ gap:var(--s-2) }
.oc-n{
  flex:0 0 auto; width:20px; height:20px; border-radius:var(--r-pill);
  display:inline-flex; align-items:center; justify-content:center;
  background:rgba(150,133,202,.18); border:1px solid var(--purple); color:var(--ink);
  font-family:var(--font-lat); font-size:var(--fs-micro); font-weight:900;
  direction:ltr; unicode-bidi:isolate;
}
.oc-body{ width:100%; resize:vertical; line-height:var(--lh-base); font-family:inherit }
.oc-list{ list-style:none; margin:0; padding:0; display:grid; gap:var(--s-2) }
.oc-list li{
  display:flex; gap:var(--s-3); flex-wrap:wrap; align-items:baseline;
  padding:var(--s-2) var(--s-3);
  border:1px solid var(--hair-2); border-radius:var(--r-sm);
  font-size:var(--fs-xs); color:var(--ink-3);
}
.oc-list b{ color:var(--ink) }

.mw-more{ margin-block-start:var(--s-4); border-top:1px solid var(--hair-2); padding-block-start:var(--s-3) }
.mw-more summary{ cursor:pointer; font-size:var(--fs-xs); color:var(--ink-3); font-weight:var(--w-head) }
.mw-more .field{ margin-block-start:var(--s-3) }

@media (pointer:coarse){
  .mw-moves .btn,.mw-advance .btn,.close-opt,.oc-body,.toast-act{ min-height:44px }
}






/* --- storage, and the sync that is the only durable copy -------------------- */
.st-bar{ height:8px; border-radius:var(--r-pill); background:var(--hair-2); overflow:hidden;
  margin-block-end:var(--s-3) }
.st-bar span{ display:block; height:100%; background:var(--grad) }
.st-line{ margin:var(--s-2) 0 0; font-size:var(--fs-xs); color:var(--ink-3) }
.st-miss{ color:var(--state-stall) }
.st-keys{ list-style:none; margin:var(--s-2) 0 0; padding:0; display:grid; gap:4px }
.st-keys li{ display:flex; gap:var(--s-2); font-size:var(--fs-micro); color:var(--ink-4) }
.st-keys .mono-iso{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis }
/* Held human replies, each named with a one-tap attach picker. Wraps on narrow widths, aligned on one axis. */
/* The replies inbox rows: each held reply on its own line, its who/subject on the start, its attach
   control on the end, aligned and never touching. Every control the same height, one gap value. */
/* Part 3: replies grouped by opportunity, each numbered #N (the one number the card badge and card list
   also show). A quiet row, the number in the lane's replied-green, the parent named, the whole row a target
   that opens its card. No new chrome: the same list idiom the attachment rows use. */
.rp-byopp{ list-style:none; margin:var(--s-2) 0 var(--s-3); padding:0; display:flex; flex-direction:column }
.rp-byopp-row{ display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap; cursor:pointer;
  padding:var(--s-2) 0; border-block-end:1px solid var(--hair) }
.rp-byopp-row:last-child{ border-block-end:0 }
.rp-byopp-row:hover,.rp-byopp-row:focus-visible{ background:var(--lane-replied-t); border-radius:var(--r-md); outline:none }
.rp-byopp-who{ min-width:0; overflow:hidden; text-overflow:ellipsis }
.rp-byopp-opp{ margin-inline-start:auto; font-size:var(--fs-micro); color:var(--ink-4); unicode-bidi:isolate }
.rp-num{ font-weight:var(--w-head); color:var(--lane-replied); unicode-bidi:isolate; flex:none }
.rp-held{ list-style:none; margin:var(--s-3) 0 0; padding:0; display:flex; flex-direction:column; gap:var(--s-3) }
.rp-held-row{ display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:var(--s-3);
  padding:var(--s-2) 0; border-block-end:1px solid var(--hair) }
.rp-held-row:last-child{ border-block-end:0 }
.rp-held-who{ display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 12rem }
.rp-held-subj{ font-size:var(--fs-micro); color:var(--ink-4); overflow:hidden; text-overflow:ellipsis }
.rp-attach{ display:flex; align-items:center; gap:var(--s-2); flex:0 0 auto }
/* Select and Attach button share one height and a single gap, aligned and never touching, in both
   directions. The height is in rem, not em, so it is identical for both controls regardless of each
   one's font size (an em height rendered them a pixel apart). */
.rp-attach .rp-attach-sel,.rp-attach .rp-attach-btn{ height:2rem; box-sizing:border-box }
.rp-attach .rp-attach-sel{ max-width:15rem }
.rp-attach .rp-attach-btn{ white-space:nowrap }
.rp-noise{ margin:var(--s-3) 0 0; padding-block-start:var(--s-2); border-block-start:1px solid var(--hair) }
.rp-noise>summary{ cursor:pointer; color:var(--ink-4); font-size:var(--fs-micro); padding:var(--s-1) 0 }
.board-inbox .bar{ margin-block:var(--s-3) 0 }
/* The badge highlight: a card opened from its badge flashes the exact new item it announced, then calms. */
.th-flash{ animation:th-flash 2.1s ease-out 1 }
@keyframes th-flash{ 0%,18%{ box-shadow:0 0 0 3px var(--rose, #b46a7a) inset; background:color-mix(in srgb, var(--rose, #b46a7a) 12%, transparent) } 100%{ box-shadow:none; background:transparent } }
@media (prefers-reduced-motion: reduce){ .th-flash{ animation:none; outline:2px solid var(--rose, #b46a7a) } }
.rp-ambig{ font-size:var(--fs-micro); color:var(--warn, #b26a00) }
/* The signed-in operator email and one-tap sign-out in the header. Only the email, no role. */
.op-chip{ display:inline-flex; align-items:center; gap:var(--s-2); min-width:0 }
.op-chip .op-email{ font-size:var(--fs-micro); color:var(--ink-4); max-width:14rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* Board replies inbox, opened from the header badge. One surface for attribution. */
.inbox-badge{ display:inline-flex; align-items:center; justify-content:center; min-width:1.2em; padding:0 .35em; margin-inline-start:.4em;
  border-radius:999px; background:var(--rose, #b46a7a); color:#fff; font-size:var(--fs-micro); line-height:1.5 }
/* The replies inbox wears the console's dark identity: the panel surface, a hairline border, brand ink.
   Before this it read background:var(--bg-2,#fff) with --bg-2 undefined, so it fell back to a glaring
   white panel, and its dividers used the undefined --line and vanished. Defined dark tokens only now. */
.board-inbox{ margin-block:var(--s-3); padding:var(--s-3); border:1px solid var(--hair); border-radius:var(--r-2, 10px); background:var(--panel); color:var(--ink-2) }
/* Quiet activity badges: on a card and summed on its lane header. Presentation only. */
.card-badge{ position:absolute; inset-block-start:6px; inset-inline-end:6px; min-width:1.1em; padding:0 .3em; border-radius:999px;
  background:var(--rose, #b46a7a); color:#fff; font-size:var(--fs-micro); line-height:1.5; text-align:center }
.tok{ position:relative }
.lane-new{ display:inline-flex; align-items:center; justify-content:center; min-width:1.1em; padding:0 .3em; margin-inline-start:.3em;
  border-radius:999px; background:var(--rose, #b46a7a); color:#fff; font-size:var(--fs-micro) }
/* The living surface inside a group card: recipients panel and aggregate. */
.cg-panel{ margin-block-start:var(--s-4) }
.cg-h{ margin:0 0 var(--s-2) }
.cg-agg{ display:flex; flex-wrap:wrap; gap:var(--s-2); margin-block-end:var(--s-3) }
.cg-tile{ display:flex; flex-direction:column; gap:2px; min-width:4.5rem; padding:var(--s-2); border:1px solid var(--line); border-radius:var(--r-1, 8px) }
.cg-tile .cg-n{ font-weight:700 } .cg-tile .cg-l{ font-size:var(--fs-micro); color:var(--ink-4) }
/* P8: the campaign in-flight control panel */
.cq-panel{ border:1px solid var(--line); border-radius:var(--r-1,8px); padding:var(--s-3); background:var(--panel-2) }
.cq-panel.is-sending{ border-color:var(--purple) }
.cq-panel.is-paused{ border-color:var(--state-stall, #b26a00) }
.cq-panel.is-complaint{ border-color:var(--ops-rose, #b23a48) }
.cq-panel.is-done{ opacity:.85 }
.cq-line{ margin:2px 0; font-size:var(--fs-micro); color:var(--ink-3) }
.cq-line.cq-k{ font-size:var(--fs-body); font-weight:700; color:var(--ink) }
.cq-acts{ margin-block-start:var(--s-2); display:flex; gap:var(--s-2); flex-wrap:wrap }
[dir="rtl"] .cq-line,[dir="rtl"] .cq-k{ letter-spacing:normal }
.rc-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--s-2) }
.rc-row{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2) }
.rc-name{ font-weight:600; flex:1 1 8rem; min-width:0 }
/* P5 roster editor (D3) */
.roster-ed{ margin-block-start:var(--s-4); border:1px solid var(--line); border-radius:var(--r-2); padding:var(--s-3) }
.roster-ed > summary{ cursor:pointer; list-style:revert }
.rst-rows{ display:flex; flex-direction:column; gap:var(--s-2); margin-block:var(--s-2) }
.rst-row{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2) }
.rst-row .rst-name{ flex:1 1 8rem; min-width:0 }
.rst-row .rst-email{ flex:1 1 12rem; min-width:0 }
.rst-row input{ background:var(--panel-2); color:var(--ink); border:1px solid var(--line); border-radius:var(--r-1); padding:var(--s-1) var(--s-2) }
.rst-paste{ width:100%; box-sizing:border-box; background:var(--panel-2); color:var(--ink); border:1px solid var(--line); border-radius:var(--r-1); padding:var(--s-2); resize:vertical }
.rst-acts{ display:flex; flex-wrap:wrap; gap:var(--s-2); margin-block-start:var(--s-2) }
.rst-review{ margin-block-start:var(--s-2) }
.rst-rev-list{ list-style:none; margin:0 0 var(--s-2); padding:0; display:flex; flex-direction:column; gap:var(--s-1) }
.rst-rev{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2) }
.rst-rev-nm{ flex:1 1 8rem; min-width:0 }
.rst-rev.is-bad .rst-rev-em{ color:#EE8C9D; text-decoration:line-through }
.rst-rev.is-dup{ opacity:.6 }
.rst-note{ margin-block-start:var(--s-2) }
.rc-addr{ flex:1 1 10rem; min-width:0; overflow:hidden; text-overflow:ellipsis; color:var(--ink-4); font-size:var(--fs-micro) }
.rc-last{ font-size:var(--fs-micro); color:var(--ink-4) }
/* One status chip, one state logic. Every status chip - the recipient list, and anywhere else a recipient
   state is shown - wears a muted Thrive tone, colored by the SAME lane the board already speaks, so a
   state's colour means the same thing everywhere: Sent the Sent lane's violet, Opened the Opened rose,
   Replied the Replied green, a bounce a muted warning rose (calm, not alarming), and pending a quiet
   neutral surface. Dark low-alpha fills with toned text, never a solid or a white pill; AA-readable on the
   near-black surface. Geometry (padding, radius, height) lives here once and is reused, never per-surface. */
.chip-st{ display:inline-block; white-space:nowrap;
  font-size:var(--fs-micro); font-weight:var(--w-head); line-height:1.5;
  padding:.15em .6em; border-radius:var(--r-pill);
  background:rgba(255,255,255,.06); color:var(--ink-3);   /* pending / not sent: a quiet neutral surface */
  border:1px solid transparent; }
.chip-st.is-sent{    background:var(--lane-sent-t);    color:var(--lane-sent) }     /* Sent lane violet */
.chip-st.is-opened{  background:var(--lane-opened-t);  color:var(--lane-opened) }   /* Opened lane rose */
.chip-st.is-replied{ background:var(--lane-replied-t); color:var(--lane-replied) }  /* Replied lane green */
.chip-st.is-bounced{ background:var(--ops-rose-fill);  color:var(--ops-rose); border-color:var(--ops-rose-bd) }  /* muted warning */
[dir="rtl"] .chip-st{ letter-spacing:normal }           /* Arabic never carries letter-spacing */
/* Group-send pre-send review. */
.gr-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--s-2) }
.gr-row{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2) }
.gr-name{ font-weight:600; flex:1 1 8rem }
.gr-addr{ flex:1 1 10rem; min-width:0; overflow:hidden; text-overflow:ellipsis; color:var(--ink-4); font-size:var(--fs-micro) }
.gr-greet{ color:var(--ink-3); font-size:var(--fs-micro) }
.gr-row.gr-blocked{ opacity:.8 }
.gr-block,.gr-miss{ color:var(--warn, #b26a00); font-size:var(--fs-micro) }
#boardBand:not([hidden]){ margin-block-end:var(--s-4) }
#boardBand .mw-band{ margin:0; align-items:center }
#boardBand .btn{ margin-inline-start:auto; flex:0 0 auto }

/* --- moving a card ---------------------------------------------------------
   The card is a row: the label opens the window, the overflow control lists
   every legal destination, and the grip picks it up. WCAG 2.2 SC 2.5.7 is why
   the overflow control exists at all, and it was built before the drag so it
   could not be cut at the end.                                               */

/* WO-024: the card breathes. One internal geometry: a title zone that grows and wraps to two lines, a
   reserved corner zone (the menu and the grip) the title can never enter, and the meta line below on the
   same baseline. ONE padding token (--tok-pad, equal insets) and ONE gap token (--tok-gap, between every
   zone). align-items:flex-start pins the corner to the top, so the chevron centres on the title's FIRST
   line, never against the wrapped block. --tok-line is the first line's box, so a corner control's icon
   sits on line one whatever the control's tap height. */
.tok{
  --tok-pad:var(--s-3);
  --tok-gap:var(--s-2);
  --tok-line:calc(var(--fs-xs) * var(--lh-tight));
  display:flex; align-items:flex-start; gap:var(--tok-gap);
  padding:var(--tok-pad);
  position:relative;
}
.tok-open{
  flex:1 1 auto; min-width:0;
  display:flex; flex-direction:column; gap:var(--tok-gap);
  background:transparent; border:0; padding:0; margin:0;
  font:inherit; color:inherit; text-align:start; cursor:pointer;
}
.tok-open:focus-visible,.tok-more:focus-visible{ outline:2px solid var(--purple); outline-offset:2px }
.tok-more,.tok-grip{
  flex:0 0 auto; align-self:flex-start;
  display:inline-flex; align-items:center; justify-content:center;
  min-width:22px; width:22px; height:var(--tok-line); min-height:0;   /* the first-line box: the icon centres on line one */
  background:transparent; border:0; padding:0;
  color:var(--ink-4); cursor:pointer;
  position:relative;
}
/* The tap target stays generous without moving the icon off the first line: a transparent expander grows
   the clickable area downward and sideways into the gap, never changing where the glyph sits. */
.tok-more::after,.tok-grip::after{ content:""; position:absolute; inset:-11px -7px; }
.tok-grip{ touch-action:none; cursor:grab; opacity:.5 }
.tok:hover .tok-grip,.tok:hover .tok-more{ opacity:.95; color:var(--ink-3) }
body.is-dragging .tok-grip{ cursor:grabbing }

.tok.is-drag{
  position:fixed; z-index:90; margin:0;
  pointer-events:none;
  box-shadow:var(--el-raised); border-color:var(--purple);
  transform-origin:center; opacity:.96;
}
.tok-ph{ border:1px dashed var(--purple); border-radius:var(--r-md); background:rgba(150,133,202,.07) }
.tok.is-held{ border-color:var(--purple); box-shadow:0 0 0 3px rgba(150,133,202,.22) }
body.is-dragging{ cursor:grabbing; user-select:none }

/* Only the legal destinations, never a list of greyed rules. */
.cardmenu{
  position:absolute; z-index:40; inset-block-start:100%; inset-inline-end:0;
  min-width:180px; padding:var(--s-1);
  background:var(--panel-2); border:1px solid var(--hair); border-radius:var(--r-sm);
  box-shadow:var(--el-raised);
  display:flex; flex-direction:column; gap:2px;
}
.cardmenu button{
  background:transparent; border:0; border-radius:var(--r-sm);
  color:var(--ink-2); font:inherit; font-size:var(--fs-xs); font-weight:var(--w-strong);
  padding:8px 10px; text-align:start; cursor:pointer;
}
.cardmenu button:hover,.cardmenu button:focus-visible{ background:rgba(150,133,202,.16); color:var(--ink) }
.cardmenu-sep{ height:1px; background:var(--hair-2); margin:var(--s-1) 0 }
.cardmenu-none{ padding:8px 10px; font-size:var(--fs-xs); color:var(--ink-4) }

/* Announcements are for assistive technology, so they are read and never seen. */
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

@media (pointer:coarse){
  /* The whole card is the handle, and it clears 44 by 44. The corner controls keep their icon ON the first
     line (height stays --tok-line); the 44px tap target comes from the transparent expander, so a finger
     gets a full target without the glyph drifting down to the middle of a wrapped card. */
  .tok{ min-height:44px }
  .tok-open{ min-height:40px; display:flex; flex-direction:column }
  .tok-more::after,.tok-grip::after{ inset:-15px -11px; }
  .cardmenu button{ min-height:44px }

  /* A lane wide enough to hold what the card now contains. The card gained two
     controls in WO-012 phase 5, and under a finger they are 44 wide each. The
     chrome therefore costs 122px: 24 of inline padding, 2 of border, 88 of
     controls and 8 of gap. In a 132px lane that left the name 10px, measured,
     so the whole card body collapsed to a sliver and a tap on the middle of a
     card landed on the overflow control instead of opening the opportunity.
     236 leaves the name 114, which is the number this lane was sized for
     before the card had controls at all. */
  :root{ --lane-min:236px }
}

/* --- two libraries, one per document language ------------------------------
   The badge exists so it is never ambiguous which library you are editing, and
   the direction of a document field comes from the document, never from the
   chrome around it.                                                          */

.loc-tabs{ margin-block-end:var(--s-3) }
.loc-count{ display:block; margin-block-start:var(--s-2); font-size:var(--fs-xs); color:var(--ink-4) }
.loc-badge{
  display:inline-flex; align-items:center; gap:.4em;
  font-size:var(--fs-micro); font-weight:var(--w-head);
  padding:4px 10px; border-radius:var(--r-pill);
  border:1px solid var(--hair); color:var(--ink-3);
  margin-block-end:var(--s-3);
}
.loc-badge.loc-ar{ border-color:var(--lane-replied); color:var(--lane-replied) }
.loc-badge.loc-en{ border-color:var(--lane-draft); color:var(--lane-draft) }
/* An Arabic document keeps Arabic typography whatever the chrome says: the Arabic
   face, no letter-spacing, no uppercase. */
.doc-ar{ font-family:var(--font-ar); letter-spacing:normal; text-transform:none }

.loc-mig{ margin-block-start:var(--s-5); padding:var(--s-4);
  border:1px solid rgba(246,207,91,.30); border-radius:var(--r-sm);
  background:var(--state-stall-t) }
.loc-mig-row{ display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
  padding:var(--s-2) 0; border-block-end:1px solid var(--hair-2) }
.loc-mig-name{ font-size:var(--fs-sm); color:var(--ink); font-weight:var(--w-head); min-width:0 }
.loc-mig-kind,.loc-mig-read{ font-size:var(--fs-micro); color:var(--ink-4) }
.loc-mig-read b{ color:var(--ink-2) }
.loc-mig-sel{ margin-inline-start:auto }
.loc-mig .btn{ margin-block-start:var(--s-3) }

/* --- the day's batch ------------------------------------------------------
   The report comes before anything is written, so it is the tallest thing in
   this block. An import that quietly drops two of eleven is worse than one that
   refuses: the two that vanished look exactly like two that were never there. */

.intake{
  border:1px dashed var(--hair); border-radius:var(--r-md);
  padding:var(--s-4); margin-block-end:var(--s-5);
  transition:border-color var(--t-quick) var(--e-standard), background var(--t-quick) var(--e-standard);
}
.intake.on{ border-color:var(--purple); background:rgba(150,133,202,.07) }
.intake-head{ display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap }
.intake-head .block-h{ margin:0 }
.intake-head label{ margin-inline-start:auto }
.intake .sub{ margin:var(--s-2) 0 0; font-size:var(--fs-xs) }
.intake-out:not(:empty){ margin-block-start:var(--s-4) }

.in-status{ font-size:var(--fs-xs); color:var(--ink-4); margin:0 }
.in-status.warn{ color:var(--state-stall) }
.in-head{ display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap; margin-block-end:var(--s-3) }
.in-head b{ font-size:var(--fs-sm); color:var(--ink) }
.in-actions{ display:flex; gap:var(--s-2); margin-inline-start:auto }

.in-report{ padding:var(--s-3); border:1px solid var(--hair-2); border-radius:var(--r-sm);
  margin-block-end:var(--s-3) }
.in-counts{ display:flex; flex-wrap:wrap; gap:var(--s-4) }
.in-count{ display:flex; flex-direction:column; gap:2px }
.in-count b{ font-family:var(--font-lat); font-size:var(--fs-lg); font-weight:900; color:var(--ink);
  direction:ltr; unicode-bidi:isolate }
.in-count span{ font-size:var(--fs-micro); color:var(--ink-4) }
.in-count.warn b{ color:var(--state-stall) }
.in-named{ list-style:none; margin:var(--s-3) 0 0; padding:0; display:flex; flex-wrap:wrap; gap:var(--s-2) }
.in-named li{ font-size:var(--fs-micro); color:var(--state-stall); padding:3px 9px;
  border:1px solid rgba(246,207,91,.30); border-radius:var(--r-pill) }

.in-list{ display:grid; gap:var(--s-2) }
.in-card{ display:flex; align-items:flex-start; gap:var(--s-3);
  border:1px solid var(--hair); border-radius:var(--r-sm);
  padding:var(--s-3); background:var(--panel); cursor:pointer }
.in-card:hover{ border-color:var(--purple) }
.in-card.is-dupe{ border-color:rgba(246,207,91,.34) }
.in-body{ min-width:0; flex:1 }
.in-name{ display:block; font-size:var(--fs-sm); font-weight:var(--w-head); color:var(--ink);
  unicode-bidi:plaintext; text-align:start }
.in-meta{ display:block; margin-block-start:2px; font-size:var(--fs-xs); color:var(--ink-4) }
.in-tags{ display:flex; flex-wrap:wrap; gap:var(--s-1); margin-block-start:var(--s-2) }
.in-tag,.in-warn{ display:inline-flex; align-items:center; gap:.4em;
  font-size:var(--fs-micro); font-weight:var(--w-strong);
  padding:3px 9px; border-radius:var(--r-pill);
  border:1px solid var(--hair); color:var(--ink-4);
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* What the manifest did not say. Named, never hidden: a thin manifest must
   produce a thin record you can see, not a confident wrong one. */
.in-warn{ color:var(--state-stall); border-color:rgba(246,207,91,.30); background:var(--state-stall-t) }
.in-dupe-row{ display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap;
  margin-block-start:var(--s-2); font-size:var(--fs-xs); color:var(--state-stall) }
.in-dupe-row .btn.on{ border-color:var(--purple); color:var(--ink) }

@media (pointer:coarse){ .in-card{ min-height:44px } }

/* --- the batch match report, the warn-before-write gate in upload mode -----
   One row per slug: five presence marks and a verdict. A matched row is calm,
   a warned row carries the amber the rest of the console uses for "look here".
   text-align:start and unicode-bidi keep it correct right to left, and the slug
   stays a left to right identifier in both directions. */
.bt-wrap{ overflow-x:auto; border:1px solid var(--hair-2); border-radius:var(--r-sm);
  margin-block-end:var(--s-3) }
/* A min-width keeps the seven columns readable: on a wide screen the table fills its frame, and on a phone
   the frame (.bt-wrap) scrolls rather than squeezing a slug into one letter per line. So columns stay
   legible at every width and the page never overflows. */
.bt{ border-collapse:collapse; width:100%; min-width:32rem; font-size:var(--fs-xs); table-layout:auto }
/* One padding and one line-height on every cell, top-aligned, so a row is only as tall as its content and a
   wrapped verdict lines up with the checks beside it rather than floating on the baseline. */
.bt th,.bt td{ padding:8px 8px; text-align:start; vertical-align:top; line-height:1.45; border-block-end:1px solid var(--hair) }
.bt th{ font-size:var(--fs-micro); font-weight:var(--w-head); color:var(--ink-4); white-space:nowrap }
.bt tr:last-child td{ border-block-end:0 }
.bt td.mono-iso{ font-family:var(--font-lat); direction:ltr; unicode-bidi:isolate; overflow-wrap:break-word }
/* The five check columns (HTML, MFST, SUBJ, BODY, SEND) share ONE even, tight width and centre, header and
   cell aligned on the same axis, so the ticks read as a clean grid. */
.bt th:nth-child(n+2):nth-child(-n+6){ text-align:center; width:2.7rem }
.bt td.bt-c{ text-align:center; width:2.7rem }
.bt-y{ display:inline-flex; color:var(--lane-live) }
.bt-y svg{ width:14px; height:14px }
.bt-n{ color:var(--ink-4) }
.bt tr.is-warned{ background:var(--state-stall-t) }
/* The verdict reads clearly and wraps within its own column, never forcing the table wider than its frame. */
.bt td:last-child{ overflow-wrap:anywhere }
.bt-ok{ color:var(--lane-live); font-weight:var(--w-head) }
.bt-warn{ color:var(--state-stall); font-weight:var(--w-strong) }
/* R8 provenance: the ladder rung that resolved an opportunity, shown quietly so the truth is visible. */
.bt-prov{ display:inline-block; font-size:var(--fs-micro); color:var(--ink-3); white-space:nowrap }
.bt-prov-opp_md{ color:var(--lane-live) }
.bt-prov-page_partial,.bt-prov-needs_message{ color:var(--state-stall) }
.bt tr.is-needs{ background:var(--state-stall-t) }
.bt-write{ white-space:nowrap }
/* R13 (P19): the per-row re-import action. A new row is neutral; an update reads in the live accent (it heals
   a card); a history-kept row is amber (its body is deliberately left alone); an archived slug asks, on the
   row, with two explicit choices. Chips wrap within the cell and never widen the table. */
.bt-verdict{ display:flex; flex-wrap:wrap; align-items:center; gap:6px }
.bt-act{ display:inline-flex; padding:2px 8px; border-radius:var(--r-pill,999px); font-size:var(--fs-micro);
  border:1px solid var(--hair); white-space:nowrap }
.bt-act.is-new{ color:var(--ink-3) }
.bt-act.is-update{ color:var(--lane-live); background:var(--lane-live-t); border-color:rgba(127,159,212,.34) }
.bt-act.is-locked{ color:var(--state-stall); background:var(--state-stall-t); border-color:var(--state-stall-b) }
.bt-act.is-decision{ color:var(--state-stall); background:var(--state-stall-t); border-color:var(--state-stall-b) }
.bt-dec{ display:inline-flex; flex-wrap:wrap; gap:6px }
[dir="rtl"] .bt-act{ letter-spacing:normal }
/* P17: the report row identity - the business display name (natural direction, so Arabic reads RTL) with
   the slug as a small isolated identity beneath it. */
.bt-id .bt-biz{ display:block; font-weight:var(--w-strong); color:var(--ink-1) }
.bt-id .bt-slug{ font-size:var(--fs-micro); color:var(--ink-3) }

/* --- P16: the join's states below the table ------------------------------
   The count line reads pages / matched / confirm / needs / orphans, each count
   in an isolated <bdi class="n"> so a Western numeral never reverses inside an
   Arabic phrase. Confirm, orphan and duplicate sections are small labelled lists,
   each row a business and its one or two actions, never a row among the pages. */
.ing-count{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2);
  font-size:var(--fs-sm); color:var(--ink-2); margin:var(--s-3) 0 var(--s-2) }
.ing-count .ing-c{ white-space:nowrap }
.ing-count .n{ font-weight:var(--w-strong); color:var(--ink-1) }
.ing-count .ing-sep{ color:var(--ink-3) }
.ing-block{ margin-top:var(--s-3); border:1px solid var(--hair-2); border-radius:var(--r-sm);
  padding:var(--s-2) var(--s-3) }
.ing-block .ing-h{ font-size:var(--fs-sm); font-weight:var(--w-strong); color:var(--ink-2);
  margin:0 0 var(--s-2) }
.ing-confirm{ background:var(--state-stall-t) }
.ing-dup{ background:var(--lane-live-t, var(--state-stall-t)) }
.ing-row{ display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:var(--s-2); padding:var(--s-1) 0 }
.ing-row + .ing-row{ border-top:1px solid var(--hair-1) }
.ing-row-main{ display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--s-2); min-width:0 }
.ing-biz{ font-weight:var(--w-strong); color:var(--ink-1) }
.ing-note,.ing-suggest{ font-size:var(--fs-micro); color:var(--ink-3) }
.ing-row-act{ display:flex; gap:var(--s-1); flex-shrink:0 }

/* --- the symbol set -------------------------------------------------------
   Stroke only, inheriting currentColor, so a symbol is never a second colour
   decision after the text beside it. Decoration in the accessibility sense: the
   label carries the meaning, so every one of these is aria-hidden and every
   control keeps its words.                                                   */

/* The one alignment rule for every icon. In a flex row (buttons, chips, headings, the
   .has-ic wrapper applyIcons adds) align-items:center governs and this is ignored. In an
   inline run of text the icon rides the baseline, and this em offset drops it onto the text's
   optical centre. It is em, not px, so it tracks the text size, and it is measured: at -.16em
   the icon sat 1.3px high (the float), at -.28em it centres (+0.4px). One value, every context,
   so no page has to nudge its own icons. */
.ic{ flex:0 0 auto; vertical-align:-.28em; opacity:.85 }
/* An element given a symbol becomes a row, so the symbol and its label share a
   baseline instead of the symbol pushing the line height around. */
.has-ic{ display:inline-flex; align-items:center; gap:.45em }
.btn.has-ic,.chip.has-ic,.modal-tab.has-ic{ gap:.4em }
.block-h.has-ic,.title.has-ic,.mw-h.has-ic{ display:flex; align-items:center; gap:.5em }

/* Warmth, item two of three: a lane header symbol takes its lane colour, which
   is already in the token table and already carries a meaning. No new colour is
   introduced, so IDENTITY §11 G3 has nothing to ask. */
.lane-ic{ display:inline-flex; align-items:center; color:var(--lane-c) }
.lane-ic .ic{ opacity:1 }
.lane-head{ gap:var(--s-2) }
.lane-head > .lane-title{ margin-inline-end:auto }

/* Warmth, item one of three: every empty state gets one symbol above its
   sentence, at 32px and 40%, and nothing else. */
.mw-empty-i,.empty-ic{ color:var(--ink-3); opacity:.4 }
.empty .empty-ic{ display:block; margin:0 auto var(--s-3) }

/* --- the opportunity window ------------------------------------------------
   Centred, not pinned to an edge. The drawer was specified when this view was
   mostly a single action; it has become a workspace with several tabs, and a
   580px column against the edge of a 1440px screen pushes the eye sideways
   while the board sits idle behind it. The problem grows with every tab added.
   A centred surface is the correct shape for a workspace.

   Logical properties only. No left, no right, no mirrored stylesheet, and no
   letter-spacing anywhere in this block: letter-spacing breaks the joins
   between Arabic letters, and the safest way not to apply it to Arabic is not
   to write it.                                                              */

.modal-scrim{
  position:fixed; inset:0; z-index:80;
  background:rgba(0,0,0,.62);
  -webkit-backdrop-filter:blur(6px);
  backdrop-filter:blur(6px);
  opacity:0; pointer-events:none;
  transition:opacity calc(var(--t-quick) * .7) var(--e-exit);
}
.modal-scrim.on{
  opacity:1; pointer-events:auto;
  transition:opacity var(--t-quick) var(--e-enter);
}

.modal{
  position:fixed; z-index:81;
  inset:0; margin:auto;                     /* equal margins on all four sides */
  width:min(920px,92vw);
  height:max-content;
  max-height:88vh;
  background:var(--panel);
  border:1px solid var(--hair);
  border-radius:var(--r-xl);
  box-shadow:var(--el-modal);
  display:flex; flex-direction:column;
  overflow:hidden;                          /* so the rounded corners clip the body */
  opacity:0;
  transform:translateY(10px) scale(.985);
  pointer-events:none;
  /* Exit leaves at 70% of the enter duration, on the exit curve. */
  transition:opacity calc(var(--t-base) * .7) var(--e-exit),
             transform calc(var(--t-base) * .7) var(--e-exit);
}
.modal.on{
  opacity:1; transform:none; pointer-events:auto;
  transition:opacity var(--t-base) var(--e-enter),
             transform var(--t-base) var(--e-enter);
}

/* Travel is what causes vestibular trouble, not opacity, and a hard cut between
   two full surfaces is its own kind of disorienting. So the transform goes and
   the fade stays. */
@media (prefers-reduced-motion:reduce){
  .modal, .modal.on{
    transform:none!important;
    transition:opacity var(--t-quick) var(--e-standard)!important;
  }
  .modal-scrim, .modal-scrim.on{
    transition:opacity var(--t-quick) var(--e-standard)!important;
  }
}

/* --- header: fixed, never scrolls ------------------------------------------ */

.modal-head{
  flex:0 0 auto;
  display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
  padding:var(--s-4) var(--s-5);
  border-block-end:1px solid var(--hair);
}
.modal-heading{
  min-width:0; flex:1 1 auto;
  display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap;
}
.modal-title{
  margin:0;
  font-size:var(--fs-md); font-weight:var(--w-head); color:var(--ink);
  line-height:var(--lh-tight);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  min-width:0;
}
/* A business name is a name, not a sentence: it keeps its own reading order so
   "2 Faces" is not reordered into "Faces 2" on an Arabic screen. */
[dir="rtl"] .modal-title{ unicode-bidi:plaintext; text-align:start }
/* The actions can wrap onto a second row on a narrow sheet rather than push a
   button past the sheet edge: with three controls plus Close, 390px cannot hold
   one line. It stays a single right-hand row wherever it fits. */
.modal-acts{ display:flex; align-items:center; justify-content:flex-end; gap:var(--s-2); flex:0 1 auto; flex-wrap:wrap; min-width:0 }
.modal-close{
  background:transparent; border:1px solid var(--hair);
  border-radius:var(--r-sm); color:var(--ink-3); cursor:pointer;
  padding:6px 12px; font:inherit; font-size:var(--fs-sm); font-weight:var(--w-head);
}
.modal-close:hover{ border-color:var(--purple); color:var(--ink) }
.modal-close:focus-visible,.modal-tab:focus-visible{ outline:2px solid var(--purple); outline-offset:2px }
/* A disabled control that does not say why is a control that reads as broken. */
.modal-why{
  flex:1 0 100%; margin:0;
  font-size:var(--fs-xs); color:var(--ink-4);
}

/* --- tabs: fixed, never scroll --------------------------------------------- */

.modal-tabs{
  flex:0 0 auto;
  display:flex; gap:var(--s-1); flex-wrap:wrap;
  padding:var(--s-3) var(--s-5) 0;
}
.modal-tab{
  background:transparent; border:1px solid var(--hair); border-radius:var(--r-sm);
  color:var(--ink-3); font:inherit; font-size:var(--fs-sm); font-weight:var(--w-head);
  padding:7px 13px; cursor:pointer;
}
.modal-tab.on{ color:var(--ink); background:rgba(150,133,202,.16); border-color:var(--purple) }

/* --- body: the only thing that scrolls ------------------------------------- */

.modal-body{
  flex:1 1 auto; min-height:0; overflow:auto;
  padding:var(--s-5);
}
/* A view the window has adopted stops being a page and becomes panel content. */
.modal-host > .view{ max-width:none; margin:0; padding:0 }

/* --- the panels this window renders itself --------------------------------- */

.mw-rows{ margin:0; display:grid; gap:var(--s-3) }
.mw-row{ display:grid; grid-template-columns:minmax(0,10rem) minmax(0,1fr); gap:var(--s-3); align-items:baseline }
@media(max-width:560px){ .mw-row{ grid-template-columns:minmax(0,1fr) } }
.mw-row dt{ font-size:var(--fs-xs); color:var(--ink-4); font-weight:var(--w-strong) }
.mw-row dd{ margin:0; font-size:var(--fs-sm); color:var(--ink-2); min-width:0; word-break:break-word }
.mw-row dd a{ color:var(--purple) }
.mw-row dd a:hover{ text-decoration:underline }
.mw-muted{ color:var(--ink-4) }

/* A slug, a URL, an address and a timestamp read left to right whatever the
   interface language is. Without the isolation the bidi algorithm reorders them
   inside an Arabic line and the value then reads as simply wrong. */
.mono-iso{
  font-family:var(--font-lat);
  direction:ltr; unicode-bidi:isolate;
  display:inline-block;
}

.mw-state{
  font-size:var(--fs-xs); font-weight:var(--w-head);
  padding:4px 10px; border-radius:var(--r-pill);
  border:1px solid var(--hair); color:var(--ink);
  display:inline-block;
}
.mw-state-draft{   border-color:var(--lane-draft);   color:var(--lane-draft) }
.mw-state-live{    border-color:var(--lane-live);    color:var(--lane-live) }
.mw-state-sent{    border-color:var(--lane-sent);    color:var(--lane-sent) }
.mw-state-opened{  border-color:var(--lane-opened);  color:var(--lane-opened) }
.mw-state-replied{ border-color:var(--lane-replied); color:var(--lane-replied) }

/* An empty state is a designed state, not a missing one. One icon, one
   sentence, and no action, because the action does not exist yet and offering
   one that does nothing is worse than offering none. */
.mw-empty{
  display:flex; flex-direction:column; align-items:center; gap:var(--s-3);
  padding:var(--s-8) var(--s-4);
  text-align:center;
}
.mw-empty-i{ color:var(--ink-3); opacity:.4 }
.mw-empty p{ margin:0; font-size:var(--fs-sm); color:var(--ink-4) }

/* The card-History conversation has ONE renderer: threadListHtml, mounted by renderHistory into
   #modalHistory; its styles are the .th-list / .th-sent / .th-reply bubbles below. The old .mw-hist list
   renderer was retired long ago; its fossil CSS is removed here, so there is a single conversation component
   in code AND in style, with no dead copy for a future fix to drift into. */
/* The rendered thread self-identifies: a low-contrast marker painted in a corner of the History view the
   moment it opens (analogous to the build stamp, scoped to this component), so it is unmistakable on device
   which renderer is mounted. Non-interactive, aria-hidden in the markup. */
.th-ver{
  font-size:var(--fs-micro); color:var(--ink-4); opacity:.55;
  text-align:end; margin:0 0 var(--s-2); user-select:none; pointer-events:none;
}

/* WO-015 Phase A: the thread, one continuous record oldest to newest. */
.th-list{ list-style:none; margin:0; padding:0; display:grid; gap:var(--s-2) }
.th-line{
  display:flex; gap:var(--s-3); align-items:baseline; flex-wrap:wrap;
  padding:var(--s-2) var(--s-3);
  border:1px solid var(--hair-2); border-radius:var(--r-sm);
}
.th-icn{ flex:0 0 auto; color:var(--ink-4); display:inline-flex; align-items:center }
.th-what{ font-size:var(--fs-xs); color:var(--ink); font-weight:var(--w-head) }
.th-detail{ font-size:var(--fs-xs); color:var(--ink-4); min-width:0; word-break:break-word; flex:1 1 auto }
.th-when{ font-size:var(--fs-micro); color:var(--ink-4); flex:0 0 auto; margin-inline-start:auto }
.th-chan{ color:var(--ink-4) }
/* P21 · the card activity trail: quiet typography, no chrome, newest-first. Each entry is a calm row of
   icon + what + optional summary, with the actor and time trailing. A send/reply is a full-width button that
   expands its message IN PLACE (the pre-rendered P12 bubble) - one scroll, no modal, no second surface. */
.tr-list{ list-style:none; margin:0; padding:0; display:grid; gap:0 }
.tr-line, .tr-head{ display:flex; gap:var(--s-3); align-items:baseline; inline-size:100%;
  padding:var(--s-2) var(--s-3); border:0; background:transparent; text-align:start; font:inherit; color:inherit;
  border-block-end:1px solid var(--hair-2) }
.tr-item:last-child > .tr-line, .tr-item:last-child > .tr-head{ border-block-end:0 }
.tr-head{ cursor:pointer; border-radius:var(--r-sm); transition:background .15s }
.tr-head:hover{ background:var(--hair-2) }
.tr-icn{ flex:0 0 auto; color:var(--ink-4); display:inline-flex; align-items:center; transform:translateY(2px) }
.tr-what{ font-size:var(--fs-sm); color:var(--ink); font-weight:var(--w-head); min-inline-size:0; overflow-wrap:anywhere }
.tr-sum{ font-weight:400; color:var(--ink-3) }
.tr-meta{ margin-inline-start:auto; display:inline-flex; gap:var(--s-2); align-items:baseline; flex:0 0 auto; white-space:nowrap }
.tr-actor{ font-size:var(--fs-micro); color:var(--ink-3) }
.tr-when{ font-size:var(--fs-micro); color:var(--ink-4) }
.tr-chev{ flex:0 0 auto; color:var(--ink-4); display:inline-flex; align-items:center; transition:transform .2s }
.tr-item.is-open .tr-chev{ transform:rotate(90deg) }
.tr-expand{ padding:var(--s-1,4px) var(--s-3) var(--s-3) }
.tr-expand[hidden]{ display:none }
.th-embed{ margin:0 }
[dir="rtl"] .tr-item.is-open .tr-chev{ transform:rotate(-90deg) }
[dir="rtl"] .tr-actor, [dir="rtl"] .tr-when{ letter-spacing:normal }
@media (prefers-reduced-motion:reduce){ .tr-chev{ transition:none } }
/* Part 1: the thread is a list of message bubbles, ONE component for every message. Our send and their
   reply share the same header/body skeleton and are told apart by TWO signals at once: opposite alignment
   (our send at the far edge, their reply at the reading-start edge) AND a distinct surface tint (our send on
   a neutral raised panel, their reply on the replied-green wash). So send / reply / next reply alternate down
   one timeline, each an unmistakable side, without reading a word. Every bubble is bounded (border, radius,
   padding); the header is a ruled-off row whose name takes the width and whose timestamp is pinned to the
   opposite edge, so sender and date never collide; the address is on its own line; the body follows with an
   even vertical rhythm from one gap token. */
.th-list{ gap:var(--s-3) }                                  /* even breathing room between bubbles */
/* P12 · the thread panel shares the composer's content column. The reply editor directly beneath it is a
   .compose-panel (max-width:820px) inside a .panel (20px inline padding); the thread now takes the SAME
   max-width and the SAME inline gutter, so the conversation and the reply box line up on both edges with
   equal gutters and neither overflows. Box-sizing is border-box globally, so the two content columns match
   to the pixel (bar the composer's 1px frame). */
#modalHistory .th-list{ max-width:820px; padding-inline:var(--s-5); box-sizing:border-box; }
.th-sent{ display:flex; justify-content:flex-end }
.th-reply{ display:flex; justify-content:flex-start }
.msg-out,.rp-card{
  max-width:88%; min-width:0; box-sizing:border-box;
  padding:var(--s-3); border:1px solid var(--hair); border-radius:var(--r-lg);
}
.msg-out{ background:var(--panel-2); border-inline-end:3px solid var(--ink-4); }    /* ours: neutral, far edge */
.rp-card{ background:var(--lane-replied-t); border-inline-start:3px solid var(--lane-replied); }  /* theirs: green, start edge */
/* the header: a ruled-off row; the name takes the width, the timestamp is pinned to the opposite edge */
.rp-head{ padding-block-end:var(--s-2); margin-block-end:var(--s-2); border-block-end:1px solid var(--hair); }
.rp-head:last-child{ padding-block-end:0; margin-block-end:0; border-block-end:0; }   /* no dangling rule when the bubble has no body */
.rp-top{ display:flex; align-items:baseline; gap:var(--s-2) var(--s-3); flex-wrap:wrap }
.rp-who{ font-weight:600; unicode-bidi:isolate; min-width:0; flex:1 1 auto; overflow-wrap:anywhere }
.rp-when{ flex:0 0 auto; margin-inline-start:auto; white-space:nowrap }
.rp-from{ margin-block-start:2px; unicode-bidi:isolate; overflow-wrap:anywhere; max-width:100% }
.rp-when,.rp-from{ font-size:12px; color:var(--ink-4) }
/* R10 header: the sender leads (it carries the bubble's weight), the recipient follows quietly behind one
   localized "to" label. Each name is its own isolated run so an Arabic and a Latin name never reorder. */
.msg-from{ unicode-bidi:isolate }
.msg-to{ color:var(--ink-3); font-weight:400; unicode-bidi:isolate }
.msg-to-t{ color:var(--ink-4) }
.rp-chan{ font-size:12px; color:var(--ink-4) }
.th-opti{ opacity:.72 }   /* the optimistic reply, dimmed until the confirmed-write reconciles it in place */
.rp-body{ display:flex; flex-direction:column; gap:var(--s-2); }
.rp-body > *{ margin-block-start:0; margin-block-end:0; }   /* spacing comes from the flex gap, not stacked margins */
/* The chapter divider marks where the offer began. One quiet rule with a word, not a banner. */
.th-chapter{
  display:flex; align-items:center; gap:var(--s-3);
  font-size:var(--fs-micro); color:var(--ink-4); font-weight:var(--w-head);
  margin-block:var(--s-3) var(--s-2);
}
.th-chapter::before,.th-chapter::after{ content:""; height:1px; background:var(--hair-2); flex:1 1 auto }

/* The textarea the clipboard fallback selects. Off the canvas, never display:none,
   because a hidden element cannot be selected and the copy would silently fail. */
.mw-offscreen{
  position:fixed; inset-block-start:0; inset-inline-start:-9999px;
  width:1px; height:1px; opacity:0; pointer-events:none;
}

/* --- under 720: the same component, a different set of values --------------
   A full height sheet on the bottom edge. The enter transform is unchanged, so
   it rises the same 10px it does everywhere else and never slides in from an
   edge.                                                                      */
@media(max-width:720px){
  .modal{
    inset-block:var(--s-4) 0;
    inset-inline:var(--s-2);
    width:auto;
    height:auto;
    max-height:none;
    margin:0;
    border-radius:var(--r-xl) var(--r-xl) 0 0;
    border-block-end:0;
  }
}

/* Locking the body is what actually holds on iOS. It costs the scroll position,
   which initModal remembers and puts back. */
body.modal-open{
  position:fixed;
  inset-inline:0;
  width:100%;
  overflow:hidden;
}

.lane-count{ transition:color var(--t-quick) var(--e-standard) }

/* Touch targets. The three destinations in the bar, the lock, the language switch and the
   board chips measured 24 to 33px tall, and they are the most-tapped controls in the console.
   A pointer keeps the tighter density; a finger gets the 44px floor. The interactive chrome (the
   nav, the language toggle, the window tabs and its close, every button, field and select) meets
   the floor on a coarse pointer; an informational chip stays its quiet label size. */
@media (pointer:coarse){
  .nav a,.langbtn{min-height:44px;display:inline-flex;align-items:center}
  .chip{min-height:40px;display:inline-flex;align-items:center}
  .tok{min-height:44px}
  .modal-tab,.modal-close{min-height:44px}
  .btn,.btn.sm{min-height:44px}
  .tray-toggle{min-height:44px}
  .input,.search input,select,textarea{min-height:44px}
}

/* --- the phone is a first-class console: safe-area insets ------------------------
   With viewport-fit=cover the shell paints edge to edge, so content can reach a notch, a rounded
   corner and the home indicator. env() insets are zero on a desktop or a non-notched screen, so
   max(existing, env(...)) adds room ONLY where the hardware takes it and changes nothing elsewhere.
   One language, adaptive expression: the same sticky header, the same window and gate, given the
   clearance the device asks for rather than a separate mobile layout. */
.top{
  padding-top: max(14px, env(safe-area-inset-top));
  padding-inline: max(20px, env(safe-area-inset-left)) max(20px, env(safe-area-inset-right));
}
.wrap{
  padding-inline: max(20px, env(safe-area-inset-left)) max(20px, env(safe-area-inset-right));
}
/* The fixed feedback surfaces sit clear of the home indicator and the notch. */
.toast{ inset-block-end: max(20px, env(safe-area-inset-bottom)); }
.act-status{ padding-top: max(12px, env(safe-area-inset-top)); }
/* The entry gate keeps clear of every inset on all four edges. */
#thriveGate{
  padding: max(22px, env(safe-area-inset-top)) max(22px, env(safe-area-inset-right))
           max(22px, env(safe-area-inset-bottom)) max(22px, env(safe-area-inset-left));
}
/* The card window is a full height sheet on the phone: its foot clears the home indicator, so the
   composer and its Post never sit under it. */
@media(max-width:720px){
  .modal{ padding-bottom: max(0px, env(safe-area-inset-bottom)); }
}

/* --- replies ---------------------------------------------------------------
   The words a prospect wrote are the most valuable thing on the History tab, so
   they sit above the log rather than inside it. A log entry is a fact about the
   console; this is a fact about the business. */
.mw-replies{ margin-block-end:var(--s-5) }
.mw-h{
  display:flex; align-items:center; gap:var(--s-2);
  margin:0 0 var(--s-3); font-size:14px; color:var(--ink-2);
}
/* Part 4: the newest reply on a multi-reply thread, marked with the lane's own green so it reads as the
   latest at a glance. The bubble already carries the start accent; the latest gets a bordered chip that
   stays legible on the green wash, plus a faint inset ring. No new chrome (existing tokens only). */
.rp-latest{
  font-size:var(--fs-micro); font-weight:var(--w-head); color:var(--lane-replied);
  border:1px solid var(--lane-replied); border-radius:var(--r-sm);
  padding:0 var(--s-2); unicode-bidi:isolate; flex:none;
}
.rp-card.is-latest{ box-shadow:inset 0 0 0 1px var(--lane-replied) }
.rp-subj{ color:var(--ink-2); unicode-bidi:isolate }
/* The snippet is 300 characters by design: the full body stays in Gmail, so
   private correspondence never enters a blob that syncs to every device.
   dir="auto" in the markup plus unicode-bidi:isolate here gives each message its own reading
   direction, so an Arabic reply reads right-to-left and never interleaves with a neighbour. */
.rp-snip{
  margin:var(--s-2) 0 0; color:var(--ink-3); font-size:14px; line-height:1.6;
  unicode-bidi:isolate;   /* wrapping comes from the shared .msg-body rule; this keeps only its isolation */
}
/* Each reply line is its OWN direction-isolated block, so a mixed line (an Arabic date, a Latin sender and
   address, a URL, angle brackets, all in one line: a Gmail quote header) reads in order instead of being
   reordered into a scramble. dir="auto" in the markup picks each line's base direction; the isolate keeps a
   line from reordering against its neighbours; embedded URLs and addresses sit in <bdi class="rp-ltr"> so
   they stay left-to-right and wrap rather than overflow the card. */
.rp-line{ display:block; unicode-bidi:isolate; }   /* wrapping from the shared .msg-body rule */
.rp-line:empty{ min-height:1.6em; }                    /* a blank line keeps its height without an invisible char */
.rp-ltr{ unicode-bidi:isolate; overflow-wrap:anywhere; }
/* Quoted history (the "wrote:" header and the quoted lines) reads as a quieter section, still per-line
   direction-correct. The rule is logical (inline-start), so it sits on the right in Arabic and the left in
   English without a second rule. */
.rp-quote{ color:var(--ink-4); border-inline-start:2px solid var(--hair); padding-inline-start:var(--s-2); }
[dir="rtl"] .rp-line,[dir="rtl"] .rp-ltr{ letter-spacing:normal }   /* Arabic never carries letter-spacing */

/* The thread, structured. A reply renders as typed blocks with one margin token and one vertical rhythm:
   the new message prominent, the quote header recomposed and quiet, the quoted history collapsed, the
   signature muted. Each block owns its layout so the thread reads as a conversation, not a raw dump. */
.rp-msg{ display:block; }
.rp-sig{ display:block; margin-block-start:var(--s-2); color:var(--ink-4); font-size:13px; }
/* The recomposed quote header: built from isolated parts (each a <bdi>), so no part reorders against
   another. It sits quiet under the message, one rhythm step down, direction taken from its own language. */
.rp-qhead{ margin-block-start:var(--s-3); color:var(--ink-4); font-size:13px; line-height:1.6;
  overflow-wrap:anywhere; }
.rp-qhead .rp-qh-t{ color:var(--ink-4); }
.rp-qh-date,.rp-qh-name,.rp-qh-addr{ unicode-bidi:isolate; }
.rp-qh-name{ color:var(--ink-3); }
.rp-qh-addr{ overflow-wrap:anywhere; }
[dir="rtl"] .rp-qhead,[dir="rtl"] .rp-qh-date,[dir="rtl"] .rp-qh-name{ letter-spacing:normal }
/* The quoted history: a quieter, indented, collapsible section. Collapsed by default so the reply's own
   words lead; the summary is the one control that opens it. The rule is logical, so it sits on the correct
   side in both languages, and the body inside keeps its per-line direction isolation. */
.rp-quoted{ margin-block-start:var(--s-3); border-inline-start:2px solid var(--hair); padding-inline-start:var(--s-2); }
.rp-quoted-sum{ color:var(--ink-4); font-size:12px; cursor:pointer; list-style:none; user-select:none; }
.rp-quoted-sum::-webkit-details-marker{ display:none; }
.rp-quoted-sum::before{ content:"+"; display:inline-block; inline-size:1em; color:var(--ink-4); }
.rp-quoted[open] .rp-quoted-sum::before{ content:"\2212"; }   /* a minus when open, one glyph either way */
.rp-quoted-body{ margin-block-start:var(--s-2); color:var(--ink-4); font-size:13px; line-height:1.6; }

/* The reply composer: answer the prospect in this thread, dark and aligned, mirrored in RTL. */
/* (Audit S4) The bare-textarea reply composer (.th-reply-box and its parts) was retired with its builder:
   the thread reply is the same full send editor in reply mode. Its CSS is removed so no dead style remains. */
.rp-foot{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-2); margin-block-start:var(--s-3); flex-wrap:wrap;
}
/* Which rule matched is on screen, not only in the record. A wrong attribution
   is then diagnosable by the person looking at it rather than mysterious. */
.rp-rule{ font-size:12px; color:var(--ink-4) }
[dir="rtl"] .rp-rule,[dir="rtl"] .rp-when,[dir="rtl"] .rp-from{ letter-spacing:normal }

/* P22: how this reply was joined, from the one derivation. A DETERMINISTIC basis reads inline with a
   quiet check, so a certain match reads as certain at a glance; a HEURISTIC basis is a tap-open
   disclosure marked with an alert, so a guess never masquerades as a fact. */
.rp-basis{ font-size:12px; display:inline-flex; align-items:center; gap:4px }
.rp-basis svg{ width:12px; height:12px; flex:none }
.rp-basis.is-det{ color:var(--lane-replied, #7fb98a) }               /* certain: a settled green */
.rp-basis.is-heur{ color:var(--warn, #b26a00) }                       /* a guess: the warn tone */
.rp-basis.is-none{ color:var(--ink-4) }
details.rp-basis.is-heur{ display:inline-block }
details.rp-basis.is-heur > summary{ cursor:pointer; list-style:none; display:inline-flex; align-items:center; gap:4px }
details.rp-basis.is-heur > summary::-webkit-details-marker{ display:none }
.rp-basis-why{ display:block; margin-block-start:4px; font-size:11px; color:var(--ink-3); max-width:34ch }
[dir="rtl"] .rp-basis{ letter-spacing:normal }

/* --- the open team discussion (inside a card) ------------------------------
   One calm room: comments oldest-first, each in its own panel with the author's real name and the one
   date composer, a reply indented one step under its parent, a quiet composer at the foot. Every body is
   direction-isolated (dir="auto" in the markup, isolate here), so an Arabic comment reads right-to-left
   with joined letters and never interleaves with an English neighbour, and Arabic never carries
   letter-spacing. The palette is the console's own tokens, so the discussion belongs to the surface. */
.dc-wrap{ display:flex; flex-direction:column; gap:var(--s-3) }
.dc-title{ margin:0; font-size:var(--fs-sm); color:var(--ink-3); font-weight:600 }
.dc-list{ list-style:none; margin:0; padding:0; display:grid; gap:var(--s-2) }
.dc-item{ display:block }
.dc-reply{ margin-inline-start:var(--s-4) }   /* one level of replies, indented on the correct side per dir */
.dc-bubble{
  border:1px solid var(--hair); border-radius:var(--r-md);
  padding:var(--s-3); background:var(--panel);
}
.dc-reply .dc-bubble{ background:transparent; border-style:dashed }
.dc-head{ display:flex; align-items:baseline; gap:var(--s-2); flex-wrap:wrap }
.dc-who{ font-weight:600; unicode-bidi:isolate }
.dc-when{ font-size:12px; color:var(--ink-4) }
.dc-pending{ display:inline-flex; align-items:center; gap:4px; font-size:11px; color:var(--ink-4);
  border:1px solid var(--hair); border-radius:999px; padding:0 var(--s-2) }
.dc-pending .ic{ inline-size:12px; block-size:12px }
.dc-body{ margin-block-start:var(--s-2); color:var(--ink-2); line-height:1.6;
  unicode-bidi:isolate; overflow-wrap:anywhere; white-space:pre-wrap }
.dc-foot{ display:flex; gap:var(--s-3); margin-block-start:var(--s-2); flex-wrap:wrap }
.dc-act{ background:none; border:0; padding:0; cursor:pointer; font-size:12px; color:var(--ink-4) }
.dc-act:hover{ color:var(--ink-2) }
.dc-edit-bar{ display:flex; gap:var(--s-2); justify-content:flex-end; margin-block-start:var(--s-2) }
.dc-composer{ display:flex; flex-direction:column; gap:var(--s-2);
  margin-block-start:var(--s-3); padding-block-start:var(--s-3); border-block-start:1px solid var(--hair) }
.dc-replyto{ display:flex; align-items:center; justify-content:space-between; gap:var(--s-2);
  font-size:var(--fs-micro); color:var(--ink-4) }
.dc-replyto-txt{ unicode-bidi:isolate }
.dc-replyto-x{ background:none; border:0; padding:0; cursor:pointer; color:var(--ink-4); display:inline-flex }
.dc-replyto-x .ic{ inline-size:14px; block-size:14px }
.dc-input,.dc-edit-input{ min-height:4.5em; resize:vertical; line-height:1.6 }
.dc-bar{ display:flex; align-items:center; justify-content:space-between; gap:var(--s-2); flex-wrap:wrap }
.dc-hint{ font-size:var(--fs-micro); color:var(--ink-4) }
.dc-out{ font-size:var(--fs-micro); color:var(--ink-3); min-height:1.2em }
.dc-out.is-err{ color:var(--state-stall) }
[dir="rtl"] .dc-who,[dir="rtl"] .dc-when,[dir="rtl"] .dc-body,[dir="rtl"] .dc-act,
[dir="rtl"] .dc-hint,[dir="rtl"] .dc-pending,[dir="rtl"] .dc-replyto{ letter-spacing:normal }

.mw-autos{ margin-block-end:var(--s-4) }
.rp-auto{
  display:flex; align-items:center; gap:var(--s-2);
  font-size:13px; color:var(--ink-4);
  padding:var(--s-2) var(--s-3);
  border:1px dashed var(--hair); border-radius:var(--r-md);
  margin-block-end:var(--s-2);
}
.rp-auto .ic{ color:var(--state-stall) }

/* --- three kinds, three treatments -----------------------------------------
   The review's confusion was structural: three objects through overlapping
   paths, so nobody could predict what uploading a file would do. They never
   look alike now. WO-013 §3.4. */
.lib-rule{
  margin:0 0 var(--s-4);
  padding:var(--s-3) var(--s-4);
  border-inline-start:3px solid var(--purple);
  background:var(--lane-draft-t);
  border-radius:var(--r-md);
  color:var(--ink-2); font-size:14px; line-height:1.7;
}
.kind-tag{
  font-size:11px; color:var(--ink-4);
  border:1px solid var(--hair); border-radius:var(--r-pill);
  padding:1px var(--s-2);
}
[dir="rtl"] .kind-tag{ letter-spacing:normal }
/* Template taxonomy: a migrated item is flagged for review, and a snippet's binding to an
   editable template is a labelled reference. Logic surfaced quietly, the visual polish is later. */
.type-mig{ color:var(--state-stall); font-weight:var(--w-strong) }
.et-bind{ display:flex; align-items:center; gap:var(--s-2); margin-block-start:var(--s-2);
  font-size:var(--fs-xs); color:var(--ink-4) }
.et-bind select{ flex:0 1 auto; min-width:0 }
[dir="rtl"] .et-bind{ letter-spacing:normal }
/* .tpl .id carries letter-spacing and uppercase, and until this phase it only
   ever held an id and a two letter locale. It now holds the kind and the locale
   NAME, which in Arabic is Arabic text, and tracking breaks the cursive joins.
   IDENTITY §7 and WO-013 §8.2 rule 6. */
[dir="rtl"] .tpl .id{ letter-spacing:normal; text-transform:none }
/* A page template is teal and always carries its field count and its locale. */
.tpl.kind-page .name .ic{ color:var(--lane-live); margin-inline-end:var(--s-2) }
.tpl.kind-page{ border-inline-start:2px solid var(--lane-live) }
/* Outreach text takes no colour: it is words, and colouring words is noise. */
.kind-text .mw-h .ic{ color:var(--ink-4) }
.tok-name .ic{ margin-inline-end:var(--s-1); color:var(--purple) }

/* --- what the console decided, said out loud -------------------------------
   Every upload ends with one sentence. A file that vanishes into the right
   place without saying so is the same experience as one that vanished into the
   wrong place. */
.kd-report:empty{ display:none }
.kd-report{ margin-block-start:var(--s-3) }
.kd-said{
  display:flex; align-items:flex-start; gap:var(--s-2);
  margin:0 0 var(--s-2); color:var(--ink-2); font-size:14px; line-height:1.6;
}
.kd-said .ic{ flex:0 0 auto; margin-block-start:2px; color:var(--lane-replied) }
.kd-said.bad{ color:var(--state-stall) }
.kd-said.bad .ic{ color:var(--state-stall) }
.kd-q{ margin:0 0 var(--s-3); font-weight:600 }
.kd-prev{
  border:1px solid var(--hair); border-radius:var(--r-md);
  overflow:hidden; block-size:220px; margin-block-end:var(--s-3);
}
.kd-prev iframe{ inline-size:100%; block-size:100%; border:0; background:#fff }
.kd-line{ margin:0 0 var(--s-2); font-size:14px; color:var(--ink-3) }
.kd-fields{ margin:0 0 var(--s-3); padding-inline-start:var(--s-5); color:var(--ink-3) }
.kd-fields li{ font-size:13px; line-height:1.9 }
.kd-unk{ color:var(--state-stall); font-size:12px }
.kd-warn{
  display:flex; align-items:flex-start; gap:var(--s-2);
  margin:0 0 var(--s-2); color:var(--state-stall); font-size:13px; line-height:1.6;
}
.kd-blank-l{ font-size:13px; color:var(--ink-4); align-self:center }

/* --- the channel question ---------------------------------------------------
   Two choices, sized as real decisions rather than chips. The tab used to open
   on a row of send-from options, one of which rendered as a bare dot because
   its label was a literal ".". WO-013 §4.1. */
.och-grid{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  gap:var(--s-3); margin-block-end:var(--s-5);
}
.och-card{
  display:flex; flex-direction:column; align-items:flex-start; gap:var(--s-2);
  min-block-size:96px; padding:var(--s-4);
  border:1px solid var(--hair); border-radius:var(--r-lg);
  background:var(--panel); text-align:start; cursor:pointer;
  font:inherit; color:inherit;
  transition:border-color var(--t-quick) var(--e-standard);
}
.och-card:hover{ border-color:var(--purple) }
.och-card:focus-visible{ outline:2px solid var(--purple); outline-offset:2px }
.och-card .ic{ color:var(--purple) }
.och-card.is-off{ opacity:.55; cursor:default; border-style:dashed }
.och-card.is-off .ic{ color:var(--ink-4) }
.och-t{ font-weight:600 }
/* Arabic runs 20 to 30 percent longer than English, so nothing here is a fixed
   width and nothing truncates. WO-013 §8.2 rule 7. */
.och-s{ font-size:12px; color:var(--ink-4); overflow-wrap:anywhere }
[dir="rtl"] .och-s,[dir="rtl"] .och-t{ letter-spacing:normal }
.och-change{ margin-block-end:var(--s-3) }

/* --- R11 universal contact model (P18): the one channel list ----------------
   Every channel the extractor captured, each with its type, value, and a tier
   chip whose colour is the basis: a sighting is green (settled), a research
   claim is amber (asks to be confirmed), an inference is muted. The primary
   channel, the one a send targets, is marked and tinted. WO-013 §4.2. */
.oc-chs{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--s-2) }
.oc-ch{
  display:flex; align-items:center; flex-wrap:wrap; gap:var(--s-2) var(--s-3);
  padding:var(--s-3); border:1px solid var(--hair); border-radius:var(--r-md);
  background:var(--panel);
}
.oc-ch .ic{ color:var(--purple); flex:none }
.oc-ch.is-primary{ border-color:rgba(150,133,202,.42); background:var(--lane-sent-t) }
.oc-ch-type{ font-weight:600 }
.oc-ch-val{ color:var(--ink-2); font-size:13px; overflow-wrap:anywhere; min-inline-size:0 }
.oc-tier{
  display:inline-flex; align-items:center; gap:var(--s-1);
  padding:2px var(--s-2); border-radius:var(--r-pill); font-size:11px;
  border:1px solid var(--hair);
}
.oc-tier .n{ font-weight:700 }
.oc-tier.is-sighted{ color:var(--state-good); background:var(--lane-replied-t); border-color:rgba(126,224,184,.34) }
.oc-tier.is-stated{ color:var(--state-stall); background:var(--state-stall-t); border-color:var(--state-stall-b) }
.oc-tier.is-inferred{ color:var(--ink-4) }
.oc-primary{
  display:inline-flex; padding:2px var(--s-2); border-radius:var(--r-pill);
  font-size:11px; font-weight:600; color:var(--purple);
  background:var(--lane-sent-t); border:1px solid rgba(150,133,202,.42);
}
.oc-ch .oc-confirm{ margin-inline-start:auto }
/* Arabic never tracks its letters apart, and nothing here truncates. */
[dir="rtl"] .oc-ch-type,[dir="rtl"] .oc-tier,[dir="rtl"] .oc-primary{ letter-spacing:normal }

/* --- the link card, one component, used by both paths ----------------------
   Both paths deliver the same two things: the text and the page link. A prospect
   reading either should see the same object. WO-013 §4.4. */
.link-card{
  display:flex; align-items:flex-start; justify-content:space-between;
  gap:var(--s-3); flex-wrap:wrap;
  border:1px solid var(--hair); border-radius:var(--r-md);
  padding:var(--s-3); margin-block:var(--s-3);
  background:var(--panel-2);
}
.lc-body{ min-inline-size:0; flex:1 1 240px }
.lc-title{ font-weight:600; margin-block-end:2px }
.lc-desc{ font-size:13px; color:var(--ink-3); margin-block-end:var(--s-2) }
.lc-url{ font-size:12px; color:var(--ink-4); text-decoration:none; overflow-wrap:anywhere }
.lc-url:hover{ color:var(--purple) }
.lc-copy{ flex:0 0 auto }
[dir="rtl"] .lc-desc,[dir="rtl"] .lc-title{ letter-spacing:normal }

/* --- what the editor gained ------------------------------------------------
   Each of these removes a failure the team has actually hit. WO-013 §5.2. */
.subj-meter.is-long{ color:var(--state-stall) }
[dir="rtl"] .subj-meter{ letter-spacing:normal }
.sig-loc{ font-size:12px; color:var(--ink-4) }
[dir="rtl"] .sig-loc{ letter-spacing:normal }
.cmp-preview{
  border:1px solid var(--hair); border-radius:var(--r-md);
  overflow:hidden; block-size:360px; background:#fff;
}
.cmp-preview iframe{ inline-size:100%; block-size:100%; border:0 }
/* P7/D5: the recipient switcher above the preview */
.recip-switch{display:flex;align-items:center;gap:var(--s-2);margin:0 0 var(--s-2);flex-wrap:wrap}
.recip-switch .rec-nav{min-inline-size:2rem;text-align:center;font-size:16px;line-height:1;padding:4px 10px}
.recip-label{flex:1 1 auto;min-inline-size:0;font-size:var(--fs-micro);color:var(--ink-3);display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.recip-label .mono-iso{color:var(--ink-4);overflow:hidden;text-overflow:ellipsis}
.recip-label .tp-noname{color:var(--warn,#b26a00)}
[dir="rtl"] .recip-label{letter-spacing:normal}
/* Three lines, shown once, before it leaves. */
.pre-send{ margin-block:var(--s-3) }
.ps-list{ list-style:none; margin:0; padding:0 }
.ps-list li{
  display:flex; align-items:center; gap:var(--s-2);
  font-size:13px; line-height:2; color:var(--ink-3);
}
.ps-list li.ok .ic{ color:var(--lane-replied) }
.ps-list li.no{ color:var(--state-stall) }
.ps-list li.no .ic{ color:var(--state-stall) }
.ps-list li.wait{ color:var(--ink-4) }
.ps-list li.wait .ic{ color:var(--ink-4) }
[dir="rtl"] .ps-list li{ letter-spacing:normal }
/* The one honest line under the Send buttons when a send is not allowed: the reason, and the single
   action that unblocks it. It sits inside its own box, wraps cleanly, and centers its row. */
.send-gate{ display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap;
  margin-block:var(--s-2); padding:9px 12px; border-radius:var(--r-md);
  background:var(--ops-rose-fill); border:1px solid var(--ops-rose-bd);
  color:var(--ink-2); font-size:var(--fs-sm) }
.send-gate .send-gate-ic{ flex:none; display:inline-flex; color:var(--ops-rose) }
.send-gate .send-gate-msg{ flex:1 1 auto; min-width:0; line-height:var(--lh-base); overflow-wrap:anywhere }
.send-gate .btn.sm{ flex:none }
[dir="rtl"] .send-gate{ letter-spacing:normal }

/* --- back, and work that survives a dismissal -------------------------------
   Losing a draft by tapping outside the window was the most common frustration
   in the review, and there was no back anywhere. WO-013 §6. */
.modal-back{ order:-1 }
.draft-band{
  display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
  padding:var(--s-2) var(--s-4);
  background:var(--lane-draft-t);
  border-block-end:1px solid var(--hair);
  font-size:13px; color:var(--ink-2);
}
[dir="rtl"] .draft-band{ letter-spacing:normal }
/* A question with three answers, because the one a person wants when they tap
   outside a half written message is the third: keep editing. */
.tw-scrim{
  position:fixed; inset:0; z-index:120;
  display:flex; align-items:center; justify-content:center;
  padding:var(--s-4);
  background:rgba(0,0,0,.62);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
}
.tw-box{
  inline-size:min(460px,92vw);
  background:var(--panel); border:1px solid var(--hair);
  border-radius:var(--r-xl); padding:var(--s-5);
  box-shadow:var(--el-raised);
}
.tw-t{ margin:0 0 var(--s-2); font-size:17px }
.tw-p{ margin:0 0 var(--s-4); color:var(--ink-3); font-size:14px; line-height:1.7 }
.tw-acts{ display:flex; gap:var(--s-2); flex-wrap:wrap }
[dir="rtl"] .tw-t,[dir="rtl"] .tw-p{ letter-spacing:normal }

/* P24: the two-path Send chooser. Reuses the .tw-scrim / .tw-box shell; the two paths are real decisions,
   sized as cards (mirroring the channel chooser .och-card), and the box widens to hold both side by side. */
.send-box{ inline-size:min(560px,94vw) }
.send-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); gap:var(--s-3); margin-block-end:var(--s-4) }
.send-card{
  display:flex; flex-direction:column; align-items:flex-start; gap:var(--s-2);
  min-block-size:110px; padding:var(--s-4);
  border:1px solid var(--hair); border-radius:var(--r-lg);
  background:var(--bg); text-align:start; cursor:pointer; font:inherit; color:inherit;
  transition:border-color var(--t-quick) var(--e-standard);
}
.send-card:hover{ border-color:var(--purple) }
.send-card:focus-visible{ outline:2px solid var(--purple); outline-offset:2px }
.send-card .ic{ color:var(--purple) }
.send-card.is-off{ opacity:.55; cursor:default; border-style:dashed }
.send-card.is-off .ic{ color:var(--ink-4) }
.send-t{ font-weight:700; color:var(--ink) }
.send-d{ font-size:12.5px; color:var(--ink-3); line-height:1.5 }
.send-meta{ margin-block-start:auto; font-size:11.5px; color:var(--muted) }
.send-foot{ display:flex; justify-content:flex-end }
.send-list{ list-style:none; margin:0 0 var(--s-3); padding:0; display:flex; flex-direction:column; gap:var(--s-1); max-block-size:52vh; overflow-y:auto }
.send-op{ inline-size:100%; display:flex; align-items:center; justify-content:space-between; gap:var(--s-3);
  padding:var(--s-2) var(--s-3); border:1px solid var(--hair); border-radius:var(--r-md);
  background:var(--bg); text-align:start; cursor:pointer; font:inherit; color:inherit; }
.send-op:hover{ border-color:var(--purple) }
.send-op:focus-visible{ outline:2px solid var(--purple); outline-offset:2px }
.send-op-n{ font-weight:600; color:var(--ink); overflow-wrap:anywhere }
.send-op-m{ flex:none; font-size:11.5px; color:var(--muted) }
.send-empty{ color:var(--muted); padding:var(--s-3) 0 }

/* P24: the Send call-to-action on the card overview, and the campaign plan on the campaign screen. */
.ov-send{ margin-block:var(--s-3) }
.ov-send-btn{ inline-size:100% }
.cmp-plan{ margin-block:var(--s-3) 0 }
.cpl-panel{ border:1px solid var(--hair); border-radius:var(--r-lg); padding:var(--s-3) var(--s-4); background:var(--panel) }
.cpl-panel .cg-h{ display:flex; align-items:center; gap:var(--s-2); margin:0 0 var(--s-2) }
.cpl-row{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--s-3); padding:var(--s-1) 0; font-size:13px }
.cpl-k{ color:var(--ink-3) }
.cpl-v{ color:var(--ink); font-weight:600; text-align:end }
.cpl-note{ margin:var(--s-2) 0 0; font-size:11.5px }

/* Law 4: an empty state is an icon, a sentence, and exactly one action. */
.lane-empty{
  display:flex; flex-direction:column; align-items:center; gap:var(--s-2);
  text-align:center;
}
.lane-empty .ic{ opacity:.4; color:var(--lane-c, var(--ink-4)) }
.lane-empty p{ margin:0 }

/* --- the operator profile (WO-029 Phase A) ---------------------------------
   One coherent surface: an identity header, three quiet regions, then the owner-only zone. Calm,
   panelled, logical properties throughout so it mirrors in Arabic with no second rule. */
.page-head{ margin-block-end:var(--s-5) }
.page-title{ margin:0 0 var(--s-1); font-size:var(--fs-lg); font-weight:var(--w-head); color:var(--ink); line-height:var(--lh-tight) }
.page-sub{ margin:0; font-size:var(--fs-sm); color:var(--ink-3) }

.pf-id{
  display:flex; align-items:flex-start; gap:var(--s-4); flex-wrap:wrap;
  background:var(--panel); border:1px solid var(--hair); border-radius:var(--r-lg);
  padding:var(--s-5); margin-block-end:var(--s-5);
}
.pf-avatar{
  flex:0 0 auto; width:56px; height:56px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:rgba(150,133,202,.18); color:var(--purple);
  font-size:var(--fs-lg); font-weight:var(--w-head); line-height:1;
}
.pf-id-body{ flex:1 1 260px; min-width:0 }
.pf-name-field{ margin-block-end:var(--s-3) }
.pf-id-rows{ display:grid; gap:var(--s-2); margin:0 }
.pf-id-row{ display:flex; gap:var(--s-3); align-items:baseline; font-size:var(--fs-sm) }
.pf-id-row dt{ flex:0 0 auto; min-width:96px; color:var(--ink-4); font-weight:var(--w-head) }
.pf-id-row dd{ margin:0; color:var(--ink-2); min-width:0; word-break:break-word }
.pf-signin-note{ margin:var(--s-3) 0 0; font-size:var(--fs-xs); color:var(--ink-4) }

.pf-regions{ display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:var(--s-4); align-items:start }
@media(max-width:960px){ .pf-regions{ grid-template-columns:1fr } }
.pf-region{ padding:var(--s-5) }
.pf-region-h{ margin:0 0 var(--s-1); font-size:var(--fs-md); font-weight:var(--w-head); color:var(--ink) }
.pf-region-sub{ margin:0 0 var(--s-4); font-size:var(--fs-xs); color:var(--ink-4); line-height:var(--lh-base) }
.pf-check{ display:flex; align-items:center; gap:var(--s-2); font-size:var(--fs-sm); color:var(--ink-2); margin-block-end:var(--s-3); cursor:pointer }
.pf-check input{ inline-size:auto; width:auto }
.pf-sig textarea{ resize:vertical }
.pf-saved{ margin:var(--s-2) 0 0; font-size:var(--fs-xs); color:var(--lane-replied) }

/* R14 (P20): the per-sender signature manager. A list of the actor's own signatures, each with a live
   text preview (name above the fixed agency block), then one add form. Composer shows only a read-only
   preview of the chosen one (.sig-preview). No styling beyond the shared tokens. */
.sig-preview{ resize:vertical; background:var(--hair-2); color:var(--ink-2); font-size:var(--fs-sm); line-height:var(--lh-base) }
.pf-sig-mgr{ margin-block:var(--s-4) 0 }
.pf-sig-head{ margin-block-end:var(--s-3) }
.pf-sig-h{ margin:0 0 var(--s-1); font-size:var(--fs-sm); font-weight:600; color:var(--ink) }
.sig-list{ list-style:none; margin:0 0 var(--s-3); padding:0; display:grid; gap:var(--s-3) }
.sig-item{ border:1px solid var(--hair); border-radius:var(--r-md); padding:var(--s-3) var(--s-4); background:var(--panel-2) }
.sig-item-def{ border-color:var(--lane-replied); box-shadow:inset 0 0 0 1px var(--lane-replied) }
.sig-item-top{ display:flex; align-items:center; justify-content:space-between; gap:var(--s-2); margin-block-end:var(--s-2) }
.sig-item-label{ font-size:var(--fs-sm); font-weight:600; color:var(--ink); min-inline-size:0; overflow:hidden; text-overflow:ellipsis }
.sig-item-badge{ flex:0 0 auto; font-size:var(--fs-micro); color:var(--lane-replied); border:1px solid var(--lane-replied); border-radius:999px; padding:1px 8px }
.sig-item-prev{ margin:0 0 var(--s-2); font-size:var(--fs-xs); color:var(--ink-3); line-height:var(--lh-base); white-space:pre-wrap; font-family:inherit }
.sig-item-act{ display:flex; gap:var(--s-2); flex-wrap:wrap }
.sig-item-edit{ display:grid; gap:var(--s-2) }
.sig-edit-l{ font-size:var(--fs-micro); color:var(--ink-3) }
.sig-add{ display:grid; gap:var(--s-2); border-block-start:1px dashed var(--hair); padding-block-start:var(--s-3) }
#sigPickField[hidden], #sigPick[hidden]{ display:none }
[dir="rtl"] .sig-item-prev, [dir="rtl"] .sig-preview{ letter-spacing:normal }

.pf-mem-rows{ display:grid; gap:var(--s-2); margin-block-end:var(--s-4) }
.pf-mem-row{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--s-3);
  font-size:var(--fs-sm); color:var(--ink-2); border-block-end:1px solid var(--hair-2); padding-block-end:var(--s-2) }
.pf-mem-k{ color:var(--ink-3) }
.pf-mem-row b{ color:var(--ink); font-variant-numeric:tabular-nums }

.pf-stats{ display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:var(--s-3); margin-block-end:var(--s-3) }
.pf-stat{ display:flex; flex-direction:column; gap:2px; align-items:flex-start;
  background:var(--hair-2); border:1px solid var(--hair); border-radius:var(--r-md); padding:var(--s-3) var(--s-4) }
.pf-stat b{ font-size:var(--fs-lg); font-weight:var(--w-head); color:var(--ink); line-height:1; font-variant-numeric:tabular-nums }
.pf-stat span{ font-size:var(--fs-xs); color:var(--ink-4) }
.pf-perf-note{ margin:0; font-size:var(--fs-xs); color:var(--ink-4) }

/* cadence: seven daily columns with the rhythm-of-3 line across the middle */
.pf-cadence-wrap{ margin-block-start:var(--s-3) }
.pf-cadence{ position:relative; display:grid; grid-template-columns:repeat(7, minmax(0,1fr)); gap:var(--s-2);
  align-items:end; height:64px; padding:var(--s-2) 0; border-block-end:1px solid var(--hair) }
.pf-cad-target{ position:absolute; inset-inline:0; inset-block-end:50%; height:0; border-block-start:1px dashed var(--hair-strong, var(--hair)) }
.pf-cad-col{ position:relative; display:flex; flex-direction:column; align-items:center; justify-content:flex-end; gap:2px; height:100% }
.pf-cad-bar{ inline-size:100%; max-inline-size:18px; border-radius:var(--r-sm); background:var(--hair-strong, var(--ink-6, var(--ink-4)));
  opacity:.5; min-block-size:4px }
.pf-cad-col.is-hit .pf-cad-bar{ background:var(--accent, var(--brand, var(--ink))); opacity:1 }
.pf-cad-n{ font-size:10px; color:var(--ink-4); font-variant-numeric:tabular-nums; line-height:1 }
.pf-cadence-cap{ margin:var(--s-2) 0 0; font-size:var(--fs-xs); color:var(--ink-4) }

/* the honest pre-stamp bucket */
.pf-history{ display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:var(--s-2);
  margin-block-start:var(--s-4); padding-block-start:var(--s-3); border-block-start:1px solid var(--hair) }
.pf-history-k{ font-size:var(--fs-xs); color:var(--ink-4) }
.pf-history-v{ font-size:var(--fs-xs); color:var(--ink-4) }
.pf-history-v b{ color:var(--ink); font-variant-numeric:tabular-nums }

/* operations ledger: a wide region spanning the row, a filter, and a paged timeline */
.pf-region-wide{ grid-column:1 / -1 }
.pf-ops-filter{ display:flex; flex-wrap:wrap; gap:var(--s-2); margin-block-end:var(--s-3) }
.pf-ops-tab{ appearance:none; border:1px solid var(--hair); background:var(--hair-2); color:var(--ink-4);
  border-radius:999px; padding:4px var(--s-3); font-size:var(--fs-xs); cursor:pointer; min-height:32px }
.pf-ops-tab.is-on{ background:var(--ink); color:var(--surface, #fff); border-color:var(--ink) }
.pf-ops-list{ list-style:none; margin:0; padding:0; display:grid; gap:1px }
.pf-op-row{ display:flex; align-items:center; gap:var(--s-3); padding:var(--s-2) var(--s-1);
  border-block-end:1px solid var(--hair-2) }
.pf-op-ic{ flex:0 0 auto; color:var(--ink-4); display:inline-flex }
.pf-op-ic svg{ inline-size:15px; block-size:15px }
.pf-op-main{ flex:1 1 auto; min-inline-size:0; display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--s-2) }
.pf-op-act{ font-size:var(--fs-sm); color:var(--ink) }
.pf-op-opp{ font-size:var(--fs-xs); color:var(--ink-4); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-inline-size:100% }
.pf-op-when{ flex:0 0 auto; font-size:var(--fs-xs); color:var(--ink-4); font-variant-numeric:tabular-nums }
.pf-ops-more-wrap{ margin-block-start:var(--s-3) }

.pf-infra{ margin-block-start:var(--s-5); padding:var(--s-5); border-color:rgba(246,207,91,.28) }

/* The always-visible build stamp (bottom corner, low contrast). It names which code is live and when it
   was built, so a device capture is never unlabeled again. pointer-events:none and a low z-index under the
   gate, modals, toasts and the runner, so it never obstructs the board or intercepts a tap. */
.build-stamp{position:fixed;inset-block-end:6px;inset-inline-end:8px;z-index:5;pointer-events:none;
  font:10px/1.3 ui-monospace,Menlo,Consolas,monospace;color:var(--ink-4);opacity:.42;letter-spacing:.02em;
  white-space:nowrap;max-inline-size:96vw;overflow:hidden;text-overflow:ellipsis}
.build-stamp bdi{unicode-bidi:isolate}

/* ============================ The Thrive Contact Book (P10) ============================ */
.cb-tools{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.cb-search{flex:1 1 260px;min-width:0}
.cb-sort{display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--ink-2)}
.cb-sort select{padding:8px 10px;border-radius:10px;border:1px solid var(--hair);background:var(--panel-2);color:var(--ink)}
.cb-tagfilter{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0 6px}
.cb-tagf{padding:5px 12px;border-radius:999px;border:1px solid var(--hair);background:var(--panel-2);
  color:var(--ink-2);font-size:12.5px;font-weight:700;cursor:pointer}
.cb-tagf.on{border-color:var(--purple);color:var(--ink);background:var(--panel)}
.cb-tagf .n{unicode-bidi:isolate;opacity:.7;margin-inline-start:5px}
.cb-chip{display:inline-block;padding:2px 9px;border-radius:999px;font-size:11.5px;font-weight:700;
  background:var(--panel-2);border:1px solid var(--hair);color:var(--ink-2);vertical-align:middle}
.cb-chip.cb-warn{border-color:var(--warn,#c76b2a);color:var(--warn,#c76b2a)}
.cb-chip.cb-merged{border-color:var(--purple);color:var(--ink-2)}
.cb-chip.cb-tag{background:var(--grad,var(--panel-2));border-color:transparent;color:var(--ink)}
/* review items */
.cb-review{border:1px solid var(--hair);border-radius:14px;padding:14px 16px;margin:8px 0 22px;background:var(--panel)}
.cb-h{font-size:15px;font-weight:800;margin:0 0 10px}
.cb-count{unicode-bidi:isolate;color:var(--ink-2);font-weight:700}
.cb-item{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;
  padding:10px 0;border-top:1px solid var(--hair)}
.cb-item:first-of-type{border-top:0}
.cb-item-people{display:flex;gap:16px;flex-wrap:wrap}
.cb-cand{display:flex;flex-direction:column;gap:2px}
.cb-addr{unicode-bidi:isolate;font-size:12px;color:var(--ink-3)}
.cb-addr.bad{color:var(--warn,#c76b2a)}
/* person list */
.cb-people{display:flex;flex-direction:column;gap:12px;margin-top:8px}
.cb-person{border:1px solid var(--hair);border-radius:14px;padding:14px 16px;background:var(--panel)}
.cb-main{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;flex-wrap:wrap}
.cb-name{font-size:15.5px}
.cb-addrs{display:flex;gap:12px;flex-wrap:wrap;margin-top:5px}
.cb-flag{color:var(--warn,#c76b2a);font-weight:800}
.cb-tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
.cb-metrics{display:flex;gap:16px;flex-wrap:wrap;align-items:baseline;font-size:12.5px;color:var(--ink-2)}
.cb-metrics .n{unicode-bidi:isolate;font-weight:800;color:var(--ink)}
.cb-last{unicode-bidi:isolate;color:var(--ink-3)}
.cb-threads{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:12px;
  padding-top:10px;border-top:1px solid var(--hair)}
.cb-threads-l{font-size:12px;color:var(--ink-3)}
.cb-opp{padding:4px 11px;border-radius:8px;border:1px solid var(--hair);background:var(--panel-2);
  color:var(--ink-2);font-size:12px;cursor:pointer}
.cb-opp:hover{border-color:var(--purple);color:var(--ink)}
.cb-curate{display:flex;gap:7px;flex-wrap:wrap;align-items:center;margin-top:12px}
.cb-tagtoggle{padding:4px 11px;border-radius:999px;border:1px dashed var(--hair);background:transparent;
  color:var(--ink-3);font-size:12px;cursor:pointer}
.cb-tagtoggle.on{border-style:solid;border-color:var(--purple);color:var(--ink);background:var(--panel-2)}
.cb-unmerge{margin-inline-start:auto}
@media (max-width:640px){ .cb-main{flex-direction:column;gap:10px} .cb-metrics{gap:12px} }
