Web frontend: perf panel, cover warmup, incremental browse rendering

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 11:42:33 +02:00
parent 9fd106b757
commit 2db4368fc9
24 changed files with 1178 additions and 255 deletions

View File

@@ -104,12 +104,11 @@ button {
height: 100vh;
height: 100dvh;
overflow: hidden;
background: linear-gradient(
180deg,
oklch(55% 0.07 210) 0%,
oklch(38% 0.06 210) 45%,
var(--sea-deep) 100%
);
/* Gradient stops are plain sRGB hex, not oklch(): a gradient with an oklch stop
interpolates in Oklab, which Gecko expands stop by stop on the CPU every time the
display list is rebuilt - about 15% of a Pi-profile's busy time. Values are the
oklch(55% 0.07 210) / (38% 0.06 210) / (20% 0.045 210) they replace. */
background: linear-gradient(180deg, #397d88 0%, #0e4b54 45%, #001b21 100%);
}
/* Still used by the room-control page's own `<Bubbles>` field (`RoomView.tsx`) - the
@@ -270,7 +269,7 @@ button {
flex: none;
border-radius: 999px;
font-size: 17px;
background: linear-gradient(160deg, oklch(97% 0.01 210 / 0.38), oklch(97% 0.01 210 / 0.12));
background: linear-gradient(160deg, #eef7f961, #eef7f91f) /* oklch(97% 0.01 210 / .38 -> .12) */;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid oklch(97% 0.01 210 / 0.4);
@@ -282,7 +281,7 @@ button {
/* A frosted panel wrapped around a shelf/list of rows so they read as a distinct
surface floating over the stage gradient, rather than flat text on a gradient. */
.glass-panel {
background: linear-gradient(160deg, oklch(97% 0.01 210 / 0.14), oklch(97% 0.01 210 / 0.05));
background: linear-gradient(160deg, #eef7f924, #eef7f90d) /* oklch(97% 0.01 210 / .14 -> .05) */;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid oklch(97% 0.01 210 / 0.16);
@@ -344,3 +343,89 @@ input[type="number"],
input[type="range"] {
font: inherit;
}
/* The search field. Big on purpose: typing is how the whole app is driven, so where the
letters land has to be unmissable. */
.searchbox-row {
flex: none;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
margin: 6px 32px 10px;
}
.searchbox {
display: flex;
align-items: center;
gap: 16px;
width: min(680px, 100%);
min-height: 68px;
box-sizing: border-box;
padding: 10px 26px 10px 14px;
border-radius: 999px;
background: var(--paper);
color: var(--ink);
border: 3px solid var(--accent);
box-shadow: 0 6px 22px var(--shadow);
font-size: 30px;
font-weight: 800;
}
.searchbox-mode {
flex: none;
font-size: 16px;
padding: 8px 14px;
border-radius: 999px;
background: var(--ink);
color: #fff;
border: none;
font: inherit;
font-size: 16px;
cursor: pointer;
}
.searchbox-mode:hover {
background: var(--accent);
}
.searchbox-text {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.searchbox-placeholder {
color: oklch(30% 0.04 210 / .45);
}
.searchbox-caret {
display: inline-block;
width: 3px;
height: 1em;
margin-left: 3px;
vertical-align: -0.12em;
background: var(--accent);
animation: searchbox-blink 1.1s steps(1) infinite;
}
/* Empty: the caret sits where the first letter will land, ahead of the placeholder. */
.searchbox[data-empty] .searchbox-caret {
margin: 0 3px 0 0;
}
.searchbox-note {
color: oklch(90% 0.02 210 / .85);
font-size: 15px;
font-weight: 700;
}
@keyframes searchbox-blink {
50% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.searchbox-caret { animation: none; }
}