/* n9-desk-widget.css — the site-wide front-desk overlay. LIVE on the host (the chip rotation landed at
   16 Oct 2026 15:08 SAST, sha head 44c7964185a7 at 16 001 B); the change carried here is job
   n9-fd-chat-mobile, staged and NOT deployed. See measurement/ENVELOPE.txt for how that was checked.
   Installs to: /srv/ninty9-wordpress/public/assets/n9-desk-widget.css
   Rendered by app/includes/desk-widget.php on every page. The bubble keeps the estate's existing
   .n9-wa look (assets/whatsapp-button.css, loaded by the same partial), so the launcher the family
   already knows simply opens a panel instead of leaving the page. Panel shape follows the owner's
   refs (design-refs/apollo-1.png / apollo-2-menu.png): desktop = fixed card bottom-right ~380x600,
   phone = full-width bottom sheet. No inline <style> anywhere: the CSP allows style elements from
   'self' only (style-src-elem), so every rule lives in this file. */

.n9dw-root { position: relative; }

/* ---- the panel ---- */
.n9dw-panel {
  position: fixed; right: 18px; bottom: 86px; z-index: 9998;
  width: 380px; max-width: calc(100vw - 24px); height: auto; max-height: calc(100vh - 104px);
  display: flex; flex-direction: column; overflow: hidden;
  background: #fff; color: #10243e; border: 1px solid #d7dde5; border-radius: 16px;
  box-shadow: 0 12px 34px rgba(7, 23, 43, .18);
}
.n9dw-panel[hidden] { display: none; }

/* ---- green bar ---- */
.n9dw-bar { display: flex; align-items: center; gap: 6px; background: #f2c94c; color: #07172b; padding: 8px 10px 8px 12px; }
/* DEFECT 3 (desk-polish-20261015) MEASURED CAUSE: the bar was re-skinned from green to yellow #f2c94c
   and the icon ink stayed white. White glyph on that yellow = 1.59:1 (WCAG non-text floor 3:1), so the
   arrow was painted all along but unreadable, i.e. it did not look like part of the chat header.
   The estate's own dark ink #07172b on the same yellow = 11.35:1. */
.n9dw-icon {
  display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  flex: 0 0 32px; border: 0; border-radius: 50%; background: transparent; color: #07172b; cursor: pointer; padding: 0;
}
.n9dw-icon:hover { background: #07172b26; }
.n9dw-avatar {
  display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  flex: 0 0 32px; border-radius: 50%; background: #fff; color: #3f9d5c;
}
.n9dw-avatar--sm { width: 26px; height: 26px; flex: 0 0 26px; margin: 1px 8px 0 0; background: #3f9d5c; color: #fff; }
/* DEFECT 1 (desk-polish-20261015): the greeting avatar now carries the desk's own headset glyph (see
   desk-widget.php); keep it on the text's first line and stop it shrinking. */
.n9dw-avatar--sm svg { display: block; flex: 0 0 auto; }
.n9dw-id { margin: 0; flex: 1 1 auto; min-width: 0; line-height: 1.15; }
.n9dw-name { display: block; font-weight: 700; font-size: 15px; }
.n9dw-role { display: block; font-size: 11px; opacity: .9; }
.n9dw-langs { display: flex; gap: 4px; flex: 0 0 auto; }
.n9dw-lang {
  padding: 3px 8px; border-radius: 999px; font-size: 11px; cursor: pointer;
  border: 1px solid #07172b59; background: transparent; color: #07172b;
}
.n9dw-lang.is-on { background: #fff; color: #07172b; border-color: #fff; font-weight: 700; }
.n9dw-status { display: flex; align-items: center; gap: 6px; }
.n9dw-dot { width: 10px; height: 10px; border-radius: 50%; background: #16a34a; flex: 0 0 10px; box-shadow: 0 0 0 0 rgba(22,163,74,.55); animation: n9dwpulse 2s infinite; }
@keyframes n9dwpulse { 70% { box-shadow: 0 0 0 7px rgba(22,163,74,0); } 100% { box-shadow: 0 0 0 0 rgba(22,163,74,0); } }
.n9dw-menuwrap { position: relative; flex: 0 0 auto; }
.n9dw-menu {
  position: absolute; right: 0; top: 36px; z-index: 30; min-width: 140px; margin: 0; padding: 6px;
  list-style: none; background: #fff; color: #10243e; border: 1px solid #d7dde5; border-radius: 12px;
  box-shadow: 0 10px 26px rgba(7, 23, 43, .18);
}
.n9dw-menu li { margin: 0; }
.n9dw-menuitem {
  display: block; width: 100%; text-align: left; padding: 9px 10px; font-size: 14px; cursor: pointer;
  background: transparent; border: 0; border-radius: 8px; color: #10243e;
}
.n9dw-menuitem:hover { background: #f1f4f1; }

/* ---- notice + body ---- */
.n9dw-notice { margin: 8px 12px 0; padding: 8px 10px; font-size: 13px; border-radius: 10px; background: #fdeeee; color: #7a1f1f; }
.n9dw-notice[hidden] { display: none; }
/* THE DEAD-AIR STATE (item 4 and 6a). One line in the sheet, worded in both languages BY THE PAGE, that
   says the connection is gone and offers the tap that tries again. Outside any media query on purpose: a
   dropped bundle is not a feature of small screens. Never red, never an error box — a family on a moving
   train has not done anything wrong, and the audit measured this state as 16 empty label slots and silence. */
.n9dw-net { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin: 8px 12px 0; padding: 8px 10px; font-size: 13px; line-height: 1.35;
  border-radius: 10px; background: #fff7e6; color: #6b4a12; border: 1px solid #f2c94c66; }
.n9dw-net[hidden] { display: none; }
.n9dw-net button { flex: 0 0 auto; min-height: 44px; padding: 6px 12px; font-size: 13px; font-weight: 700;
  color: #6b4a12; background: #fff; border: 1px solid #d9a400; border-radius: 999px; cursor: pointer; }
/* Reply-time line (job n9-fd-reply-window, 15 Oct 2026). The element is ALWAYS present in the markup and
   is filled from a boot measurement, so the honest state is an empty one — and an empty one must cost the
   family nothing: no strip, no dot, no gap. That is what :empty is for, and it is why there is no seeded
   number in the code any more. */
.n9dw-timing { display: flex; align-items: center; margin: 6px 12px 0; padding: 0; font-size: 12px; line-height: 1.4; color: #5b6470; }
.n9dw-timing:empty { display: none; margin: 0; padding: 0; }
/* "We will answer by 14:35" (owner item 4, 16 Oct 2026 11:39 SAST). Same rule as the timing line above:
   the element is ALWAYS in the markup and the server either fills it or blanks it, so the honest state —
   we already answered, or there is nothing measured — costs a family no words and no blank strip. */
.n9dw-answerby { display: flex; align-items: center; margin: 4px 12px 0; padding: 0; font-size: 12.5px; line-height: 1.4; color: #2b7a46; font-weight: 600; }
.n9dw-answerby:empty { display: none; margin: 0; padding: 0; }
/* Item 5: the sender removing their own message. Small, on the row, never a red button that looks like
   it could destroy something. It hides the row from THIS screen; the row itself is never deleted. */
.n9dw-remove { align-self: flex-end; background: none; border: 0; padding: 1px 2px; font-size: 11.5px; color: #5b6470; text-decoration: underline; cursor: pointer; }
.n9dw-remove:disabled { opacity: .5; cursor: default; }
/* Item 3: the ledger's mark, printed under a desk message. It is quiet on purpose — a fact, not a badge. */
.n9dw-mark { font-size: 11px; color: #5b6470; font-style: italic; }
.n9dw-body { flex: 1 1 auto; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; background: #fff; }
.n9dw-body [hidden] { display: none; }

.n9dw-greet { display: flex; align-items: flex-start; margin: 0; padding: 10px 12px; font-size: 15px; line-height: 1.45; background: #eceeea; border-radius: 14px; }
/* CHIP ROTATION (job n9-fd-chips-rotate). desk-polish-20261015 laid all four declared questions out at
   once; the owner overturned that on this exact element, 16 Oct 13:38 SAST: "This has to be 1 that
   changes every 5 seconds." So: ONE question on the row, handing over to the next every 5 000 ms, phone
   and desktop alike. A chip is still only a question, and the desk is still the ontvangstoonbank.
   WHY CSS AND NOT A JS TIMER: n9-desk-widget.js already runs a 12 s setInterval for messages (line 232).
   A second loop would tick on a hidden tab, need a mount in desk-widget.php and cost a request. These
   rules add no file, no request and no script, and a closed panel (display:none) has nothing to run.
   NO LAYOUT SHIFT (item 2): the four questions share ONE grid cell sized by the tallest of them, so the
   box is the same before the first change and after the hundredth. Only opacity animates, never
   visibility: every question stays in the accessibility tree and stays tabbable, while the three not
   showing take pointer-events:none so nobody taps a question they cannot see. The DOM is never touched,
   so nothing is ever announced (item 4: no aria-live was added). Numbers: measurement/cadence.out.
   .n9dw-flash, which the widget still adds, is inert now: the first fade IS the first slot. */
.n9dw-chips {
  display: grid; justify-items: start; align-content: center;
  gap: 6px; white-space: normal; margin: 10px 2px; padding: 8px 10px; background: rgba(63,157,92,0.08); border-radius: 12px;
}
.n9dw-chips:empty { display: none; }
/* 5 000 ms per slot: each question owns a quarter of a 20 s cycle (25% of 20 s = 5 s exactly).
   0-0.5% invisible and not tappable, 0.5-2% fades up (0.4 s), 2-23% readable, 23-25% fades out. */
@keyframes n9dwchipslot {
  0%    { opacity: 0; pointer-events: none; }
  0.5%  { opacity: 0; pointer-events: auto; }
  2%    { opacity: 1; pointer-events: auto; }
  23%   { opacity: 1; pointer-events: auto; }
  25%   { opacity: 0; pointer-events: none; }
  100%  { opacity: 0; pointer-events: none; }
}
.n9dw-chip {
  flex: 1 1 auto; grid-area: 1 / 1; max-width: 100%; padding: 7px 12px; font-size: 13.5px; line-height: 1.25; text-align: left; cursor: pointer;
  background: #fff; color: #2b7a46; border: 1px solid #3f9d5c; border-radius: 999px;
  animation: n9dwchipslot 20s linear infinite both;
}
.n9dw-chip:nth-child(2) { animation-delay: 5s; }
.n9dw-chip:nth-child(3) { animation-delay: 10s; }
.n9dw-chip:nth-child(4) { animation-delay: 15s; }
.n9dw-chip:hover { background: #f1f8f3; }
/* RESPECT THE PERSON (item 4). Two ways the row stops by itself, with no script:
   1. focus in the row (a keyboard parent) — the cycle stops and all four questions lay out, so no
      question can be unreachable and a focused chip is never left transparent;
   2. not exactly four questions — fewer would leave an empty slot in the 20 s cycle; more would stack a
      question behind the visible one so it could never be read. Nothing is hidden to keep a rhythm: the
      row goes back to the static all-visible layout it has today.
   :has() is progressive enhancement (this file already uses it for the phone launcher): a browser
   without it keeps the rotation and loses only guard 2. */
.n9dw-chips:focus-within,
.n9dw-chips:not(:has(.n9dw-chip:nth-child(4))),
.n9dw-chips:has(.n9dw-chip:nth-child(5)) { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; }
.n9dw-chips:focus-within .n9dw-chip,
.n9dw-chips:not(:has(.n9dw-chip:nth-child(4))) .n9dw-chip,
.n9dw-chips:has(.n9dw-chip:nth-child(5)) .n9dw-chip { grid-area: auto; animation: none; opacity: 1; pointer-events: auto; }
/* A mouse over the row reveals all four questions at once — the one owner-visible side effect: the row
   grows back to its today-height while the cursor is on it, and that is user-initiated, not a rotation
   change. Gated on (hover: hover) so a phone's sticky tap-hover can never leave the row open. To drop
   the reveal and let the cycle keep turning under the cursor, delete this block. */
@media (hover: hover) {
  .n9dw-chips:hover { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; }
  .n9dw-chips:hover .n9dw-chip { grid-area: auto; animation: none; opacity: 1; pointer-events: auto; }
}
/* OPTIONAL (measured, NOT staged): 44 px tap targets on a phone. Today the chips measure 32.88 px
   tall, below the 44 px floor; this job does not change that. If the owner says yes, this one block
   is the whole change. On the CAND bytes it costs 345 B and the row box goes 48.88 -> 60.00 px. */
@media (max-width: 640px) { .n9dw-chip { min-height: 44px; display: flex; align-items: center; } }

/* prefers-reduced-motion: nothing fades and nothing moves, and all four questions are still there to
   read — the row is useful, it simply is not animated. */
@media (prefers-reduced-motion: reduce) {
  .n9dw-chips { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; }
  .n9dw-chip { animation: none; opacity: 1; pointer-events: auto; }
}

.n9dw-card { display: flex; flex-direction: column; gap: 8px; padding: 12px; background: #fff; border: 1px solid #d7dde5; border-radius: 14px; }
.n9dw-card h2 { margin: 0; font-size: 15px; }
.n9dw-label { font-size: 13px; font-weight: 600; }
.n9dw-label em { font-style: normal; font-weight: 400; color: #5b6470; }
.n9dw-card input, .n9dw-card textarea { width: 100%; font-size: 16px; }
.n9dw-hint { margin: 0; font-size: 12px; color: #5b6470; }
.n9dw-start {
  padding: 11px 14px; font-size: 15px; font-weight: 700; cursor: pointer;
  color: #fff; background: #3f9d5c; border: 1px solid #3f9d5c; border-radius: 999px;
}
.n9dw-start:hover { background: #2b7a46; border-color: #2b7a46; }
.n9dw-again { padding: 9px 12px; font-size: 14px; cursor: pointer; color: #2b7a46; background: #fff; border: 1px solid #3f9d5c; border-radius: 999px; }
.n9dw-forget { align-self: flex-start; background: none; border: 0; padding: 2px 0; font-size: 12px; color: #5b6470; text-decoration: underline; cursor: pointer; }
.n9dw-return { margin-top: 2px; }

/* ---- the conversation ---- */
.n9dw-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 0; font-size: 12px; color: #5b6470; }
.n9dw-ref { letter-spacing: .3px; text-transform: uppercase; }
/* The pill rule that used to sit here was written for a thread-view status chip that no element
   ever rendered (grep: .n9dw-status appears once in the markup, in the header bar). It landed on
   the bar instead, so its 1px border drew a capsule across "Nandi" and clipped "Aanlyn · Ontvangs"
   on Afrikaans. The bar keeps the flex rule above. Director, 15 Oct 2026 20:22 SAST. */
.n9dw-duty { margin: 0; font-size: 12px; color: #5b6470; text-align: center; }
.n9dw-log { display: flex; flex-direction: column; gap: 10px; min-height: 120px;
  max-height: 232px;                       /* fallback = 3 short messages; JS measures the real 3 */
  overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  padding-right: 4px; scrollbar-width: thin; }
.n9dw-log::-webkit-scrollbar { width: 6px; }
.n9dw-log::-webkit-scrollbar-thumb { background: #c9d1cb; border-radius: 999px; }
.n9dw-row { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; margin: 0; }
.n9dw-row.is-me { align-items: flex-end; }
.n9dw-who { font-size: 11px; color: #5b6470; }
.n9dw-when { font-size: 11px; color: #5b6470; }
/* TWO LINES OF SMALL PRINT PER ROW, NOT FOUR (owner, 20 Oct 2026 18:1x SAST: "Improve the chat layout").
   The sender and the time share the header line above the words; the read / delivered mark and the family's own
   "remove" share ONE footer line under them. Same elements, same words, same order - the header always carries the
   time, the footer exists only when the row has something to say about itself. `baseline` keeps a bold name and a
   light time sitting on one line instead of each finding its own. */
.n9dw-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; line-height: 1.3; }
.n9dw-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; line-height: 1.3; }
.n9dw-foot:empty { display: none; }
.n9dw-head .n9dw-who { font-weight: 600; }
/* The footer's controls are the row's own small print: they keep its size instead of inheriting the bubble's. */
.n9dw-foot .n9dw-remove, .n9dw-foot .n9dw-mark { font-size: 11px; line-height: 1.3; }
.n9dw-bubble { max-width: 88%; padding: 9px 12px; font-size: 15px; line-height: 1.45; border-radius: 14px; background: #eceeea; color: #10243e; }
.n9dw-row.is-me .n9dw-bubble { background: #fff; border: 1px solid #3f9d5c; color: #2b7a46; }
.n9dw-row--new { animation: n9dw-in .18s ease-out; }
@keyframes n9dw-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* ONE SCROLLER AT EVERY WIDTH, AND A COMPOSER THAT NEVER SCROLLS AWAY.
   The phone sheet got this on 16 Oct; the desktop card kept TWO nested scrollers (a 303 px log inside a body
   that scrolled itself), so a parent on a laptop could scroll the log down and push their own typing box out
   of the panel - the same trap, one screen size away, never fixed. These rules are that fix at both sizes: the
   thread view fills the body, the log is the only thing that moves, and the composer is pinned to the bottom
   edge. The desktop's own three-row cap (row 169, set inline by the JS) still wins over `flex: 1 1 auto`, so
   the compact window the owner asked for is untouched; on a short laptop window the log now SHRINKS instead of
   shoving the composer off the panel. `:has()` is progressive enhancement: a browser without it keeps the old
   body scroll rather than getting a clipped panel. */
.n9dw-body:has(> [data-desk-view="thread"]:not([hidden])) { overflow: hidden; }
[data-desk-view="thread"]:not([hidden]) {
  display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; gap: 10px;
}
[data-desk-view="thread"] .n9dw-log { flex: 1 1 auto; min-height: 0; }
[data-desk-view="thread"] .n9dw-composer {
  flex: 0 0 auto; position: sticky; bottom: 0;
  padding: 8px 0 calc(8px + env(safe-area-inset-bottom, 0px)); background: #fff;
}

.n9dw-composer { display: flex; align-items: flex-end; gap: 8px; }
.n9dw-composer textarea { flex: 1 1 auto; min-height: 42px; font-size: 16px; border-radius: 999px; padding: 10px 14px; }
.n9dw-send {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 42px; width: 42px; height: 42px;
  border: 0; border-radius: 50%; background: #3f9d5c; color: #fff; cursor: pointer; padding: 0;
}
.n9dw-send:hover { background: #2b7a46; }

/* THE "start a new chat" DOOR (owner, 21 Oct 2026). It replaces the typing row when a conversation is closed, so it
   sits where the family was about to write. Same green as the send button, and never smaller than 44 px. */
.n9dw-newchat {
  display: flex; align-items: center; justify-content: center; box-sizing: border-box;
  width: calc(100% - 32px); margin: 12px 16px 16px; min-height: 44px; padding: 10px 16px;
  border: 0; border-radius: 999px; background: #3f9d5c; color: #fff; font: inherit; font-size: 15px;
  font-weight: 600; cursor: pointer; text-align: center;
}
.n9dw-newchat:hover { background: #2b7a46; }

.n9dw-visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ---- phone: full-width bottom sheet ---- */
/* THE KEYBOARD, and why this block is the whole fix for it (job n9-fd-chat-mobile, item 2/6b).
   Measured on 390x844 with the keyboard taking 40 % of the screen: the visible strip is 507 px and the
   typing box sat at y=515 — BELOW it (DEFECTS.tsv: insideScreen=false, scrollIntoView=0). An iPhone does
   NOT shrink the layout viewport for the keyboard, so `vh` alone cannot solve this and the file the meta
   viewport lives in (`app/includes/header.php`) is PINNED, so the usual `interactive-widget` answer is
   outside this job's envelope and is reported to the Director instead.
   So the sheet reads one number the browser already knows: `--n9kb`, the pixels the keyboard has taken,
   written by n9-desk-widget.js off `window.visualViewport` (0 on a browser that has no such API, and 0
   with no keyboard up — `min()` then returns exactly today's 84vh, so nothing moves until a keyboard does). */
/* NOT `.n9dw-panel { --n9kb: 0px }`: a custom property DECLARED on the panel shadows the one the
   script sets on the ancestor, so the lift would have applied to nothing. Found by
   measurement/measure-keyboard-ios.mjs, which reads the box the browser laid out, not the intent —
   the fallback belongs in `var()` instead. */

@media (max-width: 640px) {
  .n9dw-panel {
    right: 0; left: 0; bottom: var(--n9kb, 0px); width: 100%; max-width: 100%;
    height: auto; max-height: min(84vh, calc(100vh - var(--n9kb, 0px)));
    border-radius: 18px 18px 0 0; border-left: 0; border-right: 0; border-bottom: 0;
  }
  /* DEFECT 3 (desk-polish-20261015), second half: the arrow's box started 6px from the screen edge,
     42px left of the header's own content column (the avatar starts at x=48), so it read as page
     furniture rather than a control in the bar. Inset the bar to x=18 on phones.
     NOT the cause, ruled out by measurement: the sheet's 18px top corners clip 0.0% of the arrow box. */
  .n9dw-bar { padding: 10px 10px 10px 18px; }
  /* Phone: the launcher floats over the sheet's bottom-right corner and covers the "Start chat"
     button. Hide it while the sheet is open — the sheet has its own back arrow, so closing stays
     obvious. Browsers without :has() simply keep the launcher (harmless). Director, 15 Oct 18:45 SAST. */
  .n9dw-root:has(.n9dw-panel:not([hidden])) > .n9-wa[data-desk-bubble] { display: none; }

  [data-desk-view="thread"] .n9dw-log { max-height: none; }
  /* While a parent is actually typing, the four suggested questions move out of the way: measured on
     390x506 with the keyboard proxy, the chips row was taking 60 px of a 425 px sheet and the log was down
     to ONE fully visible row. The questions come straight back on blur. `:has()` again, so a browser
     without it simply keeps the row — nothing is hidden from an assistive tech, this row is not the only
     way to ask a question. */
  [data-desk-view="thread"]:has(.n9dw-composer:focus-within) [data-desk-chips-thread] { display: none; }
  /* THE THUMB FLOOR (item 1 / defect 6b): measured on the live panel — send 42x42, close 32x32, EN 33.3x25,
     AF 32.1x25, remove-message 14 px tall. Eight of nine controls were under the 44 px the owner set for
     the chips, so most of what a parent has to hit on a phone was smaller than the pad that hits it.
     These are REAL boxes, not a transparent pseudo-element standing in for one: a hit area a measuring
     script cannot see is a claim, and this job is only allowed to hand over numbers. The visible marks keep
     their own size (the arrow and the avatar are still 20 px and 32 px) — the control around them is 44.
     The chips' 44 px floor is already live and is NOT re-staged or reverted here. */
  .n9dw-send { flex: 0 0 46px; width: 46px; height: 46px; }
  .n9dw-composer textarea { min-height: 46px; }
  .n9dw-start, .n9dw-again { min-height: 44px; }
  .n9dw-forget { min-height: 44px; display: inline-flex; align-items: center; }
  .n9dw-remove { min-height: 44px; padding: 0 10px; display: inline-flex; align-items: center; }
  .n9dw-icon { width: 44px; height: 44px; flex: 0 0 44px; }
  .n9dw-lang { min-width: 44px; min-height: 44px; padding: 0 10px; }
  /* The bar grows with the controls, so its own padding comes back out again: measured at 64 px tall on
     390x844 with 44 px pills and 10 px of padding, which is the cost of the floor and is in phone.out. */
  .n9dw-bar { padding: 6px 8px 6px 18px; }
}

/* The same floor for a coarse pointer on a wider screen (a tablet in landscape, a phone held sideways at
   700+ px of CSS): the thumb does not get smaller because the viewport did. */
@media (min-width: 641px) and (pointer: coarse) {
  .n9dw-send { width: 44px; height: 44px; flex: 0 0 44px; }
  .n9dw-icon { width: 44px; height: 44px; flex: 0 0 44px; }
  .n9dw-lang { min-width: 44px; min-height: 44px; }
  .n9dw-remove { min-height: 44px; display: inline-flex; align-items: center; }
}
@media (prefers-reduced-motion: reduce) { .n9dw-row--new { animation: none; } }
@media print { .n9dw-panel, .n9dw-root .n9-wa { display: none; } }

/* THE LINK BUTTON (owner, 20 Oct 2026 17:4x SAST: "put a button in the chat that links to where they want to go and say
   Click here"). A reply that carries one of our pages now ends in a real tap target, so the family is not asked to
   retype a URL from a phone screen. Sized to its own text like the bubble beside it, and never smaller than the 44 px
   a thumb needs on the phone this estate is mostly used on. */
.n9dw-goto {
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px; max-width: 88%;
  margin-top: 4px; padding: 10px 18px; border-radius: 999px; background: #2b7a46; color: #fff;
  font-size: 15px; font-weight: 700; line-height: 1.2; text-decoration: none;
}
.n9dw-goto:hover, .n9dw-goto:focus { background: #24663c; color: #fff; text-decoration: none; }
.n9dw-goto:focus-visible { outline: 3px solid #3f9d5c; outline-offset: 2px; }

/* ALREADY-VISITED CHAT LINKS ARE GOLD. Owner, 20 Oct 2026 17:5x SAST, verbatim: "When clicked the link colour looks
   horrible. Make already visited link in chat gold."
   WHAT WAS HAPPENING, measured: the purple was the BROWSER'S OWN visited-link colour beating this button. A UA rule
   like `a:visited { color: purple }` carries one element selector, which outranks a plain class like `.n9dw-goto`, so
   a family who had already tapped "Click here" got purple text on the green pill. Nothing was wrong with the button
   itself — the author simply never claimed the visited state.
   WHY ONLY COLOURS APPEAR HERE: a browser deliberately ignores every non-colour property on `:visited` (so a page
   cannot sniff a visitor's history), which is why this rule changes background and colour and nothing else. The gold
   is the estate's own `#e6bd5b` with the estate ink `#172337` on it, readable at a glance and clearly "already read".
   `:visited:hover` is set too, because a gold pill that snapped back to green under a thumb would look broken. */
.n9dw-goto:visited { background: #e6bd5b; color: #172337; }
.n9dw-goto:visited:hover, .n9dw-goto:visited:focus { background: #d8ad55; color: #172337; }
/* The same defect in the bubble's own inline link, on the pale grey bubble: gold, darkened to stay readable there. */
.n9-desk-link:visited { color: #8a6a1f; }

/* ---- the Principal's chat is BLACK, not gold (owner, 20 Oct 2026 21:1x SAST: "the moment the principal messages,
   the chat system needs to become black instead of gold") ----
   The bar above is the chat's gold (#f2c94c). When the Principal has replied on this thread the widget carries
   data-desk-principal="1" and the bar goes black. The ink has to be overridden too: the icon, the avatar and the
   name are painted #07172b, which is 1.1:1 on black — the same unreadable-glyph defect this file already records
   from the day the bar was re-skinned. Light on near-black measures 15.8:1. */
[data-desk-principal="1"] .n9dw-bar { background: #0b0f14; color: #f3f7fb; }
[data-desk-principal="1"] .n9dw-bar .n9dw-icon,
[data-desk-principal="1"] .n9dw-bar .n9dw-avatar,
[data-desk-principal="1"] .n9dw-bar .n9dw-id,
[data-desk-principal="1"] .n9dw-bar .n9dw-status,
[data-desk-principal="1"] .n9dw-bar .n9dw-role,
[data-desk-principal="1"] .n9dw-bar .n9dw-lang { color: #f3f7fb; }
[data-desk-principal="1"] .n9dw-bar .n9dw-name { color: #ffffff; }
/* THE SELECTED LANGUAGE WAS INVISIBLE ON THE BLACK BAR — a defect this page would have shown every visitor, not once
   in a while. MEASURED in the harness: `.n9dw-lang.is-on` paints a white pill with #07172b ink (specificity 0,2,0),
   but the black-bar ink rule above is 0,3,1 and wins, so "EN" was white on white. The on-pill now takes the room's
   gold, which is the estate's own selection colour and 12.5:1 against the bar. Nothing else about the pill changes. */
[data-desk-principal="1"] .n9dw-bar .n9dw-lang.is-on { background: #f2c94c; border-color: #f2c94c; color: #0b0f14; }

/* ---- the Principal's own message is a BLACK bubble with WHITE text (owner, 20 Oct 2026 22:1x SAST) ----
   Deliberately NOT scoped to [data-desk-principal]: the message keeps its identity after the chat has gone back to
   the front desk, because it was the Principal who said it. The bubble class is set from the payload's per-row
   marker (`principal`), which only reply_public_support_message() writes, and only on a Principal thread. */
.n9dw-row.is-principal .n9dw-bubble { background: #0b0f14; color: #ffffff; }
.n9dw-row.is-principal .n9dw-who { color: #0b0f14; font-weight: 600; }

/* ---- THE PRINCIPAL'S ROOM, ON HIS OWN PAGE (owner, 22 Oct 2026 22:0x SAST) ----
   Verbatim: "That page is the principals page. So the chat system there should say, Chat with the Principal. ANd it
   should be black. The Principal chats in black not gold as the other one.  And his chats come back as black bubble
   and white text and the user only in black text with gold bubble."
   The black bar and the black Principal bubble already ship above; what was missing is the launcher (still WhatsApp
   green with "Chat with us" on the Principal's page) and the family's own words (white bubble, green rule, green text).
   Both are keyed off the same `data-desk-principal` attribute the script sets on that one page, so nothing here has to
   know which page it is on. Gold is the estate's #f2c94c, black its #0b0f14 — the same two the bar already uses. */
.n9-wa.n9-wa--principal { background: #0b0f14; color: #ffffff; }
.n9-wa.n9-wa--principal:hover, .n9-wa.n9-wa--principal:focus { filter: none; background: #1b232c; color: #ffffff; }
.n9-wa.n9-wa--principal:focus-visible { outline: 3px solid #f2c94c; outline-offset: 2px; }
[data-desk-principal="1"] .n9dw-row.is-me .n9dw-bubble { background: #f2c94c; border-color: #f2c94c; color: #0b0f14; }
[data-desk-principal="1"] .n9dw-row.is-me .n9dw-when, [data-desk-principal="1"] .n9dw-row.is-me .n9dw-who { color: #5b6470; }
/* The one green left in the room was the send button, sitting under a black bar and beside gold words. In the
   Principal's room it takes the room's own colour; the gold stays for the family's bubble and the focus ring, so a
   thumb still lands on something it can see. Nothing moves or resizes — colour only. */
[data-desk-principal="1"] .n9dw-send { background: #0b0f14; color: #ffffff; }
[data-desk-principal="1"] .n9dw-send:hover { background: #1b232c; }
[data-desk-principal="1"] .n9dw-send:focus-visible { outline: 3px solid #f2c94c; outline-offset: 2px; }

/* THE REFRESH BUTTON (owner, 23 Oct 2026 21:4x SAST). It borrows the back arrow's geometry — 32px, round,
   transparent, the estate's dark ink on the gold bar (11.35:1) — and adds only the two things a button that
   works needs: a hand while it is working, and a still frame for anyone who asked for less motion. */
.n9dw-refresh { margin-left: 2px; }
.n9dw-refresh.is-busy { cursor: progress; opacity: .55; }
.n9dw-refresh.is-busy svg { animation: n9dw-refresh-spin .9s linear infinite; }
@keyframes n9dw-refresh-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .n9dw-refresh.is-busy svg { animation: none; } }
/* On the Principal's black bar the button is his colour, like the language pills beside it. */
[data-desk-principal="1"] .n9dw-bar .n9dw-refresh { color: #f3f7fb; }
[data-desk-principal="1"] .n9dw-bar .n9dw-refresh:hover { background: #ffffff26; }
