Upload 4 files
Browse files- background.js +60 -0
- index.html +3 -3
- script.js +60 -48
- style.css +18 -0
background.js
CHANGED
|
@@ -27,5 +27,65 @@
|
|
| 27 |
}
|
| 28 |
}
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
});
|
| 31 |
})();
|
|
|
|
| 27 |
}
|
| 28 |
}
|
| 29 |
|
| 30 |
+
// ---- Grass strip ----
|
| 31 |
+
if (!document.querySelector(".grass")) {
|
| 32 |
+
const NS = "http://www.w3.org/2000/svg";
|
| 33 |
+
const W = isMobile ? 1200 : 2400;
|
| 34 |
+
const H = 70;
|
| 35 |
+
const svg = document.createElementNS(NS, "svg");
|
| 36 |
+
svg.setAttribute("class", "grass");
|
| 37 |
+
svg.setAttribute("viewBox", "0 0 " + W + " " + H);
|
| 38 |
+
svg.setAttribute("preserveAspectRatio", "xMidYMax slice");
|
| 39 |
+
svg.setAttribute("aria-hidden", "true");
|
| 40 |
+
|
| 41 |
+
svg.innerHTML =
|
| 42 |
+
'<defs><linearGradient id="grass-base" x1="0" y1="1" x2="0" y2="0">' +
|
| 43 |
+
'<stop offset="0" stop-color="#3e7a46" stop-opacity="0.55"/>' +
|
| 44 |
+
'<stop offset="1" stop-color="#3e7a46" stop-opacity="0"/>' +
|
| 45 |
+
"</linearGradient></defs>" +
|
| 46 |
+
'<rect x="0" y="' + (H - 16) + '" width="' + W + '" height="16" fill="url(#grass-base)"/>';
|
| 47 |
+
|
| 48 |
+
const layers = isMobile
|
| 49 |
+
? [
|
| 50 |
+
{ count: 50, hMin: 18, hMax: 32, colors: ["#4f9854"], opacity: 0.9 },
|
| 51 |
+
{ count: 40, hMin: 24, hMax: 42, colors: ["#63ad64"], opacity: 1 },
|
| 52 |
+
]
|
| 53 |
+
: [
|
| 54 |
+
{ count: 190, hMin: 18, hMax: 36, colors: ["#3e7a46", "#457f4b", "#4a8a50"], opacity: 0.8 },
|
| 55 |
+
{ count: 160, hMin: 26, hMax: 52, colors: ["#4f9854", "#57a05a", "#63ad64", "#6db36e"], opacity: 1 },
|
| 56 |
+
];
|
| 57 |
+
|
| 58 |
+
layers.forEach((layer) => {
|
| 59 |
+
const g = document.createElementNS(NS, "g");
|
| 60 |
+
g.setAttribute("opacity", layer.opacity);
|
| 61 |
+
for (let i = 0; i < layer.count; i++) {
|
| 62 |
+
const x = Math.random() * W;
|
| 63 |
+
const h = layer.hMin + Math.random() * (layer.hMax - layer.hMin);
|
| 64 |
+
const lean = (Math.random() - 0.5) * 16;
|
| 65 |
+
const w = 3 + Math.random() * 3;
|
| 66 |
+
const color = layer.colors[(Math.random() * layer.colors.length) | 0];
|
| 67 |
+
const blade = document.createElementNS(NS, "path");
|
| 68 |
+
blade.setAttribute(
|
| 69 |
+
"d",
|
| 70 |
+
"M " + x + " " + H +
|
| 71 |
+
" Q " + (x + lean * 0.2) + " " + (H - h * 0.6) + " " + (x + lean) + " " + (H - h) +
|
| 72 |
+
" Q " + (x + lean * 0.6 + w * 0.5) + " " + (H - h * 0.55) + " " + (x + w) + " " + H + " Z"
|
| 73 |
+
);
|
| 74 |
+
blade.setAttribute("fill", color);
|
| 75 |
+
blade.style.transformBox = "fill-box";
|
| 76 |
+
blade.style.transformOrigin = "50% 100%";
|
| 77 |
+
if (!isMobile) {
|
| 78 |
+
blade.style.animation =
|
| 79 |
+
"blade-sway " + (2.6 + Math.random() * 2.4).toFixed(2) + "s ease-in-out " +
|
| 80 |
+
(-(x / W) * 3 - Math.random()).toFixed(2) + "s infinite alternate";
|
| 81 |
+
}
|
| 82 |
+
g.appendChild(blade);
|
| 83 |
+
}
|
| 84 |
+
svg.appendChild(g);
|
| 85 |
+
});
|
| 86 |
+
|
| 87 |
+
document.body.appendChild(svg);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
});
|
| 91 |
})();
|
index.html
CHANGED
|
@@ -51,12 +51,12 @@
|
|
| 51 |
<div class="range-slider" id="params-range-slider">
|
| 52 |
<div class="range-slider-track"></div>
|
| 53 |
<div class="range-slider-fill" id="params-range-fill"></div>
|
| 54 |
-
<input type="range" id="params-range-min" class="range-thumb range-thumb-min" min="0" max="
|
| 55 |
-
<input type="range" id="params-range-max" class="range-thumb range-thumb-max" min="0" max="
|
| 56 |
</div>
|
| 57 |
<div class="range-slider-values">
|
| 58 |
<span id="params-range-min-label">0B</span>
|
| 59 |
-
<span id="params-range-max-label">
|
| 60 |
</div>
|
| 61 |
<button type="button" class="filter-reset" id="params-filter-reset">Reset</button>
|
| 62 |
</div>
|
|
|
|
| 51 |
<div class="range-slider" id="params-range-slider">
|
| 52 |
<div class="range-slider-track"></div>
|
| 53 |
<div class="range-slider-fill" id="params-range-fill"></div>
|
| 54 |
+
<input type="range" id="params-range-min" class="range-thumb range-thumb-min" min="0" max="1000" step="1" value="0" aria-label="Minimum parameters in billions">
|
| 55 |
+
<input type="range" id="params-range-max" class="range-thumb range-thumb-max" min="0" max="1000" step="1" value="1000" aria-label="Maximum parameters in billions">
|
| 56 |
</div>
|
| 57 |
<div class="range-slider-values">
|
| 58 |
<span id="params-range-min-label">0B</span>
|
| 59 |
+
<span id="params-range-max-label">1000B+</span>
|
| 60 |
</div>
|
| 61 |
<button type="button" class="filter-reset" id="params-filter-reset">Reset</button>
|
| 62 |
</div>
|
script.js
CHANGED
|
@@ -6,8 +6,7 @@
|
|
| 6 |
activeBenchmark: null,
|
| 7 |
search: "",
|
| 8 |
sort: "score-desc",
|
| 9 |
-
|
| 10 |
-
paramsFilter: { min: 0, max: 100 }, // current filter selection
|
| 11 |
};
|
| 12 |
|
| 13 |
const els = {};
|
|
@@ -154,31 +153,39 @@
|
|
| 154 |
|
| 155 |
// -------------------- PARAMS FILTER --------------------
|
| 156 |
|
| 157 |
-
//
|
| 158 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
function initParamsBounds() {
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
state.paramsBounds = { min, max };
|
| 171 |
-
state.paramsFilter = { min, max };
|
| 172 |
-
|
| 173 |
-
els.rangeMin.min = String(min);
|
| 174 |
-
els.rangeMin.max = String(max);
|
| 175 |
-
els.rangeMin.step = "0.1";
|
| 176 |
-
els.rangeMin.value = String(min);
|
| 177 |
-
|
| 178 |
-
els.rangeMax.min = String(min);
|
| 179 |
-
els.rangeMax.max = String(max);
|
| 180 |
-
els.rangeMax.step = "0.1";
|
| 181 |
-
els.rangeMax.value = String(max);
|
| 182 |
}
|
| 183 |
|
| 184 |
function bindParamsFilter() {
|
|
@@ -201,12 +208,16 @@
|
|
| 201 |
});
|
| 202 |
|
| 203 |
const onSlide = () => {
|
| 204 |
-
let
|
| 205 |
-
let
|
| 206 |
// keep the two thumbs from crossing
|
| 207 |
-
if (
|
| 208 |
-
[
|
| 209 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
state.paramsFilter = { min: lo, max: hi };
|
| 211 |
updateRangeVisuals();
|
| 212 |
render();
|
|
@@ -216,10 +227,9 @@
|
|
| 216 |
els.rangeMax.addEventListener("input", onSlide);
|
| 217 |
|
| 218 |
els.filterReset.addEventListener("click", () => {
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
els.
|
| 222 |
-
els.rangeMax.value = String(max);
|
| 223 |
updateRangeVisuals();
|
| 224 |
render();
|
| 225 |
});
|
|
@@ -233,26 +243,29 @@
|
|
| 233 |
}
|
| 234 |
|
| 235 |
function isParamsFilterActive() {
|
| 236 |
-
|
| 237 |
-
return state.paramsFilter.min > min || state.paramsFilter.max < max;
|
| 238 |
}
|
| 239 |
|
| 240 |
function updateRangeVisuals() {
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
const
|
|
|
|
| 245 |
|
| 246 |
els.rangeFill.style.left = loPct.toFixed(2) + "%";
|
| 247 |
els.rangeFill.style.right = (100 - hiPct).toFixed(2) + "%";
|
| 248 |
|
| 249 |
-
const fmt = (
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
| 253 |
};
|
| 254 |
-
els.rangeMinLabel.textContent = fmt(state.paramsFilter.min);
|
| 255 |
-
els.rangeMaxLabel.textContent = fmt(state.paramsFilter.max);
|
| 256 |
|
| 257 |
const active = isParamsFilterActive();
|
| 258 |
els.filterBtn.classList.toggle("has-active-filter", active);
|
|
@@ -346,11 +359,10 @@
|
|
| 346 |
|
| 347 |
if (isParamsFilterActive()) {
|
| 348 |
const { min, max } = state.paramsFilter;
|
| 349 |
-
const { max: dataMax } = state.paramsBounds;
|
| 350 |
rows = rows.filter((r) => {
|
| 351 |
const p = r.model.params_b;
|
| 352 |
if (p == null) return false; // unknown size excluded once a filter is set
|
| 353 |
-
if (max >=
|
| 354 |
return p >= min && p <= max;
|
| 355 |
});
|
| 356 |
}
|
|
|
|
| 6 |
activeBenchmark: null,
|
| 7 |
search: "",
|
| 8 |
sort: "score-desc",
|
| 9 |
+
paramsFilter: { min: 0, max: 1000 }, // current filter selection, in billions of params
|
|
|
|
| 10 |
};
|
| 11 |
|
| 12 |
const els = {};
|
|
|
|
| 153 |
|
| 154 |
// -------------------- PARAMS FILTER --------------------
|
| 155 |
|
| 156 |
+
// Absolute ceiling the slider will ever offer, regardless of what's in the
|
| 157 |
+
// data today — 1000B (1T) parameters, so future large-model submissions
|
| 158 |
+
// don't require touching this code.
|
| 159 |
+
const PARAMS_ABSOLUTE_MAX = 1000;
|
| 160 |
+
|
| 161 |
+
// The slider works in log-space (0.1B .. 1000B) so both a 0.5B and a
|
| 162 |
+
// 400B model are draggable to precisely — a linear 0..1000 slider would
|
| 163 |
+
// make anything under ~10B unreachable with a mouse.
|
| 164 |
+
const LOG_MIN = -1; // 10^-1 = 0.1B
|
| 165 |
+
const LOG_MAX = Math.log10(PARAMS_ABSOLUTE_MAX); // 10^3 = 1000B
|
| 166 |
+
|
| 167 |
+
function paramsToSlider(b) {
|
| 168 |
+
const clamped = Math.max(0.1, Math.min(PARAMS_ABSOLUTE_MAX, b));
|
| 169 |
+
return (Math.log10(clamped) - LOG_MIN) / (LOG_MAX - LOG_MIN);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
function sliderToParams(t) {
|
| 173 |
+
return Math.pow(10, LOG_MIN + t * (LOG_MAX - LOG_MIN));
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
// Derive slider bounds from the actual dataset, but always offer the full
|
| 177 |
+
// 0.1B–1000B range so filtering isn't capped to whatever's in models.json
|
| 178 |
+
// today.
|
| 179 |
function initParamsBounds() {
|
| 180 |
+
state.paramsFilter = { min: 0, max: PARAMS_ABSOLUTE_MAX };
|
| 181 |
+
|
| 182 |
+
[els.rangeMin, els.rangeMax].forEach((el) => {
|
| 183 |
+
el.min = "0";
|
| 184 |
+
el.max = "1000";
|
| 185 |
+
el.step = "1";
|
| 186 |
+
});
|
| 187 |
+
els.rangeMin.value = "0";
|
| 188 |
+
els.rangeMax.value = "1000";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
}
|
| 190 |
|
| 191 |
function bindParamsFilter() {
|
|
|
|
| 208 |
});
|
| 209 |
|
| 210 |
const onSlide = () => {
|
| 211 |
+
let loPos = parseFloat(els.rangeMin.value);
|
| 212 |
+
let hiPos = parseFloat(els.rangeMax.value);
|
| 213 |
// keep the two thumbs from crossing
|
| 214 |
+
if (loPos > hiPos) {
|
| 215 |
+
[loPos, hiPos] = [hiPos, loPos];
|
| 216 |
}
|
| 217 |
+
// slider position 0 is a true "no lower bound" (0B); anything above
|
| 218 |
+
// maps through the log curve so small models stay draggable.
|
| 219 |
+
const lo = loPos <= 0 ? 0 : sliderToParams(loPos / 1000);
|
| 220 |
+
const hi = hiPos >= 1000 ? PARAMS_ABSOLUTE_MAX : sliderToParams(hiPos / 1000);
|
| 221 |
state.paramsFilter = { min: lo, max: hi };
|
| 222 |
updateRangeVisuals();
|
| 223 |
render();
|
|
|
|
| 227 |
els.rangeMax.addEventListener("input", onSlide);
|
| 228 |
|
| 229 |
els.filterReset.addEventListener("click", () => {
|
| 230 |
+
state.paramsFilter = { min: 0, max: PARAMS_ABSOLUTE_MAX };
|
| 231 |
+
els.rangeMin.value = "0";
|
| 232 |
+
els.rangeMax.value = "1000";
|
|
|
|
| 233 |
updateRangeVisuals();
|
| 234 |
render();
|
| 235 |
});
|
|
|
|
| 243 |
}
|
| 244 |
|
| 245 |
function isParamsFilterActive() {
|
| 246 |
+
return state.paramsFilter.min > 0 || state.paramsFilter.max < PARAMS_ABSOLUTE_MAX;
|
|
|
|
| 247 |
}
|
| 248 |
|
| 249 |
function updateRangeVisuals() {
|
| 250 |
+
// thumbs are already in slider-position units (0-1000); just read them
|
| 251 |
+
// back for the fill bar rather than re-deriving from log space, so the
|
| 252 |
+
// fill always matches exactly where the thumbs are.
|
| 253 |
+
const loPct = (parseFloat(els.rangeMin.value) / 1000) * 100;
|
| 254 |
+
const hiPct = (parseFloat(els.rangeMax.value) / 1000) * 100;
|
| 255 |
|
| 256 |
els.rangeFill.style.left = loPct.toFixed(2) + "%";
|
| 257 |
els.rangeFill.style.right = (100 - hiPct).toFixed(2) + "%";
|
| 258 |
|
| 259 |
+
const fmt = (b, isMax) => {
|
| 260 |
+
if (isMax && b >= PARAMS_ABSOLUTE_MAX) return "1000B+";
|
| 261 |
+
if (b <= 0) return "0B";
|
| 262 |
+
if (b >= 1000) return (b / 1000).toFixed(1).replace(/\.0$/, "") + "T";
|
| 263 |
+
if (b < 1) return b.toFixed(2) + "B";
|
| 264 |
+
if (b < 10) return b.toFixed(1) + "B";
|
| 265 |
+
return Math.round(b) + "B";
|
| 266 |
};
|
| 267 |
+
els.rangeMinLabel.textContent = fmt(state.paramsFilter.min, false);
|
| 268 |
+
els.rangeMaxLabel.textContent = fmt(state.paramsFilter.max, true);
|
| 269 |
|
| 270 |
const active = isParamsFilterActive();
|
| 271 |
els.filterBtn.classList.toggle("has-active-filter", active);
|
|
|
|
| 359 |
|
| 360 |
if (isParamsFilterActive()) {
|
| 361 |
const { min, max } = state.paramsFilter;
|
|
|
|
| 362 |
rows = rows.filter((r) => {
|
| 363 |
const p = r.model.params_b;
|
| 364 |
if (p == null) return false; // unknown size excluded once a filter is set
|
| 365 |
+
if (max >= PARAMS_ABSOLUTE_MAX) return p >= min; // top thumb at the ceiling = open-ended "+"
|
| 366 |
return p >= min && p <= max;
|
| 367 |
});
|
| 368 |
}
|
style.css
CHANGED
|
@@ -116,6 +116,23 @@ a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-
|
|
| 116 |
to { transform: translateX(120vw) scale(var(--cloud-scale, 1)); }
|
| 117 |
}
|
| 118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
/* -------------------- LAYOUT -------------------- */
|
| 120 |
|
| 121 |
.container {
|
|
@@ -222,6 +239,7 @@ a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-
|
|
| 222 |
/* -------------------- SUBMIT PANEL -------------------- */
|
| 223 |
|
| 224 |
.submit-panel {
|
|
|
|
| 225 |
margin-bottom: 26px;
|
| 226 |
border-radius: var(--radius);
|
| 227 |
background: var(--surface);
|
|
|
|
| 116 |
to { transform: translateX(120vw) scale(var(--cloud-scale, 1)); }
|
| 117 |
}
|
| 118 |
|
| 119 |
+
.grass {
|
| 120 |
+
position: fixed;
|
| 121 |
+
bottom: -2px; left: 0;
|
| 122 |
+
width: 100%; height: 70px;
|
| 123 |
+
z-index: 50;
|
| 124 |
+
pointer-events: none;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
@keyframes blade-sway {
|
| 128 |
+
from { transform: rotate(-2.4deg); }
|
| 129 |
+
to { transform: rotate(3.2deg); }
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
@media (prefers-reduced-motion: reduce) {
|
| 133 |
+
.grass path { animation: none !important; }
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
/* -------------------- LAYOUT -------------------- */
|
| 137 |
|
| 138 |
.container {
|
|
|
|
| 239 |
/* -------------------- SUBMIT PANEL -------------------- */
|
| 240 |
|
| 241 |
.submit-panel {
|
| 242 |
+
margin-top: 32px;
|
| 243 |
margin-bottom: 26px;
|
| 244 |
border-radius: var(--radius);
|
| 245 |
background: var(--surface);
|