diff --git a/web/app/globals.css b/web/app/globals.css index 8727292..3d2de0b 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -1,21 +1,23 @@ @import "tailwindcss"; -/* Warm paper-and-ink palette: off-white stock, near-black ink, serif display, - monospace for labels and figures. Method colors are muted to match. */ +/* Braun / Dieter Rams system: neutral gray panels, hairline rules, one functional + orange accent, a single neutral grotesk, numerals in tabular figures. */ :root { - --bg: #f1eee5; - --panel: #faf8f2; - --panel-2: #f4f1e8; - --cell: #e3dfd2; - --line: rgba(22, 21, 18, 0.14); - --text: #16150f; - --muted: #6d6758; - --faint: #9b9484; - --embeddings: #3a6ea5; - --sonnet: #c2613a; - --jev: #2f7f6b; - --jev-ink: #235f50; - --accent: #9c2f26; /* oxblood: brand marks and "the memory that matters" */ + --bg: #e6e6e3; + --panel: #f5f5f3; + --panel-2: #eeeeec; + --cell: #dcdcd8; + --cell-hover: #c9c9c4; + --line: #d0d0cb; + --line-strong: #b4b4ae; + --text: #1c1c1a; + --muted: #6f6f6a; + --faint: #9a9a94; + --embeddings: #64798a; /* baseline: cool blue-gray */ + --sonnet: #33383b; /* graphite */ + --jev: #e2571e; /* Braun orange */ + --jev-ink: #b8430f; + --accent: #e2571e; } @theme inline { @@ -24,6 +26,7 @@ --color-panel-2: var(--panel-2); --color-cell: var(--cell); --color-line: var(--line); + --color-line-strong: var(--line-strong); --color-text: var(--text); --color-muted: var(--muted); --color-faint: var(--faint); @@ -32,9 +35,8 @@ --color-jev: var(--jev); --color-jev-ink: var(--jev-ink); --color-accent: var(--accent); - --font-sans: var(--font-serif); - --font-serif: var(--font-serif); - --font-mono: var(--font-mono); + --font-sans: var(--font-sans); + --font-mono: var(--font-sans); } html, @@ -42,22 +44,23 @@ body { height: 100%; background: var(--bg); color: var(--text); - font-family: var(--font-serif), Georgia, serif; + font-family: var(--font-sans), "Helvetica Neue", Arial, sans-serif; + font-feature-settings: "tnum" 1, "cv05" 1; overflow: hidden; } -/* Small uppercase monospace labels, as on the reference spec sheet. */ +/* Equipment labels: small, uppercase, letter-spaced, like panel silkscreen. */ .label { - font-family: var(--font-mono), ui-monospace, monospace; - font-size: 11px; - letter-spacing: 0.12em; + font-size: 10.5px; + font-weight: 500; + letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); } .tabular { - font-family: var(--font-mono), ui-monospace, monospace; font-variant-numeric: tabular-nums; + font-feature-settings: "tnum" 1; } @keyframes caret { @@ -83,12 +86,12 @@ body { animation: shimmer 1.2s linear infinite; } -/* Entrance animations run as CSS, not JS: they keep playing even when the - rAF-driven animation loop is throttled (long screen recordings, background tabs). */ +/* Entrance animations run as CSS so they keep playing when the JS animation + loop is throttled (long screen recordings, background tabs). */ @keyframes fade-up { from { opacity: 0; - transform: translateY(8px); + transform: translateY(6px); } to { opacity: 1; @@ -96,12 +99,12 @@ body { } } .fade-up { - animation: fade-up 0.42s cubic-bezier(0.2, 0.7, 0.3, 1) both; + animation: fade-up 0.36s cubic-bezier(0.2, 0.7, 0.3, 1) both; } @keyframes pulse-dot { 50% { - opacity: 0.2; + opacity: 0.25; } } .pulse-dot { diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 64c6933..a71e9b1 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,9 +1,8 @@ import type { Metadata } from "next"; -import { IBM_Plex_Mono, Newsreader } from "next/font/google"; +import { Inter } from "next/font/google"; import "./globals.css"; -const serif = Newsreader({ variable: "--font-serif", subsets: ["latin"], style: ["normal", "italic"] }); -const mono = IBM_Plex_Mono({ variable: "--font-mono", subsets: ["latin"], weight: ["400", "500"] }); +const sans = Inter({ variable: "--font-sans", subsets: ["latin"] }); export const metadata: Metadata = { title: "Jev Recall · Catches the memories semantic search misses", @@ -13,7 +12,7 @@ export const metadata: Metadata = { export default function RootLayout({ children }: LayoutProps<"/">) { return ( - + {children} ); diff --git a/web/components/Demo.tsx b/web/components/Demo.tsx index 37609da..cac85f7 100644 --- a/web/components/Demo.tsx +++ b/web/components/Demo.tsx @@ -205,21 +205,20 @@ export function Demo({ return ( -
+
{/* Headline + mode controls */}
-
- - - - +
+ + +
-

+

Jev Recall catches the memories semantic search{" "} - misses. + misses.

-

+

Same answers as Claude Sonnet 5, at a fraction of the time and cost.

@@ -227,7 +226,7 @@ export function Demo({
@@ -237,18 +236,18 @@ export function Demo({ {/* Preset nav */} {!present && ( -
-
+
+
{presets.map((p) => { const active = p.id === preset.id && isPreset; return ( ); })} @@ -257,14 +256,10 @@ export function Demo({ )} {/* Request */} -
-
- - - - -
-
+
+
+
Request
+
{editable ? ( ev.key === "Enter" && run(preset, request, { type: false })} - className="w-full bg-transparent text-[20px] text-text outline-none" + className="w-full bg-transparent text-[18px] text-text outline-none" /> ) : ( -
+
{request.slice(0, typed)} - {phase === "typing" && ▍} + {phase === "typing" && ▍}
)} +
{/* Scan field + results */} -
+
-
+
{SYSTEMS.map((s) => ( {/* Results strip: this run once it finishes, the full benchmark before that */} -
-
- - - +
+
{runStats ? "This request" : "Benchmark"} - + {runStats ? runStats.caption : `${BENCHMARK.requests} requests, ${memories.length} memories`}
{SYSTEMS.map(({ id }) => ( - - {id === "sonnet" ? "Sonnet" : id === "embeddings" ? "Embeddings" : "Jev"}{" "} - + + + + {id === "sonnet" ? "Sonnet" : id === "embeddings" ? "Embeddings" : "Jev"} + + {runStats ? runStats.found[id] : BENCHMARK[id]} ))}
-
+
{runStats ? runStats.faster : BENCHMARK.faster}
-
+
{runStats ? runStats.cost : BENCHMARK.cost}
diff --git a/web/components/ResultCard.tsx b/web/components/ResultCard.tsx index aea22f7..4182b4c 100644 --- a/web/components/ResultCard.tsx +++ b/web/components/ResultCard.tsx @@ -11,20 +11,17 @@ export type CardState = { const ROWS = 2; -function DocIcon() { - return ( - - - - - ); +/** Found: a filled indicator square in the method's colour. */ +function FoundMark({ color, dim }: { color: string; dim?: boolean }) { + return ; } -function MissIcon() { +/** Missed: an empty square struck through. */ +function MissMark() { return ( - - - + + + ); } @@ -63,41 +60,41 @@ export function ResultCard({ ].slice(0, ROWS); return ( -
+
-
- - {name} +
+ + {name}
-
+
{method} {r && ( - - · {r.ids.length} {r.ids.length === 1 ? "result" : "results"} + + {r.ids.length} {r.ids.length === 1 ? "result" : "results"} )} - {state.status === "running" && · searching…} - {state.status === "error" && · {state.error}} + {state.status === "running" && searching…} + {state.status === "error" && {state.error}}
-
+
Time
-
+
{state.status === "idle" ? "–" : `${(state.elapsedMs / 1000).toFixed(2)}s`}
-
+
Cost
-
{r ? formatCost(r.costUsd) : "–"}
+
{r ? formatCost(r.costUsd) : "–"}
-
+
{state.status === "running" && - [0, 1].map((i) =>
)} + [0, 1].map((i) =>
)} {rows.map((row, i) => { const score = row.kind !== "miss" ? r?.scores?.[row.id] : undefined; const tinted = row.kind === "hit"; @@ -107,36 +104,32 @@ export function ResultCard({ key={`${id}-${row.id}`} onMouseEnter={() => onHoverMemory(hoverId)} onMouseLeave={() => onHoverMemory(null)} - className="fade-up flex h-[42px] shrink-0 cursor-default items-center gap-3 rounded-xl border px-3 transition-colors" + className="fade-up group flex min-h-[44px] shrink-0 cursor-default items-start gap-3 rounded-[2px] border px-3 py-2 transition-colors hover:bg-[color-mix(in_srgb,var(--text)_5%,transparent)]" style={{ animationDelay: `${80 + i * 120}ms`, - borderColor: tinted ? `color-mix(in srgb, ${color} 30%, transparent)` : "var(--line)", - background: tinted - ? `color-mix(in srgb, ${color} 12%, var(--panel))` - : row.kind === "miss" - ? "var(--panel-2)" - : "var(--panel)", + borderColor: tinted ? `color-mix(in srgb, ${color} 45%, var(--line))` : "var(--line)", + background: tinted ? `color-mix(in srgb, ${color} 8%, var(--panel))` : "var(--panel)", }} > - {row.kind === "miss" ? : } + {row.kind === "miss" ? : }
{row.kind === "miss" ? ( <> -
+
{keyLabel.charAt(0).toUpperCase() + keyLabel.slice(1)} - Doesn't find this detail. + not found
) : ( -
{memoryById[row.id]?.text}
+
{memoryById[row.id]?.text}
)}
{score !== undefined && tinted && ( - Relevance {score.toFixed(2)} + {score.toFixed(2)} )}
diff --git a/web/components/ScanField.tsx b/web/components/ScanField.tsx index e1f248c..d63ea4f 100644 --- a/web/components/ScanField.tsx +++ b/web/components/ScanField.tsx @@ -61,17 +61,20 @@ export function ScanField({ const hoverPos = shownIndex !== undefined && shownIndex !== null ? cellXY(shownIndex) : null; return ( -
-
-
- {headline} - - {(elapsedMs / 1000).toFixed(2)}s - {running && } +
+
+
+
Memory field
+
{headline}
-
- - Each square is a memory +
+ {running && } +
+
Elapsed
+
+ {(elapsedMs / 1000).toFixed(2)}s +
+
@@ -85,18 +88,14 @@ export function ScanField({
setHover(i)} - className="absolute rounded-[6px] transition-colors duration-300 hover:brightness-95" + className="absolute rounded-[1px] transition-colors duration-200" style={{ left: x, top: y, width: CELL, height: CELL, - background: isKey ? "var(--panel)" : "var(--cell)", - boxShadow: isKey - ? "0 0 0 2px var(--accent), 0 0 12px color-mix(in srgb, var(--accent) 40%, transparent)" - : shownIndex === i - ? "0 0 0 2px var(--text)" - : undefined, + background: isKey ? "var(--panel)" : shownIndex === i ? "var(--cell-hover)" : "var(--cell)", + boxShadow: isKey ? "0 0 0 1.5px var(--accent)" : shownIndex === i ? "0 0 0 1.5px var(--text)" : undefined, }} > {pickedBy[m.id] && ( @@ -106,7 +105,7 @@ export function ScanField({ return ( ); @@ -141,26 +140,25 @@ export function ScanField({
{sweeping && (
)}
@@ -171,7 +169,7 @@ export function ScanField({ {/* hover readout */} {hovered && hoverPos && (
70 ? hoverPos.y - 12 : hoverPos.y + CELL + 12, @@ -192,7 +190,7 @@ export function ScanField({ const above = y > 60; return (