From 4ff06db902daaef7b1d3a246d32a98b11148da53 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 16:48:24 +0000 Subject: [PATCH 1/4] Add hover tooltip animations for solder, symmetry, profile, and pack buttons Bring the symmetry mode, solder line, lamp profile editor, and pack buttons in line with the toolbar tools by giving them the same rich hover tooltip with an SVG tutorial animation instead of a plain text tip. https://claude.ai/code/session_014nr68gix3HY6c1HwwW95Pk --- frontend/src/components/ResultPanel.tsx | 26 ++- frontend/src/components/SheetPanel.tsx | 12 +- frontend/src/components/ToolTooltip.tsx | 2 +- .../src/components/ToolTooltipAnimations.tsx | 196 +++++++++++++++++- frontend/src/i18n.ts | 8 + 5 files changed, 236 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/ResultPanel.tsx b/frontend/src/components/ResultPanel.tsx index 5067e83..dff31a0 100644 --- a/frontend/src/components/ResultPanel.tsx +++ b/frontend/src/components/ResultPanel.tsx @@ -11,7 +11,8 @@ import { computeCentroid, flattenCurves, ctrlToHandle, handleToCtrl } from '../u import { Toolbar, SelectIcon, CropIcon, MeasureIcon, BoxIcon, DetectAllIcon, ViewIcon, HandIcon, PenIcon, PencilIcon } from './Toolbar'; import { IconUpload, IconSquare, IconLamp } from './icons'; import type { ToolId } from './Toolbar'; -import { SelectAnimation, BoxAnimation, CropAnimation, MeasureAnimation, DetectAllAnimation, InspectAnimation, PanAnimation, PenAnimation, PencilAnimation } from './ToolTooltipAnimations'; +import { SelectAnimation, BoxAnimation, CropAnimation, MeasureAnimation, DetectAllAnimation, InspectAnimation, PanAnimation, PenAnimation, PencilAnimation, SolderAnimation, SymmetryAnimation, ProfileAnimation } from './ToolTooltipAnimations'; +import { ToolTooltip } from './ToolTooltip'; import { CropOverlay } from './CropOverlay'; import { MeasureInput } from './MeasureInput'; import { MeasureLineOverlay } from './MeasureLineOverlay'; @@ -1532,7 +1533,14 @@ export function ResultPanel({ - {!isSolderPopoverOpen && {t('solderThicknessTooltip')}} + {!isSolderPopoverOpen && ( + } + /> + )} {isSolderPopoverOpen && (
@@ -1599,7 +1607,12 @@ export function ResultPanel({ - {t('lampSymmetryTooltip')} + } + />
)} @@ -1619,7 +1632,12 @@ export function ResultPanel({ - {t('lampProfileButtonTooltip')} + } + /> )} diff --git a/frontend/src/components/SheetPanel.tsx b/frontend/src/components/SheetPanel.tsx index ca10dcb..2091299 100644 --- a/frontend/src/components/SheetPanel.tsx +++ b/frontend/src/components/SheetPanel.tsx @@ -12,7 +12,8 @@ import { packPiecesSmart, defaultCuttingGapPx } from '../utils/packing'; import { toImageCoords, toScreenCoords } from '../utils/viewport'; import { Toolbar, SelectIcon, CropIcon, MeasureIcon, HandIcon } from './Toolbar'; import type { ToolId } from './Toolbar'; -import { SelectAnimation, CropAnimation, MeasureAnimation, PanAnimation } from './ToolTooltipAnimations'; +import { SelectAnimation, CropAnimation, MeasureAnimation, PanAnimation, PackAnimation } from './ToolTooltipAnimations'; +import { ToolTooltip } from './ToolTooltip'; import { CropOverlay } from './CropOverlay'; import { MeasureInput } from './MeasureInput'; import { MeasureLineOverlay } from './MeasureLineOverlay'; @@ -502,7 +503,14 @@ export function SheetPanel({ {isPacking ? t('packing', 'Packing...') : t('toolPack')} - {!isPackPopoverOpen && {t('tooltipPackDesc')}} + {!isPackPopoverOpen && ( + } + /> + )} {isPackPopoverOpen && (
diff --git a/frontend/src/components/ToolTooltip.tsx b/frontend/src/components/ToolTooltip.tsx index b91a2ac..e3f15d9 100644 --- a/frontend/src/components/ToolTooltip.tsx +++ b/frontend/src/components/ToolTooltip.tsx @@ -14,7 +14,7 @@ export function ToolTooltip({ name, shortcut, description, animation }: ToolTool
{name} - {shortcut} + {shortcut && {shortcut}}

{description}

diff --git a/frontend/src/components/ToolTooltipAnimations.tsx b/frontend/src/components/ToolTooltipAnimations.tsx index 2a782c5..e7b6dcd 100644 --- a/frontend/src/components/ToolTooltipAnimations.tsx +++ b/frontend/src/components/ToolTooltipAnimations.tsx @@ -608,7 +608,7 @@ export function PencilAnimation() { " keyTimes="0; 0.15; 0.35; 0.55; 0.70; 0.85; 1" dur={dur} repeatCount="indefinite" /> - + {/* Pencil body */} @@ -620,3 +620,197 @@ export function PencilAnimation() { ); } + +/* ═══════════════════════════════════════════════════════════════════════════ + Solder Animation — the lead lines between glass pieces grow and shrink as + the solder width is adjusted with a slider + ═══════════════════════════════════════════════════════════════════════════ */ +export function SolderAnimation() { + const dur = '3.5s'; + const KT = '0; 0.45; 0.55; 1'; + const SPL = '0.4,0,0.2,1; 0,0,1,1; 0.4,0,0.2,1'; + const SW = '1.5;6;6;1.5'; + const SOLDER = '#3f3f46'; + + return ( + + + + {/* Glass petals — the solder outline pulses thick → thin */} + {ANGLES.map(a => ( + + + + ))} + + + + + {/* Thickness slider */} + + + + + + + + + ); +} + +/* ═══════════════════════════════════════════════════════════════════════════ + Symmetry Animation — a mark drawn in one facet is replicated radially across + all facets at once + ═══════════════════════════════════════════════════════════════════════════ */ +export function SymmetryAnimation() { + const dur = '4s'; + const cx = 110, cy = 55; + const R = 40; // facet boundary radius + const r = 22; // drawn-mark orbit radius + const angs = [-90, -30, 30, 90, 150, 210]; + const pt = (ang: number, rad: number) => { + const t = (ang * Math.PI) / 180; + return { x: cx + rad * Math.cos(t), y: cy + rad * Math.sin(t) }; + }; + const top = pt(-90, r); + const SPRING = '0,0,1,1; 0.3,1.3,0.4,1; 0,0,1,1; 0,0,1,1; 0,0,1,1'; + + return ( + + + + {/* Facet boundary + radial spokes */} + + {angs.map(a => { + const e = pt(a, R); + return ; + })} + + + {/* Drawn mark in the top facet */} + + + + + + {/* Replicated marks in the other five facets */} + {angs.slice(1).map(a => { + const p = pt(a, r); + return ( + + + + + ); + })} + + {/* Cursor drawing the first mark */} + + + + + + + + + ); +} + +/* ═══════════════════════════════════════════════════════════════════════════ + Profile Animation — dragging a control handle reshapes the lamp's silhouette, + mirrored across its vertical axis + ═══════════════════════════════════════════════════════════════════════════ */ +export function ProfileAnimation() { + const dur = '4s'; + const KT = '0; 0.5; 1'; + const SPL = '0.4,0,0.2,1; 0.4,0,0.2,1'; + const dNarrow = 'M 92,26 Q 88,57 80,86 L 140,86 Q 132,57 128,26 Z'; + const dWide = 'M 92,26 Q 66,57 80,86 L 140,86 Q 154,57 128,26 Z'; + + return ( + + + + {/* Vertical axis of revolution */} + + + {/* Lamp body — silhouette bulges in and out */} + + + + + {/* Top + bottom rings */} + + + + {/* Right control handle (dragged) + left mirror */} + + + + + + + + ); +} + +/* ═══════════════════════════════════════════════════════════════════════════ + Pack Animation — scattered pieces translate and rotate into a tightly + packed arrangement on the sheet, then scatter again + ═══════════════════════════════════════════════════════════════════════════ */ +export function PackAnimation() { + const dur = '4.5s'; + const KT = '0; 0.14; 0.55; 0.82; 1'; + const SPL = '0,0,1,1; 0.4,0,0.2,1; 0,0,1,1; 0.45,0,0.55,1'; + + const pieces = [ + { w: 40, h: 34, fill: 'rgba(59,130,246,0.32)', stroke: '#2563eb', sx: 150, sy: 30, sr: 18, px: 40, py: 33 }, + { w: 30, h: 34, fill: 'rgba(16,185,129,0.30)', stroke: '#059669', sx: 58, sy: 82, sr: -22, px: 79, py: 33 }, + { w: 72, h: 22, fill: 'rgba(139,92,246,0.28)', stroke: '#7c3aed', sx: 150, sy: 84, sr: 12, px: 53, py: 62 }, + ]; + + return ( + + + + {/* Glass sheet */} + + + {pieces.map((p, i) => ( + + + + + + + + ))} + + ); +} diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 3acf4d2..7cee2d5 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -55,6 +55,10 @@ const resources = { tooltipInspectDesc: "Hide glass pieces to see the original pattern clearly", tooltipPanName: "Pan Tool", tooltipPanDesc: "Click and drag to pan around the workspace. Hold Spacebar to temporarily activate from any tool.", + tooltipPackName: "Pack Sheet", + tooltipSolderName: "Solder Line", + tooltipSymmetryName: "Symmetry", + tooltipProfileName: "Lamp Profile", clickToRename: "Click to rename", sheet: "Sheet", addSheetOption: "Add sheet…", @@ -277,6 +281,10 @@ const resources = { tooltipInspectDesc: "Cachez les pièces de verre pour voir le patron original clairement", tooltipPanName: "Outil Main", tooltipPanDesc: "Cliquez et glissez pour vous déplacer. Maintenez Espace pour l'activer temporairement depuis n'importe quel outil.", + tooltipPackName: "Tasser", + tooltipSolderName: "Soudure", + tooltipSymmetryName: "Symétrie", + tooltipProfileName: "Profil de Lampe", clickToRename: "Cliquez pour renommer", sheet: "Plaque", addSheetOption: "Ajouter une plaque…", From ce269d4607c1ba983dd138a2138c4a3059a95130 Mon Sep 17 00:00:00 2001 From: Dominique Piche-Meunier Date: Sun, 31 May 2026 09:28:34 -0400 Subject: [PATCH 2/4] trigger cloudflare From 1b5bdfabf72e58b67cc0186ec5e41f9804e771e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 13:36:46 +0000 Subject: [PATCH 3/4] =?UTF-8?q?Place=20lamp=20profile=20handles=20on=20the?= =?UTF-8?q?=20curve,=20not=20at=20the=20B=C3=A9zier=20control=20point?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A quadratic Bézier only reaches halfway toward its control point, so as the lamp bulged out the handles drifted outside the silhouette. Position them at the curve midpoint instead: B(0.5) = 0.25·P0 + 0.5·ctrl + 0.25·P2. https://claude.ai/code/session_014nr68gix3HY6c1HwwW95Pk --- frontend/src/components/ToolTooltipAnimations.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/ToolTooltipAnimations.tsx b/frontend/src/components/ToolTooltipAnimations.tsx index e7b6dcd..7fa144f 100644 --- a/frontend/src/components/ToolTooltipAnimations.tsx +++ b/frontend/src/components/ToolTooltipAnimations.tsx @@ -761,13 +761,15 @@ export function ProfileAnimation() { - {/* Right control handle (dragged) + left mirror */} - - + - - + From 3f13912eb30a018f4cd6fff6f4cc846dd45a1509 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 13:38:37 +0000 Subject: [PATCH 4/4] Use the shared idle-glass blue for pack animation pieces Match the petal/glass color used across the other tooltip animations instead of the green/purple/blue mix. https://claude.ai/code/session_014nr68gix3HY6c1HwwW95Pk --- frontend/src/components/ToolTooltipAnimations.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ToolTooltipAnimations.tsx b/frontend/src/components/ToolTooltipAnimations.tsx index 7fa144f..ca08006 100644 --- a/frontend/src/components/ToolTooltipAnimations.tsx +++ b/frontend/src/components/ToolTooltipAnimations.tsx @@ -786,9 +786,9 @@ export function PackAnimation() { const SPL = '0,0,1,1; 0.4,0,0.2,1; 0,0,1,1; 0.45,0,0.55,1'; const pieces = [ - { w: 40, h: 34, fill: 'rgba(59,130,246,0.32)', stroke: '#2563eb', sx: 150, sy: 30, sr: 18, px: 40, py: 33 }, - { w: 30, h: 34, fill: 'rgba(16,185,129,0.30)', stroke: '#059669', sx: 58, sy: 82, sr: -22, px: 79, py: 33 }, - { w: 72, h: 22, fill: 'rgba(139,92,246,0.28)', stroke: '#7c3aed', sx: 150, sy: 84, sr: 12, px: 53, py: 62 }, + { w: 40, h: 34, sx: 150, sy: 30, sr: 18, px: 40, py: 33 }, + { w: 30, h: 34, sx: 58, sy: 82, sr: -22, px: 79, py: 33 }, + { w: 72, h: 22, sx: 150, sy: 84, sr: 12, px: 53, py: 62 }, ]; return ( @@ -809,7 +809,7 @@ export function PackAnimation() { values={`${p.sr};${p.sr};0;0;${p.sr}`} keyTimes={KT} dur={dur} repeatCount="indefinite" calcMode="spline" keySplines={SPL} /> + fill={IF} stroke={IS} strokeWidth="1.5" strokeLinejoin="round" /> ))}