feat(frontend): visual QA pass — unified page titles, CTAs, native controls, 404 fixes

- add PageTitle and apply it on /download, /themes, /configs, /showcase, /link, 404
- reset range-slider thumbs in the theme editor; fold color picker into the swatch
- hide horizontal scrollbars in the ClickGui mock; clip html to kill 100vw pan
- pin the footer under short pages via flex AppShell; split 404 art from caption
- one landing CTA geometry (lv-cta-lg / quiet); CLOUD eyebrow; auth caption scrim
- showcase error hides sort/pager; query retry limited to 1
This commit is contained in:
loki5512344 2026-09-28 13:11:46 +02:00
parent 10d90dce93
commit 7bb981b58e
Signed by: boba
GPG key ID: 253067914055423B
26 changed files with 198 additions and 73 deletions

View file

@ -47,8 +47,11 @@
background-position: 0 0, 5px 5px;
}
.lv-alpha-slider::-webkit-slider-thumb,
.lv-alpha-slider::-moz-range-thumb {
/* Alpha slider: `appearance: none` on the input alone leaves the UA thumb in Chrome,
so reset it on the pseudo-elements and paint our own. */
.lv-alpha-slider::-webkit-slider-thumb {
appearance: none;
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
@ -56,3 +59,42 @@
border: 2px solid #0b0f22;
cursor: pointer;
}
.lv-alpha-slider::-moz-range-thumb {
appearance: none;
-moz-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--color-ice, #6cf);
border: 2px solid #0b0f22;
cursor: pointer;
}
/* Native color input rendered as the checkerboard swatch itself: the real picker is
transparent and fills the swatch, so a click opens it without showing UA chrome. */
.lv-color-swatch {
position: relative;
display: block;
width: 100%;
height: 100%;
}
.lv-color-swatch input[type='color'] {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
border: none;
padding: 0;
}
.lv-color-swatch::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--color-ice, #6cf) 70%, transparent);
opacity: 0;
transition: opacity 150ms ease;
}
.lv-color-swatch:focus-within::after {
opacity: 1;
}

View file

@ -29,19 +29,20 @@ export function ColorField({ label, value, onChange }: Props) {
return (
<label htmlFor={id} className="lv-color-field flex w-full min-w-0 items-center gap-2">
<span className="lv-alpha-check size-8 shrink-0 overflow-hidden rounded-md border border-white/15" aria-hidden="true">
<span className="block size-full" style={{ background: argbToCss(value) }} />
</span>
<span className="flex min-w-0 flex-1 flex-col gap-1">
<span className="truncate text-xs text-frost">{label}</span>
<span className="flex min-w-0 items-center gap-2">
<span className="lv-alpha-check size-8 shrink-0 overflow-hidden rounded-md border border-white/15">
<span className="lv-color-swatch" style={{ background: argbToCss(value) }}>
<input
type="color"
aria-label={label}
value={toRgb6(value)}
onChange={(e) => onChange(withAlpha(value, e.target.value))}
className="size-7 shrink-0 cursor-pointer rounded border-0 bg-transparent p-0"
className="size-full cursor-pointer border-0 bg-transparent p-0 opacity-0"
/>
</span>
</span>
<span className="flex min-w-0 flex-1 flex-col gap-1">
<span className="truncate text-xs text-frost">{label}</span>
<span className="flex min-w-0 items-center gap-2">
<input
type="range"
min={0}

View file

@ -36,7 +36,7 @@ export function GradientField({ title, value, onChange }: Props) {
max={359}
value={Math.round(value.angle)}
onChange={(e) => onChange({ angle: Number(e.target.value) })}
className="h-1.5 flex-1 cursor-pointer appearance-none rounded-full bg-white/20"
className="lv-alpha-slider h-1.5 flex-1 cursor-pointer appearance-none rounded-full bg-white/20"
/>
<span className="w-10 shrink-0 text-right font-code text-snow">{Math.round(value.angle)}°</span>
</label>