feat(frontend): provisional design system tokens + Logo placeholder (F0.2)

Aesthetic direction: instrumentation-grade telemetry (mission-control / SDR ops),
NOT shadcn defaults, NOT generic AI/SaaS. Mature palette: graphite surface scale,
CRT-amber for RT accent, steel-blue for SOC accent, sage/ochre/rust for detection
status — no neon, no rainbow.

Token layout designed to host the PR3 graphic charter without component churn:
  1. Primitives (--mc-*)        raw OKLCH scales
  2. Semantics (--accent-*, --status-*, --state-*, --surface-*, --line-*, …)
  3. Tailwind @theme mapping    semantic tokens → utilities

Includes:
- src/styles/theme.css       full token surface + base reset + scrollbars + grain
- src/styles/fonts.css       IBM Plex @font-face (self-host only)
- src/styles/globals.css     entry CSS file
- Logo (full/compact/mark) with corner-mark composition
- Panel, Pill, Button primitives reading exclusively from semantic tokens
- Logo.test.tsx (3 cases, Vitest + Testing Library)
This commit is contained in:
ux-frontend
2026-05-21 20:30:41 +02:00
parent 80ca4641a3
commit 1562478a54
8 changed files with 728 additions and 0 deletions

View File

@@ -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(<Logo />);
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(<Logo variant="mark" />);
expect(screen.getByText('MIMIC')).toBeInTheDocument();
expect(screen.queryByText('BAS')).not.toBeInTheDocument();
});
it('renders build identifier in full variant', () => {
render(<Logo build="0.1.0" />);
expect(screen.getByText('0.1.0')).toBeInTheDocument();
});
});

View File

@@ -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 (
<span
className={clsx(
'inline-flex items-center gap-3 select-none corner-mark px-2.5 py-1.5',
className,
)}
aria-label="Mimic — Breach & Attack Simulation"
>
<span
className="font-display font-medium text-fg-default"
style={{
letterSpacing: '0.32em',
fontSize: variant === 'compact' ? '11px' : '13px',
}}
>
MIMIC
</span>
{variant !== 'mark' && (
<>
<span
aria-hidden="true"
className="font-mono text-fg-faint"
style={{ fontSize: '9px', letterSpacing: '0.15em' }}
>
··
</span>
<span
className="label-system"
style={{
fontSize: variant === 'compact' ? '8.5px' : '9.5px',
color: 'var(--accent-rt)',
letterSpacing: '0.18em',
}}
>
BAS
</span>
{build && variant === 'full' && (
<span
aria-hidden="true"
className="font-mono text-fg-faint tabular"
style={{ fontSize: '9.5px' }}
>
{build}
</span>
)}
</>
)}
</span>
);
}

View File

@@ -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<ButtonHTMLAttributes<HTMLButtonElement>, '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 (
<button type="button" className={clsx(base, sizeClass, extra, className)} style={style} {...rest}>
{children}
</button>
);
}

View File

@@ -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 (
<section
className={clsx(
'relative',
cornered && 'corner-mark',
className,
)}
style={{
backgroundColor: variant === 'inset' ? 'var(--surface-inset)' : 'var(--surface-2)',
border: '1px solid var(--line-default)',
borderRadius: 'var(--radius-md)',
boxShadow: variant === 'default' ? 'var(--shadow-panel)' : 'none',
}}
>
{(title || meta) && (
<header
className="flex items-center justify-between gap-3 px-3 py-2 border-b"
style={{ borderColor: 'var(--line-default)' }}
>
{title && (
<h2 className="label-system" style={{ color: 'var(--fg-default)' }}>
{title}
</h2>
)}
{meta && <div className="label-system">{meta}</div>}
</header>
)}
<div>{children}</div>
</section>
);
}

View File

@@ -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<PillTone, { bg: string; fg: string; border: string }> = {
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 (
<span
className={clsx('inline-flex items-center gap-1.5 label-system px-1.5 py-0.5', className)}
style={{
color: t.fg,
border: `1px solid ${t.border}`,
background: t.bg,
borderRadius: 'var(--radius-sm)',
}}
>
{children}
</span>
);
}

View File

@@ -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');
}

View File

@@ -0,0 +1,2 @@
@import './fonts.css';
@import './theme.css';

View File

@@ -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);
}