Something went wrong. Try again.
Multi-runtime Japanese morphological tokenizer in TypeScript, inspired by Sudachi. Performs Viterbi tokenization with NFKC normalization.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Kiri 切り — Benchmarks</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,500;0,700;1,300&family=IBM+Plex+Mono:wght@300;400;500&family=Noto+Serif+JP:wght@200;400;700&display=swap" rel="stylesheet"><style>*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
:root { --copper: #dea584; --copper-bright: #f0c4a0; --copper-dim: #a06a3c; --copper-glow: rgba(222,165,132,0.35); --bun-orange: #f89b4b; --bun-orange-bright: #fbb878; --bun-orange-dim: #b06820; --bun-orange-glow: rgba(248,155,75,0.35); --ts-blue: #7b8ec9; --ts-blue-bright: #a0b4e8; --ts-blue-dim: #4a5a8a; --ts-glow: rgba(123,142,201,0.35); --bun-native: #e8a05a; --bun-native-bright: #f0be80; --bun-native-dim: #b07028; --bun-native-glow: rgba(232,160,90,0.35); --node-native: #9a9cc8; --node-native-bright: #b8bae0; --node-native-dim: #606490; --node-native-glow: rgba(154,156,200,0.35); --elixir-native: #8aab7e; --elixir-native-bright: #a8c8a0; --elixir-native-dim: #4a7a4a; --elixir-native-glow: rgba(138,171,126,0.35); --emerald: #5a9e6f; --emerald-bright: #7ec492; --emerald-dim: #3a6e4a; --emerald-glow: rgba(90,158,111,0.35); --bg-deep: #0c0a12; --bg-mid: #14111e; --bg-card: rgba(20,17,32,0.85); --text-primary: #e8e0d4; --text-secondary: #9a8e7e; --text-dim: #5a5248; --border-subtle: rgba(222,165,132,0.08); --font-display: 'Cormorant Garamond', serif; --font-jp: 'Noto Serif JP', serif; --font-mono: 'IBM Plex Mono', monospace;}
html { font-size: 16px; scroll-behavior: smooth;}
body { background: var(--bg-deep); color: var(--text-primary); font-family: var(--font-mono); font-weight: 300; line-height: 1.6; overflow-x: hidden; min-height: 100vh;}
/* === TEXTURE OVERLAY === */body::before { content: ''; position: fixed; inset: 0; background: radial-gradient(ellipse 80% 60% at 20% 10%, rgba(110,74,126,0.08) 0%, transparent 60%), radial-gradient(ellipse 60% 50% at 80% 80%, rgba(222,165,132,0.06) 0%, transparent 50%), radial-gradient(ellipse 40% 40% at 50% 50%, rgba(90,158,111,0.03) 0%, transparent 40%); pointer-events: none; z-index: 0;}
/* Noise texture via SVG data URI */body::after { content: ''; position: fixed; inset: 0; opacity: 0.025; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); background-size: 256px 256px; pointer-events: none; z-index: 0;}
/* === HEXAGONAL BACKGROUND PATTERN === */.hex-grid { position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0.025; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='100' viewBox='0 0 56 100'%3E%3Cpath d='M28 66L0 50L0 16L28 0L56 16L56 50L28 66Z' fill='none' stroke='%23dea584' stroke-width='0.5'/%3E%3Cpath d='M28 166L0 150L0 116L28 100L56 116L56 150L28 166Z' fill='none' stroke='%23dea584' stroke-width='0.5'/%3E%3C/svg%3E");}
/* === LAYOUT === */.container { position: relative; z-index: 1; max-width: 1200px; margin: 0 auto; padding: 0 2rem;}
/* === HERO === */.hero { min-height: 50vh; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 6rem 2rem 4rem; position: relative;}
.hero-watermark { position: absolute; font-family: var(--font-jp); font-weight: 200; font-size: clamp(12rem, 25vw, 22rem); color: transparent; background: linear-gradient(135deg, rgba(222,165,132,0.04), rgba(110,74,126,0.03)); -webkit-background-clip: text; background-clip: text; user-select: none; pointer-events: none; line-height: 1; top: 50%; left: 50%; transform: translate(-50%, -50%);}
.hero-blade { width: 120px; height: 1px; background: linear-gradient(90deg, transparent, var(--copper), transparent); margin-bottom: 2rem; opacity: 0; animation: bladeSlice 1s ease-out 0.3s forwards;}
@keyframes bladeSlice { 0% { width: 0; opacity: 0; } 50% { opacity: 1; } 100% { width: 120px; opacity: 1; }}
.hero-title { font-family: var(--font-display); font-weight: 300; font-size: clamp(3.5rem, 8vw, 6rem); letter-spacing: 0.15em; color: var(--text-primary); opacity: 0; animation: fadeUp 0.8s ease-out 0.5s forwards;}
.hero-title .jp { font-family: var(--font-jp); font-weight: 200; font-size: 0.6em; color: var(--copper); display: block; letter-spacing: 0.3em; margin-top: 0.15em;}
.hero-subtitle { font-family: var(--font-display); font-weight: 300; font-style: italic; font-size: 1.1rem; color: var(--text-secondary); margin-top: 1.5rem; letter-spacing: 0.08em; opacity: 0; animation: fadeUp 0.8s ease-out 0.7s forwards;}
.hero-tagline { font-family: var(--font-jp); font-weight: 400; font-size: 1.05rem; color: var(--copper); margin-top: 1rem; letter-spacing: 0.25em; opacity: 0; animation: fadeUp 0.8s ease-out 0.85s forwards;}
.hero-meta { display: flex; gap: 2rem; margin-top: 2.5rem; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.15em; color: var(--text-dim); opacity: 0; animation: fadeUp 0.8s ease-out 1s forwards;}
.hero-meta span { display: flex; align-items: center; gap: 0.5rem;}
.hero-meta .dot { width: 4px; height: 4px; border-radius: 50%; background: var(--copper-dim);}
.hero-meta .dot.green { background: var(--emerald);}
@keyframes fadeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); }}
/* === SECTION === */.section { padding: 5rem 0; position: relative;}
.section-header { display: flex; align-items: baseline; gap: 1.5rem; margin-bottom: 3rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border-subtle);}
.section-jp { font-family: var(--font-jp); font-weight: 200; font-size: 2.2rem; color: var(--copper); opacity: 0.5; line-height: 1;}
.section-title { font-family: var(--font-display); font-weight: 300; font-size: 1.6rem; letter-spacing: 0.08em; color: var(--text-primary);}
.section-subtitle { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.15em; color: var(--text-dim); margin-left: auto;}
/* === LEGEND === */.legend { display: flex; gap: 1.8rem; margin-bottom: 2.5rem; flex-wrap: wrap;}
.legend-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-secondary);}
.legend-swatch { width: 12px; height: 3px; border-radius: 1px;}
.legend-swatch.rust { background: var(--copper); box-shadow: 0 0 6px var(--copper-glow); }.legend-swatch.bun-native { background: linear-gradient(90deg, var(--bun-orange), var(--copper)); box-shadow: 0 0 6px var(--bun-native-glow); }.legend-swatch.bun { background: var(--bun-orange); box-shadow: 0 0 6px var(--bun-orange-glow); }.legend-swatch.node-native { background: linear-gradient(90deg, var(--ts-blue), var(--copper)); box-shadow: 0 0 6px var(--node-native-glow); }.legend-swatch.ts { background: var(--ts-blue); box-shadow: 0 0 6px var(--ts-glow); }.legend-swatch.elixir-native { background: linear-gradient(90deg, var(--emerald), var(--copper)); box-shadow: 0 0 6px var(--elixir-native-glow); }.legend-swatch.elixir { background: var(--emerald); box-shadow: 0 0 6px var(--emerald-glow); }
/* === CHART GROUP === */.chart-group { margin-bottom: 3rem;}
.chart-group-title { font-family: var(--font-display); font-weight: 500; font-size: 1rem; color: var(--text-secondary); letter-spacing: 0.06em; margin-bottom: 1.5rem; padding-left: 0.5rem; border-left: 2px solid var(--copper-dim);}
/* === BAR CHART === */.chart { display: flex; flex-direction: column; gap: 0.4rem;}
.chart-row { display: grid; grid-template-columns: 160px 1fr; gap: 1rem; align-items: center; min-height: 28px;}
.chart-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); text-align: right; white-space: nowrap; display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem;}
.chart-label .runtime-badge { font-size: 0.58rem; padding: 0.1em 0.4em; border-radius: 2px; letter-spacing: 0.05em; font-weight: 500;}
.runtime-badge.rust { background: rgba(222,165,132,0.15); color: var(--copper-bright); border: 1px solid rgba(222,165,132,0.2); }.runtime-badge.bun { background: rgba(248,155,75,0.12); color: var(--bun-orange); }.runtime-badge.node { background: rgba(123,142,201,0.12); color: var(--ts-blue); }.runtime-badge.elixir { background: rgba(90,158,111,0.12); color: var(--emerald); }
.chart-bars { display: flex; flex-direction: column; gap: 3px; position: relative;}
.bar-row { display: flex; align-items: center; gap: 0.75rem; height: 22px;}
.bar-dict-label { font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); width: 32px; text-align: right; flex-shrink: 0;}
.bar-track { flex: 1; height: 100%; position: relative; border-radius: 1px; overflow: visible;}
.bar { height: 100%; border-radius: 1px; position: relative; width: 0; transition: width 1.2s cubic-bezier(0.16, 1, 0.3, 1); display: flex; align-items: center;}
.bar.rust { background: linear-gradient(90deg, var(--copper-dim), var(--copper)); box-shadow: 0 0 12px var(--copper-glow), inset 0 1px 0 rgba(255,255,255,0.08);}
.bar.bun-native { background: linear-gradient(90deg, var(--bun-orange-dim), var(--bun-native), var(--copper)); box-shadow: 0 0 12px var(--bun-native-glow), inset 0 1px 0 rgba(255,255,255,0.1);}
.bar.bun { background: linear-gradient(90deg, var(--bun-orange-dim), var(--bun-orange)); box-shadow: 0 0 12px var(--bun-orange-glow), inset 0 1px 0 rgba(255,255,255,0.08);}
.bar.node-native { background: linear-gradient(90deg, var(--ts-blue-dim), var(--node-native), var(--copper)); box-shadow: 0 0 12px var(--node-native-glow), inset 0 1px 0 rgba(255,255,255,0.1);}
.bar.ts { background: linear-gradient(90deg, var(--ts-blue-dim), var(--ts-blue)); box-shadow: 0 0 12px var(--ts-glow), inset 0 1px 0 rgba(255,255,255,0.08);}
.bar.elixir-native { background: linear-gradient(90deg, var(--emerald-dim), var(--elixir-native), var(--copper)); box-shadow: 0 0 12px var(--elixir-native-glow), inset 0 1px 0 rgba(255,255,255,0.1);}
.bar.elixir { background: linear-gradient(90deg, var(--emerald-dim), var(--emerald)); box-shadow: 0 0 12px var(--emerald-glow), inset 0 1px 0 rgba(255,255,255,0.08);}
.bar-value { position: absolute; right: -8px; transform: translateX(100%); font-size: 0.65rem; font-weight: 400; color: var(--text-dim); white-space: nowrap; opacity: 0; transition: opacity 0.3s ease 1.2s; letter-spacing: 0.03em;}
.bar-row:hover .bar-value { opacity: 1 !important; color: var(--text-secondary); transition-delay: 0s;}
.chart-row.revealed .bar-value { opacity: 0.6;}
.bar-row:hover .bar { filter: brightness(1.15);}
/* Tooltip on hover */.bar-tooltip { position: absolute; top: -36px; left: 50%; transform: translateX(-50%); background: var(--bg-mid); border: 1px solid var(--border-subtle); padding: 0.3em 0.6em; border-radius: 3px; font-size: 0.68rem; color: var(--text-primary); white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; z-index: 10; box-shadow: 0 4px 16px rgba(0,0,0,0.4);}
.bar-row:hover .bar-tooltip { opacity: 1;}
/* Chart divider line */.chart-row + .chart-row { border-top: 1px solid rgba(222,165,132,0.03); padding-top: 0.4rem;}
/* === SEPARATOR === */.separator { height: 1px; background: linear-gradient(90deg, transparent, var(--border-subtle), var(--copper-dim), var(--border-subtle), transparent); margin: 1rem 0;}
/* === DUAL CHARTS LAYOUT === */.dual-charts { display: grid; grid-template-columns: 1fr 1fr; gap: 3rem;}
@media (max-width: 900px) { .dual-charts { grid-template-columns: 1fr; gap: 2rem; }}
/* === LOAD TIME CHART (vertical bars) === */.load-chart { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1.5rem; margin-top: 1rem;}
.load-group { background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 4px; padding: 1.2rem 1rem; text-align: center; transition: border-color 0.3s ease, box-shadow 0.3s ease;}
.load-group:hover { border-color: rgba(222,165,132,0.15); box-shadow: 0 4px 24px rgba(0,0,0,0.3);}
.load-group-title { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-secondary); margin-bottom: 1rem; display: flex; align-items: center; justify-content: center; gap: 0.4rem;}
.load-bars { display: flex; align-items: flex-end; justify-content: center; gap: 6px; height: 120px; margin-bottom: 0.8rem;}
.load-bar-wrapper { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1; max-width: 32px; height: 100%; justify-content: flex-end;}
.load-bar { width: 100%; border-radius: 2px 2px 0 0; position: relative; min-height: 2px; height: 0; transition: height 1s cubic-bezier(0.16, 1, 0.3, 1); cursor: default;}
.load-bar.rust { background: linear-gradient(0deg, var(--copper-dim), var(--copper)); box-shadow: 0 -2px 8px var(--copper-glow);}.load-bar.bun-native { background: linear-gradient(0deg, var(--bun-orange-dim), var(--bun-native), var(--copper)); box-shadow: 0 -2px 8px var(--bun-native-glow);}.load-bar.bun { background: linear-gradient(0deg, var(--bun-orange-dim), var(--bun-orange)); box-shadow: 0 -2px 8px var(--bun-orange-glow);}.load-bar.node-native { background: linear-gradient(0deg, var(--ts-blue-dim), var(--node-native), var(--copper)); box-shadow: 0 -2px 8px var(--node-native-glow);}.load-bar.ts { background: linear-gradient(0deg, var(--ts-blue-dim), var(--ts-blue)); box-shadow: 0 -2px 8px var(--ts-glow);}.load-bar.elixir-native { background: linear-gradient(0deg, var(--emerald-dim), var(--elixir-native), var(--copper)); box-shadow: 0 -2px 8px var(--elixir-native-glow);}.load-bar.elixir { background: linear-gradient(0deg, var(--emerald-dim), var(--emerald)); box-shadow: 0 -2px 8px var(--emerald-glow);}
.load-bar-label { font-size: 0.55rem; color: var(--text-dim); letter-spacing: 0.05em; writing-mode: horizontal-tb;}
.load-bar-val { position: absolute; top: -18px; left: 50%; transform: translateX(-50%); font-size: 0.58rem; color: var(--text-dim); white-space: nowrap; opacity: 0; transition: opacity 0.3s ease;}
.load-bar:hover .load-bar-val { opacity: 1; color: var(--text-secondary);}
.load-group.revealed .load-bar-val { opacity: 0.5;}
.load-dict-labels { display: flex; justify-content: center; gap: 6px;}
.load-dict-label { font-size: 0.52rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); flex: 1; max-width: 32px; text-align: center;}
/* === FOOTER === */.footer { padding: 4rem 0 3rem; text-align: center; border-top: 1px solid var(--border-subtle); margin-top: 2rem;}
.footer-line { font-family: var(--font-display); font-style: italic; font-weight: 300; font-size: 0.85rem; color: var(--text-dim); letter-spacing: 0.05em;}
.footer-line a { color: var(--copper-dim); text-decoration: none; border-bottom: 1px solid transparent; transition: border-color 0.3s ease, color 0.3s ease;}
.footer-line a:hover { color: var(--copper); border-bottom-color: var(--copper-dim);}
/* === SCROLL REVEAL === */.reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.6s ease, transform 0.6s ease;}
.reveal.visible { opacity: 1; transform: translateY(0);}
/* === RESPONSIVE === */@media (max-width: 768px) { .container { padding: 0 1rem; } .hero { padding: 4rem 1rem 3rem; min-height: 40vh; } .chart-row { grid-template-columns: 110px 1fr; gap: 0.5rem; } .chart-label { font-size: 0.62rem; } .section-header { flex-wrap: wrap; gap: 0.5rem; } .section-subtitle { margin-left: 0; } .hero-meta { flex-direction: column; gap: 0.5rem; } .load-chart { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }}
@media (max-width: 480px) { .chart-row { grid-template-columns: 80px 1fr; } .bar-value { font-size: 0.55rem; }}</style></head><body>
<div class="hex-grid"></div>
<!-- HERO --><header class="hero"> <div class="hero-watermark">切</div> <div class="hero-blade"></div> <h1 class="hero-title"> KIRI <span class="jp">切り</span> </h1> <p class="hero-subtitle">Performance Benchmarks — Japanese Morphological Analysis</p> <p class="hero-tagline">言葉を切る刃</p> <div class="hero-meta"> <span><span class="dot"></span>Apple M4 Pro</span> <span><span class="dot"></span>SudachiDict v20260116</span> </div> <div class="hero-meta"> <span><span class="dot"></span>Rust & TS: single-threaded</span> <span><span class="dot green"></span>Elixir: 512 concurrent workers</span> </div></header>
<div class="container">
<!-- THROUGHPUT SECTION --> <section class="section reveal"> <div class="section-header"> <span class="section-jp">速度</span> <h2 class="section-title">Tokenization Throughput</h2> <span class="section-subtitle">chars / second</span> </div>
<div class="legend"> <div class="legend-item"><span class="legend-swatch rust"></span>Rust</div> <div class="legend-item"><span class="legend-swatch bun-native"></span>Bun + Rust</div> <div class="legend-item"><span class="legend-swatch bun"></span>Bun</div> <div class="legend-item"><span class="legend-swatch node-native"></span>Node + Rust</div> <div class="legend-item"><span class="legend-swatch ts"></span>Node</div> <div class="legend-item"><span class="legend-swatch elixir-native"></span>Elixir + Rust</div> <div class="legend-item"><span class="legend-swatch elixir"></span>Pure Elixir</div> </div>
<div class="dual-charts"> <!-- SHORT TEXT --> <div class="chart-group reveal"> <h3 class="chart-group-title">Short texts <span style="color:var(--text-dim)">~750 chars/iter</span></h3> <div class="chart" id="chart-short"></div> </div>
<!-- LONG TEXT --> <div class="chart-group reveal"> <h3 class="chart-group-title">Long texts <span style="color:var(--text-dim)">~10k chars/iter</span></h3> <div class="chart" id="chart-long"></div> </div> </div> </section>
<div class="separator"></div>
<!-- DICT LOAD TIME SECTION --> <section class="section reveal"> <div class="section-header"> <span class="section-jp">辞書</span> <h2 class="section-title">Dictionary Load Time</h2> <span class="section-subtitle">milliseconds</span> </div>
<div class="legend"> <div class="legend-item"><span class="legend-swatch rust"></span>Rust</div> <div class="legend-item"><span class="legend-swatch bun-native"></span>Bun + Rust</div> <div class="legend-item"><span class="legend-swatch bun"></span>Bun</div> <div class="legend-item"><span class="legend-swatch node-native"></span>Node + Rust</div> <div class="legend-item"><span class="legend-swatch ts"></span>Node</div> <div class="legend-item"><span class="legend-swatch elixir-native"></span>Elixir + Rust</div> <div class="legend-item"><span class="legend-swatch elixir"></span>Pure Elixir</div> </div>
<div class="load-chart" id="chart-load"></div> </section>
<!-- FOOTER --> <footer class="footer"> <p class="footer-line"> <a href="https://tangled.org/russ-fugal.smart-knowledge-systems.com/kiri/blob/main/README.md">Kiri</a> — Built with <a href="https://github.com/WorksApplications/Sudachi">Sudachi</a> algorithms — forged in Rust, tempered in TypeScript, distilled in Elixir </p> <p class="footer-line" style="margin-top: 0.8rem"> <a href="https://smart-knowledge-systems.com">Smart Knowledge Systems</a> </p> </footer>
</div>
<script>// ─── DATA (generated by bench/gen-html.ts) ───const shortData = [{"runtime":"Rust","badge":"rust","backend":"kiri-yaiba","type":"rust","small":1150579,"core":1130554,"full":1132060},{"runtime":"Bun","badge":"bun","backend":"Native (Rust)","type":"bun-native","small":701279,"core":695581,"full":684738},{"runtime":"Bun","badge":"bun","backend":"Core (TS)","type":"bun","small":787615,"core":807657,"full":797426},{"runtime":"Node","badge":"node","backend":"Native (Rust)","type":"node-native","small":528165,"core":524234,"full":521996},{"runtime":"Node","badge":"node","backend":"Core (TS)","type":"ts","small":527582,"core":524584,"full":519331},{"runtime":"Elixir","badge":"elixir","backend":"NIF (×512)","type":"elixir-native","small":1446717,"core":1532255,"full":1552956},{"runtime":"Elixir","badge":"elixir","backend":"Pure (×512)","type":"elixir","small":210829,"core":200458,"full":208609}];const longData = [{"runtime":"Rust","badge":"rust","backend":"kiri-yaiba","type":"rust","small":1108802,"core":1113922,"full":1083867},{"runtime":"Bun","badge":"bun","backend":"Native (Rust)","type":"bun-native","small":661862,"core":648995,"full":650270},{"runtime":"Bun","badge":"bun","backend":"Core (TS)","type":"bun","small":661191,"core":671210,"full":671544},{"runtime":"Node","badge":"node","backend":"Native (Rust)","type":"node-native","small":420940,"core":414393,"full":409737},{"runtime":"Node","badge":"node","backend":"Core (TS)","type":"ts","small":414079,"core":417542,"full":409033},{"runtime":"Elixir","badge":"elixir","backend":"NIF (×512)","type":"elixir-native","small":594298,"core":553437,"full":610063},{"runtime":"Elixir","badge":"elixir","backend":"Pure (×512)","type":"elixir","small":97158,"core":78544,"full":69047}];const loadData = [{"runtime":"Rust","badge":"rust","backend":"kiri-yaiba","type":"rust","small":27.4,"core":44.1,"full":65.6},{"runtime":"Bun","badge":"bun","backend":"Native","type":"bun-native","small":29.4,"core":67.1,"full":143.5},{"runtime":"Bun","badge":"bun","backend":"Core","type":"bun","small":7.7,"core":19.1,"full":46.9},{"runtime":"Node","badge":"node","backend":"Native","type":"node-native","small":21.3,"core":37,"full":64.9},{"runtime":"Node","badge":"node","backend":"Core","type":"ts","small":20.6,"core":36.4,"full":68.4},{"runtime":"Elixir","badge":"elixir","backend":"NIF","type":"elixir-native","small":23.8,"core":38.9,"full":64.4},{"runtime":"Elixir","badge":"elixir","backend":"Pure","type":"elixir","small":23.5,"core":39.3,"full":63.3}];
// ─── FORMAT ───function fmt(n) { return n.toLocaleString('en-US');}
function fmtMs(n) { return n % 1 === 0 ? n + 'ms' : n.toFixed(1) + 'ms';}
// ─── BUILD THROUGHPUT CHART ───function buildThroughputChart(containerId, data) { const container = document.getElementById(containerId); const allValues = data.flatMap(d => [d.small, d.core, d.full].filter(v => v != null)); const maxVal = Math.max(...allValues);
data.forEach(row => { const chartRow = document.createElement('div'); chartRow.className = 'chart-row';
const label = document.createElement('div'); label.className = 'chart-label'; label.innerHTML = '<span class="runtime-badge ' + row.badge + '">' + row.runtime + '</span> ' + row.backend;
const barsContainer = document.createElement('div'); barsContainer.className = 'chart-bars';
const dicts = [ { key: 'small', label: 'sm' }, { key: 'core', label: 'co' }, { key: 'full', label: 'fl' }, ];
dicts.forEach(dict => { const val = row[dict.key]; if (val == null) return; const pct = (val / maxVal) * 100;
const barRow = document.createElement('div'); barRow.className = 'bar-row';
const dictLabel = document.createElement('span'); dictLabel.className = 'bar-dict-label'; dictLabel.textContent = dict.label;
const track = document.createElement('div'); track.className = 'bar-track';
const bar = document.createElement('div'); bar.className = 'bar ' + row.type; bar.dataset.width = pct;
const tooltip = document.createElement('div'); tooltip.className = 'bar-tooltip'; tooltip.textContent = fmt(val) + ' chars/s';
const barValue = document.createElement('span'); barValue.className = 'bar-value'; barValue.textContent = fmt(val);
bar.appendChild(tooltip); bar.appendChild(barValue); track.appendChild(bar); barRow.appendChild(dictLabel); barRow.appendChild(track); barsContainer.appendChild(barRow); });
chartRow.appendChild(label); chartRow.appendChild(barsContainer); container.appendChild(chartRow); });}
// ─── BUILD LOAD TIME CHART ───function buildLoadChart(containerId, data) { const container = document.getElementById(containerId); const allValues = data.flatMap(d => [d.small, d.core, d.full].filter(v => v != null)); const maxVal = Math.max(...allValues);
data.forEach(row => { const group = document.createElement('div'); group.className = 'load-group';
const title = document.createElement('div'); title.className = 'load-group-title'; title.innerHTML = '<span class="runtime-badge ' + row.badge + '" style="font-size:0.6rem">' + row.runtime + '</span> ' + row.backend;
const bars = document.createElement('div'); bars.className = 'load-bars';
const dicts = ['small', 'core', 'full'];
dicts.forEach(dict => { const val = row[dict]; if (val == null) return; const pct = (val / maxVal) * 100;
const wrapper = document.createElement('div'); wrapper.className = 'load-bar-wrapper';
const bar = document.createElement('div'); bar.className = 'load-bar ' + row.type; bar.dataset.height = pct;
const valLabel = document.createElement('span'); valLabel.className = 'load-bar-val'; valLabel.textContent = fmtMs(val);
bar.appendChild(valLabel); wrapper.appendChild(bar); bars.appendChild(wrapper); });
const dictLabels = document.createElement('div'); dictLabels.className = 'load-dict-labels'; ['sm', 'co', 'fl'].forEach(d => { const lbl = document.createElement('span'); lbl.className = 'load-dict-label'; lbl.textContent = d; dictLabels.appendChild(lbl); });
group.appendChild(title); group.appendChild(bars); group.appendChild(dictLabels); container.appendChild(group); });}
// ─── INIT ───buildThroughputChart('chart-short', shortData);buildThroughputChart('chart-long', longData);buildLoadChart('chart-load', loadData);
// ─── SCROLL REVEAL + ANIMATE BARS ───const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible');
// Animate horizontal bars const bars = entry.target.querySelectorAll('.bar[data-width]'); bars.forEach((bar, i) => { setTimeout(() => { bar.style.width = bar.dataset.width + '%'; bar.closest('.chart-row')?.classList.add('revealed'); }, i * 40); });
// Animate vertical bars const loadBars = entry.target.querySelectorAll('.load-bar[data-height]'); loadBars.forEach((bar, i) => { setTimeout(() => { bar.style.height = bar.dataset.height + '%'; bar.closest('.load-group')?.classList.add('revealed'); }, i * 60); });
observer.unobserve(entry.target); } });}, { threshold: 0.15, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));</script></body></html>