/* =========================================================
   The assistant.

   Self-contained on purpose, widescreen rules included, rather
   than split across styles.css and responsive.css the way the
   page components are. Nothing else on the site references any
   class in here, so the whole feature is one file to read and
   one file to delete.

   It takes its surfaces, rules and type from the same tokens as
   everything else: square corners, hairline borders, mono for
   anything that is a label rather than a sentence. A chat widget
   is the easiest thing on a site to make look bought in, and a
   rounded blue bubble in the corner of this page would look
   exactly that.
   ========================================================= */

/* ---------- Launcher ----------
   Sits above the sticky header (z-index 20) and below the skip
   link (100), so tabbing into the page still reaches the skip
   link first. The paper grain in theme.css is at 9999 and takes
   no pointer events, so it lands on this the same as on every
   other surface. */
.chat-launcher {
  position: fixed;
  right: max(var(--gutter), env(safe-area-inset-right));
  bottom: max(var(--gutter), env(safe-area-inset-bottom));
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 3rem;
  padding-inline: 1rem;
  background: var(--invert-bg);
  border: 1px solid var(--invert-bg);
  border-radius: var(--radius);
  color: var(--invert-fg);
  font-family: var(--font-mono);
  font-size: var(--step-000);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: var(--shadow-md);
  transition:
    background-color 0.15s ease,
    color 0.15s ease,
    border-color 0.15s ease,
    transform 0.3s var(--ease),
    box-shadow 0.3s var(--ease);
}

.chat-launcher[hidden] {
  display: none;
}

/* The mark, as a mask rather than an image: the glyph carries the shape and
   currentColor carries the colour, so it inverts with the button on hover
   without a second asset. Same technique as .divider in styles.css. */
.chat-launcher__mark {
  width: 1.125rem;
  height: 1.125rem;
  flex: 0 0 auto;
  background: currentColor;
  mask: var(--skull-glyph) center / contain no-repeat;
  -webkit-mask: var(--skull-glyph) center / contain no-repeat;
}

/* The label swaps to "Close" while the panel is open, so it holds the width of
   the longer of the two words. Without this the button changes size under the
   pointer at the moment it is clicked. */
.chat-launcher__label {
  min-width: 4.2em;
  text-align: center;
}

.chat-launcher:hover {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--fg);
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
}

.chat-launcher:active {
  transform: translateY(0);
}

/* Open is a held state, not a hover one. The lift belongs to the closed button
   reacting to the pointer; repeating it here makes the anchor of an open panel
   bob every time the pointer crosses it. */
.chat-launcher.is-active,
.chat-launcher.is-active:hover {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--fg);
  transform: none;
  box-shadow: var(--shadow-md);
}

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

.chat-panel {
  position: fixed;
  right: max(var(--gutter), env(safe-area-inset-right));
  bottom: calc(max(var(--gutter), env(safe-area-inset-bottom)) + 3.75rem);
  left: max(var(--gutter), env(safe-area-inset-left));
  z-index: 60;
  display: flex;
  flex-direction: column;
  /* svh rather than vh: on a phone the mobile browser's own chrome is counted,
     so the panel does not run under the address bar when it is showing. */
  height: min(32rem, calc(100svh - 9rem));
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
  overflow: hidden;
  opacity: 0;
  transform: translateY(0.75rem);
  transition:
    opacity 0.2s var(--ease),
    transform 0.2s var(--ease),
    width 0.24s var(--ease),
    height 0.24s var(--ease);
}

.chat-panel[hidden] {
  display: none;
}

.chat-panel.is-open {
  opacity: 1;
  transform: none;
}

/* ---------- Expanded ----------
   The maximise toggle in the head. The default panel is a corner widget, which
   is the right size for "how much does it cost" and the wrong size for an
   answer that runs to a paragraph — so the reader gets to say which they are
   having, the way any application window works. The state is a class on the
   panel, so it survives closing and reopening.

   The type inside grows with the box rather than staying at widget size. A wider
   panel at the same 14px would just set longer lines of small text, which is not
   what somebody who pressed maximise was asking for. */
/* Not 100svh: the panel is anchored above the launcher, so its own bottom
   offset has to come out of the height as well or the top runs off the screen.
   What is left over is the gap above it, and 7rem keeps that gap positive even
   on a phone whose home indicator has pushed the launcher up. */
.chat-panel.is-expanded {
  height: calc(100svh - 7rem);
}

.chat-panel.is-expanded .chat-log {
  gap: 1rem;
  padding: 1.125rem;
}

.chat-panel.is-expanded .chat-msg {
  max-width: 94%;
}

.chat-panel.is-expanded .chat-bubble {
  padding: 0.75rem 0.9375rem;
  font-size: 0.9375rem;
  line-height: 1.6;
}

/* ---------- Head ---------- */

.chat-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex: 0 0 auto;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--bg-inset);
}

.chat-head__title {
  font-family: var(--font-mono);
  font-size: var(--step-00);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg);
}

.chat-head__subtitle {
  font-size: var(--step-000);
  line-height: 1.4;
  color: var(--faint);
}

/* Maximise, then close, in that order: the same order and the same corner as
   every application window, so nobody has to be told which is which. The group
   carries the negative margin, so the head's optical padding is unchanged. */
.chat-head__actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.125rem;
  margin: -0.25rem -0.375rem -0.25rem 0;
}

/* 2.25rem rather than the 1.75rem the glyph needs: the icon stays small, the
   target gets close enough to a thumb's width to be hit on a phone. */
.chat-close,
.chat-expand {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--faint);
  cursor: pointer;
  transition:
    color 0.15s ease,
    border-color 0.15s ease;
}

.chat-close svg,
.chat-expand svg {
  width: 0.875rem;
  height: 0.875rem;
}

.chat-close:hover,
.chat-expand:hover {
  border-color: var(--line-strong);
  color: var(--fg);
}

/* One svg, two glyphs: a square to maximise and the offset pair to restore.
   Swapping which path is drawn keeps the button's box and its focus ring
   identical across the toggle, which replacing the whole element would not. */
.chat-expand__restore,
.chat-panel.is-expanded .chat-expand__max {
  display: none;
}

.chat-panel.is-expanded .chat-expand__restore {
  display: block;
}

/* ---------- Log ---------- */

.chat-log {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  padding: 1rem;
}

/* Messages arrive rather than appear. 160ms is short enough that a fast
   exchange does not feel staged, long enough that the eye is told where the
   new text is. theme.css collapses this to nothing under reduced motion. */
.chat-msg {
  display: flex;
  flex-direction: column;
  max-width: 88%;
  animation: chat-msg-in 0.16s var(--ease) both;
}

@keyframes chat-msg-in {
  from {
    opacity: 0;
    transform: translateY(0.375rem);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

.chat-msg--bot {
  align-self: flex-start;
  align-items: flex-start;
}

.chat-msg--user {
  align-self: flex-end;
  align-items: flex-end;
}

.chat-bubble {
  padding: 0.625rem 0.8125rem;
  border-radius: var(--radius);
  font-size: 0.875rem;
  line-height: 1.55;
  text-wrap: pretty;
  overflow-wrap: anywhere;
}

.chat-msg--bot .chat-bubble {
  background: var(--bg-inset);
  border: 1px solid var(--line);
  color: var(--fg);
}

.chat-msg--user .chat-bubble {
  background: var(--invert-bg);
  color: var(--invert-fg);
}

/* Links under a reply. Squared, hairline, mono: the same treatment as the
   page-level table of contents, so they read as routes into the site rather
   than as buttons in a product. */
.chat-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin-top: 0.5rem;
}

.chat-link,
.chat-chip {
  display: inline-block;
  padding: 0.375rem 0.625rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--step-000);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition:
    border-color 0.15s ease,
    color 0.15s ease,
    background-color 0.15s ease;
}

.chat-link:hover,
.chat-chip:hover {
  border-color: var(--line-strong);
  background: var(--accent-tint);
  color: var(--fg);
}

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

.chat-typing {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

.chat-typing span {
  width: 0.25rem;
  height: 0.25rem;
  background: var(--faint);
  animation: chat-pulse 1s infinite;
}

.chat-typing span:nth-child(2) {
  animation-delay: 0.15s;
}

.chat-typing span:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes chat-pulse {
  0%,
  80%,
  100% {
    opacity: 0.3;
  }

  40% {
    opacity: 1;
  }
}

/* ---------- Suggestions ---------- */

.chat-chips {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  padding: 0 1rem 0.75rem;
}

.chat-chips:empty {
  display: none;
}

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

.chat-form {
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border-top: 1px solid var(--line);
}

.chat-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.625rem 0.75rem;
  background: var(--bg-inset);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
  font-family: inherit;
  /* 16px is the threshold below which iOS zooms the page on focus. */
  font-size: 1rem;
  transition: border-color 0.15s ease;
}

.chat-input:focus {
  border-color: var(--line-strong);
  outline: none;
}

.chat-input:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 2px;
}

.chat-input::placeholder {
  color: var(--faint);
}

.chat-send {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  background: var(--invert-bg);
  border: 1px solid var(--invert-bg);
  border-radius: var(--radius);
  color: var(--invert-fg);
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    color 0.15s ease,
    border-color 0.15s ease;
}

.chat-send svg {
  width: 1rem;
  height: 1rem;
}

.chat-send:hover {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--fg);
}

/* An empty box has nothing to send. The button is dimmed rather than disabled,
   because disabling the only submit button in a form also stops Enter from
   submitting it, and Enter is how most of these questions get sent. */
.chat-send.is-empty {
  background: transparent;
  border-color: var(--line);
  color: var(--faint);
  cursor: default;
}

.chat-send.is-empty:hover {
  border-color: var(--line);
  color: var(--faint);
}

.chat-note {
  flex: 0 0 auto;
  padding: 0 1rem 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--step-000);
  line-height: 1.4;
  letter-spacing: 0.04em;
  color: var(--faint);
}

/* ---------- Widescreen ----------
   The panel stops spanning the viewport and becomes a column in the corner. */

@media (min-width: 40em) {
  .chat-panel {
    left: auto;
    width: 23rem;
    height: min(34rem, calc(100svh - 10rem));
  }

  .chat-panel.is-expanded {
    width: min(32rem, calc(100vw - var(--gutter) * 2));
    height: min(46rem, calc(100svh - 6rem));
  }
}

/* ---------- Reduced motion ----------
   theme.css already collapses every transition and animation globally. The
   panel's opacity transition is part of how it appears, so it is restated
   here as an instant state change rather than left to a 0.01ms fade. */

@media (prefers-reduced-motion: reduce) {
  .chat-launcher:hover {
    transform: none;
  }

  .chat-panel {
    opacity: 1;
    transform: none;
  }
}
