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..ca08006 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,199 @@ 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 — placed on the curve + midpoint: B(0.5) = 0.25·P0 + 0.5·ctrl + 0.25·P2, not on the Bézier + control point itself */} + + + + + + + + ); +} + +/* ═══════════════════════════════════════════════════════════════════════════ + 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, 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 ( + + + + {/* 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…",