Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions packages/extension/media/brand.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* brand.css — style variables only. Prefer --vscode-* (inherit the theme);
* literals are reserved for brand identity. */

:root {
/* color */
--color-accent: #FFF676;
--color-on-accent: #000000;
--color-ok: var(--vscode-testing-iconPassed);
--color-fail: var(--vscode-errorForeground);
--color-run: var(--vscode-progressBar-background);
--color-dim: var(--vscode-descriptionForeground);

/* text */
--text-font: var(--vscode-font-family);
--text-mono: var(--vscode-editor-font-family, monospace);
--text-body: var(--vscode-font-size, 12px);
--text-label: 0.72em;
--text-small: 0.8em;
--text-value: 1.08em;
--text-hero: 1.3em;

/* space */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 12px;
--space-lg: 16px;

/* square */
--square-dot: 8px;
--square-icon: 16px;

/* border */
--border-width: 1px;
--border-radius: 4px;
--border-radius-round: 1000px;
--border-color: var(--vscode-widget-border, var(--vscode-panel-border));
--border-color-hero: color-mix(in srgb, var(--color-accent) 45%, var(--vscode-panel-border));

/* background */
--bg-box: var(--vscode-editorWidget-background, transparent);
--bg-plot: var(--vscode-editor-background);
}
52 changes: 0 additions & 52 deletions packages/extension/media/inspector.css

This file was deleted.

19 changes: 0 additions & 19 deletions packages/extension/media/inspector.html

This file was deleted.

13 changes: 13 additions & 0 deletions packages/extension/media/layout.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* layout.css — formal layout selectors. Composition only; values from brand.css. */
* { box-sizing: border-box; }
.stack { display: flex; flex-direction: column; gap: var(--space-md); }
.row { display: flex; align-items: center; gap: var(--space-md); }
.wrap { flex-wrap: wrap; }
.grid-fit { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--grid-min, 112px), 1fr)); gap: var(--space-sm); }
.grow { flex: 1; }
.push-end { margin-left: auto; }
.scroll-y { overflow-y: auto; }
.gap-xs { gap: var(--space-xs); }
.gap-sm { gap: var(--space-sm); }
.gap-lg { gap: var(--space-lg); }
.pad-lg { padding: var(--space-lg); }
17 changes: 17 additions & 0 deletions packages/extension/media/ui/atoms/icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Icon atoms. The mark is the <0||0> brand ket — intentionally not native.

import { defineStyle } from "../style";

defineStyle("mark", `
.mark { font-family: var(--text-mono);
letter-spacing: 1px; font-weight: 700;
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius); padding: 1px 7px; }
`);

export function mark(): HTMLSpanElement {
const el = document.createElement("span");
el.className = "mark";
el.textContent = "<0||0>";
return el;
}
36 changes: 36 additions & 0 deletions packages/extension/media/ui/atoms/pill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Pill atom — a status indicator. State is a class applied here, in TS.

import { defineStyle } from "../style";

defineStyle("pill", `
.pill { font-size: var(--text-small); font-weight: 600; letter-spacing: 0.5px;
text-transform: uppercase; padding: var(--space-xs) var(--space-md);
border-radius: var(--border-radius-round);
border: var(--border-width) solid currentColor;
display: inline-flex; align-items: center; gap: var(--space-sm); }
.pill::before { content: ""; width: var(--square-dot); height: var(--square-dot);
border-radius: 50%; background: currentColor; }
.pill.idle { color: var(--color-dim); }
.pill.running { color: var(--color-run); }
.pill.running::before { animation: pill-pulse 1.1s ease-in-out infinite; }
.pill.done { color: var(--color-ok); }
.pill.failed { color: var(--color-fail); }
@keyframes pill-pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.35; transform: scale(0.7); } }
`);

export type PillState = "idle" | "running" | "done" | "failed";

export interface PillAtom {
el: HTMLSpanElement;
set(state: PillState, label: string): void;
}

export function pill(state: PillState = "idle", label = state): PillAtom {
const el = document.createElement("span");
const set = (s: PillState, l: string) => {
el.className = "pill " + s;
el.textContent = l;
};
set(state, label);
return { el, set };
}
23 changes: 23 additions & 0 deletions packages/extension/media/ui/atoms/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Text atom — a span that owns its text content.

import { defineStyle } from "../style";

defineStyle("text", `
.mono { font-family: var(--text-mono); }
.dim { color: var(--color-dim); }
.small { font-size: var(--text-small); }
.label-k { font-size: var(--text-label); text-transform: uppercase;
letter-spacing: 0.6px; font-weight: 600; color: var(--color-dim); }
`);

export interface TextAtom {
el: HTMLSpanElement;
set(text: string): void;
}

export function text(className = "", initial = ""): TextAtom {
const el = document.createElement("span");
if (className) el.className = className;
el.textContent = initial;
return { el, set(t) { el.textContent = t; } };
}
35 changes: 35 additions & 0 deletions packages/extension/media/ui/components/metric.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Metric component — one labeled number card. hero = the number that matters.

import { defineStyle } from "../style";
import { text } from "../atoms/text";

defineStyle("metric", `
.metric { background: var(--bg-box);
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius); padding: var(--space-sm) var(--space-md);
display: flex; flex-direction: column; gap: var(--space-xs); }
.metric .v { font-family: var(--text-mono); font-size: var(--text-value); }
.metric.hero { border-color: var(--border-color-hero); }
.metric.hero .v { font-size: var(--text-hero); font-weight: 600; }
`);

export interface Metric {
el: HTMLDivElement;
value(v: string): void;
label(l: string): void;
clear(): void;
}

export function metric(labelText: string, opts: { hero?: boolean } = {}): Metric {
const el = document.createElement("div");
el.className = opts.hero ? "metric hero" : "metric";
const l = text("label-k", labelText);
const v = text("v", "–");
el.append(l.el, v.el);
return {
el,
value: (t) => v.set(t),
label: (t) => l.set(t),
clear: () => v.set("–"),
};
}
74 changes: 74 additions & 0 deletions packages/extension/media/ui/components/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Preview component — double-buffered image host with a placeholder overlay.
// Preloads each frame into the hidden buffer and flips opacity on decode, so
// iter frames swap at 5 Hz with zero flicker.

import { defineStyle } from "../style";
import { text } from "../atoms/text";
import { mark } from "../atoms/icon";

defineStyle("preview", `
.preview-host { flex: 1 1 240px; min-height: 240px; min-width: 0; position: relative;
background: var(--bg-plot);
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius); padding: var(--space-sm);
display: grid; place-items: stretch; overflow: hidden; }
.preview-host img { grid-column: 1; grid-row: 1; width: 100%; height: 100%;
object-fit: contain; display: block; opacity: 0;
transition: opacity 120ms ease; }
.preview-placeholder { place-self: center; text-align: center; opacity: 0.55;
display: flex; flex-direction: column; align-items: center;
gap: var(--space-sm); }
.preview-placeholder .mark { font-size: var(--text-hero); padding: var(--space-xs) var(--space-md); opacity: 0.8; }
.preview-placeholder .hint { font-style: italic; max-width: 240px; line-height: 1.5; }
`);

export interface Preview {
el: HTMLDivElement;
/** Show a frame; onShown fires after the buffer flip. */
show(url: string, onShown: () => void): void;
/** Clear both buffers and surface the placeholder with a hint. */
waiting(hint: string): void;
}

export function preview(initialHint: string): Preview {
const el = document.createElement("div");
el.className = "preview-host";
const a = document.createElement("img");
const b = document.createElement("img");
const hint = text("hint", initialHint);
const placeholder = document.createElement("div");
placeholder.className = "preview-placeholder";
placeholder.append(mark(), hint.el);
el.append(a, b, placeholder);
let visible = a;

return {
el,
show(url, onShown) {
placeholder.style.display = "none";
const incoming = visible === a ? b : a;
const outgoing = visible;
const flip = () => {
incoming.style.opacity = "1";
outgoing.style.opacity = "0";
visible = incoming;
onShown();
};
incoming.src = url;
if (typeof incoming.decode === "function") {
incoming.decode().then(flip).catch(flip);
} else {
incoming.addEventListener("load", function once() {
incoming.removeEventListener("load", once);
flip();
});
}
},
waiting(hintText) {
a.style.opacity = "0";
b.style.opacity = "0";
hint.set(hintText);
placeholder.style.display = "flex";
},
};
}
13 changes: 13 additions & 0 deletions packages/extension/media/ui/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Style registry — atoms/components/views own their styles in TS, injected
// once per key via constructable stylesheets (not governed by style-src CSP).
// Values come from brand.css variables; layout comes from layout.css selectors.

const registered = new Set<string>();

export function defineStyle(key: string, css: string): void {
if (registered.has(key)) return;
registered.add(key);
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
}
Loading
Loading