/*!
 * OSSA WIDGET, the help assistant's front end.
 * ------------------------------------------------------------------
 * Paired with /assets/js/ossa-widget.js, which mounts itself. This file
 * paints two things and nothing else: the closed honeycomb launcher, and
 * the open panel that reads like Messages on a Mac.
 *
 * Load order on any page:
 *   /css/styles.css                      <- brand tokens live here
 *   /assets/css/dashboard-theme.css      <- authenticated pages only, remaps
 *                                           the same token NAMES for dark
 *   /assets/css/ossa-widget.css          <- this file
 *
 * WHY THERE IS NO DARK BLOCK IN THIS FILE.
 * Almost none of it needs one. dashboard-theme.css remaps the VALUES of
 * --bone-cream, --bone-white, --bone-black, --steel and --line under
 * :root[data-mz-theme="dark"], so a rule written against those token names
 * flips for free and can never drift from the rest of the dashboard. The
 * places that genuinely cannot follow a token are called out at the site
 * of the decision below: the ink on the teal fill, the two halo stops that
 * lift on a black ground, the panel shadow, and the hairline that separates
 * Ossa's bubble from the panel on dark.
 *
 * Rules honored:
 *   - NO NEW BRAND TOKENS. This file declares no custom property at all.
 *     Every colour is read from a token that already ships. The only
 *     literals are var() fallbacks, and each one is byte identical to the
 *     value css/styles.css or dashboard-theme.css carries for that token,
 *     which is what tests/one-brand-token-source.test.mjs part C checks.
 *     The teal token NAMES are historical. --marrow-red is #2C685E.
 *   - Vanilla CSS. No build step, no framework, no library.
 *   - prefers-reduced-motion stills every animation here, including the
 *     launcher glow and the typing dots, with a static fallback rather
 *     than a frozen frame.
 *   - No em or en dashes anywhere in this file.
 */

/* =====================================================================
   1. THE LAUNCHER, CLOSED STATE
   ---------------------------------------------------------------------
   A CELL OF BONE WITH THE MARROW LIT INSIDE IT.

   The metaphor is the brand's own and it is pushed literally here. The
   disc is the marrow, painted in the accent so the launcher reads as teal
   from across the room. The honeycomb drawn over it is trabecular bone, in
   the constant bone ink an accent fill always takes. Nothing here is a
   drop shadow or a glow: the bloom, the inner light and the spark were
   radial gradients and were deleted on 2026-09-16 under the orb ban
   (tests/no-light-sources.test.mjs). The mark is a flat coin.

   Layers, painted in this order by honeycombMarkup() in ossa-widget.js:
     body     the marrow, a solid disc of the brand accent
     wall     the cortical rim, a hairline of bone at the disc's edge
     lattice  the six bone cells of the mark
     core     the centre cell, the marrow itself, filled with the lifted
              accent so it is the one cell that stands out in the comb

   Every colour below is a token that already ships.
   ===================================================================== */

/*
 * z-index 900 is the SAME rule the view switcher uses, for the same
 * reason it gives: above ordinary page content, below a modal scrim. A
 * floating control that paints over a confirm dialog is how somebody taps
 * the wrong thing. Both controls sitting on one layer also means they
 * stack predictably against each other rather than by accident.
 *
 * The value is duplicated in ossa-widget.js because the JS writes the
 * inline position on the host it creates. tests/ossa-widget.test.mjs
 * reads BOTH files and fails if they disagree, so the duplicate cannot
 * rot in silence.
 *
 * BOTTOM RIGHT, ON PURPOSE. Edwin offered bottom centre as the first
 * option. The corner won: a centred control sits on the iOS home indicator
 * and on Safari's bottom bar, it covers content where a corner covers
 * margin, and positionLauncher() already measures what else is parked in
 * this corner and climbs above it. The safe area inset is folded in here
 * AND in the inline write the JS makes, because the inline value wins.
 */
.ossa-launcher {
  position: fixed;
  right: 20px;
  bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  z-index: 900;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--bone-black, #0A0908);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
}

/* The body is the accent, so the ring has to be the page's ink rather than
   the accent, or it vanishes into the halo. Same pairing .ossa-handoff uses
   for an accent filled control. */
.ossa-launcher:focus-visible {
  outline: 2px solid var(--bone-black, #0A0908);
  outline-offset: 4px;
  border-radius: 50%;
}

.ossa-launcher[hidden] { display: none !important; }

.ossa-hive {
  width: 56px;
  height: 56px;
  display: block;
  overflow: visible;
  transform-origin: 50% 50%;
  transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.ossa-launcher:hover  .ossa-hive { transform: scale(1.06); }
.ossa-launcher:active .ossa-hive { transform: scale(0.97); transition-duration: 80ms; }

/* THE MARROW. The launcher disc is the mark's tile (DESIGN SYSTEM 11.13c),
   the same teal blue the header lockup sits on, in both themes. */
.ossa-hive-body { fill: var(--atelier-mark-tile, #42767C); }


/* THE BONE. The circle and the seven hollow cells (the FINAL mark, DESIGN
   SYSTEM 11.13c: no filled core, no lit cell) take the constant light ink for
   an accent fill. It is deliberately NOT --bone-black, which flips to bone on
   dark and would put near black lines on the teal on light. --mzv-onfill is
   the same value on both themes, which is the point. The circle is drawn
   heavier than the cells, as the partial draws it. */
.ossa-hive-wall {
  fill: none;
  stroke: var(--color-pure-white, #FFFFFF);
  stroke-width: 1.4;
  opacity: 1;
}

.ossa-hive-lattice polygon {
  fill: none;
  stroke: var(--color-pure-white, #FFFFFF);
  stroke-width: 1;
  stroke-linejoin: round;
  opacity: 1;
  transition: opacity 220ms ease;
}
.ossa-launcher:hover .ossa-hive-lattice polygon { opacity: 1; }


.ossa-launcher[data-ossa-intro="1"] .ossa-hive-lattice polygon { opacity: 1; }

/* The one word beneath the mark, on hover and on focus only. Sits just
   under the halo's reach. */
.ossa-launcher-word {
  position: absolute;
  bottom: -16px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--mono, 'JetBrains Mono', 'SF Mono', Menlo, monospace);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--steel, #475569);
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease;
}
.ossa-launcher:hover .ossa-launcher-word,
.ossa-launcher:focus-visible .ossa-launcher-word { opacity: 1; }

/*
 * THE UNREAD MARK IS A TEAL DOT, NOT A RED BADGE.
 * Red is retired as Marrow branding and survives only as meaning, so a red
 * dot on this launcher would read as "something is wrong" when the truth is
 * "your answer arrived". No count either: a number invites counting, and
 * there is only ever one conversation here.
 *
 * It sits in the button's corner, mostly OFF the disc, and wears a ring of
 * the constant bone ink so a teal dot never has to be found on a teal body
 * and reads on both page grounds.
 */
.ossa-launcher-dot {
  position: absolute;
  top: 1px;
  right: 1px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--marrow-red-glow, #2F6F65);
  box-shadow: 0 0 0 2px var(--mzv-onfill, #FBF8F2);
  display: none;
}
.ossa-launcher[data-ossa-unread="1"] .ossa-launcher-dot { display: block; }

/* COLLAPSED. Close and minimize both land here: a pill that keeps her on
   the page as a small teal coin beside her name. The halo is dropped
   because a glow bleeding over a pill's own ground reads as a smudge.

   44 tall, not 40. The pill is the thing a person taps to get her back, on
   a phone more often than not, and 40 is under the touch target floor. The
   JS measures the pill at 40 when it climbs past a corner blocker; that
   number is informational and a 4px difference cannot cause an overlap, but
   it should read 44 to match. */
.ossa-launcher--pill {
  width: auto;
  height: 44px;
  padding: 0 15px 0 9px;
  gap: 9px;
  border-radius: 22px;
  background: var(--bone-white, #FBF8F2);
  border: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  transition: border-color 160ms ease;
}
.ossa-launcher--pill .ossa-hive { width: 26px; height: 26px; }
.ossa-launcher--pill:hover .ossa-hive { transform: none; }
.ossa-launcher--pill:hover { border-color: var(--marrow-red, #2C685E); }
.ossa-launcher--pill .ossa-launcher-word {
  position: static;
  transform: none;
  opacity: 1;
  font-size: 11px;
  color: var(--bone-black, #0A0908);
}
/* The pill has its own edge, so its focus ring goes back to the accent. */
.ossa-launcher--pill:focus-visible {
  outline: 2px solid var(--marrow-red, #2C685E);
  outline-offset: 2px;
  border-radius: 22px;
}

/*
 * AT 26PX THE COMB IS REDRAWN, NOT SHRUNK.
 *
 * The full mark is tuned for 56px. Scaled to 26 the cells become a 0.4px grey
 * fuzz and the circle noise at 0.57px, which is exactly what made the coin in
 * the pill look like a broken copy of the launcher rather than a deliberate
 * small version of it. So the two small instances, the pill and the panel
 * header, thicken the circle and the cells to the one weight 11.13c gives a
 * mark under 60px (one weight for both) and keep the circle: a mark with no
 * enclosure is refused everywhere. Same object, drawn for its size, the way
 * a favicon is drawn for its.
 */
.ossa-launcher--pill .ossa-hive-wall,
.ossa-head .ossa-hive-wall { stroke-width: 2.2; }
.ossa-launcher--pill .ossa-hive-lattice polygon,
.ossa-head .ossa-hive-lattice polygon { stroke-width: 2.1; opacity: 0.9; }

/* =====================================================================
   1b. THE INTRODUCTION
   ---------------------------------------------------------------------
   SHE SAYS HELLO ONCE, IN HER OWN BUBBLE, AND THEN NEVER AGAIN.

   The person who has never seen her does not know the honeycomb is help.
   The one thing that solves that without becoming furniture is the thing a
   person would do: she introduces herself. One small bubble rises beside
   the mark, in the same shape her bubbles take inside the panel, with a
   caret pointing at the disc so the sentence "I'm right here" is literally
   true. It arrives after the page has settled, not on load. It leaves on
   its own. It never takes focus, never covers more than the margin the
   launcher already owns, and it fires ONCE per person per browser, which
   is the whole reason it can be allowed to exist at all. The JS writes the
   storage key the moment it is shown; there is no second showing, ever.

   Tapping the card opens her, because the card is an invitation and the
   only sensible answer to an invitation is to accept it. Anything else,
   a click elsewhere, a real scroll, Escape, or fourteen seconds, lets it
   go. There is no close control on it: an X is what a cookie banner wears,
   and a thing that leaves on its own does not need one.

   It is aria-hidden. The launcher already carries the full accessible
   name, "Open Ossa, the Marrow help assistant", which is this same
   introduction delivered the way assistive tech expects, so announcing the
   card would tell a screen reader user something twice while interrupting
   whatever they were reading.
   ===================================================================== */
.ossa-intro {
  position: fixed;
  /* Beside the disc, 10px clear of it: 20 margin + 56 disc + 10. */
  right: 86px;
  /* The JS overwrites this with launcherBottomCss(bottom, 8), so the card
     rides up with the launcher when it climbs past a corner blocker. The
     value here is the resting position for the moment before that runs. */
  bottom: calc(28px + env(safe-area-inset-bottom, 0px));
  z-index: 900;
  max-width: min(300px, calc(100vw - 102px));
  padding: 10px 14px 11px;
  border-radius: 18px;
  /* The caret sits on this corner, and a caret on a full radius corner
     lands on the curve. */
  border-bottom-right-radius: 10px;
  background: var(--bone-white, #FBF8F2);
  border: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  /* The same single floating shadow the panel wears, light theme only. */
  box-shadow: 0 18px 48px rgba(10, 9, 8, 0.16);
  color: var(--bone-black, #0A0908);
  font-family: var(--sans, 'Inter', -apple-system, system-ui, sans-serif);
  font-size: 14px;
  line-height: 1.4;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  animation: ossa-intro-in 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
:root[data-mz-theme="dark"] .ossa-intro { box-shadow: none; }
.ossa-intro[hidden] { display: none !important; }
.ossa-intro[data-ossa-leaving="1"] {
  animation: ossa-intro-out 220ms ease forwards;
  pointer-events: none;
}

/* Arrives from the disc's side, six pixels, and settles. Leaves by fading
   only, because a thing that slides away draws the eye on its way out. */
@keyframes ossa-intro-in {
  from { opacity: 0; transform: translateX(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes ossa-intro-out {
  to { opacity: 0; }
}

/* THE CARET. A square in the card's own fill, turned so one corner points
   at the disc, with a border on the two edges that show. It is painted
   over the card's right edge so the card border does not run across its
   base. Its vertical seat is set so the point lands on the disc's centre:
   the card's bottom edge sits 8px above the launcher's, the disc centre
   is 28px above the launcher's, so the centre is 20px up the card's edge. */
.ossa-intro::after {
  content: "";
  position: absolute;
  right: -6px;
  bottom: 15px;
  width: 10px;
  height: 10px;
  background: var(--bone-white, #FBF8F2);
  border-top: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  border-right: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  transform: rotate(45deg);
}

/* The eyebrow says WHAT she is in the site's own eyebrow voice, so the
   sentence under it can spend its words on what to do with her. Same
   metrics as .ossa-status in the panel header. */
.ossa-intro-eyebrow {
  display: block;
  margin-bottom: 3px;
  font-family: var(--mono, 'JetBrains Mono', 'SF Mono', Menlo, monospace);
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--steel, #475569);
}
.ossa-intro p { margin: 0; }

/* =====================================================================
   1c. THE LOGO IS ALSO A DOOR
   ---------------------------------------------------------------------
   The launcher and the Marrow mark are the same drawing at two sizes, cell
   for cell, and clicking the mark in the nav opens her. The JS marks the
   element it wires with data-ossa-open and this block is everything a
   person is given to notice that: the cursor, a hover that is the same
   wake the launcher has (the mark lifts, the marrow cell brightens to the
   lifted accent), a pressed state, and a focus ring. Nothing is added to
   the mark at rest. No badge, no caption, no underline, no pulse: a logo
   that advertises a second job stops being a logo.

   The centre cell is targeted by its fill attribute because it is the one
   <use> in the shipped mark that carries one; the six ring cells inherit
   their stroke from the group. A stylesheet rule beats a presentation
   attribute, which is why this works without touching the SVG.
   ===================================================================== */
[data-ossa-open] {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
button[data-ossa-open] {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  display: inline-flex;
  align-items: center;
  line-height: 0;
}
/* The attribute may sit on a wrapper (a button around the mark) or on the
   svg itself (the JS wiring the mark inside an existing home link), and
   both shapes get the same wake. */
[data-ossa-open] svg,
svg[data-ossa-open] {
  transform-origin: 50% 50%;
  transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
[data-ossa-open] use[fill] { transition: fill 220ms ease; }
[data-ossa-open]:hover svg,
svg[data-ossa-open]:hover { transform: scale(1.06); }
[data-ossa-open]:hover use[fill] { fill: var(--marrow-ember-text, #53B2A2); }
[data-ossa-open]:active svg,
svg[data-ossa-open]:active { transform: scale(0.97); transition-duration: 80ms; }
[data-ossa-open]:focus-visible {
  outline: 2px solid var(--marrow-red, #2C685E);
  outline-offset: 4px;
  border-radius: 50%;
}

/* =====================================================================
   2. THE PANEL
   ===================================================================== */

.ossa-panel {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 900;
  width: 380px;
  max-width: calc(100vw - 32px);
  height: 560px;
  max-height: calc(100vh - 40px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bone-white, #FBF8F2);
  border: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  border-radius: 16px;
  /* One sanctioned floating shadow, light theme only, mirroring the
     .mz-floating rule in mz-primitives.css. Dark takes depth from the
     raised surface instead, so the shadow is removed below. */
  box-shadow: 0 18px 48px rgba(10, 9, 8, 0.16);
  color: var(--bone-black, #0A0908);
  font-family: var(--sans, 'Inter', -apple-system, system-ui, sans-serif);
  animation: ossa-panel-in 180ms ease;
}
:root[data-mz-theme="dark"] .ossa-panel { box-shadow: none; }

@keyframes ossa-panel-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.ossa-panel[hidden] { display: none !important; }

/* On a phone it is a sheet, because a 380px card floating on a 390px screen
   is a card with nowhere to go. */
@media (max-width: 520px) {
  .ossa-panel {
    right: 0;
    left: 0;
    bottom: 0;
    width: auto;
    max-width: none;
    height: 88vh;
    border-radius: 16px 16px 0 0;
    border-bottom: 0;
  }
  /* The sheet is docked on the bottom edge, so the composer pads for the
     home indicator or the send button sits under a gesture zone. */
  .ossa-composer { padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); }
}

/* ---- Header ---- */

.ossa-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 10px 10px 14px;
  border-bottom: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  background: var(--bone-white, #FBF8F2);
}

.ossa-head .ossa-hive { width: 26px; height: 26px; flex: 0 0 auto; }
.ossa-head-text { min-width: 0; flex: 1 1 auto; }

.ossa-title {
  margin: 0;
  font-family: var(--display, 'Source Serif 4', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif);
  font-weight: var(--weight-serif, 400);
  font-size: 15px;
  line-height: 1.1;
  letter-spacing: 0.01em;
}

.ossa-status {
  font-family: var(--mono, 'JetBrains Mono', 'SF Mono', Menlo, monospace);
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--steel, #475569);
  display: block;
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ossa-head-actions { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }

.ossa-icon-btn {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--steel, #475569);
  cursor: pointer;
}
.ossa-icon-btn:hover { background: var(--paper-nested, #EFE9DC); color: var(--bone-black, #0A0908); }
.ossa-icon-btn:focus-visible { outline: 2px solid var(--marrow-red, #2C685E); outline-offset: 1px; }
.ossa-icon-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.ossa-icon-btn[hidden] { display: none !important; }

/* Muted is the default state, so the pressed look has to read as the quiet
   one rather than as the active one. Sound ON is what gets the accent. */
.ossa-icon-btn[aria-pressed="false"] { color: var(--marrow-red, #2C685E); }

/* ---- The transcript ---- */

.ossa-log {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 14px 14px 6px;
  display: flex;
  flex-direction: column;
  /* Messages spaces RUNS apart and keeps bubbles inside a run close. The
     tight value is the default and the run boundary opens it up below. */
  gap: 2px;
  background: var(--bone-white, #FBF8F2);
}

.ossa-row { display: flex; position: relative; }
.ossa-row--user { justify-content: flex-end; }
.ossa-row--ossa { justify-content: flex-start; }
/* First bubble of a new run gets the breathing room. Everything inside a run
   keeps the 2px gap above, which is what makes a run read as one block. */
.ossa-row[data-ossa-pos="first"],
.ossa-row[data-ossa-pos="only"] { margin-top: 10px; }
.ossa-log > .ossa-row:first-child { margin-top: 0; }

/* ---- The bubbles ---- */

.ossa-bubble {
  position: relative;
  max-width: 78%;
  padding: 7px 13px 8px;
  /* The Apple radius. 18px on a 15px/1.35 line is the ratio that makes a one
     line bubble read as a capsule and a five line one still read as a bubble. */
  border-radius: 18px;
  font-size: 15px;
  line-height: 1.35;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

/* THE USER BUBBLE IS TEAL AND STAYS TEAL, in both themes.
   --marrow-red is the teal #2C685E. The name is historical and the value is
   the brand accent, so this consumes the token rather than restating a hex.
   It is deliberately NOT wrapped in a theme block: a bubble that changes
   colour when somebody flips to dark is a bubble that lost its identity. */
.ossa-bubble--user {
  background: var(--marrow-red, #2C685E);
  /* --mzv-onfill is the constant light ink for an accent fill and it is NOT
     remapped for dark, which is exactly why it is the right token here.
     --bone-white would flip to #171411 on dark and put near black ink on the
     teal at about 2.4 to 1. This pairing measures 6.10 to 1 on both themes,
     recomputed when the parchment sweep moved the accent to #2C685E. */
  color: var(--mzv-onfill, #FBF8F2);
}

/* Ossa is the quiet one. The Messages grey equivalent drawn from the surface
   tokens, so the teal stays the only saturated thing on the screen. The
   hairline is what keeps it separate from the panel on dark, where the page
   ground token is DARKER than the card token rather than lighter. */
.ossa-bubble--ossa {
  background: var(--paper-nested, #EFE9DC);
  border: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  color: var(--bone-black, #0A0908);
}

/*
 * RUN GROUPING, THE MESSAGES RULE.
 * Consecutive bubbles from one sender tighten the corners on THEIR OWN side
 * so the run reads as one block, and only the last bubble of a run carries
 * the tail. The positions are stamped by the JS grouping function, which is
 * the thing under test, so this stylesheet stays a pure consequence of it.
 */
.ossa-row[data-ossa-pos="first"]  .ossa-bubble--user { border-bottom-right-radius: 5px; }
.ossa-row[data-ossa-pos="middle"] .ossa-bubble--user { border-top-right-radius: 5px; border-bottom-right-radius: 5px; }
.ossa-row[data-ossa-pos="last"]   .ossa-bubble--user { border-top-right-radius: 5px; }

.ossa-row[data-ossa-pos="first"]  .ossa-bubble--ossa { border-bottom-left-radius: 5px; }
.ossa-row[data-ossa-pos="middle"] .ossa-bubble--ossa { border-top-left-radius: 5px; border-bottom-left-radius: 5px; }
.ossa-row[data-ossa-pos="last"]   .ossa-bubble--ossa { border-top-left-radius: 5px; }

/*
 * THE TAIL. Two shapes: a curved lobe in the bubble's own fill, then a
 * cut out in the PANEL's fill that carves the hook. It is the same two
 * pseudo element construction Messages draws, and it is why the panel
 * background token appears twice below. Both follow the theme because both
 * are tokens.
 */
.ossa-row[data-ossa-tail="1"] .ossa-bubble::before,
.ossa-row[data-ossa-tail="1"] .ossa-bubble::after {
  content: "";
  position: absolute;
  bottom: 0;
  height: 18px;
  pointer-events: none;
}

.ossa-row[data-ossa-tail="1"] .ossa-bubble--user::before {
  right: -6px;
  width: 18px;
  background: var(--marrow-red, #2C685E);
  border-bottom-left-radius: 15px 13px;
}
.ossa-row[data-ossa-tail="1"] .ossa-bubble--user::after {
  right: -17px;
  width: 11px;
  background: var(--bone-white, #FBF8F2);
  border-bottom-left-radius: 9px;
}

.ossa-row[data-ossa-tail="1"] .ossa-bubble--ossa::before {
  left: -6px;
  width: 18px;
  background: var(--paper-nested, #EFE9DC);
  border-bottom-right-radius: 15px 13px;
}
.ossa-row[data-ossa-tail="1"] .ossa-bubble--ossa::after {
  left: -17px;
  width: 11px;
  background: var(--bone-white, #FBF8F2);
  border-bottom-right-radius: 9px;
}

/* ---- Typing indicator ---- */

.ossa-typing {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 11px 14px;
}
.ossa-typing i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--steel, #475569);
  opacity: 0.45;
  animation: ossa-typing 1.25s ease-in-out infinite;
}
.ossa-typing i:nth-child(2) { animation-delay: 0.16s; }
.ossa-typing i:nth-child(3) { animation-delay: 0.32s; }

@keyframes ossa-typing {
  0%, 60%, 100% { opacity: 0.32; transform: translateY(0); }
  30%           { opacity: 0.9;  transform: translateY(-3px); }
}

/* ---- The answer shape: links, copy, chips, handoff ---- */

.ossa-extra {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 6px 0 2px;
  padding-left: 2px;
}
.ossa-row--ossa + .ossa-extra { align-items: flex-start; }

.ossa-extra-label {
  font-family: var(--mono, 'JetBrains Mono', 'SF Mono', Menlo, monospace);
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--steel, #475569);
}

.ossa-chips { display: flex; flex-wrap: wrap; gap: 6px; }

/* A quick reply is a real tappable control that sends the next turn, so it
   is a button. A source is a place to go, so it is a link. Never the two
   swapped: a link that secretly sends a message is the thing people learn
   to distrust. */
.ossa-chip {
  font: inherit;
  font-size: 13px;
  line-height: 1.2;
  padding: 7px 12px;
  border-radius: 15px;
  border: 1px solid var(--marrow-red, #2C685E);
  background: transparent;
  color: var(--marrow-red, #2C685E);
  cursor: pointer;
  text-align: left;
}
:root[data-mz-theme="dark"] .ossa-chip {
  /* The accent as TYPE needs the lifted value on a dark ground. Same hue,
     7.21 to 1 on the dark card, which is what --marrow-ember-text is for.
     Was 7.14 on the retired #5FAEB0, recomputed for #53B2A2. */
  border-color: var(--marrow-ember-text, #53B2A2);
  color: var(--marrow-ember-text, #53B2A2);
}
.ossa-chip:hover { background: var(--paper-nested, #EFE9DC); }
.ossa-chip:focus-visible { outline: 2px solid var(--marrow-red, #2C685E); outline-offset: 2px; }

.ossa-link {
  font-size: 13px;
  color: var(--marrow-red, #2C685E);
  text-decoration: underline;
  text-underline-offset: 2px;
}
:root[data-mz-theme="dark"] .ossa-link { color: var(--marrow-ember-text, #53B2A2); }

.ossa-links { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }

/* The copy control is a real button with a confirmed state, because a copy
   that silently did nothing is indistinguishable from a copy that worked. */
.ossa-copy {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: inherit;
  font-size: 13px;
  padding: 7px 12px;
  border-radius: 10px;
  border: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  background: var(--paper-nested, #EFE9DC);
  color: var(--bone-black, #0A0908);
  cursor: pointer;
  max-width: 100%;
}
.ossa-copy:focus-visible { outline: 2px solid var(--marrow-red, #2C685E); outline-offset: 2px; }
.ossa-copy[data-ossa-copied="1"] { border-color: var(--marrow-red, #2C685E); color: var(--marrow-red, #2C685E); }
:root[data-mz-theme="dark"] .ossa-copy[data-ossa-copied="1"] {
  border-color: var(--marrow-ember-text, #53B2A2);
  color: var(--marrow-ember-text, #53B2A2);
}
.ossa-copy-value {
  font-family: var(--mono, 'JetBrains Mono', 'SF Mono', Menlo, monospace);
  font-size: 11px;
  color: var(--steel, #475569);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A handoff is an offer, so it is a button somebody can see and refuse. It
   is never a hidden link inside a sentence. */
.ossa-handoff {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  padding: 9px 14px;
  border-radius: 10px;
  border: 0;
  background: var(--marrow-red, #2C685E);
  color: var(--mzv-onfill, #FBF8F2);
  cursor: pointer;
  align-self: flex-start;
}
.ossa-handoff:focus-visible { outline: 2px solid var(--bone-black, #0A0908); outline-offset: 2px; }
.ossa-handoff[disabled] { opacity: 0.55; cursor: default; }

.ossa-steps {
  margin: 0;
  padding-left: 20px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--bone-black, #0A0908);
}
.ossa-steps li { margin: 3px 0; }

.ossa-note {
  font-size: 12px;
  line-height: 1.45;
  color: var(--steel, #475569);
}

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

.ossa-composer {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  background: var(--bone-white, #FBF8F2);
}

.ossa-input {
  flex: 1 1 auto;
  min-height: 36px;
  max-height: 120px;
  resize: none;
  padding: 8px 13px;
  border-radius: 18px;
  border: 1px solid var(--line, rgba(10, 9, 8, 0.08));
  background: var(--paper-nested, #EFE9DC);
  color: var(--bone-black, #0A0908);
  font: inherit;
  font-size: 15px;
  line-height: 1.3;
}
.ossa-input:focus-visible { outline: 2px solid var(--marrow-red, #2C685E); outline-offset: 0; }
.ossa-input::placeholder { color: var(--steel, #475569); }

.ossa-send {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: var(--marrow-red, #2C685E);
  color: var(--mzv-onfill, #FBF8F2);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ossa-send svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.ossa-send[disabled] { opacity: 0.4; cursor: default; }
.ossa-send:focus-visible { outline: 2px solid var(--bone-black, #0A0908); outline-offset: 2px; }

/* ---- Search mode ---- */

.ossa-results { display: flex; flex-direction: column; gap: 2px; padding: 6px 0; }

.ossa-result {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: 14px;
  padding: 9px 12px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--bone-black, #0A0908);
  cursor: pointer;
}
.ossa-result:hover,
.ossa-result[data-ossa-active="1"] { background: var(--paper-nested, #EFE9DC); }
.ossa-result:focus-visible { outline: 2px solid var(--marrow-red, #2C685E); outline-offset: -2px; }
.ossa-result small { display: block; color: var(--steel, #475569); font-size: 11px; margin-top: 2px; }

/* ---- Screen reader only, defined locally because a marketing page does not
        load mz-primitives.css and this widget mounts on both. ---- */
.ossa-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* =====================================================================
   3. REDUCED MOTION
   Completely static, no exception. Not a slower glow, not a shorter fade.
   The typing dots get a real static fallback rather than a frozen frame,
   because three dots stopped mid bounce reads as a broken widget.
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
  .ossa-hive,
  .ossa-typing i,
  .ossa-panel,
  .ossa-intro,
  [data-ossa-open] svg,
  svg[data-ossa-open] {
    animation: none !important;
    transform: none !important;
    transition: none !important;
  }
  /* The still mark: disc, bone comb and core. Nothing about it depends on
     motion to read, and since 2026-09-16 nothing about it glows. */
  .ossa-hive-lattice polygon { transition: none; }
  .ossa-typing i { opacity: 0.45; }
  .ossa-launcher-word { transition: none; }
  .ossa-launcher--pill { transition: none; }
  /* The introduction simply is there, then is not. Its caret keeps its
     turn, because that is a shape and not a motion. */
  .ossa-intro[data-ossa-leaving="1"] { opacity: 0; }
  [data-ossa-open] use[fill] { transition: none; }
}
