mirror of
https://github.com/samdotmak/jev-recall.git
synced 2026-10-27 13:55:48 +01:00
Braun/Rams visual system
Neutral gray panels, hairline rules and 2-3px radii; Inter with tabular figures replaces the serif; silkscreen uppercase labels; geometric indicator marks in place of pictogram icons; one functional orange accent (Jev, the memory that matters, the active tab). Result rows wrap instead of truncating and hovering a memory tints it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
489ce4cb99
commit
6c28855cd6
+32
-29
@@ -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 {
|
||||
|
||||
+3
-4
@@ -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 (
|
||||
<html lang="en" className={`${serif.variable} ${mono.variable} antialiased`}>
|
||||
<html lang="en" className={`${sans.variable} antialiased`}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
+36
-42
@@ -205,21 +205,20 @@ export function Demo({
|
||||
|
||||
return (
|
||||
<Stage>
|
||||
<div className="flex h-full flex-col px-10 pb-7 pt-8">
|
||||
<div className="flex h-full flex-col px-10 pb-6 pt-6">
|
||||
{/* Headline + mode controls */}
|
||||
<div className="flex items-start justify-between gap-8">
|
||||
<div>
|
||||
<div className="mb-3 flex gap-[3px]">
|
||||
<span className="h-[5px] w-[22px] rounded-full bg-embeddings" />
|
||||
<span className="h-[5px] w-[22px] rounded-full bg-sonnet" />
|
||||
<span className="h-[5px] w-[22px] rounded-full bg-jev" />
|
||||
<span className="h-[5px] w-[9px] rounded-full bg-accent" />
|
||||
<div className="mb-3 flex gap-1.5">
|
||||
<span className="h-2.5 w-2.5 bg-embeddings" />
|
||||
<span className="h-2.5 w-2.5 bg-sonnet" />
|
||||
<span className="h-2.5 w-2.5 bg-jev" />
|
||||
</div>
|
||||
<h1 className="text-[40px] font-medium leading-[1.08] tracking-[-0.01em] text-text">
|
||||
<h1 className="text-[34px] font-medium leading-[1.12] tracking-[-0.02em] text-text">
|
||||
<span className="font-bold">Jev Recall</span> catches the memories semantic search{" "}
|
||||
<em className="underline decoration-accent decoration-[3px] underline-offset-[6px]">misses.</em>
|
||||
<span className="border-b-2 border-accent pb-0.5">misses</span>.
|
||||
</h1>
|
||||
<p className="mt-2 text-[19px] leading-snug text-muted">
|
||||
<p className="mt-2 text-[16px] leading-snug text-muted">
|
||||
Same answers as Claude Sonnet 5, at a fraction of the time and cost.
|
||||
</p>
|
||||
</div>
|
||||
@@ -227,7 +226,7 @@ export function Demo({
|
||||
<div className="flex shrink-0 items-center gap-3 pt-2">
|
||||
<button
|
||||
onClick={() => setShowKeys(true)}
|
||||
className="label rounded-full border border-line bg-panel px-3.5 py-1.5 !text-text hover:bg-panel-2"
|
||||
className="label rounded-[2px] border border-line-strong bg-panel px-3.5 py-2 !text-text hover:bg-panel-2"
|
||||
>
|
||||
{live ? "Keys" : "Run live with your keys"}
|
||||
</button>
|
||||
@@ -237,18 +236,18 @@ export function Demo({
|
||||
|
||||
{/* Preset nav */}
|
||||
{!present && (
|
||||
<div className="mt-6 border-b border-line pb-2.5">
|
||||
<div className="flex items-baseline gap-7">
|
||||
<div className="mt-5 border-b border-line pb-2">
|
||||
<div className="flex items-baseline gap-8">
|
||||
{presets.map((p) => {
|
||||
const active = p.id === preset.id && isPreset;
|
||||
return (
|
||||
<button
|
||||
key={p.id}
|
||||
onClick={() => choose(p, false)}
|
||||
className={`relative pb-2.5 text-[16px] transition ${active ? "text-text" : "text-muted hover:text-text"}`}
|
||||
className={`relative pb-2 text-[14px] tracking-[-0.01em] transition ${active ? "text-text" : "text-muted hover:text-text"}`}
|
||||
>
|
||||
{p.label}
|
||||
{active && <span className="absolute inset-x-0 -bottom-[1px] h-[2px] rounded-full bg-accent" />}
|
||||
{active && <span className="absolute inset-x-0 -bottom-[9px] h-[2px] bg-accent" />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -257,14 +256,10 @@ export function Demo({
|
||||
)}
|
||||
|
||||
{/* Request */}
|
||||
<div className="mt-5 flex items-center gap-4">
|
||||
<div className="flex h-[62px] w-[62px] shrink-0 items-center justify-center rounded-xl border border-line bg-panel text-accent">
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" aria-label="Your request" role="img">
|
||||
<circle cx="12" cy="8" r="3.6" stroke="currentColor" strokeWidth="1.5" />
|
||||
<path d="M4.8 20c.6-3.8 3.6-6 7.2-6s6.6 2.2 7.2 6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex h-[62px] flex-1 items-center rounded-xl border border-line bg-panel px-6">
|
||||
<div className="mt-4 flex items-end gap-3">
|
||||
<div className="flex-1">
|
||||
<div className="label mb-1.5">Request</div>
|
||||
<div className="flex h-[52px] items-center rounded-[2px] border border-line-strong bg-panel px-4">
|
||||
{editable ? (
|
||||
<input
|
||||
value={request}
|
||||
@@ -276,28 +271,27 @@ export function Demo({
|
||||
setPhase("ready");
|
||||
}}
|
||||
onKeyDown={(ev) => 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"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[20px] text-text">
|
||||
<div className="text-[18px] text-text">
|
||||
{request.slice(0, typed)}
|
||||
{phase === "typing" && <span className="caret ml-0.5 text-jev">▍</span>}
|
||||
{phase === "typing" && <span className="caret ml-0.5 text-accent">▍</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => run(preset, request, { type: false })}
|
||||
disabled={phase === "running" || phase === "typing" || !request.trim()}
|
||||
className="flex h-[62px] shrink-0 items-center gap-3 rounded-xl bg-text px-8 text-[19px] font-medium text-[#faf8f2] transition hover:opacity-90 disabled:opacity-40"
|
||||
className="flex h-[52px] shrink-0 items-center gap-3 rounded-[2px] bg-text px-7 text-[15px] font-medium tracking-[0.01em] text-panel transition hover:bg-[#333330] disabled:opacity-40"
|
||||
>
|
||||
<svg width="14" height="16" viewBox="0 0 14 16" aria-hidden>
|
||||
<path d="M1 1.5v13l12-6.5z" fill="currentColor" />
|
||||
</svg>
|
||||
<span className="h-2.5 w-2.5 bg-accent" />
|
||||
Run comparison
|
||||
</button>
|
||||
</div>
|
||||
{/* Scan field + results */}
|
||||
<div className="mt-4 grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_600px] gap-5">
|
||||
<div className="mt-4 grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_600px] gap-4">
|
||||
<ScanField
|
||||
memories={memories}
|
||||
scans={scans}
|
||||
@@ -308,7 +302,7 @@ export function Demo({
|
||||
running={phase === "running"}
|
||||
highlightId={hoveredMemory}
|
||||
/>
|
||||
<div className="flex min-h-0 min-w-0 flex-col gap-3">
|
||||
<div className="flex min-h-0 min-w-0 flex-col gap-2.5">
|
||||
{SYSTEMS.map((s) => (
|
||||
<ResultCard
|
||||
key={s.id}
|
||||
@@ -326,30 +320,30 @@ export function Demo({
|
||||
</div>
|
||||
|
||||
{/* Results strip: this run once it finishes, the full benchmark before that */}
|
||||
<div className="mt-4 flex h-[60px] items-center rounded-xl border border-line bg-panel px-8 text-[19px]">
|
||||
<div className="flex items-center gap-3 pr-8">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden className="text-accent">
|
||||
<path d="M7 4h10v5a5 5 0 0 1-10 0V4Z M7 6H4a3 3 0 0 0 3 4 M17 6h3a3 3 0 0 1-3 4 M12 14v4 M8 20h8" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<div className="mt-3 flex h-[56px] items-center rounded-[2px] border border-line bg-panel px-8">
|
||||
<div className="flex items-baseline gap-3 pr-8">
|
||||
<span className="label !text-text">{runStats ? "This request" : "Benchmark"}</span>
|
||||
<span className="text-[15px] text-muted">
|
||||
<span className="text-[14px] text-muted">
|
||||
{runStats ? runStats.caption : `${BENCHMARK.requests} requests, ${memories.length} memories`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center gap-10 border-l border-line pl-10">
|
||||
{SYSTEMS.map(({ id }) => (
|
||||
<span key={id} className="text-muted">
|
||||
{id === "sonnet" ? "Sonnet" : id === "embeddings" ? "Embeddings" : "Jev"}{" "}
|
||||
<span className="tabular ml-2 text-[21px]" style={{ color: METHOD_COLOR[id] }}>
|
||||
<span key={id} className="flex items-center gap-2.5">
|
||||
<span className="h-2.5 w-2.5" style={{ background: METHOD_COLOR[id] }} />
|
||||
<span className="text-[14px] text-muted">
|
||||
{id === "sonnet" ? "Sonnet" : id === "embeddings" ? "Embeddings" : "Jev"}
|
||||
</span>
|
||||
<span className="tabular text-[19px] font-medium text-text">
|
||||
{runStats ? runStats.found[id] : BENCHMARK[id]}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="tabular border-l border-line px-10 text-[17px] text-text">
|
||||
<div className="tabular border-l border-line px-10 text-[16px] font-medium text-text">
|
||||
{runStats ? runStats.faster : BENCHMARK.faster}
|
||||
</div>
|
||||
<div className="tabular border-l border-line pl-10 text-[17px] text-text">
|
||||
<div className="tabular border-l border-line pl-10 text-[16px] font-medium text-text">
|
||||
{runStats ? runStats.cost : BENCHMARK.cost}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,20 +11,17 @@ export type CardState = {
|
||||
|
||||
const ROWS = 2;
|
||||
|
||||
function DocIcon() {
|
||||
return (
|
||||
<svg width="18" height="22" viewBox="0 0 22 26" fill="none" aria-hidden className="shrink-0 text-faint">
|
||||
<path d="M3 1.5h10.5L19.5 7.5V24a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5Z" stroke="currentColor" strokeWidth="1.6" />
|
||||
<path d="M13 1.5V8h6.5M6.5 12.5h9M6.5 16.5h9M6.5 20.5h6" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
/** Found: a filled indicator square in the method's colour. */
|
||||
function FoundMark({ color, dim }: { color: string; dim?: boolean }) {
|
||||
return <span className="mt-[3px] h-2.5 w-2.5 shrink-0" style={{ background: color, opacity: dim ? 0.45 : 1 }} />;
|
||||
}
|
||||
|
||||
function MissIcon() {
|
||||
/** Missed: an empty square struck through. */
|
||||
function MissMark() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden className="shrink-0 text-faint">
|
||||
<circle cx="12" cy="12" r="9.5" stroke="currentColor" strokeWidth="1.6" />
|
||||
<path d="M5.5 18.5 18.5 5.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" className="mt-[3px] shrink-0 text-faint" aria-hidden>
|
||||
<rect x="0.5" y="0.5" width="9" height="9" stroke="currentColor" fill="none" />
|
||||
<path d="M0.5 9.5 9.5 0.5" stroke="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -63,41 +60,41 @@ export function ResultCard({
|
||||
].slice(0, ROWS);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 w-full min-w-0 flex-1 flex-col rounded-xl border border-line bg-panel px-5 py-3.5">
|
||||
<div className="flex min-h-0 w-full min-w-0 flex-1 flex-col overflow-hidden rounded-[3px] border border-line bg-panel px-5 py-3">
|
||||
<div className="flex items-start">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-[18px] w-[18px] rounded-full" style={{ background: color }} />
|
||||
<span className="text-[20px] font-semibold text-text">{name}</span>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="h-3 w-3" style={{ background: color }} />
|
||||
<span className="text-[17px] font-semibold tracking-[-0.01em] text-text">{name}</span>
|
||||
</div>
|
||||
<div className="ml-[30px] mt-0.5 text-[14px] text-muted">
|
||||
<div className="ml-[22px] text-[13px] text-muted">
|
||||
{method}
|
||||
{r && (
|
||||
<span className="ml-2 text-text">
|
||||
· {r.ids.length} {r.ids.length === 1 ? "result" : "results"}
|
||||
<span className="ml-2 tabular text-text">
|
||||
{r.ids.length} {r.ids.length === 1 ? "result" : "results"}
|
||||
</span>
|
||||
)}
|
||||
{state.status === "running" && <span className="ml-2">· searching…</span>}
|
||||
{state.status === "error" && <span className="ml-2 text-[#dc2626]">· {state.error}</span>}
|
||||
{state.status === "running" && <span className="ml-2">searching…</span>}
|
||||
{state.status === "error" && <span className="ml-2 text-accent">{state.error}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="w-[86px] border-l border-line pl-4">
|
||||
<div className="w-[84px] border-l border-line pl-4">
|
||||
<div className="label">Time</div>
|
||||
<div className="tabular mt-0.5 text-[18px] text-text">
|
||||
<div className="tabular mt-0.5 text-[17px] font-medium text-text">
|
||||
{state.status === "idle" ? "–" : `${(state.elapsedMs / 1000).toFixed(2)}s`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[118px] border-l border-line pl-4">
|
||||
<div className="w-[116px] border-l border-line pl-4">
|
||||
<div className="label">Cost</div>
|
||||
<div className="tabular mt-0.5 text-[18px] text-text">{r ? formatCost(r.costUsd) : "–"}</div>
|
||||
<div className="tabular mt-0.5 text-[17px] font-medium text-text">{r ? formatCost(r.costUsd) : "–"}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2.5 flex min-h-0 flex-1 flex-col gap-1.5">
|
||||
<div className="mt-1.5 flex min-h-0 flex-1 flex-col justify-center gap-1.5">
|
||||
{state.status === "running" &&
|
||||
[0, 1].map((i) => <div key={i} className="skeleton h-[42px] shrink-0 rounded-xl" />)}
|
||||
[0, 1].map((i) => <div key={i} className="skeleton h-[44px] shrink-0 rounded-[2px]" />)}
|
||||
{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" ? <MissIcon /> : <DocIcon />}
|
||||
{row.kind === "miss" ? <MissMark /> : <FoundMark color={color} dim={!tinted} />}
|
||||
<div className="min-w-0 flex-1">
|
||||
{row.kind === "miss" ? (
|
||||
<>
|
||||
<div className="truncate text-[15px] text-muted">
|
||||
<div className="text-[14px] leading-[17px] text-muted">
|
||||
{keyLabel.charAt(0).toUpperCase() + keyLabel.slice(1)}
|
||||
<span className="ml-2 text-[14px] text-faint">Doesn't find this detail.</span>
|
||||
<span className="ml-2 text-faint">not found</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="truncate text-[15px] text-text">{memoryById[row.id]?.text}</div>
|
||||
<div className="line-clamp-2 text-[14px] leading-[16px] text-text">{memoryById[row.id]?.text}</div>
|
||||
)}
|
||||
</div>
|
||||
{score !== undefined && tinted && (
|
||||
<span
|
||||
className="fade-up tabular shrink-0 text-[13px] text-jev-ink"
|
||||
className="fade-up tabular label mt-[2px] shrink-0 !text-jev-ink"
|
||||
style={{ animationDelay: `${500 + i * 120}ms` }}
|
||||
>
|
||||
Relevance {score.toFixed(2)}
|
||||
{score.toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -61,17 +61,20 @@ export function ScanField({
|
||||
const hoverPos = shownIndex !== undefined && shownIndex !== null ? cellXY(shownIndex) : null;
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col rounded-xl border border-line bg-panel px-8 pb-7 pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-[22px] text-text">{headline}</span>
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-faint" />
|
||||
<span className="tabular text-[23px] text-text">{(elapsedMs / 1000).toFixed(2)}s</span>
|
||||
{running && <span className="pulse-dot h-1.5 w-1.5 rounded-full bg-jev" />}
|
||||
<div className="flex h-full flex-col rounded-[3px] border border-line bg-panel px-8 pb-7 pt-5">
|
||||
<div className="flex items-end justify-between border-b border-line pb-3">
|
||||
<div>
|
||||
<div className="label">Memory field</div>
|
||||
<div className="mt-1 text-[17px] text-text">{headline}</div>
|
||||
</div>
|
||||
<div className="label flex items-center gap-2">
|
||||
<span className="h-2.5 w-2.5 rounded-[3px] bg-cell" />
|
||||
Each square is a memory
|
||||
<div className="flex items-end gap-3">
|
||||
{running && <span className="pulse-dot mb-[9px] h-2 w-2 bg-jev" />}
|
||||
<div className="text-right">
|
||||
<div className="label">Elapsed</div>
|
||||
<div className="tabular mt-0.5 text-[26px] font-medium leading-none text-text">
|
||||
{(elapsedMs / 1000).toFixed(2)}s
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,18 +88,14 @@ export function ScanField({
|
||||
<div
|
||||
key={m.id}
|
||||
onMouseEnter={() => 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 (
|
||||
<span
|
||||
key={slot}
|
||||
className="h-[5px] w-[5px] rounded-full"
|
||||
className="h-[4px] w-[4px]"
|
||||
style={{ background: sys ? METHOD_COLOR[sys] : "transparent" }}
|
||||
/>
|
||||
);
|
||||
@@ -141,26 +140,25 @@ export function ScanField({
|
||||
<div key={id}>
|
||||
{sweeping && (
|
||||
<div
|
||||
className="pointer-events-none absolute rounded-[6px]"
|
||||
className="pointer-events-none absolute rounded-[1px]"
|
||||
style={{
|
||||
top: pos.y,
|
||||
left: Math.max(0, col - TRAIL) * PITCH,
|
||||
width: Math.min(col, TRAIL) * PITCH + CELL,
|
||||
height: CELL,
|
||||
background: `linear-gradient(90deg, transparent, color-mix(in srgb, ${color} 30%, transparent))`,
|
||||
background: `linear-gradient(90deg, transparent, color-mix(in srgb, ${color} 22%, transparent))`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="pointer-events-none absolute left-0 top-0 rounded-[8px] border-[3px]"
|
||||
className="pointer-events-none absolute left-0 top-0 rounded-[2px] border-2"
|
||||
style={{
|
||||
transform: `translate3d(${pos.x - 4 + inset}px, ${pos.y - 4 + inset}px, 0)`,
|
||||
transition: sweeping ? "none" : "transform 0.55s cubic-bezier(0.2, 0.8, 0.2, 1)",
|
||||
width: CELL + 8 - inset * 2,
|
||||
height: CELL + 8 - inset * 2,
|
||||
borderColor: color,
|
||||
background: inset ? "transparent" : `color-mix(in srgb, ${color} 20%, transparent)`,
|
||||
boxShadow: `0 0 14px color-mix(in srgb, ${color} 40%, transparent)`,
|
||||
background: inset ? "transparent" : `color-mix(in srgb, ${color} 16%, transparent)`,
|
||||
zIndex: id === "jev" ? 3 : 2,
|
||||
}}
|
||||
/>
|
||||
@@ -171,7 +169,7 @@ export function ScanField({
|
||||
{/* hover readout */}
|
||||
{hovered && hoverPos && (
|
||||
<div
|
||||
className="pointer-events-none absolute z-20 max-w-[420px] rounded-lg border border-line bg-panel px-3 py-2 text-[14px] leading-snug text-text shadow-[0_8px_24px_rgba(22,21,15,0.14)]"
|
||||
className="pointer-events-none absolute z-20 max-w-[420px] rounded-[2px] border border-line-strong bg-panel/95 px-3 py-2 text-[13.5px] leading-snug text-text backdrop-blur-[2px]"
|
||||
style={{
|
||||
left: Math.min(Math.max(hoverPos.x + CELL / 2, 210), width - 210),
|
||||
top: hoverPos.y > 70 ? hoverPos.y - 12 : hoverPos.y + CELL + 12,
|
||||
@@ -192,7 +190,7 @@ export function ScanField({
|
||||
const above = y > 60;
|
||||
return (
|
||||
<div
|
||||
className="fade-up absolute z-10 whitespace-nowrap rounded-lg border border-accent/30 bg-accent/12 px-3 py-1.5 text-[15px] text-accent shadow-[0_6px_18px_rgba(22,21,15,0.10)]"
|
||||
className="fade-up label absolute z-10 whitespace-nowrap rounded-[2px] border border-accent/40 bg-accent/10 px-2.5 py-1.5 !text-accent"
|
||||
style={{
|
||||
animationDelay: "450ms",
|
||||
left: Math.min(Math.max(x + CELL / 2, 120), width - 120),
|
||||
|
||||
Reference in New Issue
Block a user