Files
apparel-designer/src/components/canvas/TShirtSVG.jsx
2026-05-23 03:28:58 -05:00

150 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import '../../styles/TShirtSVG.css';
/**
* Pure-SVG t-shirt mockup. The path is a stylized tee silhouette (collar +
* sleeves + body). Color is the user-selected garment color from the Shirt
* Options panel. The print-zone rectangle in the chest area indicates where
* the design overlay will land — it's visual-only (the actual design overlay
* sits in the Konva stage on top of this).
*
* Sized via `viewBox` so the SVG scales fluidly with its container.
*/
export function TShirtSVG({ size = 360, color = '#ffffff', showPrintZone = true }) {
// Compute a complementary stroke shade so the silhouette stays visible on
// light AND dark fills. We darken the fill ~12% in HSL space — close enough
// for our small palette without pulling in a color library.
const stroke = darken(color, 0.12);
const isDark = perceptualLuminance(color) < 0.45;
return (
<svg
className="tshirt-svg"
viewBox="40 50 400 470"
role="img"
aria-label="T-shirt preview"
preserveAspectRatio="xMidYMid meet"
>
{/* Soft drop shadow under the shirt — kept subtle so the canvas remains the focus. */}
<defs>
<filter id="tshirt-shadow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="6" />
<feOffset dx="0" dy="6" result="offset" />
<feComponentTransfer>
<feFuncA type="linear" slope="0.18" />
</feComponentTransfer>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
{/* Shirt silhouette ---------------------------------------------------- */}
<g filter="url(#tshirt-shadow)">
<path
d="
M 110 90
L 170 60
Q 192 84 240 84
Q 288 84 310 60
L 370 90
L 430 130
L 400 200
L 360 180
L 360 480
Q 360 500 340 500
L 140 500
Q 120 500 120 480
L 120 180
L 80 200
L 50 130
Z
"
fill={color}
stroke={stroke}
strokeWidth="2"
strokeLinejoin="round"
/>
{/* Collar */}
<path
d="
M 200 78
Q 240 110 280 78
"
fill="none"
stroke={stroke}
strokeWidth="2.5"
strokeLinecap="round"
/>
</g>
{/* Print zone — corner brackets, not a closed rectangle, so the design
overlay isn't visually competing with a hard frame. */}
{showPrintZone && (
<g
className="tshirt-svg__print-zone"
stroke={isDark ? 'rgba(255,255,255,0.55)' : 'rgba(31, 29, 35, 0.18)'}
strokeWidth="1.5"
strokeLinecap="round"
fill="none"
>
{/* Corners of a 220×220 print zone centered in the chest */}
{(() => {
const x = 130;
const y = 180;
const w = 220;
const h = 220;
const c = 14;
return (
<>
<path d={`M ${x} ${y + c} L ${x} ${y} L ${x + c} ${y}`} />
<path d={`M ${x + w - c} ${y} L ${x + w} ${y} L ${x + w} ${y + c}`} />
<path d={`M ${x + w} ${y + h - c} L ${x + w} ${y + h} L ${x + w - c} ${y + h}`} />
<path d={`M ${x + c} ${y + h} L ${x} ${y + h} L ${x} ${y + h - c}`} />
</>
);
})()}
</g>
)}
</svg>
);
}
/* ────────────────────────────────────────────────────────────────────────── */
/* Tiny color helpers — local to this file to avoid pulling in a dependency. */
/* ────────────────────────────────────────────────────────────────────────── */
function parseHex(hex) {
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex);
if (!m) return [255, 255, 255];
const v = m[1];
if (v.length === 3) {
return [
parseInt(v[0] + v[0], 16),
parseInt(v[1] + v[1], 16),
parseInt(v[2] + v[2], 16),
];
}
return [
parseInt(v.slice(0, 2), 16),
parseInt(v.slice(2, 4), 16),
parseInt(v.slice(4, 6), 16),
];
}
function toHex(r, g, b) {
const clamp = (v) => Math.max(0, Math.min(255, Math.round(v)));
return `#${[r, g, b].map((v) => clamp(v).toString(16).padStart(2, '0')).join('')}`;
}
function darken(hex, amount = 0.1) {
const [r, g, b] = parseHex(hex);
return toHex(r * (1 - amount), g * (1 - amount), b * (1 - amount));
}
function perceptualLuminance(hex) {
const [r, g, b] = parseHex(hex);
// Rec. 601 luma — perceptually weighted, fast.
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
}