/* ===== Reset & Base ===== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  /* Layout & typography (shared) */
  --radius: 8px;
  --radius-lg: 12px;
  --transition: 0.2s ease;
  --sidebar-width: 288px;
  --detail-width: 300px;

  /* Two-set sizing system — see docs/design/surface-system.md.
     Every interactive primitive picks ONE set in full (height +
     radius bundled together). No sm/md/lg ladder inside a set.

     Set A — LIST. Compact rows on the deep surface (sidebar
       conversations, favourites, branches, worktrees). Tight
       vertical rhythm so dense scans stay legible; corners are
       gentle so the row reads as a content slot, not a chip.

     Set B — BUTTON. Buttons / pills on the panel surface
       (chat actions, settings actions, dialog footers). A bit
       taller so the click target is comfortable; corners are
       rounder so the pill silhouette is unmistakeable against
       the surrounding text. */
  --ui-list-h: 32px;
  /* 9px matches claude.ai's sidebar rows (`!rounded-[9px]`). With the 1px
     row gap, a softer corner reads as rounded separation instead of a
     sharp thin seam between adjacent pills. */
  --ui-list-radius: 9px;
  /* Button height intentionally a touch SHORTER than list height —
     a pill on the panel surface should not visually outweigh the
     sidebar rows it sits next to. The slightly larger radius
     differentiates the two without competing on height. */
  --ui-button-h: 30px;
  --ui-button-radius: 8px;
  --font-mono: var(--font-jetbrains-mono), 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
  /* SSR default = OS-native (system). Runtime inline script in
     layout.tsx overrides per agentic_font in localStorage. */
  --font-sans: var(--font-inter), -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', sans-serif;

  /* Accent colors (shared)
     ---------------------------------------------------------------
     ``--accent-orange`` is the canonical "primary action" colour —
     the warm amber sampled from Claude's own UI. All buttons /
     active states / focus rings / links that used to be blue resolve
     here now. The historical ``--accent-blue`` token is kept as an
     alias so the 300+ existing references update in one place; new
     code should prefer ``--accent-orange`` directly. Blue is no
     longer a UI colour in this product — only used informationally
     in chrome (file paths, hashes) where it survives via the alias.
     --------------------------------------------------------------- */
  --accent-orange: #d19a66;
  --accent-blue: var(--accent-orange);
  --accent-green: #18a96b;
  --accent-red: #e5534b;
  --accent-yellow: #c69026;
  --accent-purple: #a371f7;
  --accent-cyan: #3bc9db;

  /* ===== Composer / fn-form geometry tokens ============================
     The chat input wrapper, its corner buttons (send / stop / close /
     plus) and the function-form panel all share a single geometry so
     every circular control is *concentric* with the wrapper's corner
     radius. Change one token here and the whole composer re-aligns.

     Design rule: every circular button (send / stop / close / plus) is
     the same outer diameter — `--composer-button-size` — so they read
     as a matched set regardless of border thickness. They're anchored
     `(corner − size/2)` from the wrapper's edges so each button's
     centre sits on the wrapper's corner-radius centre.

       size       = 32  (outer diameter — borders counted in via
                          `box-sizing: border-box`)
       offset     = 16  (= corner − size/2 → centre at corner)
       corner     = 32  (= size, so button outer is tangent to the
                          wrapper's outer arc on the diagonal)
  */
  --composer-button-size: 32px;       /* every corner button — outer diameter */
  --composer-button-offset: 16px;     /* edge distance: corner − size/2     */
  --composer-corner: 32px;            /* wrapper border-radius = button size */
  --composer-bottom-row-pad: 16px;    /* bottom-row padding all around      */
  --composer-bottom-row-h: 64px;      /* size + 2 × pad = 32 + 32           */
  --composer-wrapper-pad-x: 16px;     /* wrapper left/right padding         */
  --composer-wrapper-pad-top: 16px;   /* wrapper top padding (chat mode)    */

  /* Composer animation timings */
  --composer-height-transition: 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
  --composer-fade-duration: 0.25s;
  --composer-fade-delay-header: 0.10s;
  --composer-fade-delay-body: 0.15s;

  /* ===== Typography scale ===============================================
     Fixed steps. Stick to these instead of arbitrary px values so font
     sizes don't drift across the app. New UI should pick one of:

       --fs-sm    13px — secondary labels, helper text, placeholders,
                          metadata tags. Don't go below this.
       --fs-base  14px — default body / form field / button text.
       --fs-md    16px — emphasized inline text, larger button labels.
       --fs-lg    20px — section / panel headings (e.g. welcome title).

     12px is intentionally absent — it tested as too small to read in
     the form panel. Anything that previously used 12px should bump up
     to `--fs-sm` (13px). */
  --fs-sm: 13px;
  --fs-base: 14px;
  --fs-md: 16px;
  --fs-lg: 20px;
}

/* Dark theme (default) — based on Claude dark */
:root, [data-theme="dark"] {
  --bg-primary: #1f1f1e;
  --bg-secondary: #171716;
  --bg-tertiary: #252524;
  --bg-hover: #2e2e2c;
  --bg-hover-contrast: #141413;
  --bg-input: #1a1a19;
  --border: rgba(226, 225, 218, 0.12);
  --border-light: rgba(226, 225, 218, 0.2);
  --text-primary: #c3c2b7;
  --text-secondary: #97958c;
  --text-bright: #f8f8f6;
  --text-muted: #6b6a63;
  --nav-color: #c3c2b7;       /* nav default — same as text-primary in dark */
  --nav-color-hover: #f8f8f6; /* nav hover — same as text-bright */
  --bg-selected: #4a4a47;     /* selection bg with clear contrast from bg-primary */
  /* Two distinguishable tints over `--bg-primary` (#1f1f1e):
     user goes warm (greenish-grey, matches the chat agent avatar),
     assistant goes cool (slate). Both stay subtle so the chat reads
     calm but the two surfaces are immediately tellable apart. */
  --user-msg-bg: #2e2f29;
  --assistant-msg-bg: #25282e;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.18);
  --shadow: 0 4px 12px rgba(0, 0, 0, 0.22);
  --shadow-popover: 0 6px 18px rgba(0, 0, 0, 0.24);
  --shadow-dialog: 0 12px 32px rgba(0, 0, 0, 0.3);
  --shadow-sidebar-left: 4px 0 20px rgba(0, 0, 0, 0.28);
  --shadow-sidebar-right: -4px 0 20px rgba(0, 0, 0, 0.28);
  /* Collapsed `off` effort-pill chip. Lighter than --bg-hover so it
     reads as a distinct chip on the dark composer rather than blending
     into the surface. */
  --effort-off-bg: #3c3c39;
  /* Text colour for hover-fill primary buttons. Near-black so warm
     orange reads as a "press me" tile rather than a glowing neon
     pill — pure white on the warm brand colour has too much chroma
     in dark mode. */
  --primary-foreground: #1a1a19;
  color-scheme: dark;
}

/* Light theme — based on Claude light */
[data-theme="light"] {
  --bg-primary: #faf9f5;
  --bg-secondary: #f5f4ed;
  --bg-tertiary: #f0eee6;
  --bg-hover: #e8e6dc;
  --bg-hover-contrast: #ffffff;
  --bg-input: #ffffff;
  --border: rgba(31, 30, 29, 0.12);
  --border-light: rgba(31, 30, 29, 0.2);
  --text-primary: #3d3d3a;
  --text-secondary: #73726c;
  --text-bright: #141413;
  --text-muted: #97958c;
  --nav-color: #73726c;       /* lighter than text-primary so light-mode icons aren't black */
  --nav-color-hover: #141413; /* same as text-bright */
  --bg-selected: #d0cec0;     /* selection bg with clear contrast from bg-primary */
  /* Light theme: same warm/cool split as dark, but lighter. */
  --user-msg-bg: #ece9d8;
  --assistant-msg-bg: #e2e7f0;
  --shadow-sm: 0 1px 2px rgba(31, 30, 29, 0.08);
  --shadow: 0 3px 10px rgba(31, 30, 29, 0.08);
  --shadow-popover: 0 8px 20px rgba(31, 30, 29, 0.12);
  --shadow-dialog: 0 14px 34px rgba(31, 30, 29, 0.16);
  --shadow-sidebar-left: 4px 0 20px rgba(31, 30, 29, 0.18);
  --shadow-sidebar-right: -4px 0 20px rgba(31, 30, 29, 0.18);
  /* Collapsed `off` effort-pill chip — light-theme counterpart. */
  --effort-off-bg: #e8e6dc;
  /* Deeper orange in light mode — the default `#d19a66` reads as a
     wash against the cream background; this burnt-orange has enough
     contrast for the function-name token + the user / pg-card icons
     in the various theme-aware components that reference it. */
  --accent-orange: #b8651f;
  /* Light mode keeps the white-on-brand convention — the deeper
     burnt orange above is dark enough that white still reads. */
  --primary-foreground: #ffffff;
  color-scheme: light;
}

html, body {
  height: 100%;
  font-family: var(--font-sans);
  background: var(--bg-primary);
  color: var(--text-primary);
  overflow: hidden;
  font-size: 14px;
  line-height: 1.5;
  /* Lock font rendering to grayscale antialiasing in BOTH themes.
     Without this, macOS / Safari swap to subpixel rendering on
     light backgrounds and to a different smoothing on dark, so
     identical font-weight rules visually read as "lighter in
     dark mode, heavier in light mode" — a swap of theme drifts
     the perceived boldness. Forcing antialiased + grayscale keeps
     weight consistent across the switch. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Native form elements default to the browser's UI font instead of
   inheriting from <body>, so without this rule buttons/inputs/selects
   ignored the global font switch on /settings/general. Force them to
   pick up `--font-sans` like everything else. Anything intentionally
   monospace declares its own `font-family: var(--font-mono)` later
   and overrides this. */
button, input, select, textarea, optgroup {
  font-family: inherit;
}

/* ===== The one list-row standard =====================================
   Every clickable list row — left sidebar nav (New chat / Functions /
   Skills / …), the sidebar chat list, the settings tab list, and the
   settings provider rows — is built from THIS rule, so there is a single
   design to maintain. Dimensions come from --ui-list-h / --ui-list-radius
   so a size change happens in one place. Consumers add only their own
   semantics (icon, label colour, active text). The sidebar's Tailwind
   strings compose it via the `ui-list-item` class; the settings CSS module
   pulls it in with `composes: ui-list-item from global`.
   Flat by design: no border, no shadow; hover + active are a --bg-hover
   tint only — matching claude.ai's sidebar. */
.ui-list-item {
  display: flex;
  align-items: center;
  gap: 12px;
  height: var(--ui-list-h);
  padding: 6px 8px;
  border-radius: var(--ui-list-radius);
  font-size: 14px;
  cursor: pointer;
  border: none;
  box-shadow: none;
  transition: background-color 150ms ease-out, color 150ms ease-out;
}
.ui-list-item:hover { background: var(--bg-hover); }
.ui-list-item.active { background: var(--bg-hover); }

/* ===== Layout: 3-column ===== */
.app {
  display: flex;
  height: 100vh;
  width: 100vw;
}

/* Pin the column order with explicit flex `order` so the layout
   doesn't depend on DOM source order. Both sidebars are injected
   asynchronously into `display: contents` wrappers (AppShell's
   sidebarRef / rightSidebarRef and PageShell's hostRef), and at very
   wide viewports the injection timing was occasionally putting the
   right sidebar to the left of the main column. order is the cheapest
   deterministic fix — left = 0, resize = 1, main = 2, right = 99. */
/* No direct-child combinator here: the chat shell and right sidebar
   are now rendered through ``display: contents`` wrappers in
   AppShell, so .main and .right-sidebar are GRANDCHILDREN of .app
   in the DOM tree. From flexbox's POV they're still flex items of
   .app, but ``.app > .main`` doesn't match them — and without that
   match, .main falls back to ``order: 0`` and ends up before
   sidebarResize, which pushed the drag handle all the way to the
   right viewport edge.

   Descendant selectors below work regardless of how deeply the
   wrappers nest, as long as the actual layout-participating element
   carries the matching class / id. */
.app #sidebar,
.app .sidebar:not(.right-sidebar) { order: 0; }
.app .col-resize,
.app #sidebarResize { order: 1; }
.app .main { order: 2; flex: 1; min-width: 0; }
.app #rightSidebar,
.app .right-sidebar,
.app #detailResize { order: 99; }
.app #rightSidebar { margin-left: auto; }

/* Column resize handle */
/* Zero layout width — main content sits flush against the sidebar's own
   border-right. The drag handle is an invisible absolute strip overlapping
   the border (extends 2px into each side but never painted, so it doesn't
   shrink any visible region). */
.col-resize {
  width: 0;
  flex-shrink: 0;
  position: relative;
  z-index: 10;
}
.col-resize::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -2px;
  width: 5px;
  cursor: col-resize;
}

/* ===== Scrollbar =====
   Custom overlay scrollbars: native scrollbars are hidden on targeted scroll
   containers (`.scroll-overlay`), and js/scrollbar.js draws a thin floating
   thumb on top of the content. The thumb is theme-aware, drag-to-scroll, and
   takes no layout width. Containers not marked `.scroll-overlay` fall back to
   the OS default scrollbar. */
.scroll-overlay {
  scrollbar-width: none; /* Firefox: hide native */
}
.scroll-overlay::-webkit-scrollbar {
  display: none; /* WebKit: hide native */
}

.overlay-thumb {
  position: fixed;
  width: 6px;
  right: 0;
  border-radius: 6px;
  background: var(--border-light);
  pointer-events: auto;
  z-index: 9999;
  opacity: 0;
  transition: opacity 0.25s, background 0.15s, width 0.12s;
  cursor: default;
  user-select: none;
}
.overlay-thumb.visible { opacity: 0.85; }
.overlay-thumb:hover,
.overlay-thumb.dragging {
  background: var(--text-muted);
  width: 8px;
}

/* ===== Responsive ===== */
/* Narrow viewport: BOTH sidebars are always position: fixed so the
   collapse/expand animation only changes their own width and never
   reflows the main column. The main column reserves fixed 48px
   gutters on each side for the collapsed rails. Expanding a sidebar
   simply grows its width over the main column — covering the topbar
   badges and chat content under it — without pushing main around.
   This kills the previous mid-transition jump where collapsing made
   main first shrink (sidebar entering flow at full width) and then
   expand back as the sidebar's width animated to 48px. */
@media (max-width: 900px) {
  .menu-btn { display: block; }

  .sidebar {
    position: fixed; top: 0; bottom: 0; z-index: 100;
  }
  .sidebar:not(.right-sidebar) { left: 0; }
  .sidebar:not(.right-sidebar):not(.collapsed) {
    box-shadow: var(--shadow-sidebar-left);
  }
  .right-sidebar,
  .detail-panel {
    position: fixed; top: 0; bottom: 0; right: 0; z-index: 100;
  }
  .right-sidebar:not(.collapsed),
  .detail-panel:not(.collapsed) {
    box-shadow: var(--shadow-sidebar-right);
  }

  /* No gutter reserved — collapsed sidebars are position:fixed at the
     edges (z-index 100) and may overlap main content briefly when
     dragging. User preferred edge-to-edge main over a 48px reserve. */
}

@media (max-width: 600px) {
  .message { padding: 12px 16px; }
  .input-area { padding: 12px 16px 16px; }
  .welcome-examples { flex-direction: column; }
}

/* Chats nav icon: two bubbles nudge apart on hover (Claude.ai detail) */
.sidebar-nav-chats .nav-chats-bubble-l,
.sidebar-nav-chats .nav-chats-bubble-r {
  transition: transform 0.2s ease;
}
.sidebar-nav-chats:hover .nav-chats-bubble-l { transform: translateX(-0.5px); }
.sidebar-nav-chats:hover .nav-chats-bubble-r { transform: translateX(0.5px); }
.sidebar-nav-chats:active .nav-chats-bubble-l,
.sidebar-nav-chats:active .nav-chats-bubble-r {
  transform: translateX(0);
}

/* ===== Sidebar collapsed-state cascade (was 02-sidebar.css) =====
   These target descendant stable classes across the sidebar tree, so
   they stay a single global cascade rather than threading an `open`
   prop through every nav row / the portal-mounted footer. */
/* Was: ``.sidebar.collapsed * { overflow: hidden !important; }``
 * which clipped icon-button focus rings + hover backgrounds across
 * every descendant. Only scrollable inner regions actually need
 * overflow suppression when collapsed; the rest can keep their
 * default overflow so icons render fully. */
.sidebar.collapsed,
.sidebar.collapsed .sidebar-scroll,
.sidebar.collapsed [data-scrollable] {
  overflow: hidden !important;
  scrollbar-width: none !important;
}
/* Nav items in collapsed sidebar:
 *  - keep them left-aligned at the original padding so icons stay at
 *    the SAME x position as when expanded — no "center, then move"
 *    visual reflow. Sidebar's overflow:hidden clips the label area on
 *    the right.
 *  - label / action just disappear; icons hold their column. */
.sidebar.collapsed .sidebar-nav-label { display: none; }
.sidebar.collapsed .sidebar-nav-action { display: none; }

/* React UserMenuFooter companion-class overrides — base styling
   lives in user-menu-footer.module.css. Only sidebar-level concerns
   (collapse animation, centered avatar) go here.

   Claude's animation pattern, replicated 1:1:
   - sidebar width: instant snap (no transition on width itself)
   - info text: opacity 1→0 over 150ms ease-out (cubic-bezier .165 .84 .44 1)
   - button gap: 12→0 over 300ms (declared on the .trigger in module CSS)
   The width visually appears smooth because the info fades while the
   inner content reflows. */
.user-menu-footer-info {
  transition: opacity 0.15s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.sidebar.collapsed .user-menu-footer-info {
  opacity: 0;
  pointer-events: none;
}
/* Footer trigger in collapsed sidebar:
 *  - height stays 64px so the top divider line stays at the same y
 *  - padding-left is a clamp on the trigger's own width (declared in
 *    user-menu-footer.module.css), so the avatar stays at x=16 during
 *    most of the collapse and only slides leftward when the sidebar
 *    width gets close to 53 (where it would otherwise clip the avatar)
 *  - chevron hidden, info opacity fades via the rule above */
.sidebar.collapsed .user-menu-footer-trigger > svg:last-child { display: none; }

/* User-message avatar (chat stream — React <UserBubble />). */
.user-avatar {
  box-sizing: content-box;
  width: 36px; height: 36px;
  border-radius: 9999px;
  border: 0.5px solid transparent;
  background: var(--text-primary);
  color: var(--bg-secondary);
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 530;
  flex-shrink: 0;
  transition: border-color 0.15s, opacity 0.15s;
}

/* Provider letter glyph (was 08-dropdown.css). Stays global: emitted
   as a raw HTML string by `lib/conversations.ts::channelIcon` and used
   by the settings provider list — neither can take a hashed module
   class. */
.provider-icon {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #f5f5f2;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
}
.provider-icon img {
  width: 72%;
  height: 72%;
  object-fit: contain;
}
.provider-icon-letter {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
}

/* ===== Composer keyframes ==============================================
   Defined globally (not in *.module.css) because turbopack's CSS-module
   pass mangled the keyframes names while leaving the corresponding
   ``animation: <name>`` reference unmangled — the animation never
   matched, so the fn-form header/body stayed at opacity:0 and the
   form rendered blank. Keeping the keyframes here keeps the names
   stable. */
@keyframes fnFormFadeIn {
  to { opacity: 1; }
}
@keyframes outgoingLayerFade {
  from { opacity: 1; }
  to { opacity: 0; }
}
@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%); }
}

/* ===== Running indicator keyframes (sidebar + branches) ================
   Same turbopack workaround as above: keep keyframe names global so the
   ``animation: <name>`` reference in CSS modules + plain CSS resolves
   correctly. Used by:
     - web/components/sidebar/sidebar.module.css → .convRunning ::before
     - web/app/styles/right-dock.css            → .branch-item.is-running
   Both animations apply to a pseudo-element so the row's text stays
   readable through the colored gradient sweep. */
@keyframes convRunningFlow {
  0%   { background-position:   0%   50%; }
  100% { background-position: -200%  50%; }
}
@keyframes convRunningBreathe {
  0%, 100% { opacity: 0.65; }
  50%      { opacity: 1.00; }
}
@keyframes convFinishingWipe {
  0%   { clip-path: inset(0   0% 0 0); }
  100% { clip-path: inset(0 100% 0 0); }
}

/* Sidebar conversation-row running indicator. Lives here (not in
   sidebar.module.css) because Turbopack's CSS-module pass mangles the
   ``animation: <name>`` value references — leaving them pointing at
   hashed keyframe names that the global @keyframes blocks above don't
   match. Keeping selector + animation reference together in plain CSS
   sidesteps the mangling. Classes are applied via string literals in
   sessions-list.tsx (not styles.convRunning). */
.convRunning,
.convFinishing {
  position: relative;
  isolation: isolate;
}

.convRunning::before,
.convFinishing::before {
  content: "";
  position: absolute;
  inset: 0;
  /* Sit above the row's own background-color (bg-bg-hover) but below
     the text/icons. Row has isolation:isolate so this z-index is
     scoped to the row's stacking context. z-index:-1 used to put
     the pseudo BEHIND the row bg, making the glow invisible whenever
     the row was selected/hovered. */
  z-index: 0;
  pointer-events: none;
  border-radius: inherit;
  background-image: linear-gradient(
    100deg,
    rgba(79,  140, 255, 0.28) 0%,
    rgba(160, 107, 255, 0.32) 25%,
    rgba(62,  207, 142, 0.28) 50%,
    rgba(160, 107, 255, 0.32) 75%,
    rgba(79,  140, 255, 0.28) 100%
  );
  background-size: 200% 100%;
  background-repeat: repeat-x;
  box-shadow:
    inset 0 0 14px rgba(79,  140, 255, 0.22),
    0 0 6px rgba(160, 107, 255, 0.20);
}

.convRunning::before {
  animation:
    convRunningFlow 2.6s linear infinite,
    convRunningBreathe 1.8s ease-in-out infinite;
}

.convFinishing::before {
  animation: convFinishingWipe 1.1s cubic-bezier(0.4, 0, 0.6, 1) forwards;
}

/* One-shot 360° spin for the avatar-picker "↻ Regenerate" glyph.
   Non-infinite (no ``infinite`` keyword) so it rotates once per
   click and settles back at 0°; the component re-triggers it by
   toggling the animation off/on across a frame. */
@keyframes avatarSpin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Marching-ants flow for attach-reference edges. The negative
   stroke-dashoffset makes the dashes travel along the path in the
   draw direction (source → anchor), so the eye sees content moving
   INTO the anchor turn. */
@keyframes attachEdgeFlow {
  to { stroke-dashoffset: -16; }
}
.history-edge.attach-edge {
  animation: attachEdgeFlow 0.9s linear infinite;
}
/* Pulse around DAG mini-map nodes whose async task is currently
   running. is-running class is toggled by branches-panel via a
   MutationObserver on .history-body so the indicator survives
   history-graph re-renders. */
@keyframes historyNodePulse {
  0%, 100% { filter: drop-shadow(0 0 2px var(--accent-blue));  }
  50%      { filter: drop-shadow(0 0 7px var(--accent-blue));  }
}
.history-node.is-running circle,
.history-node.is-running ellipse,
.history-node.is-running rect,
.history-node.is-running path,
.history-node.is-running polygon {
  animation: historyNodePulse 1.2s ease-in-out infinite;
}
