From 63214b5e2ba374b677f2659eac19e3d6bc3817cd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 21:48:03 +0000 Subject: [PATCH 1/6] =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=E2=80=94=20pf=2064?= =?UTF-8?q?=EB=B0=94=ED=80=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PzCQEsRTRtmM7tcthcCUnQ From 11e4cde69efafbff8f8bdd49848684b4094b746b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 22:03:38 +0000 Subject: [PATCH 2/6] web: text layers scroll, twinkle or run their ramp at a knob's speed, in the Lab and both exports Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PzCQEsRTRtmM7tcthcCUnQ --- web/src/app/pattern-lab/panels/TextPanel.tsx | 67 ++++++- web/src/lib/lab/capture/core.ts | 41 +--- web/src/lib/lab/engine.ts | 77 +++++++- web/src/lib/lab/flatten.ts | 109 ++++++++++- web/src/lib/lab/hExport.ts | 145 ++++++++++++-- web/src/lib/lab/serialize.ts | 19 ++ web/src/lib/lab/store/shared.ts | 19 +- web/src/lib/lab/stretch.ts | 38 ++++ web/src/lib/lab/text.ts | 159 +++++++++++++--- web/src/lib/lab/textEffects.ts | 187 +++++++++++++++++++ web/src/lib/lab/textRaster.ts | 64 +++++-- web/src/lib/lab/types.ts | 33 ++++ 12 files changed, 861 insertions(+), 97 deletions(-) create mode 100644 web/src/lib/lab/stretch.ts create mode 100644 web/src/lib/lab/textEffects.ts diff --git a/web/src/app/pattern-lab/panels/TextPanel.tsx b/web/src/app/pattern-lab/panels/TextPanel.tsx index 5163db611..e4dffedc6 100644 --- a/web/src/app/pattern-lab/panels/TextPanel.tsx +++ b/web/src/app/pattern-lab/panels/TextPanel.tsx @@ -9,6 +9,9 @@ // // Copy as code takes the letters as they are drawn now, the way Pixel's does // a drawing (lib/lab/pixelToCode.ts): a pattern that needs no font. +// +// An effect moves the letters every frame at a speed one knob sets +// (lib/lab/textEffects.ts), and the export and the firmware header carry it. import { useEffect, useRef, useState, type ChangeEvent } from "react"; import { useResettingState } from "@/lib/hooks/useResettingState"; @@ -30,10 +33,12 @@ import { } from "@/lib/lab/textFonts"; import { TEXT_ALIGNS, + TEXT_EFFECTS, TEXT_MAX_CHARS, TEXT_SIZE_MAX, TEXT_SIZE_MIN, isTextLayer, + type TextEffect, type TextFill, type TextLayer, } from "@/lib/lab/types"; @@ -55,6 +60,27 @@ const FILL_LABEL: Record = { const ALIGN_LABEL = { left: "Left", center: "Center", right: "Right" } as const; +const EFFECT_LABEL: Record = { + none: "None", + scroll: "Scroll", + twinkle: "Twinkle", + ramp: "Run the ramp", +}; + +/** What the knob does to each effect, said after the knob's name. */ +const EFFECT_SPEED: Record, string> = { + scroll: "sets how fast the letters run out to the left and come back from the right, up to a panel's width a second", + twinkle: "sets how fast each LED dims and comes back", + ramp: "sets how fast the colors run along this layer's Color Ramp and back", +}; + +/** Where a knob sits in its range, 0..1, the way the effect reads it. */ +function knobShare(value: number | undefined, range: [number, number] | undefined): number { + if (value === undefined || !range) return 0; + const share = (value - range[0]) / Math.max(0.0001, range[1] - range[0]); + return Math.max(0, Math.min(1, share)); +} + const COPY_LABEL: Record = { idle: "Copy as code", copied: "Copied ✓", @@ -144,6 +170,10 @@ export default function TextPanel() { function TextEditor({ layer }: { layer: TextLayer }) { const setTextSpec = useLabStore((state) => state.setTextSpec); + const knobLabels = useLabStore((state) => state.knobLabels); + const speed = useLabStore((state) => + Math.round(knobShare(state.knobs[layer.spec.effectKnob], state.ranges[layer.spec.effectKnob]) * 100), + ); const options = useFontOptions(); const fileRef = useRef(null); const [remember, setRemember] = useState(false); @@ -322,7 +352,7 @@ function TextEditor({ layer }: { layer: TextLayer }) { ))} - {spec.fill === "solid" && ( + {spec.fill === "solid" && spec.effect !== "ramp" && (