mezame 0.8.2

An ACP client that bridges a local agent (Kiro CLI, Claude Agent CLI, Gemini CLI, Codex, ...) to a browser UI over WebSockets.
@import 'tailwindcss';
@import 'tw-animate-css';

@custom-variant dark (&:is(.dark *));
@custom-variant touch (&:where(@media (pointer: coarse)));
@custom-variant mouse (&:where(@media (pointer: fine)));

/* mezame design tokens.
 *
 * Dark-only today. Zinc base, green primary (matching the "connected"
 * and "thinking" states so UI chrome and live status share a family),
 * monospace throughout. User-message bubble stays blue so user turns
 * read distinctly from the green ambient UI, and agent output stays
 * amber for the same reason. */

:root {
  --background: oklch(0.19 0.04 255);
  --foreground: oklch(0.922 0 0);
  --card: oklch(0.22 0.04 255);
  --card-foreground: oklch(0.922 0 0);
  --popover: oklch(0.22 0.04 255);
  --popover-foreground: oklch(0.922 0 0);
  --primary: oklch(0.65 0.13 155);
  --primary-foreground: oklch(0.145 0 0);
  --secondary: oklch(0.26 0.035 255);
  --secondary-foreground: oklch(0.922 0 0);
  --muted: oklch(0.26 0.035 255);
  --muted-foreground: oklch(0.7 0.02 255);
  --accent: oklch(0.3 0.04 255);
  --accent-foreground: oklch(0.922 0 0);
  --destructive: oklch(0.62 0.19 25);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(0.32 0.04 255);
  --input: oklch(0.26 0.035 255);
  --ring: oklch(0.6 0.11 155);
  --radius: 0.375rem;

  --user: oklch(0.72 0.09 240);
  --agent: oklch(0.82 0.1 90);
  --sys: oklch(0.55 0 0);
  --attn-done: oklch(0.72 0.12 150);
  --attn-permission: oklch(0.78 0.12 75);
  --attn-error: oklch(0.7 0.14 25);

  /* User message bubble. Kept in the blue family so user turns read
   * distinctly from the green UI chrome (primary/ring) and the amber
   * agent output: three different hues for three different roles. */
  --user-bubble: oklch(0.72 0.11 235);

  /* Virtual-keyboard inset, populated by useKeyboardInset() at runtime.
   * 0 when no keyboard is open or when the visualViewport API is
   * unavailable. Consumers (composer, log pane) add this to their
   * positioning so content lifts above the keyboard. */
  --mz-kb-inset: 0px;

  /* iOS safe-area insets, with a 0 fallback for platforms that do not
   * report them (desktop browsers, Android without a display cutout).
   * Bound once here so the rest of the app can reference them as plain
   * CSS variables rather than repeating env() calls. */
  --mz-safe-top: env(safe-area-inset-top, 0px);
  --mz-safe-bottom: env(safe-area-inset-bottom, 0px);
  --mz-safe-left: env(safe-area-inset-left, 0px);
  --mz-safe-right: env(safe-area-inset-right, 0px);
}

.dark {
  color-scheme: dark;
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --radius-sm: calc(var(--radius) - 2px);
  --radius-md: var(--radius);
  --radius-lg: calc(var(--radius) + 2px);
  --radius-xl: calc(var(--radius) + 4px);
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
}

@layer base {

  html,
  body,
  #root {
    height: 100%;
  }

  body {
    font-family: var(--font-mono);
    font-size: 14px;
    background-color: var(--background);
    color: var(--foreground);
  }

  * {
    border-color: var(--border);
  }
}

/* Tooltip content: shown only for fine pointers (mouse, trackpad).
 * Suppressed on coarse pointers (touch) where a tap would leave the
 * tooltip lingering with no dismiss affordance. Written directly in
 * CSS rather than as a Tailwind `mouse:` variant because the Tailwind
 * JIT resolves variants at build time, and a stale compiled bundle in
 * `ui/dist/` would silently hide all tooltips in production. */
.tt-fine-only {
  display: none;
}

@media (pointer: fine) {
  .tt-fine-only {
    display: block;
  }
}

/* Tab pulse keyframes. Hardcoded RGB values (no custom-property
 * indirection, no color-mix, no @layer) so nothing in Tailwind's
 * pipeline can flatten them. Applied via inline style from TabBar.tsx
 * so they win any cascade fight with hover utilities. */
@keyframes mezame-pulse-orange {

  0%,
  100% {
    background-color: rgba(217, 163, 63, 0.25);
  }

  50% {
    background-color: rgba(217, 163, 63, 0.6);
  }
}

/* Traveling-border animation for busy-in-background tabs. Uses
 * `@property --busy-border-angle` to make a CSS custom property
 * animatable; the conic gradient rotates around the tab while the
 * inner fill stays constant. Supported in all current browsers
 * (Chrome 85+, Safari 16.4+, Firefox 128+). When unsupported the
 * angle stays at 0 so the tab shows a static green outline instead
 * of animating — acceptable graceful degradation. */
@property --busy-border-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@keyframes mezame-border-spin {
  to {
    --busy-border-angle: 360deg;
  }
}

.tab-busy-border {
  /* Thicker border while busy (2px vs. 1px everywhere else) so more of
   * the rotating glow is visible. Padding is trimmed by 1px to keep
   * the overall tab footprint identical to neutral tabs. */
  border-width: 2px;
  border-color: transparent;
  padding-left: calc(0.625rem - 1px);
  padding-right: calc(0.625rem - 1px);
  /* Two-layer background: an **opaque** inner fill clipped to the
   * padding box, and a conic gradient clipped to the border box. The
   * padding-box fill hides the center of the gradient so the rotating
   * light only shows on the border itself. The gradient is mostly
   * transparent with a narrow bright arc, so only a travelling glow
   * moves around the outline, not a rotating half-disc. */
  background:
    linear-gradient(#18243a, #18243a) padding-box,
    conic-gradient(from var(--busy-border-angle),
      transparent 0deg,
      transparent 280deg,
      rgba(110, 196, 140, 0.95) 325deg,
      transparent 360deg) border-box;
  animation: mezame-border-spin 2s linear infinite;
}

.tab-busy-border:hover {
  background:
    linear-gradient(#1f2e47, #1f2e47) padding-box,
    conic-gradient(from var(--busy-border-angle),
      transparent 0deg,
      transparent 280deg,
      rgba(110, 196, 140, 0.95) 325deg,
      transparent 360deg) border-box;
}

/* Honour the user's OS-level "reduce motion" preference. The
 * conic-gradient chase and the connecting pulse are eye-catching by
 * design but distracting for users with motion sensitivity; both
 * fall back to a static resting frame, which still carries the
 * status colour so no signal is lost. */
@media (prefers-reduced-motion: reduce) {
  .tab-busy-border {
    animation: none;
  }
}

/* Pause the conic-gradient chase when the browser tab is hidden.
 * `data-visibility` is set by App.tsx on `visibilitychange`. Running
 * an animation no one can see wastes CPU, matters more on mobile. */
html[data-visibility="hidden"] .tab-busy-border {
  animation: none;
}

@layer utilities {
  .scrollbar-thin::-webkit-scrollbar {
    width: 8px;
    height: 8px;
  }

  .scrollbar-thin::-webkit-scrollbar-track {
    background: transparent;
  }

  .scrollbar-thin::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 4px;
  }

  .scrollbar-thin::-webkit-scrollbar-thumb:hover {
    background: var(--muted-foreground);
  }
}