Design pass (all themes + chat UI), model picker, reduce-motion toggle
- Aero: blue gradients back on the active segment and every primary
(Review due, the card's Study button, the composer's send button); a
frosted backing behind the page heading and the All decks label; a
bottom-weighted rail tint; chat header/composer/welcome panels; calmer
orbs; stronger card veil + a stronger blur bake.
- Dark/Light: a dedicated --track token makes progress bars read (the
first attempt shifted --surface-sunken and the pixel scan caught the
sheet-contrast regression); brighter dark secondary text; darker light
segmented labels and difficulty tints; stronger light card border and
shadow.
- Chat UI: friendly model chip ("Gemma 4 31B") with the raw name in a
tooltip, larger chips, plain centered disclaimer, vertically centered
welcome panel, distinct chat-plus icon for new chat.
- Chat models: every chat-capable model from the server's /api/tags is
selectable (favourites first, search field when long, live-list
validation in the route); default CHAT_MODEL_DEFAULT or gemma4:31b.
- Reduce motion: Settings toggle (default off) that parks the scene,
collapses CSS animations and drives framer-motion's calm mode.
- Folders hint, "New" badge on fresh decks.
Contrast: 0 findings, axe clean, mobile 126/126, chat 102/102, wizard
95/95, regression 44/44, i18n 13/13, native 33/33.
Version 0.4.6, SW -> freezmo-v71.
This commit is contained in:
parent
b70b0e83cf
commit
6635b5d405
29 changed files with 468 additions and 90 deletions
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
|
||||
import { CheckCircleIcon } from "@/components/icons";
|
||||
|
|
@ -32,6 +32,15 @@ export function ModelPicker({
|
|||
const isPhone = useIsPhone();
|
||||
const reduceMotion = useReducedMotion();
|
||||
const panel = useRef<HTMLDivElement>(null);
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
/* Every model the server lists is offered, so the list can be long — filter
|
||||
it. Cleared each time the picker opens. */
|
||||
useEffect(() => {
|
||||
if (open) setQuery("");
|
||||
}, [open]);
|
||||
const needle = query.trim().toLowerCase();
|
||||
const visible = needle ? models.filter((model) => model.name.toLowerCase().includes(needle)) : models;
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
|
@ -61,8 +70,23 @@ export function ModelPicker({
|
|||
model.badge === "vision" ? t("chat.modelVision") : model.badge === "both" ? t("chat.modelBoth") : t("chat.modelText");
|
||||
|
||||
const rows = (
|
||||
<>
|
||||
{models.length > 8 ? (
|
||||
<input
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder={t("chat.modelSearch")}
|
||||
aria-label={t("chat.modelSearch")}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
className="mb-2 w-full rounded-xl border border-[var(--hairline)] bg-[var(--surface-sunken)] px-3 py-2 text-[13px] text-[var(--text)] outline-none placeholder:text-[var(--text-subtle)] focus:border-accent-400"
|
||||
/>
|
||||
) : null}
|
||||
<ul className="max-h-[60dvh] space-y-1 overflow-y-auto overscroll-contain">
|
||||
{models.map((model) => {
|
||||
{visible.length === 0 ? (
|
||||
<li className="px-3 py-2 text-[12.5px] text-[var(--text-subtle)]">{t("chat.modelSearchEmpty")}</li>
|
||||
) : null}
|
||||
{visible.map((model) => {
|
||||
const selected = model.name === value;
|
||||
const unavailable = model.available === false;
|
||||
return (
|
||||
|
|
@ -79,7 +103,7 @@ export function ModelPicker({
|
|||
"flex w-full items-center gap-3 rounded-2xl border px-3 py-3 text-left transition",
|
||||
selected
|
||||
? "border-[var(--nav-active-border)] bg-[var(--nav-active-bg)]"
|
||||
: "border-[var(--hairline-soft)] bg-[var(--surface-bg)] hover:bg-[var(--hover)]",
|
||||
: "border-[var(--hairline-soft)] bg-[var(--surface-strong)] hover:bg-[var(--hover)]",
|
||||
unavailable && "opacity-55",
|
||||
)}
|
||||
>
|
||||
|
|
@ -100,6 +124,7 @@ export function ModelPicker({
|
|||
);
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
|
||||
if (isPhone) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue