diff --git a/components/artifacts/visual-card.tsx b/components/artifacts/visual-card.tsx
index 4ec956a..efe7ab8 100644
--- a/components/artifacts/visual-card.tsx
+++ b/components/artifacts/visual-card.tsx
@@ -2,7 +2,7 @@
import { useContext, useState } from "react";
import { ChatActionsContext } from "@/components/chat";
-import { DownloadIcon, MaximizeIcon } from "lucide-react";
+import { ContrastIcon, DownloadIcon, MaximizeIcon } from "lucide-react";
import { Toggle } from "@/components/settings/controls";
import { useSessionMermaid } from "@/components/session-overlay";
import { downloadFile } from "@/components/artifacts/download";
@@ -99,11 +99,47 @@ export function VisualOutputCard({
);
}
return (
-
-
+ /* NO CARD CHROME (owner round, 2026-10-07): the figure is document
+ material — no border, no padding box. Image first, then the CAPTION
+ ROW below: "Figure" left, the controls right, both edges following
+ the IMAGE's own width (the wrapper carries the natural-sizing law,
+ so the row hugs a centered narrow figure, not the column). */
+
+ {/* Natural sizing (owner, 2026-10-07): a declared-width svg sits at
+ ITS size; max-w-full still shrinks oversized ones to fit. No
+ declared size (viewBox-only, %-width) keeps the full-width law.
+ The invert filter rides the bg-white holder, so the backing flips
+ white→black together with the strokes; transparent areas ARE the
+ backing. */}
+
+
Figure
-
+ {/* The invert affordance (owner round, 2026-10-07): the disc IS
+ the half-filled contrast circle; the tooltip names it. */}
+ }
+ />
- {/* Natural sizing (owner, 2026-10-07): a declared-width svg sits at
- ITS size — the white figure area hugs it and centers (mx-auto),
- max-w-full still shrinks oversized ones to fit the card. No
- declared size (viewBox-only, %-width) keeps the old full-width
- law. The invert filter rides the bg-white holder, so the backing
- flips white→black together with the strokes; transparent areas
- ARE the backing. */}
-
);
}
diff --git a/components/settings/controls.tsx b/components/settings/controls.tsx
index 13199fc..c7ee2f6 100644
--- a/components/settings/controls.tsx
+++ b/components/settings/controls.tsx
@@ -9,7 +9,7 @@
* control together) and the control itself locks — the row owns the dim.
*/
-import { useState } from "react";
+import { useState, type ReactNode } from "react";
import { cn } from "@/lib/utils";
/** A section block: pronounced mono heading + content. */
@@ -87,12 +87,20 @@ export function Toggle({
disabled,
onChange,
label,
+ discIcon,
+ title,
}: {
checked: boolean;
disabled?: boolean;
onChange: (next: boolean) => void;
/** Accessible name for the switch role. */
label: string;
+ /** Optional icon disc (owner round, 2026-10-07): the figure cards' invert
+ * toggle renders the half-filled contrast circle AS the disc — the icon
+ * carries the meaning; plain toggles keep the plain disc. */
+ discIcon?: ReactNode;
+ /** Optional native tooltip on the switch. */
+ title?: string;
}) {
return (
);
}
diff --git a/tests/demos.test.tsx b/tests/demos.test.tsx
index 990814f..1f14c9e 100644
--- a/tests/demos.test.tsx
+++ b/tests/demos.test.tsx
@@ -505,6 +505,23 @@ describe("artifact chrome — scrollbar, invert toggles, downloads (release poli
assert.ok(!html.includes("hue-rotate"), "inversion OFF by default");
});
+ it("figure card: NO card chrome, image first, caption row BELOW (owner round, 2026-10-07)", () => {
+ const svg = '';
+ const html = renderToStaticMarkup();
+ // Document material: the outer box carries no card styling.
+ const rootAt = html.indexOf('data-testid="visual-card"');
+ const root = html.slice(rootAt - 200, rootAt);
+ assert.ok(!root.includes("rounded-xl border"), "no card container");
+ // Image BEFORE the caption row; the caption label and the controls follow it.
+ const svgAt = html.indexOf('data-testid="visual-svg"');
+ const captionAt = html.indexOf(">Figure<");
+ assert.ok(svgAt !== -1 && captionAt !== -1 && svgAt < captionAt, "the caption row sits below the image");
+ assert.ok(captionAt < html.indexOf("visual-download"), "the controls ride the caption row");
+ // The invert affordance: the contrast-circle disc + the tooltip.
+ assert.ok(html.includes("size-3"), "the icon disc");
+ assert.ok(html.includes('title="Invert colors"'), "the tooltip names the toggle");
+ });
+
it("svgNaturalWidth: declared px sizes parse; %, auto, and absent fall back to null", () => {
assert.equal(svgNaturalWidth(''), 200);
assert.equal(svgNaturalWidth(""), 320);