/* ------------------------------------------------------------------------- *
 * 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;
}

#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.
 */

/* Advanced setup only: with automatic listening off, a microphone button sits
   in this row too, and at 350px that leaves the message box too narrow for its
   own placeholder.

   This used to be answered by dropping the word from inside each button. That
   worked while there was one switch and "On | Off" could only mean one thing.
   With an Ear and a Voice side by side it would leave two identical pairs and
   no way to tell which was which, so the switches take a line of their own
   instead — see #wb-switches under .wb-stacked, which this now borrows. */
#wb-form.wb-compact #wb-switches { flex: 0 0 100%; }

/* §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;
}

/* "Less of this", on a coaching line.
 *
 * Deliberately quiet — it is a way out, not an invitation. It sits under the
 * encouragement it belongs to and takes no room anywhere else, which is why
 * this is a button on a bubble rather than another control in a header that
 * has already been fought over once. */
.wb-coach-less {
  display: block;
  margin: 8px 0 0;
  padding: 3px 8px;
  border: 1px solid var(--wb-line);
  border-radius: 999px;
  background: transparent;
  color: #6b6b6b;
  font-size: 11.5px;
  font-family: inherit;
  cursor: pointer;

  /* A real tap target on a phone without looking like one on a desktop. */
  min-height: 26px;
}

.wb-coach-less:hover { color: var(--wb-purple); border-color: var(--wb-purple); }
.wb-coach-less:disabled { opacity: .5; cursor: default; }

.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;
}

/* Ear and Voice travel together, and wrap together. Kept in one box so the
   row has three children rather than four, and so the pair moves to its own
   line as a pair — one switch above the box and one beside it would read as
   two unrelated controls. */
/* Buddy's two jobs, filled when it is doing them.
   ==============================================

   Small on purpose. This replaced two labelled On/Off pairs that said the same
   thing across the whole width of a phone; the state here is carried by fill
   rather than by a second word, so it fits beside the message box instead of
   taking a line of its own.

   Never shrinks. This is load-bearing: the stacking mechanism assumes the
   controls keep their natural width, so that a row short of space shows up as
   a squeezed message box, which fitComposer measures. Let this shrink instead
   and the row always "fits" — by clipping the words and wrapping the
   placeholder, which is the broken-looking result the measuring exists to
   prevent. */
#wb-switches {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
}

#wb-buddy-toggle {
  display: inline-flex;
  flex-direction: column;
  flex: 0 0 auto;
  border: 1px solid var(--wb-ink);
  border-radius: 4px;
  overflow: hidden;
  line-height: 1;
}

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

/* The name plate. Never filled, which is the point: it was solid purple to
   begin with and read as a third thing that was switched on, sitting directly
   above two halves where filled means exactly that. Purple on white keeps it
   part of the block and part of the same idea as a hollow half — it says whose
   controls these are, and fill is left to mean one thing only. */
.wb-toggle-name {
  display: block;
  padding: 4px 10px;
  background: #fff;
  color: var(--wb-purple);
  font-size: 11px;
  font-weight: 800;
  text-align: center;
  border-bottom: 1px solid var(--wb-ink);
}

.wb-toggle-row { display: flex; }

/* Hollow is off. Both halves are the same width whatever the words are, so the
   block cannot jiggle as the state changes. */
.wb-toggle-half {
  flex: 1 1 0;
  min-width: 44px;
  border: none;
  background: #fff;
  color: var(--wb-purple);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  padding: 5px 8px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.wb-toggle-half + .wb-toggle-half { border-left: 1px solid var(--wb-ink); }

.wb-toggle-half:hover { background: #f3eefa; }

/* Filled is on — told three ways at once, because a single cue is easy to miss
   at this size and this control has no second word to fall back on. */
.wb-toggle-half[aria-pressed="true"] {
  background: var(--wb-purple);
  color: #fff;
  font-weight: 800;
}

.wb-toggle-half[aria-pressed="true"]:hover { background: var(--wb-purple-dark); }

.wb-toggle-half:focus-visible { outline: 2px solid var(--wb-purple); outline-offset: -2px; }

/* Narrow panel: the switches take a line of their own so the message box keeps
   a width worth typing into.
 *
 * The row is [Buddy] [message box] [Send], and the controls are all
 * flex:0 0 auto — so every pixel the row is short comes out of the box, and
 * the row never reports an overflow because the box absorbs the squeeze in
 * silence. On a 360px Android that left 112px for a placeholder needing 113,
 * and on a 320px phone 72px, which is too narrow to type in whatever the
 * placeholder says.
 *
 * Wrapped rather than shrinking the block: Talk and Hear are what a learner is
 * told to look for, and a phone has vertical room to spare where it has no
 * horizontal room at all. word-buddy.js measures and applies this. */
#wb-form.wb-stacked { flex-wrap: wrap; row-gap: 8px; }
#wb-form.wb-stacked #wb-switches { flex: 0 0 100%; }

#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 subtitle gives way on a phone.

     This was written when the header carried four things — a title, Give
     Feedback, the voice choice and the × — and they overflowed. Both of those
     buttons have since gone, so the crowding it was written against is gone
     with them and the rule is kept on its own merit rather than that one: the
     subtitle explains what Word Buddy is, which matters on a first meeting and
     not at all once the panel is already open on a small screen. */
  #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; }
}
