/* ───────────────────────────────────────────────────────────────────────────
 * KSB catering-bot widget. Served ONLY when templates/layout.php renders the
 * widget, so it costs nothing on checkout, on the pay page, or while
 * bot_widget_live is absent.
 *
 * Brand per Craft: Cardo for the heading, Karla for everything else; brown
 * #4D2E18, yellow #FDBB15, cream #F8FAF2, tan #B7A98A. The app.css custom
 * properties (--ksb-brown etc.) are reused where they exist, with the literal
 * as the fallback so this file still renders correctly if loaded alone.
 *
 * ⚠ Z-INDEX, AND WHY THE TWO HALVES DIFFER. The closed launcher sits at 90 —
 * BELOW .product-sticky-bar (z-index 100, the mobile PDP's Add-to-cart) and
 * below .ksb-alert-overlay (100000) — so a sticky CTA always wins over a
 * bubble. The OPEN panel sits at 210, above the fixed .site-nav (200), because
 * a panel the nav overlaps is unusable. Closed yields; open is on top.
 *
 * ⚠ AND THE LAUNCHER IS RAISED ON MOBILE SO IT DOES NOT COVER A STICKY BAR.
 * Two full-width fixed bars exist at the bottom on narrow screens:
 * .product-sticky-bar on body.page-product (max-width 768px) and
 * .cart-sticky-bar on body.page-home (max-width 699px). The offsets below are
 * keyed to those two body classes rather than to a guessed global inset,
 * because on every other page there is no bar and a raised bubble would just
 * float oddly.
 * ─────────────────────────────────────────────────────────────────────────── */

.ksb-bot {
  font-family: 'Karla', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

/* ── the launcher ───────────────────────────────────────────────────────── */

.ksb-bot-launcher {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 90;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.7rem 1.1rem;
  border: 2px solid var(--ksb-brown, #4D2E18);
  border-radius: 999px;
  background: var(--ksb-yellow, #FDBB15);
  color: var(--ksb-brown, #4D2E18);
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 6px 20px rgba(45, 26, 13, 0.25);
}

.ksb-bot-launcher:hover { background: #ffc93f; }
.ksb-bot-launcher:focus-visible { outline: 3px solid var(--ksb-brown, #4D2E18); outline-offset: 2px; }

/* ── Mr Sor Hei, on the launcher and in the panel head ──────────────────────
 * Owner, 2026-10-09: "i also want to use mr sor hei as the mascot for the chat
 * widget". Scope is the LAUNCHER and the HEAD only — not an avatar beside
 * every reply.
 *
 * ⚠ DO NOT ADD `mix-blend-mode: darken` HERE, AND DO NOT "RESTORE" IT. The
 * brand fact (brand/Mr Sor Hei mascot conventions) says darken is mandatory on
 * the mascot — but the reason it gives is the published asset: /assets/img/
 * mr-sor-hei.jpg is a WHITE-BACKGROUND JPEG, and darken is the thing that
 * makes that white disappear on a cream ground. This file uses
 * mr-sor-hei-avatar.png, which carries REAL ALPHA, so there is no white
 * rectangle to hide and the convention's reason is simply absent. Applying it
 * anyway would be actively wrong on both grounds used here: darken keeps only
 * the pixels DARKER than what is behind them, so against the head's brown
 * (#4D2E18) and the launcher's yellow (#FDBB15) it would blacken the
 * illustration instead of revealing it.
 *
 * ⚠ AND NO IDLE ANIMATION. The brand fact documents a 2.4s ease-in-out
 * INFINITE bounce, which is right for a shop banner a visitor scrolls past
 * once. This launcher is fixed in the corner of EVERY page of the site, and a
 * thing that moves forever there is a different decision — so there is no
 * animation on either avatar at all, and nothing to need a
 * prefers-reduced-motion guard.
 *
 * ⚠ THE TWO PLACEMENTS GET DIFFERENT TREATMENTS, AND THAT IS A RENDERED
 * FINDING, NOT A PREFERENCE. An earlier version of this file put both at 24px
 * with no disc. The controller rendered that against the real colours on
 * 2026-10-09 and found:
 *
 *   LAUNCHER (yellow #FDBB15) — Mr Sor Hei's hood is GOLDEN, so at 24px on the
 *   brand yellow he is a smudge: only the grey face and the dark outline carry
 *   him and the hood merges into the pill with no edge. The brand fact's
 *   "shop banner 24px" is a size for the mascot on CREAM, not on the yellow.
 *   HEADER (brown #4D2E18) — excellent with no help at all; gold and grey both
 *   separate cleanly.
 *
 * So: the launcher gets 36px ON A CREAM DISC, and the header gets 28px PLAIN.
 * One size did not fit because the two grounds are not the same problem.
 *
 * ⚠ THE DISC IS DRAWN IN CSS, NOT BAKED INTO A NEW IMAGE. background +
 * border-radius: 50% on the <img> itself. A second asset would be a second
 * thing to keep in step with the first, and it would put the disc inside the
 * file that the header placement must NOT have one from.
 *
 * ⚠ AND THE DISC MAKES THE 404 GUARD STRONGER THAN IT WAS, not weaker: the
 * cream circle is painted by this rule, so a missing or broken image leaves a
 * correctly sized cream disc on the yellow pill rather than nothing. The
 * explicit box is still what stops the pill collapsing — which matters most
 * under 768px, where `.ksb-bot-launcher-text` is display:none and the picture
 * is the launcher's only content.
 *
 * ⚠ THE INSET IS ABSOLUTE AND NOT A PERCENTAGE, AND THIS IS A REAL CSS TRAP
 * RATHER THAN A STYLE CHOICE. `padding: 10%` on an <img> resolves against the
 * CONTAINING BLOCK's width — here `.ksb-bot-launcher`'s content box, which is
 * WIDER on desktop (it holds the "Chat" label) than under 768px (where the
 * label is display:none). A percentage inset would therefore give a different
 * disc on a phone than on a desktop, from one rule. 3.6px is 10% of 36px on
 * both sides, so the mascot's content box is 36 − 7.2 = 28.8px, i.e. 80% of
 * the disc — the proportion the controller compared: at a smaller inset the bow
 * tie spills over the disc edge and reads untidy, at a larger one the disc is
 * visibly empty around him. Sub-pixel padding rounds per browser, so treat 80%
 * as "about 80%".
 *
 * ⚠ SIZES AND WHERE THEY COME FROM. 36px is the brand fact's own prize-row
 * size, so the launcher is not an invented number. 28px is NOT in that family
 * (44px / 36px / 24px) — it is the controller's rendered measurement on the
 * brown, and it is recorded here as such rather than dressed up as a family
 * member. The source file is 144px, so both are covered to 4× DPR (36px) and
 * better than 5× (28px).
 *
 * ⚠ NEITHER PLACEMENT CARRIES `mix-blend-mode` OR ANY ANIMATION — see the two
 * paragraphs above; the bans apply to BOTH rules, and the disc belongs to the
 * launcher rule ONLY. BotWidgetCopyTest pins all three of those: no blend and
 * no motion in either rule, a disc in the launcher's, and no disc in the
 * header's. So a later "tidy-up" can neither apply the disc to both placements
 * nor drop it from the launcher.
 *
 * ⚠ EVERY LAYOUT CLAIM IN THIS COMMENT IS COMPUTED FROM THIS STYLESHEET OR
 * MEASURED FROM AN OFFLINE COMPOSITE — NOT FROM A BROWSER. PHPUnit never
 * executes the template and there is no JS runner in this repo. The launcher's
 * NEW HEIGHT and the disc's alignment at phone width are owed visual checks in
 * their own right, named in the task report alongside the other six.
 */
.ksb-bot-launcher-avatar {
  flex: 0 0 auto;
  display: block;
  /* border-box so the 36px is the DISC, inset included — otherwise the padding
     below would make the circle 43.2px and grow the pill further. */
  box-sizing: border-box;
  width: 36px;
  height: 36px;
  padding: 3.6px;
  background: var(--ksb-cream, #F8FAF2);
  border-radius: 50%;
}

.ksb-bot-head-avatar {
  flex: 0 0 auto;
  display: block;
  width: 28px;
  height: 28px;
}

@media (max-width: 768px) {
  .ksb-bot-launcher { padding: 0.65rem 0.75rem; }
  .ksb-bot-launcher-text { display: none; }
  /* clears .product-sticky-bar (fixed, bottom 0, ~96-110px tall) */
  body.page-product .ksb-bot-launcher { bottom: 7.5rem; }
}

@media (max-width: 699px) {
  /* clears .cart-sticky-bar (fixed, bottom 0, ~46px tall) */
  body.page-home .ksb-bot-launcher { bottom: 4.25rem; }
}

/* ── the panel ──────────────────────────────────────────────────────────── */

.ksb-bot-panel {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 210;
  width: 370px;
  max-width: calc(100vw - 2rem);
  height: min(560px, calc(100vh - 2rem));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ksb-cream, #F8FAF2);
  border: 2px solid var(--ksb-brown, #4D2E18);
  border-radius: 14px;
  box-shadow: 0 14px 44px rgba(45, 26, 13, 0.3);
}

/* Mobile first in effect rather than in source order: the narrow screen gets a
   bottom sheet that is full width and does not have to be aimed at. */
@media (max-width: 480px) {
  .ksb-bot-panel {
    right: 0;
    left: 0;
    bottom: 0;
    width: auto;
    max-width: none;
    height: min(82vh, calc(100vh - 3.5rem));
    border-radius: 14px 14px 0 0;
    border-bottom: none;
  }
}

.ksb-bot-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.7rem 0.9rem;
  background: var(--ksb-brown, #4D2E18);
  color: #fff;
}

/* The avatar + title pair. It exists so `.ksb-bot-head` keeps exactly TWO
   children: that rule is `justify-content: space-between`, so a third direct
   child would be spread across the bar and the title would drift away from its
   own avatar. `min-width: 0` lets the pair shrink rather than push the close
   button off the edge of the narrow bottom-sheet panel. */
.ksb-bot-head-id {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
}

.ksb-bot-title {
  font-family: 'Cardo', Georgia, serif;
  font-size: 1.02rem;
  font-weight: 700;
}

.ksb-bot-close {
  background: none;
  border: none;
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  padding: 0 0.25rem;
  cursor: pointer;
}
.ksb-bot-close:focus-visible { outline: 2px solid var(--ksb-yellow, #FDBB15); outline-offset: 2px; }

/* ── transcript ─────────────────────────────────────────────────────────── */

.ksb-bot-transcript {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ksb-text, #1A1A1A);
}

.ksb-bot-entry {
  max-width: 85%;
  padding: 0.55rem 0.75rem;
  border-radius: 12px;
  overflow-wrap: anywhere;
}

.ksb-bot-entry--bot {
  align-self: flex-start;
  background: #fff;
  border: 1px solid var(--ksb-tan, #B7A98A);
  border-bottom-left-radius: 4px;
}

.ksb-bot-entry--you {
  align-self: flex-end;
  background: var(--ksb-yellow, #FDBB15);
  color: var(--ksb-brown, #4D2E18);
  font-weight: 600;
  border-bottom-right-radius: 4px;
}

.ksb-bot-walink {
  color: var(--ksb-brown, #4D2E18);
  font-weight: 700;
  text-decoration: underline;
  /* The handover deep link is the only exit to a human on this surface, so it
     is a tap target rather than a word: a full line, comfortably hittable. */
  display: inline-block;
  padding: 0.2rem 0;
  min-height: 1.6rem;
}

/* ── typing indicator (§4.2) ────────────────────────────────────────────── */

.ksb-bot-typing { display: flex; align-items: center; gap: 0.3rem; }

.ksb-bot-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ksb-tan, #B7A98A);
  animation: ksbBotDot 1.1s infinite ease-in-out;
}
.ksb-bot-dot:nth-child(2) { animation-delay: 0.16s; }
.ksb-bot-dot:nth-child(3) { animation-delay: 0.32s; }

@keyframes ksbBotDot {
  0%, 70%, 100% { opacity: 0.3; transform: translateY(0); }
  35%           { opacity: 1;   transform: translateY(-3px); }
}

@media (prefers-reduced-motion: reduce) {
  .ksb-bot-dot { animation: none; opacity: 0.6; }
}

.ksb-bot-error {
  align-self: stretch;
  background: #FEF2F2;
  border: 1px solid #FECACA;
  color: #991B1B;
  border-radius: 8px;
  padding: 0.5rem 0.7rem;
  font-size: 0.85rem;
}

.ksb-bot-retry {
  margin-top: 0.4rem;
  display: block;
  background: #991B1B;
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 0.35rem 0.8rem;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}

/* ── buttons ────────────────────────────────────────────────────────────── */

/* ⚠ THE CHIP BLOCK MUST BE ABLE TO GIVE WAY, and before the eleven-option menu
   it could not. It is a SIBLING of the scrolling transcript, not inside it, so
   with `flex: 0 0 auto` and no overflow it took its full height out of the
   transcript — and once the panel ran out of room, `overflow: hidden` on
   .ksb-bot-panel pushed the composer out of sight entirely: no buttons AND no
   way to type. Ten chips wrap to roughly six rows (~246px) at the 370px panel
   width, which does not fit a short viewport (a landscape phone's ~307px panel,
   less head, exit link and composer, leaves nothing).

   So: it may shrink, it is capped at a share of the panel, and it scrolls
   inside itself. The transcript keeps its own scroll. This is what lets the
   menu stay FLAT — one tap to any option — instead of growing a sub-menu. */
.ksb-bot-buttons {
  flex: 0 1 auto;
  max-height: 40%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.6rem 0.9rem 0;
}

.ksb-bot-chip {
  background: #fff;
  color: var(--ksb-brown, #4D2E18);
  border: 2px solid var(--ksb-brown, #4D2E18);
  border-radius: 999px;
  padding: 0.42rem 0.85rem;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.ksb-bot-chip:hover:not(:disabled) { background: var(--ksb-yellow, #FDBB15); }
.ksb-bot-chip:disabled { opacity: 0.5; cursor: default; }

/* §10.1.1 "visible, not pushed": the exit is present on every step and is
   never the control the eye lands on first. A quiet underlined link, not a
   chip competing with the flow's answers. */
.ksb-bot-human { flex: 0 0 auto; padding: 0.5rem 0.9rem 0; }

.ksb-bot-humanlink {
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  font-size: 0.8rem;
  color: var(--ksb-muted, #6B6B6B);
  text-decoration: underline;
  cursor: pointer;
}
.ksb-bot-humanlink:hover:not(:disabled) { color: var(--ksb-brown, #4D2E18); }
.ksb-bot-humanlink:disabled { opacity: 0.5; cursor: default; }

/* ── composer ───────────────────────────────────────────────────────────── */

.ksb-bot-compose {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.7rem 0.9rem 0.9rem;
  position: relative;
}

.ksb-bot-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--ksb-tan, #B7A98A);
  border-radius: 8px;
  background: #fff;
  font-family: inherit;
  /* 16px minimum: anything smaller makes iOS Safari zoom the page on focus. */
  font-size: 1rem;
  color: var(--ksb-text, #1A1A1A);
}
.ksb-bot-input:focus { outline: 2px solid var(--ksb-brown, #4D2E18); outline-offset: 0; }

.ksb-bot-send {
  flex: 0 0 auto;
  background: var(--ksb-brown, #4D2E18);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 0.55rem 0.9rem;
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 700;
  cursor: pointer;
}
.ksb-bot-send:disabled { opacity: 0.45; cursor: default; }

/* ── the honeypot, and the screen-reader-only class ─────────────────────── */

/* Positioned off-screen rather than display:none or hidden: a bot that fills
   "every visible input" still finds it, while aria-hidden + tabindex=-1 in the
   markup keep it out of the accessibility tree and the tab order. */
.ksb-bot-hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px !important;
  height: 1px !important;
  opacity: 0 !important;
  pointer-events: none !important;
}

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