From 67233721094b4ac0c3ed9fe916283022b3246db0 Mon Sep 17 00:00:00 2001 From: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com> Date: Thu, 16 Jul 2026 00:42:52 -0500 Subject: [PATCH 1/4] Add MultiImageMode and supporting hooks for multi-image feature Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com> --- src/components/PrintUnlockEffect.tsx | 46 ++++++ src/hooks/useSecretCode.ts | 32 +++++ src/hooks/useTileWorkspace.ts | 51 +++++++ src/index.css | 201 +++++++++++++++++++++++++++ src/modes/MultiImageMode.tsx | 15 ++ 5 files changed, 345 insertions(+) create mode 100644 src/components/PrintUnlockEffect.tsx create mode 100644 src/hooks/useSecretCode.ts create mode 100644 src/hooks/useTileWorkspace.ts create mode 100644 src/modes/MultiImageMode.tsx diff --git a/src/components/PrintUnlockEffect.tsx b/src/components/PrintUnlockEffect.tsx new file mode 100644 index 0000000..301fe64 --- /dev/null +++ b/src/components/PrintUnlockEffect.tsx @@ -0,0 +1,46 @@ +import React, { useEffect } from 'react'; + +const DURATION_MS = 2600; + +function prefersReducedMotion(): boolean { + return ( + typeof window !== 'undefined' && + typeof window.matchMedia === 'function' && + window.matchMedia('(prefers-reduced-motion: reduce)').matches + ); +} + +/** + * A quick, self-timing FDM "print build" flourish that plays when the hidden + * feat35 code (see src/hooks/useSecretCode.ts) unlocks multi-image mode. It + * renders a full-screen, click-through overlay of a filament object printing + * layer-by-layer with a sweeping nozzle, then calls `onDone` so the parent can + * unmount it. Bows out immediately for anyone who prefers reduced motion. + */ +function PrintUnlockEffect({ onDone }: { onDone: () => void }): React.ReactElement | null { + const reduced = prefersReducedMotion(); + + useEffect(() => { + const t = window.setTimeout(onDone, reduced ? 0 : DURATION_MS); + return () => window.clearTimeout(t); + }, [onDone, reduced]); + + if (reduced) return null; + + return ( +
+ ); +} + +export default PrintUnlockEffect; diff --git a/src/hooks/useSecretCode.ts b/src/hooks/useSecretCode.ts new file mode 100644 index 0000000..14e75a8 --- /dev/null +++ b/src/hooks/useSecretCode.ts @@ -0,0 +1,32 @@ +import { useEffect, useRef } from 'react'; + +function isEditableTarget(target: EventTarget | null): boolean { + if (!(target instanceof HTMLElement)) return false; + const tag = target.tagName; + return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable; +} + +/** + * Fires `onMatch` the moment `code` is typed anywhere on the page, Chrome + * "thisisunsafe"-style — no input box, no visible prompt, keys typed into + * form fields don't count. + */ +export function useSecretCode(code: string, onMatch: () => void): void { + const bufferRef = useRef(''); + const onMatchRef = useRef(onMatch); + onMatchRef.current = onMatch; + const target = code.toLowerCase(); + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (isEditableTarget(e.target) || e.key.length !== 1) return; + bufferRef.current = (bufferRef.current + e.key.toLowerCase()).slice(-target.length); + if (bufferRef.current === target) { + bufferRef.current = ''; + onMatchRef.current(); + } + }; + window.addEventListener('keydown', onKeyDown); + return () => window.removeEventListener('keydown', onKeyDown); + }, [target]); +} diff --git a/src/hooks/useTileWorkspace.ts b/src/hooks/useTileWorkspace.ts new file mode 100644 index 0000000..7c12f92 --- /dev/null +++ b/src/hooks/useTileWorkspace.ts @@ -0,0 +1,51 @@ +import { useRef, useState } from 'react'; +import type { CanvasPreviewHandle } from '../components/CanvasPreview'; +import type { SwatchEntry, ImageDimensions } from './useSwatches'; +import type { TileSettings } from '../types/tileSettings'; + +// Pins down the Step 2 common-core hook's signature ahead of its real +// implementation (see tmp_multi_modularization.plan.txt, Step 0). Single-image +// mode will eventually be exactly one instance of this hook; multi-image mode +// one instance per tile plus one global-defaults instance. For now every +// value is inert — no image processing is wired through it yet. +export interface TileWorkspace { + imageSrc: string | null; + setImage: (url: string | null, pushHistory?: boolean) => void; + canUndo: boolean; + canRedo: boolean; + undo: () => void; + redo: () => void; + swatches: SwatchEntry[]; + swatchesLoading: boolean; + imageDimensions: ImageDimensions | null; + canvasPreviewRef: React.RefObjectMulti-image mode — coming soon.
+Building the 3D model may be slow due to:
-Do you want to continue?
-Building the 3D model may be slow due to:
+Do you want to continue?
+