/* ------------------------------------------------------------------------- *
 * Word Buddy — the conversational guide overlay.
 *
 * Everything is scoped under #wb-* so the whole feature can be removed by
 * deleting three lines from master.blade.php and these files.
 *
 * Two rules shape the layout and must not be undone:
 *   1. No backdrop and no scroll lock. The learner must be able to reach every
 *      part of the page underneath — on a quiz that means all four answers.
 *   2. z-index stays below Bootstrap's modal layer (1050+), so the Sentences
 *      and Meaning modals still open over the panel.
 * ------------------------------------------------------------------------- */

:root {
  --wb-purple: #6b4c9a;
  --wb-purple-dark: #563d7d;
  --wb-ink: #212529;
  --wb-muted: #6c757d;
  --wb-line: #e6e2ee;
  --wb-bg: #ffffff;
  --wb-surface: #f7f5fb;
  --wb-z: 1035;
}

/* --- Launcher ------------------------------------------------------------ */

/* Bottom-left everywhere. That also keeps it clear of the quiz's Next button,
   which sits bottom-right. */
/* The corner object: pill plus its Start/Stop button, positioned as one and
   dragged as one.

   The padding is not decoration — it is what makes the dock's box include the
   button perched on the pill's corner. Without it the button hangs outside the
   box the drag clamps against, and could be pushed off the top of the screen
   while the pill still looked fully on it. */
#wb-dock {
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: var(--wb-z);
  padding: 13px 9px 0 0;

  /* A finger dragging this must move it, not scroll the page behind it.
     Without this the browser claims the gesture and the dock never moves on a
     phone — which is the device the dragging is mostly for. */
  touch-action: none;
}

#wb-dock[hidden] { display: none; }
#wb-dock.wb-grabbing { cursor: grabbing; }

#wb-launcher {
  position: static;           /* the dock places it now */
  display: inline-flex;
  align-items: center;
  gap: 10px;
  max-width: calc(100vw - 48px);
  /* The right padding is the button's seat. Without it the pill's own words
     run underneath Start and the last of them cannot be read. */
  padding: 13px 58px 13px 17px;
  border: none;
  border-radius: 999px;
  background: var(--wb-purple);
  color: #fff;
  font-weight: 700;
  font-size: 15.5px;
  line-height: 1.25;
  text-align: left;
  box-shadow: 0 5px 18px rgba(0, 0, 0, .28);
  cursor: pointer;
}

#wb-launcher:hover { background: var(--wb-purple-dark); }
#wb-launcher:focus-visible { outline: 3px solid #ffc107; outline-offset: 2px; }
#wb-launcher[hidden] { display: none; }

/* No extra body padding on quiz pages. It was tried, to guarantee clearance
   under the launcher, and it backfired: it let the page scroll 80px past its
   content, which pushed the first answer up underneath ColWords' own sticky
   mobile navbar. The footer already leaves every option clear of the launcher
   at full scroll, so the padding bought nothing. word-buddy.js still pads the
   body while the sheet is open, which is the case that genuinely needs it. */

#wb-launcher .wb-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ffc107;
  flex: 0 0 auto;
  align-self: center;
}

/* Two lines: the invitation, then what Buddy actually is. The subtitle is what
   tells a first-time visitor this guides ColWords rather than replacing it. */
.wb-launcher-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  min-width: 0;
}

.wb-launcher-text strong {
  font-size: 15.5px;
  font-weight: 700;
  line-height: 1.2;
}

.wb-launcher-text small {
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.2;
  opacity: .85;
}

/* Why voice stopped, rather than what it is doing. Given its own weight
   because it is the only explanation on screen at that moment — the full
   sentence is in the panel, which is shut. Amber rather than red: nothing has
   gone wrong, a free allowance has simply run out. */
.wb-launcher-text small.wb-launcher-alert {
  color: #ffd54a;
  font-weight: 700;
  opacity: 1;
  white-space: normal;
}

/* --- Panel --------------------------------------------------------------- */

/* Opens from the same corner as the launcher it replaces, so the panel appears
   where the learner just clicked rather than jumping across the screen. */
#wb-panel {
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: var(--wb-z);
  display: flex;
  flex-direction: column;
  width: 380px;
  max-width: calc(100vw - 32px);
  max-height: min(70vh, 560px);
  background: var(--wb-bg);
  border: 1px solid var(--wb-line);
  border-radius: 14px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .28);
  overflow: hidden;
  font-size: 14px;
  color: var(--wb-ink);
}

#wb-panel[hidden] { display: none; }

#wb-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: var(--wb-purple);
  color: #fff;
  flex: 0 0 auto;
}

/* --- Moving the panel ----------------------------------------------------- *
 *
 * The purple bar is the handle. Desktop only: on a phone the panel is a bottom
 * sheet pinned across the width of the screen, where there is nowhere to move
 * it to and a drag would fight the page's own scrolling.
 */

@media (min-width: 768px) {
  #wb-head {
    cursor: grab;

    /* The header carries no selectable content worth having, and a drag that
       paints a text selection behind it looks broken. */
    user-select: none;
    -webkit-user-select: none;

    /* Stops a touch-drag on the handle from scrolling the page underneath. */
    touch-action: none;
  }

  #wb-head.wb-grabbing { cursor: grabbing; }

  /* The close button is a button, not part of the handle. */
  #wb-head button { cursor: pointer; }
}

/* --- Resizing ------------------------------------------------------------- *
 *
 * Eight strips inside the panel's edge. Hidden entirely below the desktop
 * breakpoint, where the panel is a full-width bottom sheet and there is no
 * meaningful size for a learner to choose.
 */

.wb-resize { display: none; }

@media (min-width: 768px) {
  .wb-resize {
    display: block;
    position: absolute;
    z-index: 2;          /* over the header and the composer, under nothing */
    touch-action: none;  /* a drag here must not scroll the page */
  }

  /* Corners are larger than edges and are declared after them below, so where
     the two overlap the corner wins — aiming for a corner is harder, so it
     gets the bigger target. */
  .wb-resize-n, .wb-resize-s { left: 0; right: 0; height: 6px; }
  .wb-resize-e, .wb-resize-w { top: 0; bottom: 0; width: 6px; }

  .wb-resize-n { top: 0;    cursor: ns-resize; }
  .wb-resize-s { bottom: 0; cursor: ns-resize; }
  .wb-resize-e { right: 0;  cursor: ew-resize; }
  .wb-resize-w { left: 0;   cursor: ew-resize; }

  .wb-resize-ne, .wb-resize-nw, .wb-resize-se, .wb-resize-sw {
    width: 14px;
    height: 14px;
  }

  .wb-resize-nw { top: 0;    left: 0;   cursor: nwse-resize; }
  .wb-resize-se { bottom: 0; right: 0;  cursor: nwse-resize; }
  .wb-resize-ne { top: 0;    right: 0;  cursor: nesw-resize; }
  .wb-resize-sw { bottom: 0; left: 0;   cursor: nesw-resize; }

  /* While a resize is in progress the pointer is captured, so the cursor must
     not flicker as it crosses the content underneath. */
  #wb-panel.wb-resizing, #wb-panel.wb-resizing * { cursor: inherit !important; }
}

#wb-head strong { font-size: 15px; }

#wb-head .wb-sub {
  font-size: 11px;
  opacity: .85;
  font-weight: 400;
}

/* The right-hand end of the header. This claims the free space so the × is in
   the upper-right corner whatever sits beside it — the rule used to live on
   the × itself, and moved once already when a neighbouring button was
   removed. */
#wb-head-tools {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

/* --- Segmented choices --------------------------------------------------- *
 *
 * Both options shown at once with the current one filled in, used for Buddy's
 * voice in the header and for voice on/off in the composer.
 *
 * Deliberately not a slider. A slider states one end and leaves the other to
 * be inferred — you have to know which side means what before you can read it
 * — and the same shape had to serve "which voice" as well, where there is no
 * on and off to slide between. One idea of what a choice looks like, in two
 * colourways for the two backgrounds it sits on.
 */
.wb-seg {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  overflow: hidden;
  flex: 0 1 auto;
  min-width: 0;
}

.wb-seg[hidden] { display: none; }

.wb-seg > button {
  border: none;
  background: transparent;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  padding: 5px 9px;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* The chosen one is told three ways at once — filled, contrasting text,
   heavier — because a single cue is easy to miss at this size. */
.wb-seg > button[aria-pressed="true"] { font-weight: 800; }

/* The voice choice keeps its symbols and drops its words, at every size.
   Not a phone concession: the panel is about 380px wide on a desktop too, and
   with a title, "Give feedback" and the × beside it the words did not fit
   there either — "Female" was clipped mid-letter. A viewport media query was
   the wrong lever for a box whose width barely changes.

   ♂ and ♀ read at a glance, both buttons carry a full aria-label, and each has
   a title for a pointer, so nothing is lost with the words. */
#wb-voice-gender .wb-gender-text { display: none; }
#wb-voice-gender .wb-gender { padding: 5px 12px; font-size: 14px; }

/* On the purple header. */
.wb-seg-light { border: 1px solid rgba(255, 255, 255, .55); }
.wb-seg-light > button { color: #fff; opacity: .72; }
.wb-seg-light > button + button { border-left: 1px solid rgba(255, 255, 255, .35); }
.wb-seg-light > button:hover { opacity: 1; }

.wb-seg-light > button[aria-pressed="true"] {
  background: #fff;
  color: var(--wb-purple);
  opacity: 1;
}

.wb-seg-light > button:focus-visible { outline: 2px solid #ffc107; outline-offset: -2px; }

/* On the white composer. */
.wb-seg-dark { border: 1px solid var(--wb-line); background: #fff; }
.wb-seg-dark > button { color: var(--wb-muted); }
.wb-seg-dark > button + button { border-left: 1px solid var(--wb-line); }
.wb-seg-dark > button:hover { color: var(--wb-purple); }

.wb-seg-dark > button[aria-pressed="true"] {
  background: var(--wb-purple);
  color: #fff;
}

.wb-seg-dark > button:focus-visible { outline: 2px solid var(--wb-purple); outline-offset: -2px; }

/* The word that says what the choice is about. Inside the pill, before the
   options, so "Voice" and "On/Off" read as one control rather than a stray
   label next to two buttons. */
.wb-seg-label {
  padding: 0 7px 0 10px;
  font-size: 11.5px;
  font-weight: 800;
  white-space: nowrap;
}

/* Purple on the white composer, so the word carries as much weight as the
   choice beside it. Scoped to the colourway rather than set on .wb-seg-label
   itself: the same purple on the purple header would be invisible. */
.wb-seg-dark .wb-seg-label { color: var(--wb-purple); }
.wb-seg-light .wb-seg-label { color: #fff; }

/* An offer, not a shout: outlined and quiet, because it sits in the header of
   something a learner opened to do a different job. */
#wb-feedback-open {
  flex: 0 0 auto;
  border: 1px solid rgba(255, 255, 255, .55);
  border-radius: 999px;
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  padding: 5px 9px;
  white-space: nowrap;
  cursor: pointer;
  opacity: .85;
}

#wb-feedback-open:hover { opacity: 1; background: rgba(255, 255, 255, .14); }
#wb-feedback-open:focus-visible { outline: 2px solid #ffc107; outline-offset: 1px; }

/* --- The feedback form --------------------------------------------------- */

#wb-feedback {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  background: var(--wb-surface);
}

#wb-feedback[hidden] { display: none; }

#wb-feedback label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--wb-muted);
  line-height: 1.35;
}

#wb-feedback textarea,
#wb-feedback input {
  width: 100%;
  border: 1px solid var(--wb-line);
  border-radius: 10px;
  padding: 9px 11px;
  font: inherit;
  font-size: 13.5px;
  color: var(--wb-ink);
  background: #fff;
  resize: vertical;
}

#wb-feedback textarea:focus,
#wb-feedback input:focus { outline: 2px solid var(--wb-purple); outline-offset: -1px; }
#wb-feedback input[hidden] { display: none; }

#wb-feedback-note {
  margin: 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--wb-purple);
}

#wb-feedback-note[hidden] { display: none; }
#wb-feedback-note.wb-feedback-wrong { color: #96222a; }

.wb-feedback-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.wb-feedback-actions button {
  border-radius: 999px;
  padding: 8px 16px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}

#wb-feedback-cancel {
  border: 1px solid var(--wb-line);
  background: #fff;
  color: var(--wb-muted);
}

#wb-feedback-cancel:hover { color: var(--wb-ink); }

#wb-feedback-send {
  border: none;
  background: var(--wb-purple);
  color: #fff;
}

#wb-feedback-send:hover { background: var(--wb-purple-dark); }
#wb-feedback-send[disabled] { opacity: .55; cursor: default; }

#wb-close {
  border: none;
  background: transparent;
  color: #fff;
  font-size: 26px;
  line-height: 1;
  padding: 0 6px;
  cursor: pointer;
}

#wb-close:focus-visible { outline: 2px solid #ffc107; }

/* --- Minimised, and still talking ---------------------------------------- *
 *
 * Collapsed to the launcher, Buddy carries on speaking and listening. That
 * makes the launcher the consent surface the status bar is while the panel is
 * open (section 16): it has to say what is happening, and it has to be
 * stoppable without reopening anything.
 */

body.wb-mini #wb-launcher {
  /* Nothing to invite here — the conversation is already running — so the idle
     shine would be noise, and the animation is stopped rather than merely
     hidden behind the pill. */
  animation: none;
}

/* Red only while Buddy is actually running. Stopped, it keeps the ordinary
   amber: a red dot on a Word Buddy that is doing nothing reads as a recording
   light, which is the one thing this indicator must never say falsely. */
body.wb-mini.wb-talking #wb-launcher .wb-dot {
  background: #ff5252;
}

/* Alive, and unmistakably so. Only while the microphone is actually open —
   a dot that pulses the whole time Buddy is minimised would say "recording"
   during the stretches when nothing is. */
body.wb-mini.wb-hearing #wb-launcher .wb-dot {
  animation: wb-pulse 1.1s ease-in-out infinite;
}

@keyframes wb-pulse {
  0%, 100% { transform: scale(1);   opacity: 1; }
  50%      { transform: scale(1.6); opacity: .55; }
}

/* The one button in small mode. Start when Buddy is quiet, Stop while it is
   running — the same control either way, because there is only ever one thing
   to do to a conversation that is or is not happening. Colour carries the
   difference: green invites, red halts. */
#wb-mini-toggle {
  /* Sat on the pill's top-right corner, half on and half off it, the way a
     badge sits on a thing rather than beside it. */
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1;
  border: 2px solid #fff;
  border-radius: 999px;
  padding: 4px 14px;
  /* ColWords' own blue — the same #0d6efd the "Order: On" button uses, rather
     than a new one invented here, so Buddy's Start reads as part of the site. */
  background: #0d6efd;
  color: #fff;
  font-weight: 700;
  font-size: 13px;
  line-height: 1.2;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .3);
  cursor: pointer;
}

#wb-mini-toggle[hidden] { display: none; }
#wb-mini-toggle:hover { background: #0b5ed7; }
#wb-mini-toggle:focus-visible { outline: 3px solid #ffc107; outline-offset: 2px; }

#wb-mini-toggle.wb-running { background: #ff5252; }
#wb-mini-toggle.wb-running:hover { background: #e04848; }

@media (prefers-reduced-motion: reduce) {
  body.wb-mini.wb-hearing #wb-launcher .wb-dot { animation: none; }
}

/* --- Voice ---------------------------------------------------------------- *
 *
 * The status bar is the consent surface: while the microphone is live it is
 * visible and animated, so a learner can never be recorded without seeing it.
 */

/* Voice on or off — the single control for the whole conversation mode, and
   the one the Stop button on the launcher shares. Never allowed to shrink:
   squeezed by a long placeholder it would be the first thing to lose width,
   and a half-width On/Off is unreadable. */
#wb-voice-power { flex: 0 0 auto; }

/* Advanced setup only: with automatic listening off, a microphone button sits
   in this row too. At 350px that leaves the message box too narrow for its own
   placeholder, so the control drops its word — the microphone beside it already
   says what the row is about, and a clipped placeholder looks broken in a way a
   bare On/Off does not. */
#wb-form.wb-compact #wb-voice-power .wb-seg-label { display: none; }

/* §32: while the microphone is genuinely open the status bar breathes, so the
   learner can see they are being heard without reading anything. */
#wb-voice-state.wb-hearing #wb-voice-wave i {
  animation: wb-hear 1s ease-in-out infinite;
}

#wb-voice-state.wb-hearing #wb-voice-wave i:nth-child(2) { animation-delay: .12s; }
#wb-voice-state.wb-hearing #wb-voice-wave i:nth-child(3) { animation-delay: .24s; }
#wb-voice-state.wb-hearing #wb-voice-wave i:nth-child(4) { animation-delay: .36s; }
#wb-voice-state.wb-hearing #wb-voice-wave i:nth-child(5) { animation-delay: .48s; }

@keyframes wb-hear {
  0%, 100% { transform: scaleY(.4); }
  50%      { transform: scaleY(1); }
}

@media (prefers-reduced-motion: reduce) {
  #wb-voice-state.wb-hearing #wb-voice-wave i { animation: none; }
}

#wb-talk,
#wb-mic {
  flex: 0 0 auto;
  border: 1px solid var(--wb-line);
  border-radius: 999px;
  background: #fff;
  font-size: 14px;
  line-height: 1;
  /* Deliberately tight. These only ever appear alongside the Voice switch, and
     at 350px the message box is what suffers for every pixel they take — a
     wrapped, half-clipped placeholder looks broken in a way a small icon does
     not. */
  padding: 7px 8px;
  cursor: pointer;
}

#wb-talk:hover,
#wb-mic:hover { border-color: var(--wb-purple); }

/* The way back into the conversation, so it is worth seeing. Purple rather
   than the plain outline the Talk button carries: it only appears when Buddy
   has stopped listening, and at that moment it is the one thing on the row
   the learner needs. */
#wb-mic {
  border-color: var(--wb-purple);
  color: var(--wb-purple);
  font-weight: 700;
  font-size: 12.5px;
  padding: 6px 11px;
  white-space: nowrap;
}
#wb-talk:focus-visible,
#wb-mic:focus-visible { outline: 2px solid var(--wb-purple); outline-offset: 1px; }
#wb-talk[hidden],
#wb-mic[hidden] { display: none; }

#wb-mic.wb-mic-live {
  background: #e8323c;
  border-color: #e8323c;

  /* The button carries a word now, and purple text on red is unreadable. Only
     ever shows for the moment between tapping Listen and the button hiding
     itself, but a moment is long enough to look broken. */
  color: #fff;
}

/* A live conversation is a state the learner should never be unsure about. */
#wb-talk.wb-on {
  background: var(--wb-purple);
  border-color: var(--wb-purple);
}

#wb-voice-state {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  background: #efe9f7;
  border-bottom: 1px solid var(--wb-line);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--wb-purple-dark);
  flex: 0 0 auto;
}

#wb-voice-state[hidden] { display: none; }
#wb-voice-state.wb-voice-error { background: #fff6d9; color: #6b5300; font-weight: 500; }
#wb-voice-state.wb-voice-listening { background: #fdeaec; color: #96222a; }

#wb-voice-label { flex: 1 1 auto; }

/* Silences Buddy mid-sentence. Outlined rather than filled: it is an escape
   hatch from a long answer, not something to be drawn towards. */
#wb-voice-stop {
  flex: 0 0 auto;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 11.5px;
  padding: 3px 10px;
  cursor: pointer;
}

#wb-voice-stop[hidden] { display: none; }

/* Five bars that only move while the microphone is actually open. */
#wb-voice-wave {
  display: none;
  align-items: flex-end;
  gap: 2px;
  height: 14px;
  flex: 0 0 auto;
}

#wb-voice-wave i {
  display: block;
  width: 3px;
  height: 4px;
  border-radius: 2px;
  background: currentColor;
}

.wb-voice-listening #wb-voice-wave,
.wb-voice-speaking #wb-voice-wave { display: flex; }

@media (prefers-reduced-motion: no-preference) {
  .wb-voice-listening #wb-voice-wave i,
  .wb-voice-speaking #wb-voice-wave i {
    animation: wb-wave .9s ease-in-out infinite;
  }

  #wb-voice-wave i:nth-child(2) { animation-delay: .12s; }
  #wb-voice-wave i:nth-child(3) { animation-delay: .24s; }
  #wb-voice-wave i:nth-child(4) { animation-delay: .36s; }
  #wb-voice-wave i:nth-child(5) { animation-delay: .48s; }

  @keyframes wb-wave {
    0%, 100% { height: 4px; }
    50%      { height: 14px; }
  }
}

/* --- A marked sentence --------------------------------------------------- */

.wb-mark {
  border-left: 3px solid #d4a72c;
}

.wb-mark.wb-mark-pass { border-left-color: #2e9e5b; }

.wb-score {
  margin-bottom: 5px;
  font-weight: 700;
  color: #8a6d00;
}

.wb-mark-pass .wb-score { color: #1f7a44; }

.wb-improved {
  margin: 7px 0 0;
  padding: 7px 9px;
  background: var(--wb-surface);
  border-radius: 8px;
  font-style: italic;
}

/* A practice invitation arrived while the panel was shut. */
#wb-launcher.wb-nudge .wb-dot {
  background: #fff;
  box-shadow: 0 0 0 3px rgba(255, 193, 7, .55);
}

#wb-log {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 12px;
  background: var(--wb-surface);
}

/* --- Messages ------------------------------------------------------------ */

.wb-msg {
  max-width: 88%;
  margin-bottom: 10px;
  padding: 9px 12px;
  border-radius: 12px;
  line-height: 1.45;
  word-wrap: break-word;
  overflow-wrap: anywhere;
}

.wb-msg p { margin: 0 0 .5em; }
.wb-msg p:last-child { margin-bottom: 0; }
.wb-msg ul { margin: .35em 0 0; padding-left: 1.15em; }
.wb-msg li { margin-bottom: .2em; }

.wb-buddy {
  background: #fff;
  border: 1px solid var(--wb-line);
  border-bottom-left-radius: 4px;
}

.wb-learner {
  margin-left: auto;
  background: var(--wb-purple);
  color: #fff;
  border-bottom-right-radius: 4px;
}

.wb-note {
  max-width: 100%;
  margin-bottom: 10px;
  padding: 8px 10px;
  border-radius: 8px;
  background: #fff6d9;
  border: 1px solid #ffe08a;
  color: #6b5300;
  font-size: 12.5px;
}

.wb-typing { color: var(--wb-muted); font-style: italic; }

/* --- Chips ---------------------------------------------------------------- *
 *
 * Only Practice Mode's "Skip this one" lands here now. The opening question
 * chips and the navigation links were removed: two rows of buttons under every
 * greeting made the panel tall enough to cover the words being studied.
 *
 * :empty collapsing is what keeps the row from reserving space when there is
 * nothing in it, which is most of the time.
 */

#wb-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 12px 8px;
  flex: 0 0 auto;
  background: var(--wb-surface);
}

#wb-chips:empty { display: none; }

/* Premium upsell: one quiet line above the composer, styled as a link rather
   than a button so it reads as an offer and not as the next thing to do. */
#wb-voice-upgrade {
  padding: 0 12px 8px;
  flex: 0 0 auto;
  background: var(--wb-surface);
}

#wb-voice-upgrade[hidden] { display: none; }

.wb-voice-upgrade-link {
  display: inline-block;
  font-size: 12px;
  line-height: 1.4;
  color: var(--wb-green);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.wb-voice-upgrade-link:hover,
.wb-voice-upgrade-link:focus-visible { color: var(--wb-green-dark, #04553f); }

.wb-chip {
  border: 1px solid var(--wb-purple);
  border-radius: 999px;
  padding: 5px 11px;
  background: #fff;
  color: var(--wb-purple);
  font-size: 12.5px;
  line-height: 1.2;
  cursor: pointer;
  text-decoration: none;
}

.wb-chip:hover { background: #efe9f7; }

/* --- Composer ------------------------------------------------------------ */

#wb-form {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 12px;
  border-top: 1px solid var(--wb-line);
  background: #fff;
  flex: 0 0 auto;
}

#wb-input {
  flex: 1 1 auto;
  min-width: 0;
  border: 1px solid var(--wb-line);
  border-radius: 999px;
  padding: 8px 13px;
  font-size: 14px;
  font-family: inherit;
  resize: none;
  max-height: 84px;
}

#wb-input:focus { outline: none; border-color: var(--wb-purple); }
#wb-input:disabled { background: #f1f1f1; }

#wb-send {
  flex: 0 0 auto;
  border: none;
  border-radius: 999px;
  padding: 8px 16px;
  background: var(--wb-purple);
  color: #fff;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
}

#wb-send:disabled { background: #b6a8ce; cursor: default; }

/* --- Phone: a bottom sheet, deliberately not a modal --------------------- *
 *
 * No backdrop, no focus trap, no body scroll lock — so the page underneath
 * keeps scrolling and every quiz answer can still be scrolled above the sheet
 * and tapped. word-buddy.js pads the body by the sheet's height to guarantee
 * the last option can clear it.
 */

@media (max-width: 767.98px) {
  #wb-panel {
    right: 0;
    left: 0;
    bottom: 0;
    width: auto;
    max-width: none;
    border-radius: 14px 14px 0 0;
    border-left: none;
    border-right: none;
    border-bottom: none;
    max-height: 60vh;
  }

  /* Smaller again during a quiz: the answers matter more than the chat. */
  body.wb-quiz #wb-panel { max-height: min(45vh, 300px); }

  /* Positioning belongs to the dock now — the launcher is a child of it, so
     left/bottom here would do nothing but mislead the next reader. */
  #wb-dock {
    left: 12px;
    bottom: 12px;
  }

  /* The header carries four things on a phone: a title, Give feedback, the
     voice choice and the ×. Together they overflowed — "Female" was clipped
     mid-word and the subtitle wrapped to two lines — so the two least load-
     bearing pieces give way here.

     The subtitle explains what Word Buddy is, which matters on a first meeting
     and not at all once the panel is open on a phone. The gender toggle keeps
     its symbols, which still read at a glance, and both buttons carry a full
     aria-label regardless, so nothing is lost to anyone who cannot see them. */
  #wb-head .wb-sub { display: none; }

  /* The label is long, so on a phone it is allowed to run to two lines rather
     than being truncated or pushed off-screen. The right padding is still the
     Start button's seat, and has to be repeated here or this shorthand would
     quietly take it away and the words would run underneath the button. */
  #wb-launcher {
    max-width: calc(100vw - 40px);
    padding: 10px 54px 10px 13px;
    font-size: 14px;
  }

  .wb-launcher-text strong { font-size: 14px; }
  .wb-launcher-text small  { font-size: 11px; }
}

@media (prefers-reduced-motion: no-preference) {
  #wb-panel { animation: wb-rise .16s ease-out; }
  @keyframes wb-rise {
    from { transform: translateY(8px); opacity: 0; }
    to   { transform: translateY(0);   opacity: 1; }
  }
}

/* --- Light sweep --------------------------------------------------------- *
 *
 * A slow diagonal reflection that slides across a surface. Drop .wb-shine on
 * any element that has its own background; it is purely additive and never
 * touches the host's colour, padding, radius, or hover/focus/active states.
 *
 * Tunable per element — override any of these on the host:
 *
 *   --wb-shine-sweep     how long one pass takes                    (1s)
 *   --wb-shine-rest      the quiet between passes                   (9s)
 *   --wb-shine-width     band width, as a share of the host's width (56%)
 *   --wb-shine-opacity   peak brightness at the band's centre       (.22)
 *   --wb-shine-tint      the light's colour, as "r, g, b"           (lavender)
 *   --wb-shine-skew      lean off vertical; positive tilts the top
 *                        edge to the right                          (19deg)
 *
 * e.g.  <button class="wb-shine" style="--wb-shine-opacity:.12">
 *
 * A quick pass followed by a long quiet reads as glass catching the light. A
 * slow or frequent one reads as a notification demanding to be dealt with —
 * which is the opposite of the point.
 */
.wb-shine {
  --wb-shine-sweep: 1s;
  --wb-shine-rest: 9s;
  --wb-shine-width: 56%;
  --wb-shine-opacity: .22;
  --wb-shine-tint: 240, 234, 255;
  --wb-shine-skew: 19deg;

  position: relative;
  overflow: hidden;   /* clips the band to the host's own border-radius */
  isolation: isolate; /* keeps the band's negative stack level inside the host */
}

.wb-shine::after {
  content: "";
  position: absolute;
  left: 0;
  /* Taller than the host so the skewed ends still cover the full height. */
  top: -50%;
  bottom: -50%;
  width: var(--wb-shine-width);

  /* Above the background, below the label and icon. A negative stack level
     paints over the host's own background but under its in-flow content, so
     text and icons stay crisp instead of washing out. This is why the host
     needs `isolation: isolate` — without it the band would slide behind. */
  z-index: -1;
  pointer-events: none;

  /* Feathered to fully transparent at both edges: no hard lines anywhere. The
     tint is a lavender-white rather than pure white, so over the purple it
     reads as reflected light rather than a grey wash. */
  background: linear-gradient(
    90deg,
    rgba(var(--wb-shine-tint), 0) 0%,
    rgba(var(--wb-shine-tint), calc(var(--wb-shine-opacity) * .45)) 30%,
    rgba(var(--wb-shine-tint), var(--wb-shine-opacity)) 50%,
    rgba(var(--wb-shine-tint), calc(var(--wb-shine-opacity) * .45)) 70%,
    rgba(var(--wb-shine-tint), 0) 100%
  );

  transform: translateX(-150%) skewX(calc(-1 * var(--wb-shine-skew)));
  animation: wb-shine-sweep calc(var(--wb-shine-sweep) + var(--wb-shine-rest)) ease-in-out infinite;
}

/* Transform only — no left, no background-position — so the band is composited
   on the GPU and never triggers layout or paint.

   The 10% split is a literal because CSS cannot compute keyframe offsets from a
   custom property. It is the 1s sweep of the default 10s loop; if you change the
   sweep-to-rest ratio, change this number to match. */
@keyframes wb-shine-sweep {
  0%   { transform: translateX(-150%) skewX(calc(-1 * var(--wb-shine-skew))); }
  10%  { transform: translateX(250%)  skewX(calc(-1 * var(--wb-shine-skew))); }
  100% { transform: translateX(250%)  skewX(calc(-1 * var(--wb-shine-skew))); }
}

/* Nothing to shine for while the learner is already talking to Buddy. Pausing
   rather than hiding means the compositor stops doing the work too. */
body.wb-open .wb-shine::after { animation-play-state: paused; }

/* One deliberate sweep, on demand. Removing this class restarts the idle
   animation from 0%, and its sweep is at the very start of the cycle — so the
   restart *is* the single pass, with the usual quiet after it. That avoids the
   double flash you get from layering a separate one-shot animation on top. */
.wb-shine.wb-shine-reset::after { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .wb-shine::after { display: none; }
}
