Skip to content
Draft
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
6 changes: 6 additions & 0 deletions .agents/skills/structured-liquidity/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ npx shadcn@latest add https://structured-liquidity.replit.app/r/button.json

Each `registry:ui` item is a plain React component (no Radix) that emits the SL classes and declares the base style as a `registryDependency`, so the CSS comes along. The registry index lives at `/registry.json`.

For application KPI surfaces, install `kpi.json` and use `KpiGrid` with
`KpiCard`. This is the default Structured Liquidity metric pattern: square
accent cards, mono labels, optional definition tooltips and comparison badges,
plus explicit loading and unavailable states. Applications supply audited
values; the component must never invent a hard-coded fallback metric.

**B. Any stack (plain HTML/CSS).** Include `structured-liquidity.css`, `structured-liquidity-components.css`, `structured-liquidity-kit.css`, and `structured-liquidity-kit.js`; load the three font families; then use the SL classes directly. Core kit: `sl-btn`, `sl-badge`, `sl-toggle`/`sl-toggle-group`, `sl-switch`, `sl-check`, `sl-radio-item` (inside a `[data-radio-group]`), `sl-input`/`sl-label`, `sl-slider`, `sl-progress`, `sl-ava`, `sl-tabs` (`.tablist` buttons + `.panel` nodes), and overlays (`sl-overlay` + `sl-dialog`/`sl-sheet`). In plain HTML, interactive components are wired by `structured-liquidity-kit.js` via `data-toggle-aria`, `data-toggle-group`, `data-radio-group`, `data-open-overlay`, `data-close-overlay`.

## Anatomy of a rigid container holding glass
Expand Down
23 changes: 23 additions & 0 deletions artifacts/structured-liquidity/public/r/kpi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "kpi",
"type": "registry:ui",
"title": "Kpi",
"description": "Structured Liquidity Kpi — React component driving the SL class system.",
"dependencies": [
"lucide-react"
],
"registryDependencies": [
"https://structured.glass/r/structured-liquidity.json",
"https://structured.glass/r/tooltip.json",
"utils"
],
"files": [
{
"path": "registry/ui/kpi.tsx",
"content": "import * as React from \"react\";\nimport { ArrowDownRight, ArrowUpRight, Info, Minus } from \"lucide-react\";\n\nimport { Tooltip } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\nexport function KpiGrid({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n return <div className={cn(\"sl-kpi-grid\", className)} {...props} />;\n}\n\nexport interface KpiCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n change?: React.ReactNode;\n description?: React.ReactNode;\n direction?: \"down\" | \"flat\" | \"up\";\n label: React.ReactNode;\n loading?: boolean;\n unavailableLabel?: React.ReactNode;\n value?: React.ReactNode;\n variant?: \"benchmark\" | \"default\";\n}\n\n/**\n * Default Structured Liquidity KPI card.\n *\n * Data is supplied by the adopter. Missing data renders an explicit unavailable\n * state; the component never invents a fallback metric.\n */\nexport function KpiCard({\n change,\n className,\n description,\n direction = \"flat\",\n label,\n loading = false,\n unavailableLabel = \"N/A\",\n value,\n variant = \"default\",\n ...props\n}: KpiCardProps) {\n const unavailable = !loading && (value === null || value === undefined);\n const DirectionIcon =\n direction === \"up\" ? ArrowUpRight : direction === \"down\" ? ArrowDownRight : Minus;\n\n return (\n <div\n className={cn(\"sl-kpi-card\", variant === \"benchmark\" && \"is-benchmark\", className)}\n data-state={loading ? \"loading\" : unavailable ? \"unavailable\" : \"ready\"}\n {...props}\n >\n <div className=\"sl-kpi-label\">\n <span>{label}</span>\n {description ? (\n <Tooltip content={description} className=\"sl-kpi-tip-wrap\">\n <button type=\"button\" className=\"sl-kpi-info\" aria-label={`About ${String(label)}`}>\n <Info aria-hidden=\"true\" />\n </button>\n </Tooltip>\n ) : null}\n </div>\n <div className=\"sl-kpi-row\">\n <strong className=\"sl-kpi-value\">\n {loading ? <span className=\"sl-kpi-skeleton\" aria-label=\"Loading metric\" /> : unavailable ? unavailableLabel : value}\n </strong>\n {!loading && !unavailable && change ? (\n <span className=\"sl-kpi-change\">\n <DirectionIcon aria-hidden=\"true\" />\n {change}\n </span>\n ) : null}\n </div>\n </div>\n );\n}\n",
"type": "registry:ui",
"target": "components/ui/kpi.tsx"
}
]
}

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions artifacts/structured-liquidity/public/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@
"title": "Keycap",
"description": "Structured Liquidity Keycap — React component driving the SL class system."
},
{
"name": "kpi",
"type": "registry:ui",
"title": "Kpi",
"description": "Structured Liquidity Kpi — React component driving the SL class system."
},
{
"name": "label",
"type": "registry:ui",
Expand Down
25 changes: 25 additions & 0 deletions artifacts/structured-liquidity/public/structured-liquidity-kit.css
Original file line number Diff line number Diff line change
Expand Up @@ -567,3 +567,28 @@
.sl-template-immersive { min-height: 100dvh; }
.sl-template-immersive > main { position: relative; flex: 1; overflow: hidden; }
@media (max-width: 760px) { .sl-template-guided > .sl-site-content { grid-template-columns: 1fr; } }

/* ============================================================
KPI — default responsive metric grid
============================================================ */
.sl-kpi-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:0.8rem;width:100%;}
.sl-kpi-card{position:relative;z-index:0;min-width:0;min-height:82px;display:flex;flex-direction:column;justify-content:center;gap:0.55rem;padding:0.8rem 0.9rem;color:var(--accent-ink);background:var(--accent);border:var(--border-w) solid rgb(var(--edge));border-radius:var(--radius);box-shadow:4px 4px 0 0 var(--hard-shadow);transition:transform .14s ease,box-shadow .14s ease;}
.sl-kpi-card:hover,.sl-kpi-card:focus-within{z-index:20;transform:translate(-1px,-1px);box-shadow:5px 5px 0 0 var(--hard-shadow);}
.sl-kpi-card.is-benchmark{color:var(--neg-ink);background:var(--neg);}
.sl-kpi-card[data-state="unavailable"]{color:var(--ink);background:rgb(var(--glass-tint) / 0.08);}
.sl-kpi-label{display:flex;align-items:flex-start;gap:0.25rem;font-family:var(--mono);font-size:0.62rem;font-weight:700;line-height:1.2;letter-spacing:0.06em;text-transform:uppercase;}
.sl-kpi-label > span:first-child{min-width:0;overflow-wrap:anywhere;}
.sl-kpi-tip-wrap{display:inline-flex;flex:none;}
.sl-kpi-info{display:grid;place-items:center;width:15px;height:15px;padding:0;color:currentColor;background:transparent;border:0;border-radius:var(--radius);cursor:help;opacity:0.72;}
.sl-kpi-info:hover{opacity:1;}
.sl-kpi-info:focus-visible{outline:2px solid currentColor;outline-offset:2px;}
.sl-kpi-info svg{width:13px;height:13px;}
.sl-kpi-row{display:flex;align-items:baseline;justify-content:space-between;gap:0.45rem;min-width:0;}
.sl-kpi-value{min-width:0;font-family:var(--display);font-size:clamp(1.2rem,2vw,1.55rem);font-weight:800;line-height:1;letter-spacing:-0.025em;overflow-wrap:anywhere;}
.sl-kpi-change{display:inline-flex;align-items:center;gap:0.12rem;flex:none;font-family:var(--mono);font-size:0.62rem;font-weight:700;white-space:nowrap;}
.sl-kpi-change svg{width:12px;height:12px;}
.sl-kpi-skeleton{display:block;width:3.6rem;height:1.2rem;background:currentColor;opacity:0.22;animation:sl-kpi-pulse 1.2s steps(2,end) infinite;}
@keyframes sl-kpi-pulse{50%{opacity:0.08;}}
@media (max-width:760px){.sl-kpi-grid{grid-template-columns:repeat(2,minmax(0,1fr));}}
@media (max-width:340px){.sl-kpi-grid{grid-template-columns:1fr;}}
@media (prefers-reduced-motion:reduce){.sl-kpi-card{transition:none;}.sl-kpi-card:hover,.sl-kpi-card:focus-within{transform:none;}.sl-kpi-skeleton{animation:none;}}
16 changes: 16 additions & 0 deletions artifacts/structured-liquidity/src/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,22 @@
"display"
]
},
{
"name": "KPI · default metric grid",
"registry": "kpi",
"cap": "KPI · default metric grid",
"category": "data",
"status": "stable",
"tags": [
"display",
"metric",
"responsive"
],
"kinds": [
"display",
"feedback"
]
},
{
"name": "Section header",
"registry": "section-header",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
TableCell,
} from "@/components/ui/table";
import { Meter } from "@/components/ui/meter";
import { KpiCard, KpiGrid } from "@/components/ui/kpi";

export function KitDataDisplay() {
return (
Expand All @@ -40,11 +41,48 @@ export function KitDataDisplay() {
<span className="kg-name">Data display</span>
<span className="kg-rule"></span>
<span className="kg-count">
Alert · Avatar · Calendar · Card · Media player · Stat · Table ·
Alert · Avatar · Calendar · Card · KPI · Media player · Stat · Table ·
Waveform
</span>
</div>
<div className="kit-grid">
<div className="glass kit-cell w12">
<span className="kit-cap">KPI · default metric grid</span>
<KpiGrid>
<KpiCard
label="Investments"
value={50}
change="+16%"
direction="up"
description="Use one auditable grain and source for every KPI."
/>
<KpiCard
label="Markups"
value={20}
change="+18%"
direction="up"
description="Tooltip definitions travel with the metric."
/>
<KpiCard
label="Net TVPI"
value="1.28x"
description="Adopters supply database-backed values; the component supplies presentation."
/>
<KpiCard
label="Venture performance"
value={58}
variant="benchmark"
description="A secondary benchmark may use the neutral negative surface, never a competing accent."
/>
<KpiCard label="Pending analysis" loading />
<KpiCard
label="Unavailable"
value={null}
description="Missing data stays explicit instead of falling back to a hard-coded number."
/>
</KpiGrid>
</div>

<div className="glass kit-cell w8">
<span className="kit-cap">Stat · metric display</span>
<StatStrip>
Expand Down
73 changes: 73 additions & 0 deletions artifacts/structured-liquidity/src/components/ui/kpi.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as React from "react";
import { ArrowDownRight, ArrowUpRight, Info, Minus } from "lucide-react";

import { Tooltip } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";

export function KpiGrid({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn("sl-kpi-grid", className)} {...props} />;
}

export interface KpiCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
change?: React.ReactNode;
description?: React.ReactNode;
direction?: "down" | "flat" | "up";
label: React.ReactNode;
loading?: boolean;
unavailableLabel?: React.ReactNode;
value?: React.ReactNode;
variant?: "benchmark" | "default";
}

/**
* Default Structured Liquidity KPI card.
*
* Data is supplied by the adopter. Missing data renders an explicit unavailable
* state; the component never invents a fallback metric.
*/
export function KpiCard({
change,
className,
description,
direction = "flat",
label,
loading = false,
unavailableLabel = "N/A",
value,
variant = "default",
...props
}: KpiCardProps) {
const unavailable = !loading && (value === null || value === undefined);
const DirectionIcon =
direction === "up" ? ArrowUpRight : direction === "down" ? ArrowDownRight : Minus;

return (
<div
className={cn("sl-kpi-card", variant === "benchmark" && "is-benchmark", className)}
data-state={loading ? "loading" : unavailable ? "unavailable" : "ready"}
{...props}
>
<div className="sl-kpi-label">
<span>{label}</span>
{description ? (
<Tooltip content={description} className="sl-kpi-tip-wrap">
<button type="button" className="sl-kpi-info" aria-label={`About ${String(label)}`}>
<Info aria-hidden="true" />
</button>
</Tooltip>
) : null}
</div>
<div className="sl-kpi-row">
<strong className="sl-kpi-value">
{loading ? <span className="sl-kpi-skeleton" aria-label="Loading metric" /> : unavailable ? unavailableLabel : value}
</strong>
{!loading && !unavailable && change ? (
<span className="sl-kpi-change">
<DirectionIcon aria-hidden="true" />
{change}
</span>
) : null}
</div>
</div>
);
}
22 changes: 22 additions & 0 deletions artifacts/structured-liquidity/src/styles/40-inline.css
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,28 @@
.sl-stat-cap{font-family:var(--mono);font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;color:var(--ink-dim);}
.sl-stat-cards{display:flex;gap:0.7rem;width:100%;flex-wrap:wrap;}
.sl-stat-card{flex:1;min-width:120px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:0.25rem;padding:0.9rem 0.8rem;border:var(--border-w) solid rgb(var(--edge));border-radius:var(--radius);box-shadow:2px 2px 0 0 var(--hard-shadow);background:rgb(var(--glass-tint) / 0.05);}
/* Default KPI pattern: adapted from Interspace Ventures' portfolio cards. */
.sl-kpi-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:0.8rem;width:100%;}
.sl-kpi-card{position:relative;z-index:0;min-width:0;min-height:82px;display:flex;flex-direction:column;justify-content:center;gap:0.55rem;padding:0.8rem 0.9rem;color:var(--accent-ink);background:var(--accent);border:var(--border-w) solid rgb(var(--edge));border-radius:var(--radius);box-shadow:4px 4px 0 0 var(--hard-shadow);transition:transform .14s ease,box-shadow .14s ease;}
.sl-kpi-card:hover,.sl-kpi-card:focus-within{z-index:20;transform:translate(-1px,-1px);box-shadow:5px 5px 0 0 var(--hard-shadow);}
.sl-kpi-card.is-benchmark{color:var(--neg-ink);background:var(--neg);}
.sl-kpi-card[data-state="unavailable"]{color:var(--ink);background:rgb(var(--glass-tint) / 0.08);}
.sl-kpi-label{display:flex;align-items:flex-start;gap:0.25rem;font-family:var(--mono);font-size:0.62rem;font-weight:700;line-height:1.2;letter-spacing:0.06em;text-transform:uppercase;}
.sl-kpi-label > span:first-child{min-width:0;overflow-wrap:anywhere;}
.sl-kpi-tip-wrap{display:inline-flex;flex:none;}
.sl-kpi-info{display:grid;place-items:center;width:15px;height:15px;padding:0;color:currentColor;background:transparent;border:0;border-radius:var(--radius);cursor:help;opacity:0.72;}
.sl-kpi-info:hover{opacity:1;}
.sl-kpi-info:focus-visible{outline:2px solid currentColor;outline-offset:2px;}
.sl-kpi-info svg{width:13px;height:13px;}
.sl-kpi-row{display:flex;align-items:baseline;justify-content:space-between;gap:0.45rem;min-width:0;}
.sl-kpi-value{min-width:0;font-family:var(--display);font-size:clamp(1.2rem,2vw,1.55rem);font-weight:800;line-height:1;letter-spacing:-0.025em;overflow-wrap:anywhere;}
.sl-kpi-change{display:inline-flex;align-items:center;gap:0.12rem;flex:none;font-family:var(--mono);font-size:0.62rem;font-weight:700;white-space:nowrap;}
.sl-kpi-change svg{width:12px;height:12px;}
.sl-kpi-skeleton{display:block;width:3.6rem;height:1.2rem;background:currentColor;opacity:0.22;animation:sl-kpi-pulse 1.2s steps(2,end) infinite;}
@keyframes sl-kpi-pulse{50%{opacity:0.08;}}
@media (max-width:760px){.sl-kpi-grid{grid-template-columns:repeat(2,minmax(0,1fr));}}
@media (max-width:340px){.sl-kpi-grid{grid-template-columns:1fr;}}
@media (prefers-reduced-motion:reduce){.sl-kpi-card{transition:none;}.sl-kpi-card:hover,.sl-kpi-card:focus-within{transform:none;}.sl-kpi-skeleton{animation:none;}}
.sl-sechead{display:flex;flex-direction:column;gap:0.45rem;}
.sl-sechead-eyebrow{font-family:var(--mono);font-size:0.6rem;text-transform:uppercase;letter-spacing:0.12em;color:var(--accent);}
.sl-sechead-row{display:flex;align-items:center;gap:0.6rem;}
Expand Down