diff --git a/frontend/src/components/brand/Logo.test.tsx b/frontend/src/components/brand/Logo.test.tsx
new file mode 100644
index 0000000..2bce8cc
--- /dev/null
+++ b/frontend/src/components/brand/Logo.test.tsx
@@ -0,0 +1,23 @@
+import { render, screen } from '@testing-library/react';
+import { describe, it, expect } from 'vitest';
+import { Logo } from './Logo';
+
+describe('Logo', () => {
+ it('renders the MIMIC wordmark by default', () => {
+ render();
+ expect(screen.getByLabelText(/Breach & Attack Simulation/i)).toBeInTheDocument();
+ expect(screen.getByText('MIMIC')).toBeInTheDocument();
+ expect(screen.getByText('BAS')).toBeInTheDocument();
+ });
+
+ it('hides ancillary metadata in mark variant', () => {
+ render();
+ expect(screen.getByText('MIMIC')).toBeInTheDocument();
+ expect(screen.queryByText('BAS')).not.toBeInTheDocument();
+ });
+
+ it('renders build identifier in full variant', () => {
+ render();
+ expect(screen.getByText('0.1.0')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/brand/Logo.tsx b/frontend/src/components/brand/Logo.tsx
new file mode 100644
index 0000000..d25d0c6
--- /dev/null
+++ b/frontend/src/components/brand/Logo.tsx
@@ -0,0 +1,77 @@
+import { clsx } from 'clsx';
+
+/**
+ * Mimic wordmark — provisional placeholder until PR3 (RT graphic charter).
+ *
+ * Design intent: instrument-panel nameplate, not a marketing logo.
+ * Composition:
+ * [ corner-cuts ] M I M I C · BAS · v{version}
+ *
+ * The dotted middle keeps the "instrument panel readout" feel and lets the
+ * version/build identifier sit beside the wordmark without becoming a
+ * secondary brand element.
+ *
+ * When PR3 lands, replace the inner span content with the SVG glyph; the
+ * outer composition (corner marks, tracking, sibling metadata) stays.
+ */
+export type LogoVariant = 'full' | 'compact' | 'mark';
+
+interface LogoProps {
+ variant?: LogoVariant;
+ className?: string;
+ /** Build/version suffix rendered as data. Hidden in `mark`. */
+ build?: string;
+}
+
+export function Logo({ variant = 'full', className, build }: LogoProps) {
+ return (
+
+
+ MIMIC
+
+
+ {variant !== 'mark' && (
+ <>
+
+ ··
+
+
+ BAS
+
+ {build && variant === 'full' && (
+
+ {build}
+
+ )}
+ >
+ )}
+
+ );
+}
diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx
new file mode 100644
index 0000000..02e9598
--- /dev/null
+++ b/frontend/src/components/ui/Button.tsx
@@ -0,0 +1,81 @@
+import { clsx } from 'clsx';
+import type { ButtonHTMLAttributes, CSSProperties, ReactNode } from 'react';
+
+type ButtonVariant = 'primary' | 'ghost' | 'destructive' | 'subtle';
+type ButtonSize = 'sm' | 'md';
+
+interface ButtonProps extends Omit, 'children'> {
+ children: ReactNode;
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+}
+
+/**
+ * Instrument-grade button. Flat, hairline border, no rounded pills.
+ * Variants:
+ * - primary amber accent fill — high-stakes actions (start run, generate report)
+ * - destructive rust accent — abort, revoke, delete
+ * - ghost transparent, hairline border — secondary actions
+ * - subtle no border, label-system only — tertiary / inline actions
+ */
+export function Button({
+ children,
+ variant = 'ghost',
+ size = 'md',
+ className,
+ ...rest
+}: ButtonProps) {
+ const base =
+ 'inline-flex items-center justify-center gap-1.5 font-sans transition-colors disabled:opacity-50 disabled:cursor-not-allowed';
+ const sizeClass = size === 'sm' ? 'h-6 px-2 text-[11px]' : 'h-7 px-3 text-[12px]';
+
+ let style: CSSProperties = { borderRadius: 'var(--radius-sm)' };
+ let extra: string;
+
+ switch (variant) {
+ case 'primary':
+ style = {
+ ...style,
+ background: 'var(--accent-rt)',
+ color: 'var(--fg-inverse)',
+ border: '1px solid var(--accent-rt-strong)',
+ boxShadow: 'inset 0 1px 0 0 oklch(100% 0 0 / 0.15)',
+ };
+ extra = 'hover:brightness-110 active:brightness-95';
+ break;
+ case 'destructive':
+ style = {
+ ...style,
+ background: 'transparent',
+ color: 'var(--state-failed)',
+ border: '1px solid var(--state-failed)',
+ };
+ extra = 'hover:bg-[oklch(60.5%_0.175_28_/_0.12)]';
+ break;
+ case 'subtle':
+ style = {
+ ...style,
+ background: 'transparent',
+ color: 'var(--fg-muted)',
+ border: '1px solid transparent',
+ };
+ extra = 'hover:text-fg-default hover:bg-surface-3';
+ break;
+ case 'ghost':
+ default:
+ style = {
+ ...style,
+ background: 'transparent',
+ color: 'var(--fg-default)',
+ border: '1px solid var(--line-strong)',
+ };
+ extra = 'hover:bg-surface-3';
+ break;
+ }
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/ui/Panel.tsx b/frontend/src/components/ui/Panel.tsx
new file mode 100644
index 0000000..aedc713
--- /dev/null
+++ b/frontend/src/components/ui/Panel.tsx
@@ -0,0 +1,58 @@
+import { clsx } from 'clsx';
+import type { ReactNode } from 'react';
+
+interface PanelProps {
+ children: ReactNode;
+ title?: ReactNode;
+ meta?: ReactNode;
+ className?: string;
+ variant?: 'default' | 'inset';
+ /** Show the four instrument-panel corner marks. */
+ cornered?: boolean;
+}
+
+/**
+ * The base structural unit. Hairline border + optional title strip.
+ * Variants:
+ * - default — raised surface (--surface-2)
+ * - inset — recessed (--surface-inset), used for code/log readouts
+ */
+export function Panel({
+ children,
+ title,
+ meta,
+ className,
+ variant = 'default',
+ cornered,
+}: PanelProps) {
+ return (
+
+ {(title || meta) && (
+
+ {title && (
+
+ {title}
+
+ )}
+ {meta && {meta}
}
+
+ )}
+ {children}
+
+ );
+}
diff --git a/frontend/src/components/ui/Pill.tsx b/frontend/src/components/ui/Pill.tsx
new file mode 100644
index 0000000..2617c59
--- /dev/null
+++ b/frontend/src/components/ui/Pill.tsx
@@ -0,0 +1,58 @@
+import { clsx } from 'clsx';
+import type { ReactNode } from 'react';
+
+type PillTone =
+ | 'neutral'
+ | 'rt'
+ | 'soc'
+ | 'detected'
+ | 'partial'
+ | 'missed'
+ | 'pending'
+ | 'running'
+ | 'success'
+ | 'failed'
+ | 'paused'
+ | 'aborted';
+
+interface PillProps {
+ children: ReactNode;
+ tone?: PillTone;
+ className?: string;
+}
+
+const TONE: Record = {
+ neutral: {
+ bg: 'transparent',
+ fg: 'var(--fg-muted)',
+ border: 'var(--line-strong)',
+ },
+ rt: { bg: 'transparent', fg: 'var(--accent-rt)', border: 'var(--accent-rt-muted)' },
+ soc: { bg: 'transparent', fg: 'var(--accent-soc)', border: 'var(--accent-soc-muted)' },
+ detected: { bg: 'transparent', fg: 'var(--status-detected)', border: 'var(--status-detected)' },
+ partial: { bg: 'transparent', fg: 'var(--status-partial)', border: 'var(--status-partial)' },
+ missed: { bg: 'transparent', fg: 'var(--status-missed)', border: 'var(--status-missed)' },
+ pending: { bg: 'transparent', fg: 'var(--state-pending)', border: 'var(--line-strong)' },
+ running: { bg: 'transparent', fg: 'var(--state-running)', border: 'var(--state-running)' },
+ success: { bg: 'transparent', fg: 'var(--state-success)', border: 'var(--state-success)' },
+ failed: { bg: 'transparent', fg: 'var(--state-failed)', border: 'var(--state-failed)' },
+ paused: { bg: 'transparent', fg: 'var(--state-paused)', border: 'var(--state-paused)' },
+ aborted: { bg: 'transparent', fg: 'var(--state-aborted)', border: 'var(--state-aborted)' },
+};
+
+export function Pill({ children, tone = 'neutral', className }: PillProps) {
+ const t = TONE[tone];
+ return (
+
+ {children}
+
+ );
+}
diff --git a/frontend/src/styles/fonts.css b/frontend/src/styles/fonts.css
new file mode 100644
index 0000000..abbcb5f
--- /dev/null
+++ b/frontend/src/styles/fonts.css
@@ -0,0 +1,90 @@
+/*
+ * IBM Plex family — self-hosted strategy.
+ *
+ * OPSEC stance: Mimic ships self-contained. No runtime dependency on a
+ * third-party font CDN (Google Fonts et al.). The @font-face declarations
+ * below point at /fonts/, which is served from public/fonts/.
+ *
+ * Sprint 0 status: the .woff2 files are NOT yet vendored — chore/vendor-fonts
+ * follow-up will drop the OFL-licensed binaries into public/fonts/. Until
+ * then, the `font-display: swap` directive renders the system fallback
+ * (ui-sans-serif / ui-monospace) so the UI remains usable.
+ *
+ * Source: https://github.com/IBM/plex (OFL-1.1). Files needed:
+ * public/fonts/IBMPlexSans-{400,500,600,700}.woff2
+ * public/fonts/IBMPlexSansCondensed-{400,500,600,700}.woff2
+ * public/fonts/IBMPlexMono-{400,500,600}.woff2
+ */
+
+@font-face {
+ font-family: 'IBM Plex Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url('/fonts/IBMPlexSans-400.woff2') format('woff2');
+}
+@font-face {
+ font-family: 'IBM Plex Sans';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url('/fonts/IBMPlexSans-500.woff2') format('woff2');
+}
+@font-face {
+ font-family: 'IBM Plex Sans';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url('/fonts/IBMPlexSans-600.woff2') format('woff2');
+}
+@font-face {
+ font-family: 'IBM Plex Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url('/fonts/IBMPlexSans-700.woff2') format('woff2');
+}
+
+@font-face {
+ font-family: 'IBM Plex Sans Condensed';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url('/fonts/IBMPlexSansCondensed-500.woff2') format('woff2');
+}
+@font-face {
+ font-family: 'IBM Plex Sans Condensed';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url('/fonts/IBMPlexSansCondensed-600.woff2') format('woff2');
+}
+@font-face {
+ font-family: 'IBM Plex Sans Condensed';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url('/fonts/IBMPlexSansCondensed-700.woff2') format('woff2');
+}
+
+@font-face {
+ font-family: 'IBM Plex Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url('/fonts/IBMPlexMono-400.woff2') format('woff2');
+}
+@font-face {
+ font-family: 'IBM Plex Mono';
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url('/fonts/IBMPlexMono-500.woff2') format('woff2');
+}
+@font-face {
+ font-family: 'IBM Plex Mono';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url('/fonts/IBMPlexMono-600.woff2') format('woff2');
+}
diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css
new file mode 100644
index 0000000..702b7f2
--- /dev/null
+++ b/frontend/src/styles/globals.css
@@ -0,0 +1,2 @@
+@import './fonts.css';
+@import './theme.css';
diff --git a/frontend/src/styles/theme.css b/frontend/src/styles/theme.css
new file mode 100644
index 0000000..427ed24
--- /dev/null
+++ b/frontend/src/styles/theme.css
@@ -0,0 +1,339 @@
+/*
+ * Mimic — provisional design system tokens
+ * --------------------------------------------------------------------------
+ * Aesthetic direction: instrumentation-grade telemetry.
+ * Reference points: ground-station SDR panels, oscilloscope HUDs, aviation
+ * MFDs, mission-control consoles. NOT cyberpunk, NOT shadcn, NOT marketing.
+ *
+ * Token layout
+ * 1. Primitives (--mc-*) raw values, never used directly in components
+ * 2. Semantics (--color-*, --text-*, --surface-*, --status-*, --signal-*)
+ * the API surface for the app — what components consume
+ * 3. Tailwind 4 @theme maps semantics into utility classes (bg-*, text-*…)
+ *
+ * Hosting the PR3 graphic charter
+ * When the RT charter lands, only the primitive layer (1) is touched.
+ * Components reference semantics (2). The Tailwind layer (3) is mechanical.
+ * Charter swap = ~30 lines of primitives, zero component churn.
+ */
+
+@import 'tailwindcss';
+
+/* ------------------------------------------------------------------ *
+ * 1. Primitives — raw scales (never reference directly in TSX/HTML)
+ * ------------------------------------------------------------------ */
+:root {
+ /* Graphite scale — surface foundation, cool-neutral dark */
+ --mc-graphite-50: oklch(98.4% 0.002 247);
+ --mc-graphite-100: oklch(94.2% 0.003 247);
+ --mc-graphite-200: oklch(85.6% 0.004 247);
+ --mc-graphite-300: oklch(72.8% 0.005 247);
+ --mc-graphite-400: oklch(58.2% 0.006 247);
+ --mc-graphite-500: oklch(44.5% 0.007 247);
+ --mc-graphite-600: oklch(32.1% 0.008 247);
+ --mc-graphite-700: oklch(22.8% 0.009 247);
+ --mc-graphite-800: oklch(16.4% 0.009 247);
+ --mc-graphite-850: oklch(13.2% 0.010 247);
+ --mc-graphite-900: oklch(10.2% 0.011 247);
+ --mc-graphite-950: oklch(7.4% 0.012 247);
+
+ /* Amber signal — RT operator accent (CRT-amber lineage, not neon) */
+ --mc-amber-200: oklch(94.0% 0.060 78);
+ --mc-amber-300: oklch(88.5% 0.110 76);
+ --mc-amber-400: oklch(82.2% 0.150 73);
+ --mc-amber-500: oklch(74.0% 0.165 68);
+ --mc-amber-600: oklch(63.5% 0.155 60);
+ --mc-amber-700: oklch(51.0% 0.130 52);
+
+ /* Steel blue — SOC analyst accent, stable telemetry */
+ --mc-steel-300: oklch(82.8% 0.055 235);
+ --mc-steel-400: oklch(72.5% 0.075 232);
+ --mc-steel-500: oklch(62.0% 0.090 228);
+ --mc-steel-600: oklch(51.0% 0.085 226);
+ --mc-steel-700: oklch(40.5% 0.075 224);
+
+ /* Signal: detected (good) — sage green, not jungle, not lime */
+ --mc-sage-400: oklch(76.5% 0.105 152);
+ --mc-sage-500: oklch(66.0% 0.115 148);
+ --mc-sage-600: oklch(54.5% 0.105 146);
+
+ /* Signal: missed (alert) — rust red, not stop-light red */
+ --mc-rust-400: oklch(70.5% 0.155 32);
+ --mc-rust-500: oklch(60.5% 0.175 28);
+ --mc-rust-600: oklch(50.0% 0.165 26);
+
+ /* Signal: partial (caution) — desaturated ochre between amber and rust */
+ --mc-ochre-500: oklch(70.0% 0.130 55);
+
+ /* Type — IBM Plex family. Industrial provenance, mature, OFL-licensed. */
+ --mc-font-ui:
+ 'IBM Plex Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
+ --mc-font-mono:
+ 'IBM Plex Mono', ui-monospace, 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace;
+ --mc-font-display:
+ 'IBM Plex Sans Condensed', 'IBM Plex Sans', ui-sans-serif, sans-serif;
+}
+
+/* ------------------------------------------------------------------ *
+ * 2. Semantics — the public token API
+ * ------------------------------------------------------------------ */
+:root,
+.dark {
+ /* Surface scale (z-axis: 0 = deepest, increases toward viewer) */
+ --surface-0: var(--mc-graphite-950); /* page background, void */
+ --surface-1: var(--mc-graphite-900); /* sidebar, primary chrome */
+ --surface-2: var(--mc-graphite-850); /* panels, cards */
+ --surface-3: var(--mc-graphite-800); /* raised: modal, dropdown */
+ --surface-4: var(--mc-graphite-700); /* hover lift, popovers */
+ --surface-inset: oklch(5.8% 0.012 247); /* inputs, code blocks (deeper than 0) */
+
+ /* Foreground (text on surfaces) */
+ --fg-default: var(--mc-graphite-100);
+ --fg-muted: var(--mc-graphite-300);
+ --fg-subtle: var(--mc-graphite-400);
+ --fg-faint: var(--mc-graphite-500);
+ --fg-inverse: var(--mc-graphite-950);
+
+ /* Hairlines — translucent for the instrument-panel layering effect */
+ --line-default: oklch(100% 0 0 / 0.06);
+ --line-strong: oklch(100% 0 0 / 0.12);
+ --line-faint: oklch(100% 0 0 / 0.03);
+
+ /* Role accents */
+ --accent-rt: var(--mc-amber-500); /* RT operator / lead RT */
+ --accent-rt-strong: var(--mc-amber-400);
+ --accent-rt-muted: var(--mc-amber-700);
+ --accent-soc: var(--mc-steel-400); /* SOC analyst */
+ --accent-soc-strong: var(--mc-steel-300);
+ --accent-soc-muted: var(--mc-steel-600);
+
+ /* Detection status (F7 cotation SOC) */
+ --status-detected: var(--mc-sage-500);
+ --status-detected-fg: var(--mc-graphite-950);
+ --status-partial: var(--mc-ochre-500);
+ --status-partial-fg: var(--mc-graphite-950);
+ --status-missed: var(--mc-rust-500);
+ --status-missed-fg: var(--mc-graphite-50);
+
+ /* Run / step state (F5/F6 execution) */
+ --state-pending: var(--mc-graphite-400);
+ --state-running: var(--mc-amber-500);
+ --state-success: var(--mc-sage-500);
+ --state-failed: var(--mc-rust-500);
+ --state-aborted: var(--mc-graphite-300);
+ --state-paused: var(--mc-steel-400);
+
+ /* WebSocket link health */
+ --link-up: var(--mc-sage-500);
+ --link-degraded: var(--mc-ochre-500);
+ --link-down: var(--mc-rust-500);
+
+ /* Focus ring — amber, accessible, distinct from text selection */
+ --focus-ring: var(--mc-amber-400);
+ --selection-bg: oklch(74.0% 0.165 68 / 0.28);
+
+ /* Radii — restrained, instrument-panel feel (no pill except chips) */
+ --radius-xs: 1px;
+ --radius-sm: 2px;
+ --radius-md: 3px;
+ --radius-lg: 5px;
+ --radius-pill: 999px;
+
+ /* Elevation — flat by default; depth via hairlines and inset shadows */
+ --shadow-inset: inset 0 1px 0 0 oklch(100% 0 0 / 0.04);
+ --shadow-panel: 0 1px 0 0 oklch(0% 0 0 / 0.4), inset 0 1px 0 0 oklch(100% 0 0 / 0.03);
+ --shadow-pop:
+ 0 8px 24px -8px oklch(0% 0 0 / 0.55),
+ 0 2px 6px -2px oklch(0% 0 0 / 0.4),
+ inset 0 1px 0 0 oklch(100% 0 0 / 0.04);
+ --shadow-amber-glow: 0 0 0 1px var(--mc-amber-600), 0 0 12px -2px oklch(74.0% 0.165 68 / 0.35);
+
+ /* Motion — fast, mechanical, no springy bounce */
+ --ease-mech: cubic-bezier(0.2, 0.7, 0.2, 1);
+ --ease-snap: cubic-bezier(0.4, 0, 0.1, 1);
+ --duration-instant: 80ms;
+ --duration-fast: 140ms;
+ --duration-medium: 220ms;
+}
+
+/* ------------------------------------------------------------------ *
+ * 3. Tailwind 4 @theme — map semantics into utilities
+ * ------------------------------------------------------------------ */
+@theme {
+ --font-sans: var(--mc-font-ui);
+ --font-mono: var(--mc-font-mono);
+ --font-display: var(--mc-font-display);
+
+ --color-surface-0: var(--surface-0);
+ --color-surface-1: var(--surface-1);
+ --color-surface-2: var(--surface-2);
+ --color-surface-3: var(--surface-3);
+ --color-surface-4: var(--surface-4);
+ --color-surface-inset: var(--surface-inset);
+
+ --color-fg-default: var(--fg-default);
+ --color-fg-muted: var(--fg-muted);
+ --color-fg-subtle: var(--fg-subtle);
+ --color-fg-faint: var(--fg-faint);
+ --color-fg-inverse: var(--fg-inverse);
+
+ --color-line-default: var(--line-default);
+ --color-line-strong: var(--line-strong);
+ --color-line-faint: var(--line-faint);
+
+ --color-accent-rt: var(--accent-rt);
+ --color-accent-rt-strong: var(--accent-rt-strong);
+ --color-accent-rt-muted: var(--accent-rt-muted);
+ --color-accent-soc: var(--accent-soc);
+ --color-accent-soc-strong: var(--accent-soc-strong);
+ --color-accent-soc-muted: var(--accent-soc-muted);
+
+ --color-status-detected: var(--status-detected);
+ --color-status-partial: var(--status-partial);
+ --color-status-missed: var(--status-missed);
+
+ --color-state-pending: var(--state-pending);
+ --color-state-running: var(--state-running);
+ --color-state-success: var(--state-success);
+ --color-state-failed: var(--state-failed);
+ --color-state-aborted: var(--state-aborted);
+ --color-state-paused: var(--state-paused);
+
+ --color-link-up: var(--link-up);
+ --color-link-degraded: var(--link-degraded);
+ --color-link-down: var(--link-down);
+
+ --radius-xs: var(--radius-xs);
+ --radius-sm: var(--radius-sm);
+ --radius-md: var(--radius-md);
+ --radius-lg: var(--radius-lg);
+}
+
+/* ------------------------------------------------------------------ *
+ * Base reset — instrument-grade defaults
+ * ------------------------------------------------------------------ */
+html {
+ font-family: var(--mc-font-ui);
+ font-feature-settings: 'cv11', 'ss01', 'tnum';
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+ color-scheme: dark;
+ background-color: var(--surface-0);
+ color: var(--fg-default);
+}
+
+body {
+ font-size: 13px;
+ line-height: 1.5;
+ letter-spacing: 0;
+}
+
+/* Tabular numerals everywhere data appears */
+.font-mono,
+.tabular {
+ font-variant-numeric: tabular-nums;
+ font-feature-settings: 'tnum', 'zero', 'ss01';
+}
+
+/* System labels — uppercase micro-typography */
+.label-system {
+ font-family: var(--mc-font-ui);
+ font-size: 10px;
+ font-weight: 500;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--fg-subtle);
+}
+
+/* Focus ring */
+:focus-visible {
+ outline: none;
+ box-shadow:
+ 0 0 0 1px var(--surface-0),
+ 0 0 0 3px var(--focus-ring);
+ border-radius: var(--radius-sm);
+}
+
+::selection {
+ background-color: var(--selection-bg);
+ color: var(--fg-default);
+}
+
+/* Custom scrollbars — hairline */
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--mc-graphite-700) transparent;
+}
+*::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+*::-webkit-scrollbar-thumb {
+ background-color: var(--mc-graphite-700);
+ border-radius: var(--radius-xs);
+}
+*::-webkit-scrollbar-thumb:hover {
+ background-color: var(--mc-graphite-600);
+}
+
+/* Surface grain — subtle noise on raised panels to break flat darkness */
+.surface-grain {
+ background-image:
+ radial-gradient(oklch(100% 0 0 / 0.012) 1px, transparent 1px),
+ radial-gradient(oklch(100% 0 0 / 0.008) 1px, transparent 1px);
+ background-size: 3px 3px, 7px 7px;
+ background-position: 0 0, 1px 2px;
+}
+
+/* Hairline corner mark — used at panel corners to evoke instrument cutmarks */
+.corner-mark {
+ position: relative;
+}
+.corner-mark::before,
+.corner-mark::after {
+ content: '';
+ position: absolute;
+ width: 8px;
+ height: 8px;
+ border: 1px solid var(--line-strong);
+}
+.corner-mark::before {
+ top: -1px;
+ left: -1px;
+ border-right: none;
+ border-bottom: none;
+}
+.corner-mark::after {
+ bottom: -1px;
+ right: -1px;
+ border-left: none;
+ border-top: none;
+}
+
+/* Status dot — used in lists, timelines, link-state indicators */
+.status-dot {
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background-color: currentColor;
+ box-shadow: 0 0 0 1px oklch(100% 0 0 / 0.08);
+}
+.status-dot.pulsing {
+ animation: status-pulse 1.6s var(--ease-mech) infinite;
+}
+@keyframes status-pulse {
+ 0%, 100% { opacity: 1; transform: scale(1); }
+ 50% { opacity: 0.55; transform: scale(0.85); }
+}
+
+/* Mono inline (code in narration, command snippets) */
+code {
+ font-family: var(--mc-font-mono);
+ font-size: 0.92em;
+ padding: 0.05em 0.35em;
+ background-color: var(--surface-inset);
+ border: 1px solid var(--line-default);
+ border-radius: var(--radius-xs);
+ color: var(--fg-default);
+}