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
111 changes: 50 additions & 61 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -677,31 +677,34 @@ mark { color: inherit; background: none; }
}
.copy-btn:hover { color: var(--accent); border-color: var(--accent); }

/* Ticker band: the accent, full bleed, one line of facts scrolling. */
/* Ticker band: the marks of the two protocols Wasit checks and the four
chains it reads, each in its own colour on the page's dark, one line
scrolling. The protocols' marks carry their names; the chains' are small
glyphs, so their names follow them. */
.ticker {
overflow: hidden;
background: var(--accent);
color: var(--on-accent);
border-bottom: 1px solid var(--border);
background: var(--bg);
color: var(--fg);
border-block: 1px solid var(--border-strong);
}
.ticker-track {
display: flex;
width: max-content;
animation: ticker 38s linear infinite;
}
.ticker-group { display: flex; flex: none; }
.ticker-group { display: flex; flex: none; align-items: center; }
.ticker-item {
display: inline-flex;
align-items: center;
gap: 22px;
padding: 11px 0 11px 22px;
font-family: var(--font-code);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.14em;
gap: 9px;
padding: 13px 0 13px 30px;
font-size: 14px;
font-weight: 600;
color: var(--muted);
white-space: nowrap;
}
.ticker-square { width: 7px; height: 7px; background: var(--on-accent); }
.ticker-item svg { display: block; flex: none; fill: currentColor; }
.ticker-dot { width: 4px; height: 4px; margin-left: 30px; background: var(--dim); flex: none; }
@keyframes ticker {
to { transform: translateX(-50%); }
}
Expand All @@ -710,21 +713,11 @@ mark { color: inherit; background: none; }
}

/* The stack strip under the ticker (components/StackStrip.tsx): a short
line, then one cell per project — the two protocols Wasit checks and
the chain it trusts — each with its logo in the page's own colour. */
line, then the protocols joined by a line to each chain they run on.
Two drawings of it; CSS shows the wide one from 1100px. */
.stack { border-bottom: 1px solid var(--border); }
.stack-grid {
display: grid;
grid-template-columns: minmax(0, 1.25fr) repeat(3, minmax(0, 1fr));
}
.stack-intro {
display: flex;
flex-direction: column;
justify-content: center;
gap: 10px;
padding: 32px 28px 32px 0;
border-right: 1px solid var(--border-strong);
}
.stack-fan { display: flex; flex-direction: column; gap: 24px; padding-block: 40px 44px; }
.stack-intro { display: flex; flex-direction: column; gap: 10px; }
.stack-kicker {
display: inline-flex;
align-items: center;
Expand All @@ -737,41 +730,37 @@ mark { color: inherit; background: none; }
}
.stack-kicker::before { content: ""; width: 7px; height: 7px; background: var(--accent); }
.stack-title { margin: 0; font-size: clamp(19px, 1.7vw, 24px); font-weight: 600; line-height: 1.3; letter-spacing: -0.01em; }
.stack-cell {
display: flex;
flex-direction: column;
gap: 10px;
padding: 28px 24px;
border-right: 1px solid var(--border-strong);
color: var(--fg);
transition: background 160ms ease;
}
.stack-cell:last-child { border-right: none; }
.stack-cell:hover { color: var(--fg); background: color-mix(in oklab, var(--accent), transparent 90%); }
.stack-role {
font-family: var(--font-code);
font-size: 10.5px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--accent);
}
.stack-role::before { content: "// "; color: var(--dim); }
.stack-mark { display: flex; align-items: center; height: 48px; margin: 6px 0 4px; color: var(--fg); opacity: 0.85; transition: opacity 160ms ease; }
.stack-cell:hover .stack-mark { opacity: 1; }
.stack-logo { display: block; width: auto; fill: currentColor; }
.stack-logo--x402 { height: 40px; }
.stack-logo--mpp { height: 26px; }
.stack-logo--stellar { height: 36px; }
.stack-name { font-size: 14.5px; font-weight: 600; }
.stack-meta { font-family: var(--font-code); font-size: 11.5px; letter-spacing: 0.04em; color: var(--muted); }
@media (max-width: 960px) {
.stack-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.stack-intro { grid-column: 1 / -1; padding: 28px 0 20px; border-right: none; border-bottom: 1px solid var(--border-strong); }
}
@media (max-width: 600px) {
.stack-grid { grid-template-columns: minmax(0, 1fr); }
.stack-cell { border-right: none; border-bottom: 1px solid var(--border-strong); padding: 22px 0; }
.stack-cell:last-child { border-bottom: none; }
.stack-diagram { min-width: 0; }
.fan { display: block; width: 100%; height: auto; }
.fan--narrow { display: none; max-width: 560px; }
@media (max-width: 1099px) {
.fan--wide { display: none; }
.fan--narrow { display: block; }
}
.fan-box { fill: var(--surface); stroke: var(--border-strong); stroke-width: 1; transition: fill 160ms ease, stroke 160ms ease; }
.fan-node:hover .fan-box,
.fan-node:focus-visible .fan-box { fill: color-mix(in oklab, var(--accent), var(--surface) 88%); stroke: var(--accent); }
.fan-node:focus-visible { outline: none; }
.fan-role { font-family: var(--font-code); font-size: 11.5px; letter-spacing: 0.16em; text-transform: uppercase; fill: var(--accent); }
.fan-role--sm { font-size: 9.5px; }
.fan-slash { fill: var(--dim); }
.fan-name { font-family: var(--font-body); font-size: 17px; font-weight: 600; fill: var(--fg); }
.fan-name--sm { font-size: 15px; }
.fan-name--xs { font-size: 12.5px; }
.fan-meta { font-family: var(--font-code); font-size: 13px; letter-spacing: 0.03em; fill: var(--muted); }
.fan-meta--sm { font-size: 10px; }
.fan-mark { fill: var(--fg); opacity: 0.88; transition: opacity 160ms ease; }
.fan-node:hover .fan-mark { opacity: 1; }
.fan-line { fill: none; stroke-width: 1.5; }
.fan-line--x { stroke: var(--accent); }
.fan-line--m { stroke: var(--accent-2); }
.fan-joint { stroke: var(--bg); stroke-width: 2; }
.fan-joint--x { fill: var(--accent); }
.fan-joint--m { fill: var(--accent-2); }
.fan-flow--x { fill: var(--fg); }
.fan-flow--m { fill: var(--accent-2); }
@media (prefers-reduced-motion: reduce) {
.fan-flow { display: none; }
}

/* Bands: every section below the hero. */
Expand Down
41 changes: 24 additions & 17 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FaqList, type FaqEntry } from "@/components/FaqList";
import { McpSession } from "@/components/McpSession";
import { SequenceFlow } from "@/components/SequenceFlow";
import { StackStrip } from "@/components/StackStrip";
import { BrandSprite, Mark, type MarkId } from "@/components/BrandMarks";
import { StrokeText } from "@/components/StrokeText";
import { TechText } from "@/components/TechText";
import { ScrollReveal } from "@/components/ScrollReveal";
Expand All @@ -24,17 +25,19 @@ import { CHECK_COUNT, WASIT_VERSION } from "@/lib/site-facts";
const INSTALL_CMD = "npx @wasit-dev/cli test --target <your-service-url> --read-only";
const GITHUB_URL = "https://github.com/wasit-dev/wasit";

// Words for the ticker band under the hero. Every item is a fact the page
// makes elsewhere; the band repeats them, it does not add to them. Set in
// capitals here rather than by CSS, which would also capitalise "x402".
const TICKER_ITEMS = [
"VERIFY THE SETTLEMENT",
"x402",
"MPP",
"STELLAR · BASE · ETHEREUM · SOLANA",
`${CHECK_COUNT} CHECKS`,
"CLI · MCP · CORE",
"APACHE-2.0",
// The ticker band under the hero: the two protocols Wasit checks and the
// four chains it reads the settlement from, as their marks. x402's and MPP's
// marks are their names, so they stand alone; the chains' are glyphs, so
// each is followed by its name. Brand colours, except Stellar's mark, which
// is white on this dark band.
type TickerMark = { id: MarkId; height: number; label?: string; style: React.CSSProperties }
const TICKER_MARKS: TickerMark[] = [
{ id: "x402", height: 19, style: { color: "var(--fg)" } },
{ id: "mpp", height: 13, style: { color: "var(--fg)" } },
{ id: "stellar", height: 20, label: "Stellar", style: { color: "var(--fg)" } },
{ id: "base", height: 18, label: "Base", style: { color: "#3d5afe" } },
{ id: "ethereum", height: 21, label: "Ethereum", style: { color: "#8a92f2" } },
{ id: "solana", height: 19, label: "Solana", style: { fill: "url(#wm-solana-gradient)" } },
];

type CompareRow = { without: string; with: string };
Expand Down Expand Up @@ -244,16 +247,20 @@ export default function Home() {
</div>
</section>

<BrandSprite />
<div className="ticker" aria-hidden="true">
<div className="ticker-track">
{[0, 1].map((copy) => (
<div className="ticker-group" key={copy}>
{TICKER_ITEMS.map((item) => (
<span className="ticker-item" key={item}>
{item}
<span className="ticker-square" />
</span>
))}
{[0, 1].flatMap((half) =>
TICKER_MARKS.map((m) => (
<span className="ticker-item" key={`${half}-${m.id}`}>
<Mark id={m.id} height={m.height} style={m.style} />
{m.label}
<span className="ticker-dot" />
</span>
)),
)}
</div>
))}
</div>
Expand Down
96 changes: 96 additions & 0 deletions frontend/components/BrandMarks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// The marks of the two protocols Wasit checks and the four chains it reads
// settlement from, drawn once as an SVG sprite and placed with <use>, so
// the ticker and the stack strip share one copy of each path.
//
// x402 and MPP come from their official brand files. Stellar, Ethereum and
// Solana are simple-icons' paths (CC0). Base's mark is the Square, a square
// with a 5% corner radius (brand.base.org, core identifiers). Every mark
// draws in currentColor unless the caller sets a fill, so it takes the
// page's colour by default.

export type MarkId = "x402" | "mpp" | "stellar" | "base" | "ethereum" | "solana"

/** Width over height of each mark's viewBox. */
const RATIO: Record<MarkId, number> = {
x402: 486.3 / 187.4,
mpp: 81.2195 / 18,
stellar: 1,
base: 1,
ethereum: 1,
solana: 1,
}

/** Width a mark takes at a given height, for laying marks out in SVG. */
export function markWidth(id: MarkId, height: number): number {
return height * RATIO[id]
}

/** Rendered once per page; every Mark below points into it. */
export function BrandSprite() {
return (
<svg width="0" height="0" style={{ position: "absolute" }} aria-hidden="true" focusable="false">
<defs>
<linearGradient id="wm-solana-gradient" x1="0" y1="1" x2="1" y2="0">
<stop offset="0" stopColor="#9945FF" />
<stop offset="1" stopColor="#14F195" />
</linearGradient>
</defs>
<symbol id="wm-x402" viewBox="0 0 486.3 187.4">
<path d="M10.5,59.5c.6-.6,1.6-.6,2.3,0l50.9,50.9,19.5-19.5c.6-.6,1.6-.6,2.3,0l10,10c.6.6.6,1.7,0,2.3l-14.4,14.4v10.4l45.8,45.8c.6.6.6,1.7,0,2.3l-10,10c-.6.6-1.6.6-2.3,0l-50.9-50.9-50.9,50.9c-.6.6-1.6.6-2.3,0L.5,176.1c-.6-.6-.6-1.7,0-2.3l45.8-45.8v-10.4L.5,71.8c-.6-.6-.6-1.7,0-2.3l10-10Z" />
<path d="M374.1,151.7c0-2.5,1-4.9,2.7-6.7l91-93.6c.4-.5.7-1.1.7-1.7v-7.4c0-.6-.2-1.2-.7-1.7l-18.9-19.9c-.4-.5-1.1-.7-1.7-.7h-37.9c-.6,0-1.3.3-1.7.7l-22.9,23c-.9.9-2.5.9-3.4,0l-9.1-9.1c-.9-.9-.9-2.5,0-3.4l26-26c1.8-1.8,4.3-2.8,6.8-2.8h46.7c2.6,0,5.2,1.1,7,3l24.9,26.1c1.7,1.8,2.7,4.2,2.7,6.7v15.9c0,2.5-1,4.9-2.7,6.7l-91,93.6c-.4.5-.7,1.1-.7,1.7v11.4c0,1.3,1.1,2.4,2.4,2.4h89.6c1.3,0,2.4,1.1,2.4,2.4v12.9c0,1.3-1.1,2.4-2.4,2.4h-100.1c-5.3,0-9.7-4.3-9.7-9.7v-26.1Z" />
<path d="M317.9,2.2c17.8,0,32.2,14.4,32.2,32.2v121.6c-.4,17.1-14.2,30.9-31.3,31.4h-41.2c-17.1-.4-30.9-14.2-31.3-31.4v-.8s0-120.8,0-120.8c0-17.8,14.4-32.2,32.2-32.2h39.5ZM263.8,144.8v10.5c0,8,6.5,14.5,14.5,14.5h39.5c8,0,14.5-6.5,14.5-14.5v-79l-68.5,68.6ZM278.3,19.9c-8,0-14.5,6.5-14.5,14.5v85.3l68.5-68.6v-16.7c0-8-6.5-14.5-14.5-14.5h-39.5Z" />
<path d="M199.3,0c1.3,0,2.4,1.1,2.4,2.4v78.6c0,1.3,1.1,2.4,2.4,2.4h17.3c1.3,0,2.4,1.1,2.4,2.4v12.9c0,1.3-1.1,2.4-2.4,2.4h-17.3c-1.3,0-2.4,1.1-2.4,2.4v81.5c0,1.3-1.1,2.4-2.4,2.4h-12.9c-1.3,0-2.4-1.1-2.4-2.4v-81.5c0-1.3-1.1-2.4-2.4-2.4h-60.8c-.6,0-1.3-.3-1.7-.7l-21-21c-.9-.9-.9-2.5,0-3.4L172.9.7c.5-.5,1.1-.7,1.7-.7h24.7ZM123,76c-.9.9-.9,2.5,0,3.4l3.3,3.3c.5.5,1.1.7,1.7.7h53.5c1.3,0,2.4-1.1,2.4-2.4V19.9c0-1.2-1-2.2-2.2-2.2s-1.1.2-1.5.6l-57.3,57.6Z" />
</symbol>
<symbol id="wm-mpp" viewBox="0 0 81.2195 18">
<path d="M33.1162 17.9828H29.2324V2.84079e-05H51.2711C52.7167 2.84079e-05 53.8884 1.17531 53.8884 2.62538V8.65536C53.8884 10.1054 52.7167 11.2807 51.2711 11.2807H33.7302C33.3911 11.2807 33.1162 11.5556 33.1162 11.8948V17.9828ZM33.9887 7.62911H49.1322C49.6143 7.62911 50.0047 7.2375 50.0047 6.754V4.52674C50.0047 4.04324 49.6143 3.65162 49.1322 3.65162H33.9887C33.5066 3.65162 33.1162 4.04324 33.1162 4.52674V6.754C33.1162 7.2375 33.5066 7.62911 33.9887 7.62911Z" />
<path d="M26.5978 17.9828H22.7141V5.6935C22.7141 5.30451 22.246 5.10892 21.9707 5.38284L16.511 10.8126C16.2658 11.0564 15.9347 11.1933 15.5897 11.1933H11.0099C10.6661 11.1933 10.3364 11.0577 10.0916 10.8157L4.62666 5.41609C4.35096 5.14349 3.88419 5.33952 3.88419 5.72763V17.9828H0V0H4.67988L11.8982 7.13351C12.0613 7.29496 12.2812 7.3851 12.5106 7.3851H14.0824C14.3123 7.3851 14.5334 7.29409 14.6966 7.13132L21.8669 0H26.5983V17.9828H26.5978Z" />
<path d="M60.4063 17.9828H56.5226V2.84079e-05H78.5612C80.0068 2.84079e-05 81.1786 1.17531 81.1786 2.62538V8.65536C81.1786 10.1054 80.0068 11.2807 78.5612 11.2807H61.0204C60.6812 11.2807 60.4063 11.5556 60.4063 11.8948V17.9828ZM61.2788 7.62911H76.4223C76.9044 7.62911 77.2948 7.2375 77.2948 6.754V4.52674C77.2948 4.04324 76.9044 3.65162 76.4223 3.65162H61.2788C60.7968 3.65162 60.4063 4.04324 60.4063 4.52674V6.754C60.4063 7.2375 60.7968 7.62911 61.2788 7.62911Z" />
</symbol>
<symbol id="wm-stellar" viewBox="0 0 24 24">
<path d="M12.003 1.716c-1.37 0-2.7.27-3.948.78A10.18 10.18 0 0 0 2.66 7.901a10.136 10.136 0 0 0-.797 3.954c0 .258.01.516.027.775a1.942 1.942 0 0 1-1.055 1.88L0 14.934v1.902l2.463-1.26.072-.032v.005l.77-.39.758-.385.066-.039 14.807-7.56 1.666-.847 3.392-1.732V2.694L17.792 5.86 3.744 13.025l-.104.055-.017-.115a8.286 8.286 0 0 1-.071-1.105c0-2.255.88-4.377 2.474-5.977a8.462 8.462 0 0 1 2.71-1.82 8.513 8.513 0 0 1 3.2-.654h.067a8.41 8.41 0 0 1 4.09 1.055l1.628-.83.126-.066a10.11 10.11 0 0 0-5.845-1.853zM24 7.143 5.047 16.808l-1.666.847L0 19.382v1.902l3.282-1.671 2.91-1.485 14.058-7.153.105-.055.016.115c.05.369.072.743.072 1.11 0 2.255-.88 4.383-2.475 5.978a8.461 8.461 0 0 1-2.71 1.82 8.305 8.305 0 0 1-3.2.654h-.06c-1.441 0-2.86-.369-4.102-1.061l-.066.033-1.683.857c.594.418 1.232.776 1.903 1.062a10.11 10.11 0 0 0 3.947.797 10.09 10.09 0 0 0 7.17-2.975 10.136 10.136 0 0 0 2.969-7.18c0-.259-.005-.523-.027-.781a1.942 1.942 0 0 1 1.055-1.88L24 9.044z" />
</symbol>
<symbol id="wm-base" viewBox="0 0 24 24">
<rect x="1.5" y="1.5" width="21" height="21" rx="1.05" />
</symbol>
<symbol id="wm-ethereum" viewBox="0 0 24 24">
<path d="M11.944 17.97L4.58 13.62 11.943 24l7.37-10.38-7.372 4.35h.003zM12.056 0L4.69 12.223l7.365 4.354 7.365-4.35L12.056 0z" />
</symbol>
<symbol id="wm-solana" viewBox="0 0 24 24">
<path d="m23.8764 18.0313-3.962 4.1393a.9201.9201 0 0 1-.306.2106.9407.9407 0 0 1-.367.0742H.4599a.4689.4689 0 0 1-.2522-.0733.4513.4513 0 0 1-.1696-.1962.4375.4375 0 0 1-.0314-.2545.4438.4438 0 0 1 .117-.2298l3.9649-4.1393a.92.92 0 0 1 .3052-.2102.9407.9407 0 0 1 .3658-.0746H23.54a.4692.4692 0 0 1 .2523.0734.4531.4531 0 0 1 .1697.196.438.438 0 0 1 .0313.2547.4442.4442 0 0 1-.1169.2297zm-3.962-8.3355a.9202.9202 0 0 0-.306-.2106.941.941 0 0 0-.367-.0742H.4599a.4687.4687 0 0 0-.2522.0734.4513.4513 0 0 0-.1696.1961.4376.4376 0 0 0-.0314.2546.444.444 0 0 0 .117.2297l3.9649 4.1394a.9204.9204 0 0 0 .3052.2102c.1154.049.24.0744.3658.0746H23.54a.469.469 0 0 0 .2523-.0734.453.453 0 0 0 .1697-.1961.4382.4382 0 0 0 .0313-.2546.4444.4444 0 0 0-.1169-.2297zM.46 6.7225h18.7815a.9411.9411 0 0 0 .367-.0742.9202.9202 0 0 0 .306-.2106l3.962-4.1394a.4442.4442 0 0 0 .117-.2297.4378.4378 0 0 0-.0314-.2546.453.453 0 0 0-.1697-.196.469.469 0 0 0-.2523-.0734H4.7596a.941.941 0 0 0-.3658.0745.9203.9203 0 0 0-.3052.2102L.1246 5.9687a.4438.4438 0 0 0-.1169.2295.4375.4375 0 0 0 .0312.2544.4512.4512 0 0 0 .1692.196.4689.4689 0 0 0 .2518.0739z" />
</symbol>
</svg>
)
}

/** A mark as its own inline SVG, `height` pixels tall. Decorative. */
export function Mark({
id,
height,
className,
style,
}: {
id: MarkId
height: number
className?: string
style?: React.CSSProperties
}) {
const w = 24 * RATIO[id]
return (
<svg
viewBox={`0 0 ${w} 24`}
width={markWidth(id, height)}
height={height}
className={className}
style={style}
aria-hidden="true"
focusable="false"
>
<use href={`#wm-${id}`} width={w} height={24} />
</svg>
)
}

/** A mark placed inside another SVG, at (x, y) and `height` units tall. */
export function SvgMark({ id, x, y, height, className }: { id: MarkId; x: number; y: number; height: number; className?: string }) {
return <use href={`#wm-${id}`} x={x} y={y} width={markWidth(id, height)} height={height} className={className} />
}
Loading
Loading