diff --git a/README.md b/README.md
index 7995ec8..dcdea19 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,13 @@ A visual companion to [Soroban Testkit](https://github.com/soroban-testkit/testk
The interface does not execute contracts in the browser. Generated commands run locally through `soroban-testkit`, keeping contract tests deterministic and offline as required by the core project.
+## Features
+
+- **Design System & Theme Tokens**: Comprehensive CSS custom property scale (`:root`) standardizing color schemes (canvas, surfaces, accent, text hierarchy, borders) and spacing increments.
+- **Module Filter & Empty State**: Search and filter testing modules by name or description with real-time feedback and an accessible empty-state layout with quick filter reset.
+- **Responsive & Reduced-Width Navigation**: Accessible navigation layout supporting truncated and responsive labels without overflowing viewport on mobile or narrow screens.
+- **Required vs. Optional Setting Distinction**: Visual badges (`Required` / `Optional`) and `aria-required` attributes in the CLI workbench distinguishing mandatory parameters from optional configuration flags.
+
## Development
```sh
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 8676631..76d1bbd 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -63,3 +63,118 @@ describe("App command copy", () => {
expect(await screen.findByRole("button", { name: "Copied" })).toBeInTheDocument();
});
});
+
+describe("Module filter and empty-state layout (#188)", () => {
+ it("renders all 7 modules by default", () => {
+ render();
+ expect(screen.getByText("core")).toBeInTheDocument();
+ expect(screen.getByText("ledger")).toBeInTheDocument();
+ expect(screen.getByText("money")).toBeInTheDocument();
+ expect(screen.getByText("events")).toBeInTheDocument();
+ expect(screen.getByText("tokens")).toBeInTheDocument();
+ expect(screen.getByText("auth")).toBeInTheDocument();
+ expect(screen.getByText("ttl")).toBeInTheDocument();
+ });
+
+ it("filters modules matching search query by name or description", () => {
+ render();
+ const filterInput = screen.getByLabelText("Filter modules");
+
+ fireEvent.change(filterInput, { target: { value: "ledger" } });
+
+ expect(screen.getByText("ledger")).toBeInTheDocument();
+ expect(screen.queryByText("tokens")).not.toBeInTheDocument();
+ expect(screen.queryByText("auth")).not.toBeInTheDocument();
+ });
+
+ it("renders an accessible empty-state layout when no modules match the filter", () => {
+ render();
+ const filterInput = screen.getByLabelText("Filter modules");
+
+ fireEvent.change(filterInput, { target: { value: "nonexistent-module" } });
+
+ const emptyState = screen.getByRole("status");
+ expect(emptyState).toBeInTheDocument();
+ expect(emptyState).toHaveAttribute("aria-live", "polite");
+ expect(
+ screen.getByText(/No modules match/i),
+ ).toBeInTheDocument();
+
+ const resetButton = screen.getByRole("button", { name: "Reset filter" });
+ expect(resetButton).toBeInTheDocument();
+
+ fireEvent.click(resetButton);
+
+ expect(screen.getByText("core")).toBeInTheDocument();
+ expect(screen.queryByRole("status")).not.toBeInTheDocument();
+ });
+
+ it("clears the filter using the Clear button", () => {
+ render();
+ const filterInput = screen.getByLabelText("Filter modules");
+
+ fireEvent.change(filterInput, { target: { value: "events" } });
+ expect(screen.getByText("events")).toBeInTheDocument();
+ expect(screen.queryByText("ttl")).not.toBeInTheDocument();
+
+ const clearButton = screen.getByRole("button", { name: "Clear module filter" });
+ fireEvent.click(clearButton);
+
+ expect(screen.getByText("ttl")).toBeInTheDocument();
+ });
+});
+
+describe("Reduced-width layout for navigation labels (#189)", () => {
+ it("renders accessible navigation links with responsive container attributes", () => {
+ render();
+ const nav = screen.getByRole("navigation");
+ expect(nav).toBeInTheDocument();
+
+ const brand = screen.getByLabelText("Soroban Testkit home");
+ expect(brand).toBeInTheDocument();
+
+ const links = screen.getByLabelText("Main navigation");
+ expect(links).toBeInTheDocument();
+ expect(screen.getByText("Modules")).toHaveAttribute("href", "#modules");
+ expect(screen.getByText("Commands")).toHaveAttribute("href", "#commands");
+ expect(screen.getByText("GitHub ↗")).toHaveAttribute(
+ "href",
+ "https://github.com/soroban-testkit/testkit-blockchain",
+ );
+ });
+});
+
+describe("Visual distinction between required and optional settings (#196)", () => {
+ it("displays Required badge and aria-required on required path input", () => {
+ render();
+ const pathInput = screen.getByLabelText(/project or contract path/i);
+ expect(pathInput).toHaveAttribute("aria-required", "true");
+ expect(screen.getAllByText("Required").length).toBeGreaterThanOrEqual(1);
+ });
+
+ it("displays Optional badge on optional format and open options in coverage workflow", () => {
+ render();
+ expect(screen.getByLabelText(/report format/i)).toBeInTheDocument();
+ expect(screen.getByLabelText(/open report when complete/i)).toBeInTheDocument();
+ expect(screen.getAllByText("Optional").length).toBeGreaterThanOrEqual(2);
+ });
+
+ it("displays Required badges and aria-required on function and ramp in limits workflow", () => {
+ render();
+ fireEvent.click(screen.getByRole("button", { name: "limits" }));
+
+ const fnInput = screen.getByLabelText(/function/i);
+ const rampInput = screen.getByLabelText(/ramp parameter/i);
+
+ expect(fnInput).toHaveAttribute("aria-required", "true");
+ expect(rampInput).toHaveAttribute("aria-required", "true");
+ });
+
+ it("displays Optional badge on fail on warnings option in audit workflow", () => {
+ render();
+ fireEvent.click(screen.getByRole("button", { name: "audit" }));
+
+ expect(screen.getByLabelText(/fail on warnings/i)).toBeInTheDocument();
+ expect(screen.getByText("Optional")).toBeInTheDocument();
+ });
+});
diff --git a/src/App.tsx b/src/App.tsx
index 902695f..0e00718 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -17,12 +17,22 @@ export default function App() {
const [ramp, setRamp] = useState("recipients");
const [strict, setStrict] = useState(true);
const [copied, setCopied] = useState(false);
+ const [moduleFilter, setModuleFilter] = useState("");
const generated = useMemo(
() => buildCommand({ command, path, format, open, fn, ramp, strict }),
[command, path, format, open, fn, ramp, strict],
);
+ const filteredModules = useMemo(() => {
+ const q = moduleFilter.trim().toLowerCase();
+ if (!q) return modules;
+ return modules.filter(
+ ([name, description]) =>
+ name.toLowerCase().includes(q) || description.toLowerCase().includes(q),
+ );
+ }, [moduleFilter]);
+
const copy = async () => {
await copyToClipboard(generated);
setCopied(true);
@@ -36,7 +46,7 @@ export default function App() {
S
Soroban Testkit
-
+
Modules
Commands
GitHub ↗
@@ -64,14 +74,55 @@ export default function App() {
-
-
- {modules.map(([name, description], index) => (
-
- 0{index + 1}{name}
{description}
-
- ))}
-
+
+
+ {filteredModules.length > 0 ? (
+
+ {filteredModules.map(([name, description], index) => (
+
+ 0{index + 1}{name}
{description}
+
+ ))}
+
+ ) : (
+
+
∅
+
No modules match “{moduleFilter}”
+
No testing modules found matching your filter criteria. Try adjusting your search term.
+
+
+ )}
@@ -81,19 +132,72 @@ export default function App() {
Configure a supported Testkit workflow and copy the exact command to your terminal.
{(["coverage", "limits", "audit"] as const).map((name) => (
-
+
))}
{descriptions[command]}
diff --git a/src/styles.css b/src/styles.css
index 332b692..258df30 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1,42 +1,295 @@
@import url("https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700&display=swap");
-:root { font-family: Manrope, sans-serif; color: #e9f4ef; background: #061310; font-synthesis: none; }
+:root {
+ /* Typography */
+ --font-sans: Manrope, sans-serif;
+ --font-mono: "DM Mono", monospace;
+
+ /* Theme Colors */
+ --color-canvas: #061310;
+ --color-canvas-gradient: #123a31;
+ --color-surface: #091916;
+ --color-surface-terminal: #07100e;
+ --color-surface-output: #020807;
+ --color-surface-card: #ffffff02;
+ --color-surface-card-hover: #66e2ad08;
+
+ /* Accent Colors */
+ --color-accent: #66e2ad;
+ --color-accent-text-dark: #062017;
+ --color-accent-subtle: #66e2ad0b;
+ --color-accent-border: #66e2ad22;
+ --color-accent-surface: #66e2ad0d;
+
+ /* Text Colors */
+ --color-text-primary: #e9f4ef;
+ --color-text-secondary: #a9bbb4;
+ --color-text-muted: #6f817a;
+ --color-text-body: #91a39c;
+ --color-text-label: #a7b8b1;
+ --color-text-tab: #8ea099;
+ --color-text-code: #dbe9e3;
+ --color-text-module-num: #587068;
+
+ /* Border Colors */
+ --color-border-subtle: #ffffff12;
+ --color-border-default: #ffffff1c;
+ --color-border-strong: #ffffff24;
+ --color-border-input: #ffffff1f;
+ --color-border-tab: #ffffff20;
+
+ /* Badges */
+ --color-badge-required-bg: #66e2ad1a;
+ --color-badge-required-text: #66e2ad;
+ --color-badge-required-border: #66e2ad33;
+ --color-badge-optional-bg: #ffffff10;
+ --color-badge-optional-text: #8ea099;
+ --color-badge-optional-border: #ffffff1a;
+
+ /* Form Elements */
+ --color-input-bg: #ffffff06;
+ --color-terminal-dot: #294039;
+
+ /* Spacing Scale */
+ --space-3xs: 2px;
+ --space-2xs: 4px;
+ --space-xs: 7px;
+ --space-sm: 8px;
+ --space-md: 12px;
+ --space-base: 14px;
+ --space-lg: 16px;
+ --space-xl: 18px;
+ --space-2xl: 22px;
+ --space-3xl: 24px;
+ --space-4xl: 28px;
+ --space-5xl: 32px;
+ --space-6xl: 48px;
+ --space-section: 100px;
+ --page-padding: 5vw;
+
+ /* Radii */
+ --radius-xs: 4px;
+ --radius-sm: 5px;
+ --radius-md: 6px;
+ --radius-base: 7px;
+ --radius-lg: 10px;
+ --radius-xl: 12px;
+ --radius-round: 50%;
+
+ font-family: var(--font-sans);
+ color: var(--color-text-primary);
+ background: var(--color-canvas);
+ font-synthesis: none;
+}
+
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
-body { margin: 0; min-width: 320px; background: radial-gradient(circle at 75% 8%, #123a31 0, transparent 28rem), #061310; }
+body { margin: 0; min-width: 320px; background: radial-gradient(circle at 75% 8%, var(--color-canvas-gradient) 0, transparent 28rem), var(--color-canvas); }
a { color: inherit; text-decoration: none; }
button, input, select { font: inherit; }
main { max-width: 1440px; margin: auto; overflow: hidden; }
-nav { height: 78px; display: flex; align-items: center; justify-content: space-between; padding: 0 5vw; border-bottom: 1px solid #ffffff12; }
-.brand { display: flex; align-items: center; gap: 12px; font-weight: 700; }
-.brand-mark { display: grid; place-items: center; width: 34px; height: 34px; border: 1px solid #66e2ad; border-radius: 50%; color: #66e2ad; font-family: "DM Mono"; }
-.nav-links { display: flex; gap: 30px; color: #a9bbb4; font-size: .9rem; }
-.nav-links a:hover { color: #66e2ad; }
-.hero { min-height: 680px; padding: 100px 5vw 80px; display: grid; grid-template-columns: 1.05fr .95fr; align-items: center; gap: 7vw; }
-.eyebrow { color: #66e2ad; text-transform: uppercase; letter-spacing: .14em; font: 500 .72rem "DM Mono"; }
-h1 { margin: 18px 0 24px; font-size: clamp(3.4rem, 6.5vw, 7rem); line-height: .96; letter-spacing: -.065em; }
-.lede { max-width: 650px; color: #a9bbb4; font-size: 1.15rem; line-height: 1.75; }
-.actions { display: flex; gap: 12px; margin-top: 38px; }
-.button { padding: 13px 19px; border-radius: 7px; font-weight: 600; font-size: .9rem; }
-.primary { color: #062017; background: #66e2ad; }
-.secondary { border: 1px solid #ffffff24; }
-.terminal { background: #07100e; border: 1px solid #ffffff1c; border-radius: 12px; box-shadow: 0 30px 80px #0008; transform: rotate(1deg); overflow: hidden; font-family: "DM Mono"; }
-.terminal-top { height: 48px; display: flex; align-items: center; gap: 7px; padding: 0 18px; border-bottom: 1px solid #ffffff12; color: #6f817a; font-size: .7rem; }
-.terminal-top i { width: 8px; height: 8px; border-radius: 50%; background: #294039; }.terminal-top span { margin-left: auto; }
-.terminal pre { margin: 0; padding: 34px; color: #dbe9e3; font-size: .85rem; line-height: 1.85; overflow: auto; }.terminal b, .muted { color: #66e2ad; font-weight: 400; }
-.pass { padding: 14px 24px; background: #66e2ad0d; border-top: 1px solid #66e2ad22; color: #66e2ad; font-size: .75rem; }
-.modules, .commands { padding: 100px 5vw; border-top: 1px solid #ffffff12; }
-section header { max-width: 650px; margin-bottom: 50px; } h2 { margin: 12px 0; font-size: clamp(2rem, 4vw, 4rem); letter-spacing: -.045em; line-height: 1.05; }
-.module-grid { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid #ffffff1c; border-left: 1px solid #ffffff1c; }
-.module-grid article { min-height: 210px; padding: 28px; border-right: 1px solid #ffffff1c; border-bottom: 1px solid #ffffff1c; background: #ffffff02; transition: background .2s; }.module-grid article:hover { background: #66e2ad08; }
-.module-grid span { color: #587068; font: .7rem "DM Mono"; }.module-grid h3 { margin: 48px 0 9px; color: #66e2ad; font: 500 1.1rem "DM Mono"; }.module-grid p { margin: 0; color: #91a39c; line-height: 1.5; font-size: .87rem; }
-.commands { display: grid; grid-template-columns: .8fr 1.2fr; gap: 8vw; background: #091916; }.command-copy > p:not(.eyebrow) { color: #91a39c; line-height: 1.7; }
-.command-tabs { display: flex; gap: 8px; margin-top: 36px; }.command-tabs button { border: 1px solid #ffffff20; color: #8ea099; background: transparent; padding: 9px 14px; border-radius: 5px; cursor: pointer; font: .75rem "DM Mono"; }.command-tabs button.active { border-color: #66e2ad; color: #66e2ad; background: #66e2ad0b; }
-.description { min-height: 48px; }.builder { padding: 32px; background: #061310; border: 1px solid #ffffff18; border-radius: 10px; }
-label { display: grid; gap: 9px; margin-bottom: 22px; color: #a7b8b1; font-size: .78rem; }.row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
-input, select { width: 100%; padding: 12px 13px; color: #eaf6f1; background: #ffffff06; border: 1px solid #ffffff1f; border-radius: 5px; outline: none; } input:focus, select:focus { border-color: #66e2ad; }
-.check { display: flex; align-items: center; grid-template-columns: auto 1fr; }.check input { width: auto; accent-color: #66e2ad; }
-.output { display: flex; align-items: center; gap: 15px; padding: 16px; background: #020807; border-radius: 6px; }.output code { flex: 1; color: #66e2ad; font: .75rem/1.6 "DM Mono"; overflow-wrap: anywhere; }.output button { color: #061310; background: #66e2ad; border: 0; border-radius: 4px; padding: 8px 12px; cursor: pointer; }
-footer { display: flex; justify-content: space-between; padding: 34px 5vw; border-top: 1px solid #ffffff12; color: #6f817a; font-size: .78rem; }
-@media (max-width: 900px) { .hero, .commands { grid-template-columns: 1fr; }.hero { padding-top: 70px; }.module-grid { grid-template-columns: repeat(2, 1fr); }.nav-links a:not(:last-child) { display: none; } }
-@media (max-width: 540px) { nav, .hero, .modules, .commands { padding-left: 22px; padding-right: 22px; }.module-grid { grid-template-columns: 1fr; }.row { grid-template-columns: 1fr; }.actions { flex-direction: column; }.terminal pre { padding: 22px; font-size: .7rem; } footer { gap: 20px; } }
+
+/* Navigation with reduced-width adaptation */
+nav {
+ height: 78px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 var(--page-padding);
+ border-bottom: 1px solid var(--color-border-subtle);
+}
+.brand {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ font-weight: 700;
+ flex-shrink: 0;
+}
+.brand-mark {
+ display: grid;
+ place-items: center;
+ width: 34px;
+ height: 34px;
+ border: 1px solid var(--color-accent);
+ border-radius: var(--radius-round);
+ color: var(--color-accent);
+ font-family: var(--font-mono);
+}
+.nav-links {
+ display: flex;
+ align-items: center;
+ gap: clamp(14px, 2.5vw, 30px);
+ color: var(--color-text-secondary);
+ font-size: .9rem;
+}
+.nav-links a {
+ display: inline-flex;
+ align-items: center;
+ white-space: nowrap;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.nav-links a:hover { color: var(--color-accent); }
+
+/* Hero Section */
+.hero { min-height: 680px; padding: var(--space-section) var(--page-padding) 80px; display: grid; grid-template-columns: 1.05fr .95fr; align-items: center; gap: 7vw; }
+.eyebrow { color: var(--color-accent); text-transform: uppercase; letter-spacing: .14em; font: 500 .72rem var(--font-mono); }
+h1 { margin: var(--space-xl) 0 var(--space-3xl); font-size: clamp(3.4rem, 6.5vw, 7rem); line-height: .96; letter-spacing: -.065em; }
+.lede { max-width: 650px; color: var(--color-text-secondary); font-size: 1.15rem; line-height: 1.75; }
+.actions { display: flex; gap: var(--space-md); margin-top: 38px; }
+.button { padding: 13px 19px; border-radius: var(--radius-base); font-weight: 600; font-size: .9rem; cursor: pointer; }
+.primary { color: var(--color-accent-text-dark); background: var(--color-accent); border: 0; }
+.secondary { border: 1px solid var(--color-border-strong); background: transparent; color: var(--color-text-primary); }
+
+/* Terminal */
+.terminal { background: var(--color-surface-terminal); border: 1px solid var(--color-border-default); border-radius: var(--radius-xl); box-shadow: 0 30px 80px #0008; transform: rotate(1deg); overflow: hidden; font-family: var(--font-mono); }
+.terminal-top { height: 48px; display: flex; align-items: center; gap: var(--space-xs); padding: 0 var(--space-xl); border-bottom: 1px solid var(--color-border-subtle); color: var(--color-text-muted); font-size: .7rem; }
+.terminal-top i { width: 8px; height: 8px; border-radius: var(--radius-round); background: var(--color-terminal-dot); }
+.terminal-top span { margin-left: auto; }
+.terminal pre { margin: 0; padding: 34px; color: var(--color-text-code); font-size: .85rem; line-height: 1.85; overflow: auto; }
+.terminal b, .muted { color: var(--color-accent); font-weight: 400; }
+.pass { padding: var(--space-base) var(--space-3xl); background: var(--color-accent-surface); border-top: 1px solid var(--color-accent-border); color: var(--color-accent); font-size: .75rem; }
+
+/* Modules */
+.modules, .commands { padding: var(--space-section) var(--page-padding); border-top: 1px solid var(--color-border-subtle); }
+section header { max-width: 650px; margin-bottom: 50px; }
+h2 { margin: var(--space-md) 0; font-size: clamp(2rem, 4vw, 4rem); letter-spacing: -.045em; line-height: 1.05; }
+
+/* Module filter bar */
+.module-filter-bar {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ margin-top: var(--space-xl);
+ max-width: 480px;
+}
+.module-filter-input {
+ width: 100%;
+ padding: 10px 14px;
+ background: var(--color-input-bg);
+ border: 1px solid var(--color-border-input);
+ border-radius: var(--radius-sm);
+ color: var(--color-text-primary);
+ font-size: .85rem;
+ outline: none;
+}
+.module-filter-input:focus { border-color: var(--color-accent); }
+.module-filter-clear {
+ padding: 10px 14px;
+ background: transparent;
+ border: 1px solid var(--color-border-strong);
+ border-radius: var(--radius-sm);
+ color: var(--color-text-secondary);
+ cursor: pointer;
+ font-size: .8rem;
+ white-space: nowrap;
+}
+.module-filter-clear:hover { color: var(--color-accent); border-color: var(--color-accent); }
+
+/* Module Grid */
+.module-grid { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--color-border-default); border-left: 1px solid var(--color-border-default); }
+.module-grid article { min-height: 210px; padding: var(--space-4xl); border-right: 1px solid var(--color-border-default); border-bottom: 1px solid var(--color-border-default); background: var(--color-surface-card); transition: background .2s; }
+.module-grid article:hover { background: var(--color-surface-card-hover); }
+.module-grid span { color: var(--color-text-module-num); font: .7rem var(--font-mono); }
+.module-grid h3 { margin: var(--space-6xl) 0 9px; color: var(--color-accent); font: 500 1.1rem var(--font-mono); }
+.module-grid p { margin: 0; color: var(--color-text-body); line-height: 1.5; font-size: .87rem; }
+
+/* Module Empty State */
+.module-empty-state {
+ padding: 60px var(--space-3xl);
+ text-align: center;
+ background: var(--color-surface-card);
+ border: 1px dashed var(--color-border-strong);
+ border-radius: var(--radius-lg);
+ margin-top: var(--space-xl);
+}
+.module-empty-icon { font-size: 2rem; color: var(--color-text-muted); margin-bottom: var(--space-md); font-family: var(--font-mono); }
+.module-empty-state h3 { margin: 0 0 var(--space-sm); font-size: 1.2rem; color: var(--color-text-primary); }
+.module-empty-state p { margin: 0 0 var(--space-xl); color: var(--color-text-secondary); font-size: .9rem; line-height: 1.6; }
+.module-empty-state button { display: inline-block; }
+
+/* Commands / CLI Workbench */
+.commands { display: grid; grid-template-columns: .8fr 1.2fr; gap: 8vw; background: var(--color-surface); }
+.command-copy > p:not(.eyebrow) { color: var(--color-text-body); line-height: 1.7; }
+.command-tabs { display: flex; gap: var(--space-sm); margin-top: 36px; }
+.command-tabs button { border: 1px solid var(--color-border-tab); color: var(--color-text-tab); background: transparent; padding: 9px var(--space-base); border-radius: var(--radius-sm); cursor: pointer; font: .75rem var(--font-mono); }
+.command-tabs button.active { border-color: var(--color-accent); color: var(--color-accent); background: var(--color-accent-subtle); }
+.description { min-height: 48px; }
+.builder { padding: var(--space-5xl); background: var(--color-canvas); border: 1px solid var(--color-border-default); border-radius: var(--radius-lg); }
+
+/* Field & Required/Optional distinction */
+label { display: grid; gap: 9px; margin-bottom: var(--space-2xl); color: var(--color-text-label); font-size: .78rem; }
+.field-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-sm);
+}
+.badge {
+ display: inline-flex;
+ align-items: center;
+ padding: var(--space-3xs) var(--space-xs);
+ border-radius: var(--radius-xs);
+ font-size: .65rem;
+ font-family: var(--font-mono);
+ font-weight: 500;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+}
+.badge-required {
+ background: var(--color-badge-required-bg);
+ color: var(--color-badge-required-text);
+ border: 1px solid var(--color-badge-required-border);
+}
+.badge-optional {
+ background: var(--color-badge-optional-bg);
+ color: var(--color-badge-optional-text);
+ border: 1px solid var(--color-badge-optional-border);
+}
+
+.row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xl); }
+input, select { width: 100%; padding: var(--space-md) 13px; color: var(--color-text-primary); background: var(--color-input-bg); border: 1px solid var(--color-border-input); border-radius: var(--radius-sm); outline: none; }
+input:focus, select:focus { border-color: var(--color-accent); }
+.check { display: flex; align-items: center; justify-content: space-between; }
+.check-content { display: flex; align-items: center; gap: var(--space-sm); }
+.check input { width: auto; accent-color: var(--color-accent); }
+.output { display: flex; align-items: center; gap: 15px; padding: var(--space-lg); background: var(--color-surface-output); border-radius: var(--radius-md); }
+.output code { flex: 1; color: var(--color-accent); font: .75rem/1.6 var(--font-mono); overflow-wrap: anywhere; }
+.output button { color: var(--color-canvas); background: var(--color-accent); border: 0; border-radius: var(--radius-xs); padding: var(--space-sm) var(--space-md); cursor: pointer; font-weight: 600; }
+.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; }
+
+footer { display: flex; justify-content: space-between; padding: 34px var(--page-padding); border-top: 1px solid var(--color-border-subtle); color: var(--color-text-muted); font-size: .78rem; }
+
+/* Responsive adjustments */
+@media (max-width: 900px) {
+ .hero, .commands { grid-template-columns: 1fr; }
+ .hero { padding-top: 70px; }
+ .module-grid { grid-template-columns: repeat(2, 1fr); }
+ .nav-links a { max-width: 140px; }
+}
+
+@media (max-width: 600px) {
+ nav {
+ height: auto;
+ min-height: 64px;
+ padding: var(--space-md) var(--page-padding);
+ flex-wrap: wrap;
+ gap: var(--space-md);
+ }
+ .nav-links {
+ gap: var(--space-md);
+ font-size: .85rem;
+ flex-wrap: wrap;
+ }
+ .nav-links a { max-width: 120px; }
+}
+
+@media (max-width: 540px) {
+ nav, .hero, .modules, .commands { padding-left: 22px; padding-right: 22px; }
+ .module-grid { grid-template-columns: 1fr; }
+ .row { grid-template-columns: 1fr; }
+ .actions { flex-direction: column; }
+ .terminal pre { padding: 22px; font-size: .7rem; }
+ footer { gap: 20px; }
+}