The scene stops heating the MacBook
"Terribly high CPU usage, the MacBook gets really hot" - a fanless MacBook Air on a 144Hz screen. Measured on that machine with headed engines (process CPU plus the per-process GPU time macOS keeps): an idle Aero library cost LibreWolf ~110% CPU and ~230ms/s of GPU, the study feed ~130%, Chromium up to ~57% CPU and ~350ms/s of GPU, Safari ~220-250ms/s of WindowServer GPU. Priced one by one: - the gradient mask on .ice-floats re-rendered the animated layer offscreen on every frame (~75 points of Gecko CPU). The floats now rise through a plain rectangular band clear of the header, the bottom chrome and the rail, and fade in and out inside it, so the clip never cuts a visible one; - the overlay grain forced a backdrop readback per frame for a texture that measured a mean <=0.11/255 difference: removed; - the invisible blob drift made every frame a full-screen redraw: the blobs are painted once; - static glass (answer tiles, feedback panel, chips, inputs, dropzones, lists, segmented controls, Aero hint chips) re-blurred the moving scene every frame: only floating glass blurs now, and Aero's baked chat chrome drops a live blur that never showed under the bake; - the scene also parks while the window has no focus and under any dialog or sheet scrim (.scene-pause); the loading icon is a compositor-run CSS animation instead of a framer-motion main-thread loop. After, focused and animating: LibreWolf library 109% -> 33% CPU, study 134% -> 34%, chat 130% -> 42%; Chromium GPU 2.5-4.4x lower with 7-22 points less CPU; Safari's WindowServer GPU -20-26% in Aero, about half in dark. Unfocused or under a sheet: nothing. bubbles-test sweeps every float through 41 phases against the band and pins the parking and the cost contract; tests/heat-probe.mjs (macOS) repeats the measurement; the suites find Playwright's Chromium on macOS. All 15 suites pass, lint clean, the contrast scan lost a finding. Version 0.6.7, SW v89. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b5c4f237cd
commit
bfc0f806b9
27 changed files with 613 additions and 249 deletions
|
|
@ -411,7 +411,7 @@ function HelpDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|||
<AnimatePresence>
|
||||
{open ? (
|
||||
<motion.div
|
||||
className="fixed inset-0 z-50 flex items-end justify-center bg-black/40 backdrop-blur-sm sm:items-center sm:p-6"
|
||||
className="scene-pause fixed inset-0 z-50 flex items-end justify-center bg-black/40 backdrop-blur-sm sm:items-center sm:p-6"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
|
|
|
|||
|
|
@ -3,25 +3,32 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* The fixed ice scene behind every screen: a navy (or pale) base, four slow
|
||||
* drifting glacier blobs, glossy ice orbs that rise slowly and fade at the
|
||||
* edges, and a whisper of grain. Purely decorative — it never intercepts
|
||||
* pointer events, only transform/opacity animate (each on its own composited
|
||||
* layer), the rise stops under `prefers-reduced-motion`, and everything parks
|
||||
* while the tab is hidden so a background tab costs no compositor work.
|
||||
* The fixed ice scene behind every screen: a navy (or pale) base, four soft
|
||||
* glacier blobs and glossy ice orbs that rise slowly through the open band
|
||||
* and fade at its edges. Purely decorative — it never intercepts pointer
|
||||
* events, only transform/opacity animate (each on its own composited layer),
|
||||
* the rise stops under `prefers-reduced-motion`, and everything parks while
|
||||
* the tab is hidden or the window has lost focus, so a page nobody is
|
||||
* looking at costs no compositor work.
|
||||
*/
|
||||
export function IceBackdrop() {
|
||||
const [hidden, setHidden] = useState(false);
|
||||
const [paused, setPaused] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const apply = () => setHidden(document.hidden);
|
||||
const apply = () => setPaused(document.hidden || !document.hasFocus());
|
||||
apply();
|
||||
document.addEventListener("visibilitychange", apply);
|
||||
return () => document.removeEventListener("visibilitychange", apply);
|
||||
window.addEventListener("focus", apply);
|
||||
window.addEventListener("blur", apply);
|
||||
return () => {
|
||||
document.removeEventListener("visibilitychange", apply);
|
||||
window.removeEventListener("focus", apply);
|
||||
window.removeEventListener("blur", apply);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`ice-scene${hidden ? " ice-paused" : ""}`} aria-hidden>
|
||||
<div className={`ice-scene${paused ? " ice-paused" : ""}`} aria-hidden>
|
||||
<span className="ice-blob ice-blob-a" />
|
||||
<span className="ice-blob ice-blob-b" />
|
||||
<span className="ice-blob ice-blob-c" />
|
||||
|
|
@ -53,7 +60,6 @@ export function IceBackdrop() {
|
|||
<span className="ice-bubble ice-bubble-17" />
|
||||
<span className="ice-bubble ice-bubble-18" />
|
||||
</div>
|
||||
<span className="ice-grain" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ export function InstallSheet() {
|
|||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-[4px]"
|
||||
className="scene-pause absolute inset-0 bg-black/50 backdrop-blur-[4px]"
|
||||
/>
|
||||
<motion.div
|
||||
role="dialog"
|
||||
|
|
|
|||
|
|
@ -587,7 +587,7 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" |
|
|||
type="button"
|
||||
aria-label={t("common.cancel")}
|
||||
onClick={closeSheet}
|
||||
className="absolute inset-0 bg-black/40 backdrop-blur-sm"
|
||||
className="scene-pause absolute inset-0 bg-black/40 backdrop-blur-sm"
|
||||
/>
|
||||
<motion.div
|
||||
role="dialog"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useAppReducedMotion as useReducedMotion } from "@/lib/motion-pref";
|
|||
|
||||
import { Button, ProgressBar } from "@/components/ui";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* The one loading screen for every model pipeline — deck/notes creation,
|
||||
|
|
@ -80,15 +81,10 @@ export function LoadingState({
|
|||
>
|
||||
{/* The app icon, not the bare snowflake — same logo as the header,
|
||||
sidebar and empty states. */}
|
||||
<motion.div
|
||||
aria-hidden
|
||||
animate={reduceMotion ? undefined : { rotate: [0, 8, -8, 0], scale: [1, 1.08, 1] }}
|
||||
transition={{ duration: 3.2, repeat: Infinity, ease: "easeInOut" }}
|
||||
className="relative size-28"
|
||||
>
|
||||
<div aria-hidden className={cn("relative size-28", !reduceMotion && "loading-bob")}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- static app icon, no optimisation needed */}
|
||||
<img src="/icons/icon-512.png" alt="" className="size-28 rounded-[1.75rem] shadow-lift" />
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-md space-y-3">
|
||||
<motion.p
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ export function ResultsScreen({
|
|||
{missed.map((activity) => (
|
||||
<li
|
||||
key={activity.id}
|
||||
className="rounded-2xl border border-white/50 bg-white/45 backdrop-blur-xl p-3 text-sm dark:border-white/10 dark:bg-white/5"
|
||||
className="rounded-2xl border border-white/50 bg-white/45 p-3 text-sm dark:border-white/10 dark:bg-white/5"
|
||||
>
|
||||
<p className="font-semibold text-frost-950 dark:text-frost-50">{promptFor(activity)}</p>
|
||||
{results[activity.id]?.userAnswer ? (
|
||||
|
|
@ -192,7 +192,7 @@ export function ResultsScreen({
|
|||
{unanswered.map((activity) => (
|
||||
<li
|
||||
key={activity.id}
|
||||
className="rounded-2xl border border-white/50 bg-white/45 p-3 text-sm font-semibold text-frost-950 backdrop-blur-xl dark:border-white/10 dark:bg-white/5 dark:text-frost-50"
|
||||
className="rounded-2xl border border-white/50 bg-white/45 p-3 text-sm font-semibold text-frost-950 dark:border-white/10 dark:bg-white/5 dark:text-frost-50"
|
||||
>
|
||||
{promptFor(activity)}
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ export function SettingsDialog({
|
|||
<AnimatePresence>
|
||||
{open ? (
|
||||
<motion.div
|
||||
className="fixed inset-0 z-50 flex items-end justify-center bg-frost-950/35 p-0 backdrop-blur-md sm:items-center sm:p-6"
|
||||
className="scene-pause fixed inset-0 z-50 flex items-end justify-center bg-frost-950/35 p-0 backdrop-blur-md sm:items-center sm:p-6"
|
||||
initial={reduceMotion ? false : { opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
|
|
|
|||
|
|
@ -495,12 +495,12 @@ export function UploadScreen() {
|
|||
setDifficulty(option.value);
|
||||
}}
|
||||
className={cn(
|
||||
"relative flex flex-col items-center gap-1 overflow-hidden rounded-2xl border px-2 py-3 text-center backdrop-blur-xl transition-colors",
|
||||
"relative flex flex-col items-center gap-1 overflow-hidden rounded-2xl border px-2 py-3 text-center transition-colors",
|
||||
selected && isPhone
|
||||
? "border-transparent"
|
||||
: selected
|
||||
? "border-accent-400 bg-accent-50/80 shadow-lift dark:bg-accent-500/20"
|
||||
: "border-white/60 bg-white/45 backdrop-blur-xl hover:bg-white/70 dark:border-white/12 dark:bg-white/[0.05]",
|
||||
: "border-white/60 bg-white/45 hover:bg-white/70 dark:border-white/12 dark:bg-white/[0.05]",
|
||||
)}
|
||||
>
|
||||
{selected && isPhone ? (
|
||||
|
|
@ -776,7 +776,7 @@ export function UploadScreen() {
|
|||
autoComplete="off"
|
||||
autoCapitalize="sentences"
|
||||
spellCheck
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none backdrop-blur-xl transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
|
@ -922,7 +922,7 @@ export function UploadScreen() {
|
|||
onChange={(event) => setText(event.target.value)}
|
||||
rows={4}
|
||||
placeholder={t("upload.pastedPlaceholder")}
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none backdrop-blur-xl transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
|
|
@ -942,7 +942,7 @@ export function UploadScreen() {
|
|||
autoComplete="off"
|
||||
autoCapitalize="sentences"
|
||||
spellCheck={false}
|
||||
className="w-full rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none backdrop-blur-xl transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
className="w-full rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1149,7 +1149,7 @@ export function UploadScreen() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="mt-4 flex w-full flex-col items-center gap-2 rounded-2xl border-2 border-dashed border-white/70 bg-white/40 px-4 py-10 text-center backdrop-blur-xl transition hover:border-accent-300 hover:bg-white/65 dark:border-white/15 dark:bg-white/[0.05] dark:hover:bg-white/[0.09]"
|
||||
className="mt-4 flex w-full flex-col items-center gap-2 rounded-2xl border-2 border-dashed border-white/70 bg-white/40 px-4 py-10 text-center transition hover:border-accent-300 hover:bg-white/65 dark:border-white/15 dark:bg-white/[0.05] dark:hover:bg-white/[0.09]"
|
||||
>
|
||||
<span aria-hidden className="animate-float text-frost-500 dark:text-frost-200">
|
||||
<SnowflakeIcon className="size-12" />
|
||||
|
|
@ -1189,7 +1189,7 @@ export function UploadScreen() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="grid aspect-square w-full place-items-center rounded-2xl border-2 border-dashed border-white/70 text-2xl text-accent-500 backdrop-blur-xl transition hover:border-accent-300 hover:bg-white/60 dark:border-white/15 dark:text-accent-200"
|
||||
className="grid aspect-square w-full place-items-center rounded-2xl border-2 border-dashed border-white/70 text-2xl text-accent-500 transition hover:border-accent-300 hover:bg-white/60 dark:border-white/15 dark:text-accent-200"
|
||||
aria-label={t("upload.addMorePhotos")}
|
||||
>
|
||||
+
|
||||
|
|
@ -1204,7 +1204,7 @@ export function UploadScreen() {
|
|||
{documents.map((entry) => (
|
||||
<li
|
||||
key={entry.id}
|
||||
className="flex items-start gap-3 rounded-2xl border border-white/60 bg-white/50 p-3 backdrop-blur-xl dark:border-white/12 dark:bg-white/[0.06]"
|
||||
className="flex items-start gap-3 rounded-2xl border border-white/60 bg-white/50 p-3 dark:border-white/12 dark:bg-white/[0.06]"
|
||||
>
|
||||
<span className="grid size-10 shrink-0 place-items-center rounded-xl bg-frost-100 text-[10px] font-extrabold uppercase tracking-wide text-frost-700 dark:bg-white/10 dark:text-frost-100">
|
||||
{KIND_LABELS[entry.kind]}
|
||||
|
|
@ -1300,7 +1300,7 @@ export function UploadScreen() {
|
|||
onChange={(event) => setText(event.target.value)}
|
||||
rows={4}
|
||||
placeholder={t("upload.pastedPlaceholder")}
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none backdrop-blur-xl transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -1313,7 +1313,7 @@ export function UploadScreen() {
|
|||
value={focus}
|
||||
onChange={(event) => setFocus(event.target.value)}
|
||||
placeholder={t("upload.topicPlaceholder")}
|
||||
className="w-full rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none backdrop-blur-xl transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
className="w-full rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export function FillBlank({ activity, active, language, onAnswered, onNext, isLa
|
|||
autoCapitalize="off"
|
||||
spellCheck={false}
|
||||
className={cn(
|
||||
"mx-1 inline-block rounded-xl border-b-4 bg-white/70 px-2 py-0.5 text-center text-frost-950 outline-none backdrop-blur-lg placeholder:text-frost-300 dark:bg-white/10 dark:text-frost-50 dark:placeholder:text-white/30",
|
||||
"mx-1 inline-block rounded-xl border-b-4 bg-white/70 px-2 py-0.5 text-center text-frost-950 outline-none placeholder:text-frost-300 dark:bg-white/10 dark:text-frost-50 dark:placeholder:text-white/30",
|
||||
result === null
|
||||
? "border-frost-300 focus:border-frost-500"
|
||||
: result
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export function Matching({ activity, language, onAnswered, onNext, isLast, nextL
|
|||
disabled={isMatched || done}
|
||||
onClick={() => setSelectedTerm(isSelected ? null : term.index)}
|
||||
className={cn(
|
||||
"min-h-16 rounded-2xl border px-3 py-2 text-left text-sm font-semibold backdrop-blur-xl transition-all",
|
||||
"min-h-16 rounded-2xl border px-3 py-2 text-left text-sm font-semibold transition-all",
|
||||
isMatched
|
||||
? "border-emerald-200/80 bg-emerald-50/85 text-emerald-800 dark:border-emerald-400/40 dark:bg-emerald-400/15 dark:text-emerald-100"
|
||||
: isSelected
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export function Ordering({ activity, language, onAnswered, onNext, isLast, nextL
|
|||
<Reorder.Item
|
||||
key={item}
|
||||
value={item}
|
||||
className="flex cursor-grab items-center gap-2 rounded-2xl border border-white/60 bg-white/60 px-3 py-3 text-sm font-semibold text-frost-950 shadow-sm backdrop-blur-xl active:cursor-grabbing dark:border-white/12 dark:bg-white/[0.08] dark:text-frost-50"
|
||||
className="flex cursor-grab items-center gap-2 rounded-2xl border border-white/60 bg-white/60 px-3 py-3 text-sm font-semibold text-frost-950 shadow-sm active:cursor-grabbing dark:border-white/12 dark:bg-white/[0.08] dark:text-frost-50"
|
||||
>
|
||||
<span className="grid size-7 shrink-0 place-items-center rounded-full bg-frost-100 text-xs font-extrabold text-frost-700 dark:bg-white/15 dark:text-frost-100">
|
||||
{index + 1}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export function ShortAnswer({ activity, active, language, onAnswered, onNext, is
|
|||
) : null}
|
||||
|
||||
{state.status === "grading" ? (
|
||||
<div className="flex items-center gap-3 rounded-2xl border border-white/60 bg-white/50 backdrop-blur-xl px-4 py-3 text-sm font-semibold text-frost-800 dark:border-white/15 dark:bg-white/10 dark:text-frost-100">
|
||||
<div className="flex items-center gap-3 rounded-2xl border border-white/60 bg-white/50 px-4 py-3 text-sm font-semibold text-frost-800 dark:border-white/15 dark:bg-white/10 dark:text-frost-100">
|
||||
<Spinner className="size-5" />
|
||||
{t("short.grading")}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export function ActivityHeader({
|
|||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-white/50 backdrop-blur-sm",
|
||||
"inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-white/50",
|
||||
meta.tint,
|
||||
)}
|
||||
>
|
||||
|
|
@ -129,7 +129,7 @@ export function OptionTile({
|
|||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-3 rounded-2xl border px-4 py-3.5 text-left text-[15px] font-medium backdrop-blur-xl transition-all duration-200 disabled:cursor-default",
|
||||
"flex w-full items-center gap-3 rounded-2xl border px-4 py-3.5 text-left text-[15px] font-medium transition-all duration-200 disabled:cursor-default",
|
||||
states[state],
|
||||
!disabled && "active:scale-[0.99]",
|
||||
className,
|
||||
|
|
@ -179,7 +179,7 @@ export function FeedbackPanel({
|
|||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ type: "spring", stiffness: 380, damping: 32 }}
|
||||
className={cn(
|
||||
"rounded-3xl border p-4 shadow-lift backdrop-blur-xl",
|
||||
"rounded-3xl border p-4 shadow-lift",
|
||||
correct
|
||||
? "border-emerald-200/80 bg-gradient-to-b from-emerald-50/95 to-emerald-100/70 dark:border-emerald-400/30 dark:from-emerald-500/15 dark:to-emerald-500/5"
|
||||
: "border-rose-200/80 bg-gradient-to-b from-rose-50/95 to-rose-100/70 dark:border-rose-400/30 dark:from-rose-500/15 dark:to-rose-500/5",
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ export function ContextPicker({
|
|||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={reduceMotion ? { duration: 0 } : undefined}
|
||||
className="absolute inset-0 bg-black/45"
|
||||
className="scene-pause absolute inset-0 bg-black/45"
|
||||
aria-label={t("common.close")}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ export function HistorySheet({ open, onClose, ...props }: HistoryProps & { open:
|
|||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={reduceMotion ? { duration: 0 } : undefined}
|
||||
className="absolute inset-0 bg-black/45"
|
||||
className="scene-pause absolute inset-0 bg-black/45"
|
||||
aria-label={t("common.close")}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export function ModelPicker({
|
|||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={reduceMotion ? { duration: 0 } : undefined}
|
||||
className="absolute inset-0 bg-black/45"
|
||||
className="scene-pause absolute inset-0 bg-black/45"
|
||||
aria-label={t("common.close")}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export function HomeworkGuide({
|
|||
>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center] [&>*]:shrink-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="tint-sky inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-white/50 backdrop-blur-sm">
|
||||
<span className="tint-sky inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-white/50">
|
||||
{t("homework.guide.stepOf", { n: stepIndex + 1, total: steps.length })}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -293,7 +293,7 @@ export function HomeworkGuide({
|
|||
>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center] [&>*]:shrink-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="tint-mint inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-white/50 backdrop-blur-sm">
|
||||
<span className="tint-mint inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-white/50">
|
||||
{t("homework.guide.solution")}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ export function HomeworkInput({
|
|||
autoComplete="off"
|
||||
autoCapitalize="sentences"
|
||||
spellCheck
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none backdrop-blur-xl transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
/>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
|
|
@ -531,7 +531,7 @@ export function HomeworkInput({
|
|||
autoComplete="off"
|
||||
autoCapitalize="sentences"
|
||||
spellCheck
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none backdrop-blur-xl transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
className="w-full resize-y rounded-2xl border border-white/60 bg-white/55 px-4 py-3 text-sm text-frost-950 outline-none transition placeholder:text-frost-400 focus:border-accent-400 dark:border-white/12 dark:bg-white/[0.07] dark:text-frost-50"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
|
@ -794,7 +794,7 @@ export function HomeworkInput({
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="mt-4 flex w-full flex-col items-center gap-2 rounded-2xl border-2 border-dashed border-white/70 bg-white/40 px-4 py-10 text-center backdrop-blur-xl transition hover:border-accent-300 hover:bg-white/65 dark:border-white/15 dark:bg-white/[0.05] dark:hover:bg-white/[0.09]"
|
||||
className="mt-4 flex w-full flex-col items-center gap-2 rounded-2xl border-2 border-dashed border-white/70 bg-white/40 px-4 py-10 text-center transition hover:border-accent-300 hover:bg-white/65 dark:border-white/15 dark:bg-white/[0.05] dark:hover:bg-white/[0.09]"
|
||||
>
|
||||
<span aria-hidden className="text-frost-500 dark:text-frost-200">
|
||||
<PlusIcon className="size-10" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue