/* Composer — chat input box, slash menu, plus menu, effort selector.
   Scoped via CSS Module so class names don't collide with the legacy
   stylesheet while it still exists. Values mirror the visual the user
   approved before this React migration. */

.inputArea {
  padding: 12px 24px 24px;
  background: var(--bg-primary);
  /* Anchored to the bottom of `#chatView` and floated OVER the chat
     area, not pushed into the flex flow — so growing the fn-form
     overlaps the chat content instead of squeezing it shorter (which
     made the welcome screen reflow). */
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
}
.inputArea::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 8px;
  background: linear-gradient(to top, var(--bg-primary), transparent);
  pointer-events: none;
}

/* Wrapper — the rounded rectangle the textarea sits inside.
   The fn-form open / close animation is driven by an inline `height`
   transition wired up in the React component. We deliberately do NOT
   transition padding here: padding flips instantly with the class, and
   the simultaneous height transition hides the snap. Two overlapping
   0.3s transitions used to cause visible jitter on every open / close. */
/* All geometry tokens come from `app/styles/01-base.css` (:root).
   Every circular button and every wrapper corner is laid out from the
   same `--composer-corner` value, so a single token change keeps the
   wrapper / send / stop / close / plus buttons concentric. */
/* Wrapper layout strategy:
   - top row (textarea OR fn-form header): in normal flow at the top.
   - body (fn-form only): in normal flow below the top row.
   - bottom row: `position: absolute; bottom: 0` so it's anchored to
     the wrapper's bottom edge regardless of how the wrapper's height
     changes. wrapper.padding-bottom equals the bottom row's height,
     reserving space below the in-flow content.
   Effect: when the wrapper height transitions (fn-form open/close),
   the bottom row stays put at the wrapper's bottom edge while the
   top portion expands or collapses. */
.inputWrapper {
  position: relative;
  display: flex;
  flex-direction: column;
  max-width: 800px;
  margin: 0 auto;
  background: var(--bg-tertiary);
  border: none;
  border-radius: var(--composer-corner);
  padding: 0 0 var(--composer-bottom-row-h);
  /* Keep the edge transparent until the textarea or an inner control
     receives focus. Using box-shadow avoids layout shifts from a real
     border while preserving the wrapper's rounded geometry. */
  box-shadow: 0 0 0 1px transparent;
  overflow: hidden;
  transition: box-shadow 180ms ease,
    height var(--composer-height-transition);
}
.inputWrapper:focus-within {
  box-shadow: 0 0 0 1px var(--border-light);
}

/* Crossfade overlay shown briefly during fn-form A → fn-form B
   switches. Sits on top of the new form (z-index 1, same as
   `.inputBottomRow`) and fades opacity 1→0 over one composer fade
   duration. `inset: 0` + `bottom: --composer-bottom-row-h` lets it
   cover the header + body region only, leaving the bottom row
   visible underneath. */
.outgoingLayer {
  position: absolute;
  inset: 0;
  bottom: var(--composer-bottom-row-h);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
  background: var(--bg-tertiary);
  border-radius: var(--composer-corner) var(--composer-corner) 0 0;
  animation: outgoingLayerFade var(--composer-fade-duration) ease forwards;
}
/* Suppress the cloned header/body's own fadeIn animation — we want
   them visible at opacity 1 while the LAYER fades from 1 to 0. */
.outgoingLayer [data-fn-form-header],
.outgoingLayer [data-fn-form-body] {
  animation: none;
  opacity: 1;
}
/* Keyframes kept HERE (not base.css) so turbopack mangles the
   ``@keyframes`` name and the ``animation: <name>`` reference above
   with the same hash. Same fix as the slashOpen/slashClose pair
   further down — turbopack's CSS-module pass mangles ``animation:``
   identifiers, so a globally-defined keyframe of the original name
   wouldn't match. */
@keyframes outgoingLayerFade {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Dark-mode override removed — `var(--text-bright)` already swaps
   between near-black (light) and near-white (dark) via the theme
   tokens in 01-base.css, so a single rule covers both. */

/* Top row — chat mode only. Mirrors `.inputBottomRow` except for
   `padding-bottom: 0`. The bottom row's `padding-top: 16` provides
   the visible 16px gap between the textarea and the input-options
   strip; if the top row also added its own 16px padding-bottom the
   gap would visually double to 32px and the whole wrapper would
   look unnecessarily tall. Skipping the top row's bottom padding
   collapses them into a single shared 16px breathing zone — the
   "overlapping padding" effect that keeps the chat-mode wrapper
   compact while still leaving room for the bottom row's controls. */
.inputTopRow {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  min-height: 30px;
  /* Right padding reserves space for the absolute-positioned action
     button (send / stop) that lives at the wrapper level: 16 (right
     offset) + 32 (button-size) + 8 (breathing space) = 56. */
  padding: var(--composer-bottom-row-pad)
    calc(var(--composer-bottom-row-pad) + var(--composer-button-size) + 8px) 0
    var(--composer-bottom-row-pad);
  flex-shrink: 0;
}
.inputField {
  position: relative;
  flex: 1;
  min-width: 0;
}
.chatInput {
  width: 100%;
  background: none;
  border: none;
  color: var(--text-bright);
  font-size: 14px;
  font-family: var(--font-sans);
  outline: none;
  resize: none;
  min-height: var(--composer-button-size);
  max-height: 200px;
  /* Inner padding so the caret / text sits noticeably inside the
     wrapper's rounded edge. The row already pads 16px from the
     wrapper; an extra 8px on each side puts the text at 24px from
     the edge so it never reads as touching the rounded curve. */
  padding: 0 8px;
  /* `line-height` equal to the row's button-size token so a single
     line of placeholder/value renders vertically centered inside the
     32px-tall textarea instead of sitting at the top. */
  line-height: var(--composer-button-size);
  flex: 1;
  overflow: hidden;
}
.chatPlaceholder {
  position: absolute;
  inset: 0 8px auto;
  height: var(--composer-button-size);
  line-height: var(--composer-button-size);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: 14px;
  overflow: hidden;
  pointer-events: none;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chatInput::placeholder {
  color: var(--text-muted);
}

/* Pasted-content chip row — sits ABOVE the textarea (not inside the
   flex row with it) so chips stack on their own line and don't shove
   the textarea narrower. Padding mirrors the input row's so chips
   line up with the textarea text. */
.pasteChipRow {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: var(--composer-bottom-row-pad) var(--composer-bottom-row-pad) 0;
}
.pasteChip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 4px 1px 8px;
  border-radius: 10px;
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--bg-secondary);
  color: var(--text-muted);
  border: 1px solid var(--border);
}
.pasteChip.missing {
  /* Visually distinct when the backing content is gone (reload past
     the persistence cap, manual store clear). Submit will refuse. */
  border-color: var(--accent-red, #b34242);
  color: var(--accent-red, #b34242);
}
.pasteChipRemove {
  margin-left: 2px;
  padding: 0 4px;
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
}
.pasteChipRemove:hover {
  color: var(--text-bright);
}

/* Image attachment strip — sits above the textarea (above the paste
   chip row too) and lists image thumbs with a corner × button.
   Layout matches the file-tile row (same outer padding, same hover
   ring trick) so the two strips read as one consistent system. */
.imageAttachStrip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  /* 16px inset on all sides so chips line up with the send / plus /
     thinking-pill row's geometry. Bottom row buttons are placed at
     ``--composer-button-offset: 16px`` from the wrapper edge and
     have radius 16 (50% of 32px diameter); using the same 16px inset
     here means chip + button share concentric arcs around the same
     wrapper-corner center (32, 32). */
  padding: 16px 16px 8px;
}
.imageAttachChip {
  position: relative;
  display: inline-flex;
  /* Radius 16 matches the send / stop / plus buttons exactly — all
     inner pieces of the composer now trace the same arc family,
     just at different positions. */
  border-radius: 16px;
  overflow: hidden;
  background: var(--bg-tertiary);
  /* Same outset 1px ring as the file tile — no CSS border, no
     curve-thinning. */
  box-shadow: 0 0 0 1px var(--border);
  transition: box-shadow 120ms ease;
}
.imageAttachChip:hover {
  box-shadow: 0 0 0 1px rgba(226,225,218,0.40);
}
.imageAttachImg {
  height: 64px;
  width: 64px;
  object-fit: cover;
  display: block;
}
/* Skeleton variant — same dimensions as the real <img>, with a
   subtle horizontal shimmer to read as "loading" + a centered
   spinner sitting on top so the chip visibly "spins" while the
   decode pipeline runs. The shimmer alone read as too static. */
.imageAttachImg[data-loading="true"] {
  background: linear-gradient(
    90deg,
    var(--bg-tertiary) 0%,
    var(--bg-secondary) 50%,
    var(--bg-tertiary) 100%
  );
  background-size: 200% 100%;
  animation: imageSkeletonShimmer 1.2s ease-in-out infinite;
  display: flex;
  align-items: center;
  justify-content: center;
}
.imageAttachImg[data-loading="true"]::after {
  content: "";
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.12);
  border-top-color: rgba(255, 255, 255, 0.65);
  animation: composerAttachSpin 0.8s linear infinite;
}
@keyframes imageSkeletonShimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}
@keyframes composerAttachSpin {
  to { transform: rotate(360deg); }
}
.imageAttachRemove {
  position: absolute;
  /* Inside the chip, top-right corner. Hidden by default; fades in
     when the chip is hovered or focused (matches the docx-tile ×). */
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.55);
  color: white;
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 120ms ease, background 120ms ease;
}
.imageAttachChip:hover .imageAttachRemove,
.imageAttachChip:focus-within .imageAttachRemove {
  opacity: 1;
}
.imageAttachRemove:hover {
  background: rgba(0, 0, 0, 0.75);
}
.imageAttachError {
  font-size: 11px;
  color: var(--accent-red);
  margin-left: 4px;
}
.imageAttachErrorDismiss {
  margin-left: 4px;
  padding: 0 4px;
  border: none;
  background: transparent;
  color: var(--accent-red);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
}

/* Bottom row — anchored to the wrapper's bottom edge via absolute
   positioning. Stays in place when the wrapper height transitions. */
.inputBottomRow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  min-height: 30px;
  padding: var(--composer-bottom-row-pad);
  flex-shrink: 0;
  /* Solid wrapper background so the form body sliding behind the row
     during a height transition stays hidden — without it the body's
     content shows through the row. Bottom corners match the wrapper's
     radius so they don't poke past it. */
  background: var(--bg-tertiary);
  border-radius: 0 0 var(--composer-corner) var(--composer-corner);
  z-index: 1;
  /* The container declaration lives in chat.css under the global
     `.composer-bottom-row` class (also applied in composer/index.tsx).
     CSS Modules scope `container-name`, which broke `@container` rules
     that lived in non-module stylesheets — moving the declaration to a
     global class avoids that mismatch. */
}
/* In fn-form mode the wrapper-level action button glides into the
   bottom-right corner of the row. Reserve right-side padding equal to
   `--composer-bottom-row-pad + button-size + 8 (gap)` so the
   ContextBadge can never sit under it. */
.fnFormMode .inputBottomRow {
  padding-right: calc(var(--composer-bottom-row-pad)
    + var(--composer-button-size) + 8px);
}
.inputOptions {
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
}

/* Send / stop button — a single absolute-positioned circle anchored
   to the wrapper. Its `top` is driven by inline style (set in
   composer/index.tsx) so it interpolates linearly between the chat
   position (top: button-offset) and the fn-form position
   (top: wrapper.height - button-offset - button-size), in sync with
   the wrapper's own height transition. The result is one continuous
   motion instead of the previous two-row teleport. */
.actionBtn {
  position: absolute;
  right: var(--composer-button-offset);
  top: var(--composer-button-offset);
  width: var(--composer-button-size);
  height: var(--composer-button-size);
  border-radius: 50%;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-sizing: border-box;
  flex-shrink: 0;
  /* `top` and the wrapper's `height` share the same transition curve
     so they advance in lockstep — the button glides between rows
     instead of snapping. */
  transition: top var(--composer-height-transition),
    background 0.15s,
    opacity 180ms ease;
  /* Bottom-row sits at z-index 1 to hide the form body retracting
     behind it; we need a higher value so the action button stays
     visible while it's mid-glide through that band. */
  z-index: 2;
}
.actionBtn svg {
  width: 14px;
  height: 14px;
  fill: currentColor;
}

/* ask_many 的「上一题 / 下一题（末题→确认）」按钮组 —— 占住发送按钮位
   （取代圆形箭头）。和 .actionBtn 一样绝对锚在右下，top 由 sendBtnRef 的
   inline style 驱动，跟 wrapper 高度同步滑动；只是这里是横排两颗 pill。 */
.decisionNav {
  position: absolute;
  /* 和原来的圆形发送按钮 .actionBtn 同一个右锚（right: offset），左右对齐。
     右上角的「聊聊这个」在 top:offset，导航组在底行，不在同一水平，不重叠。 */
  right: var(--composer-button-offset);
  top: var(--composer-button-offset);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: top var(--composer-height-transition);
  z-index: 2;
}
.decisionNavBtn {
  height: var(--composer-button-size);
  line-height: var(--composer-button-size);
  padding: 0 16px;
  border-radius: 999px;
  border: none;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  background: color-mix(in srgb, var(--text-secondary) 12%, transparent);
  color: var(--text-secondary);
  transition: background 180ms ease, color 180ms ease, opacity 180ms ease;
  box-sizing: border-box;
  white-space: nowrap;
}
.decisionNavBtn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--text-secondary) 20%, transparent);
  color: var(--text-primary);
}
.decisionNavBtn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
/* 主按钮（下一题 / 确认）：淡橙色 tint，跟发送按钮一个调子，不要实心亮橙。 */
.decisionNavBtnPrimary {
  background: color-mix(in srgb, var(--accent-orange) 16%, transparent);
  color: var(--accent-orange);
  font-weight: 600;
}
.decisionNavBtnPrimary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent-orange) 24%, transparent);
  color: var(--accent-orange);
}
/* Send glyph is a LINE icon (pqoqubbw arrow-up) — keep it stroked, not
   filled, overriding .actionBtn's fill. (The Stop square stays filled.) */
.sendBtn svg {
  fill: none;
}

.sendBtn {
  background: color-mix(in srgb, var(--accent-blue, #6cb4ff) 14%, transparent);
  color: var(--accent-blue, #6cb4ff);
}
.sendBtn:hover {
  background: color-mix(in srgb, var(--accent-blue, #6cb4ff) 22%, transparent);
}
.sendBtn[data-fn-missing="true"] {
  opacity: 0.58;
}
.sendBtn[data-fn-missing="true"]:hover {
  opacity: 0.72;
}
.sendBtn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.stopBtn {
  background: var(--accent-red);
  color: #fff;
  opacity: 0.85;
}
.stopBtn:hover {
  opacity: 1;
}

/* Close button — solid filled circle in the same family as the send
   button (color-mix tint, no border). Pinned to the wrapper's top-
   right corner with `--composer-button-offset`, so its centre sits
   on the corner-radius centre, matching the plus / send buttons. */
.closeBtn {
  position: absolute;
  top: var(--composer-button-offset);
  right: var(--composer-button-offset);
  width: var(--composer-button-size);
  height: var(--composer-button-size);
  border-radius: 50%;
  border: none;
  background: color-mix(in srgb, var(--text-secondary) 14%, transparent);
  color: var(--text-secondary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-sizing: border-box;
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s, opacity 180ms ease;
  z-index: 2;
}
.closeBtn:hover {
  background: color-mix(in srgb, var(--text-secondary) 22%, transparent);
  color: var(--text-bright);
}
.closeBtn svg {
  display: block;
}

/* 右上角「Chat about this」—— decision 在场时取代 ✕：放弃按它问的来，直接
   就这话题聊。文字 pill（非圆形），锚在同一右上角 offset，高度对齐别的圆控件。 */
.chatAboutBtn {
  position: absolute;
  top: var(--composer-button-offset);
  right: var(--composer-button-offset);
  height: var(--composer-button-size);
  line-height: var(--composer-button-size);
  padding: 0 14px;
  border-radius: 999px;
  border: none;
  background: color-mix(in srgb, var(--text-secondary) 12%, transparent);
  color: var(--text-secondary);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
  z-index: 2;
}
.chatAboutBtn:hover {
  background: color-mix(in srgb, var(--text-secondary) 20%, transparent);
  color: var(--text-bright);
}

/* Right-aligned group in the bottom row (context badge + optional
   send button in fn-form mode). */
.inputBottomRight {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* Plus button — 32×32 circle. Sits at bottomRow padding (12px), so its
   centre is `--composer-corner` away from the wrapper's bottom-left
   corner → concentric with that corner's quarter-circle. */
.plusBtn {
  width: var(--composer-button-size);
  height: var(--composer-button-size);
  border-radius: 50%;
  background: transparent;
  border: none;
  color: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}
.plusBtn:hover {
  background: var(--bg-hover);
  color: var(--text-bright);
}
.plusBtn.hasActive {
  color: var(--accent-blue, #6cb4ff);
}

/* Active tool chips — circular icon by default, hover expands to reveal
   close X + tooltip below. 1:1 with legacy `.tool-chip` / `.tool-chip-*`. */
.activeToolChips {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.activeToolChips:empty {
  display: none;
}
.toolChip {
  position: relative;
  display: inline-flex;
  align-items: center;
  /* Icon stays pinned to the left side; the × slot grows out the
     right edge on hover. ``justify-content: flex-start`` + fixed
     left padding = icon-position-invariant. Width is the only thing
     that animates on the chip; the circular icon area is rock-solid
     while the chip "extends" rightwards. */
  justify-content: flex-start;
  width: var(--composer-button-size);
  height: var(--composer-button-size);
  /* Left padding sized so the 16px icon centres inside the 32px
     collapsed circle (8 + 16 + 8 = 32). The right padding fades in
     with the × slot on hover. */
  padding-left: 8px;
  padding-right: 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent-blue, #6cb4ff) 14%, transparent);
  color: var(--accent-blue, #6cb4ff);
  cursor: pointer;
  transition: width 180ms cubic-bezier(0.16, 1, 0.3, 1),
              padding-right 180ms cubic-bezier(0.16, 1, 0.3, 1),
              background 0.15s;
  box-sizing: border-box;
  overflow: hidden;
}
.toolChip:hover {
  background: color-mix(in srgb, var(--accent-blue, #6cb4ff) 22%, transparent);
  /* Tight fit: 8 (padding-left) + 16 (icon) + 4 (gap) + 12 (× hit
     box at the narrower 10px stroke) + 6 (padding-right) = 46. */
  width: 46px;
  padding-right: 6px;
}
.toolChipIcon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.toolChipClose {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Collapsed: zero footprint so the chip can shrink to a perfect
     circle. Hover expands max-width to the icon width + restores
     the margin-left so the close sits next to the icon with a
     consistent gap. ``opacity`` fades it in lock-step. */
  max-width: 0;
  margin-left: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-width 180ms cubic-bezier(0.16, 1, 0.3, 1),
              margin-left 180ms cubic-bezier(0.16, 1, 0.3, 1),
              opacity 180ms ease;
  flex-shrink: 0;
}
.toolChip:hover .toolChipClose {
  max-width: 12px;
  margin-left: 4px;
  opacity: 1;
}
/* The animated XIcon draws at 24-viewBox / stroke 2; scaled to 12px the
   line thins to 1px. 3 brings it back to ~1.5px rendered, matching the
   weight of the chip's main glyph. The icon's wrapper div must not
   flex-shrink inside the max-width-animated span. */
.toolChipClose svg {
  stroke-width: 3;
}
.toolChipClose > div {
  flex-shrink: 0;
}
/* Disabled tool: muted circle, no accent, no × — click turns it on.
   Stays a fixed circle on hover (no width-expand, since there's no ×). */
.toolChipOff {
  background: transparent;
  color: var(--text-dim);
  opacity: 0.8;
}
.toolChipOff:hover {
  /* No filled circle on hover. The ON state of these chips is itself a
     filled tinted circle, so a gray fill here read as "enabled" — the
     off Tools chip looked lit up the moment you hovered it. Hover now
     just brightens the icon to signal it's clickable; the chip stays a
     transparent circle (no × to expand for, so width/padding pinned). */
  background: transparent;
  color: var(--text-secondary);
  width: var(--composer-button-size);
  padding-right: 8px;
  opacity: 1;
}
.toolChip[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  /* These chips sit at the very bottom of the viewport, so a tooltip
     below them gets clipped off-screen — show it ABOVE instead. */
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  /* Match the top-bar HoverTip exactly (.hover-tip in app/styles/chat.css):
     a borderless solid badge a touch lighter than the surface, crisp
     bright text — not the bordered default that read as "off". */
  background: color-mix(in srgb, var(--text-primary) 14%, var(--bg-secondary));
  color: var(--text-bright);
  border: none;
  box-shadow: var(--shadow-popover);
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 12px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease 0.1s;
  z-index: 50;
}
.toolChip[data-tooltip]:hover::after {
  opacity: 1;
}

/* Plus dropdown. Shadow stack mirrors shadcn's `shadow-md` (a soft 1-2px
   ambient + a wider 12-24px diffuse) instead of one heavy 16px / 0.32 alpha
   drop, which read as too dark on the light theme's beige surface. */
.plusMenu {
  position: absolute;
  bottom: 100%;
  left: 0;
  margin-bottom: 4px;
  min-width: 192px;
  max-width: 320px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-popover);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  z-index: 100;
}
.plusMenuItem {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 32px;
  padding: 6px 8px;
  border-radius: 8px;
  font-size: 14px;
  line-height: 20px;
  color: var(--text-primary);
  cursor: pointer;
  user-select: none;
  transition: background 0.15s, color 0.15s;
}
.plusMenuItem:hover {
  background: var(--bg-hover);
}
/* Selected = the label + its check turn the accent colour. No full-row
   colour fill (that read as garish when several rows were on) — just a
   tinted label + a trailing checkmark, like ChatGPT's menu. Colour var
   kept exactly as before (renders as the app accent); only the
   background fill is dropped. */
.plusMenuItem.active {
  color: var(--accent-blue, #6cb4ff);
}
.plusMenuDivider {
  height: 1px;
  margin: 4px 6px;
  background: var(--border);
}
.plusMenuLeft {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.plusMenuIcon {
  display: inline-flex;
  color: currentColor;
}
.plusMenuLabel {
  color: currentColor;
}
.plusMenuRight {
  display: inline-flex;
  color: currentColor;
}

/* Thinking-effort pill (`<ThinkingEffortPill />` in composer/index.tsx)
   is fully Tailwind. The pill collapses to a `effort: {value} ⌄` chip
   inline in the bottom row and expands rightward to a 340px slider
   when clicked — no module CSS lives here for the effort UI. */

/* Slash menu (popup above input that rolls up out of the input box) */
/* Stack wrapper: makes the inputWrapper the positioning reference
   for the slash menu. Its only job is ``position: relative`` — its
   height is the inputWrapper's natural height (slashClip is
   absolute, doesn't contribute), so anything inside positioned
   ``bottom: 100%`` lands on the wrapper's top edge. */
.composerStack {
  position: relative;
}
.slashClip {
  position: absolute;
  left: 0;
  right: 0;
  /* Bottom edge of the clip = inputWrapper's top edge.
     No magic-number offset; no breakage when the wrapper grows
     (multi-line input, fn-form open, etc.). */
  bottom: 100%;
  /* Tall enough for the menu's max-height. The clip's overflow:hidden
     is what makes the open / close translateY animation work — the
     menu starts at translateY(100%) inside the clip, visually below
     the clip's bottom, and slides up to translateY(0) anchored to the
     clip's own bottom (= wrapper top). */
  height: 500px;
  overflow: hidden;
  pointer-events: none;
}
.slashMenu {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  transform-origin: 50% 100%;
  pointer-events: auto;
  max-width: 720px;
  width: min(90%, 720px);
  /* Symmetric *visual* gap on all four sides. Top / left / right
     each get 8px padding + 1px border = 9px. The bottom border is
     removed so the menu merges with the textarea below, so we
     compensate with an extra 1px of bottom padding to keep the
     gap identical. */
  padding: 8px 8px 9px 8px;
  background: var(--bg-tertiary);
  border: 1px solid rgba(0, 0, 0, 0.18);
  border-radius: 14px 14px 0 0;
  border-bottom: none;
  box-shadow: none;
  max-height: 250px;
  overflow-y: auto;
  scrollbar-width: none;
}
.slashMenu::-webkit-scrollbar {
  display: none;
}

.opening {
  animation: slashOpen 380ms cubic-bezier(0.22, 1, 0.36, 1);
}
.closing {
  animation: slashClose 380ms cubic-bezier(0.4, 0, 0.84, 0) forwards;
  pointer-events: none;
}
/* Keyframes live HERE inside the module so turbopack mangles the
   ``@keyframes`` name and the ``animation: <name>`` reference with the
   same hash. Before this they sat in app/styles/base.css unmangled
   while the reference above was mangled to
   ``composer-module__xxx__slashOpen``, which never matched anything —
   the slash menu jumped in/out with no transition.

   ``base.css`` keeps duplicates for any non-module consumers; harmless. */
@keyframes slashOpen {
  from { transform: translateX(-50%) translateY(100%); }
  to   { transform: translateX(-50%) translateY(0); }
}
@keyframes slashClose {
  from { transform: translateX(-50%) translateY(0); }
  to   { transform: translateX(-50%) translateY(100%); }
}

.slashMenuItem {
  display: block;
  /* Equal padding on every side so the hover-state background
     extends the same distance away from the text in every
     direction — no horizontal stretch that makes vertical
     spacing look pinched. */
  padding: 10px;
  border-radius: 10px;
  cursor: pointer;
  user-select: none;
  transition: background 0.12s;
  text-align: left;
}
.slashMenuItem:hover,
.slashMenuItemActive {
  background: var(--bg-hover);
}
.slashMenuName {
  font-family: var(--font-mono, monospace);
  font-size: 13px;
  color: var(--text-bright, #111);
  font-weight: 600;
}
.slashMenuArgs {
  font-family: var(--font-mono, monospace);
  font-size: 12px;
  color: var(--text-muted);
}
.slashMenuDesc {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 3px;
  line-height: 1.4;
}

@media (prefers-color-scheme: dark) {
  .slashMenu {
    border-color: rgba(255, 255, 255, 0.10);
  }
  .slashMenuName {
    color: var(--text-bright, #fff);
  }
}

/* ===== Function-form mode ==========================================
   The bottom row stays IDENTICAL to chat mode — same padding, same
   layout, no extra border. The visual divider between the form
   fields and the bottom row lives on the body itself
   (`.body { border-bottom }` in fn-form.module.css). */

/* "Select a model first" hint — shown in the bottom-left controls slot
   (where the effort pill normally lives) when no chat model is picked.
   Muted/warning tone so it reads as guidance, not an error. */
