const fs = require('fs'); const path = require('path'); const { miniCardGroup } = require('./trox_card'); /* ============================================================ TROX – Tuck Box Generator ------------------------------------------------------------ Box-Vorderseite zeigt ECHTE TROX-Karten (identisch zu den Spielkarten, via gemeinsames Modul trox_card.js), gefächert. Teile der hinteren Karten werden durch Überlappung verdeckt, der untere Bereich durch das TROX-Logo – genau wie gewünscht. node generate_box.js front | wrap | both HINWEIS DRUCK (TGC): exakte Stanzkontur "Poker Tuck Box (90 cards)" von der TGC-Produktseite laden und PANEL_W/PANEL_H/SPINE/FLAP unten daran angleichen – das Artwork skaliert automatisch mit. ============================================================ */ const MODE = (process.argv[2] || 'both').toLowerCase(); const DPI = 300; const PANEL_W = Math.round(2.55 * DPI); // 765 const PANEL_H = Math.round(3.55 * DPI); // 1065 const SPINE = Math.round(0.90 * DPI); // 270 const FLAP = Math.round(0.55 * DPI); // 165 const BLEED = 37; const SAFE = 75; /* ------------------------------------------------------------ FÄCHER-TUNING (hier anpassen): CARD_SCALE – Größe einer Einzelkarte relativ zur Panelbreite. Höher = größere Karten. Per Kommandozeile überschreibbar: node generate_box.js front 0.32 FAN_SPREAD – wie weit der Fächer auseinandergeht (horizontaler Versatz). FAN_ARC – wie stark der Fächer-Bogen nach unten ausschwingt. FAN_TILT – maximaler Drehwinkel der äußeren Karten (Grad). FAN_CY – vertikale Lage des Fächermittelpunkts (Anteil der Höhe). Eine eingebaute Sicherung verkleinert den Fächer automatisch, falls er sonst aus dem goldenen Rahmen (Safe Zone) herausragen würde. ------------------------------------------------------------ */ const CLI_SCALE = parseFloat(process.argv[3]); const CARD_SCALE = !isNaN(CLI_SCALE) ? CLI_SCALE : 0.30; // vorher faktisch 0.42 -> jetzt kleiner const FAN_SPREAD = 0.50; const FAN_ARC = 0.11; const FAN_TILT = 24; const FAN_CY = 0.34; const DEFS = ` `; // Gefächerte ECHTE Karten. // cx,cy = Fächermittelpunkt // cardW = Breite einer Karte in px // maxRight= rechte Grenze (Safe-Zone-Rand), darf nicht überschritten werden // Gibt {svg, fittedCardW} zurück; verkleinert cardW automatisch, falls nötig. function cardFan(cx, cy, cardW, panelW) { const REF_RATIO = 1069 / 694; // Höhe/Breite einer Karte const tiltRad = FAN_TILT * Math.PI / 180; // Innenkante des goldenen Rahmens (Safe Zone) plus kleiner Puffer const margin = SAFE + 6; const leftLimit = margin; const rightLimit = panelW - margin; // Funktion: rechte Außenkante des Fächers für ein gegebenes cardW function rightEdge(cw) { const dx = cw * FAN_SPREAD; const cardH = cw * REF_RATIO; // halbe Diagonale der gedrehten Außenkarte (worst case Ausdehnung) const halfSpanX = (Math.abs(Math.cos(tiltRad)) * cw + Math.abs(Math.sin(tiltRad)) * cardH) / 2; const outerCenterX = cx + 1.55 * dx; return outerCenterX + halfSpanX; } function leftEdge(cw) { const dx = cw * FAN_SPREAD; const cardH = cw * REF_RATIO; const halfSpanX = (Math.abs(Math.cos(tiltRad)) * cw + Math.abs(Math.sin(tiltRad)) * cardH) / 2; const outerCenterX = cx - 1.55 * dx; return outerCenterX - halfSpanX; } // Auto-Fit: cardW so weit verkleinern, bis beide Außenkanten im Rahmen liegen let cw = cardW; let guard = 0; while ((rightEdge(cw) > rightLimit || leftEdge(cw) < leftLimit) && guard < 200) { cw *= 0.98; guard++; } const dx = cw * FAN_SPREAD; const dy = cw * FAN_ARC; const t = FAN_TILT; const svg = ` ${miniCardGroup('Gruen', '7', cx - 1.55 * dx, cy + 1.7 * dy, cw, -t)} ${miniCardGroup('Schwarz', '12', cx - 0.52 * dx, cy + 0.3 * dy, cw, -t / 3)} ${miniCardGroup('Rot', '17', cx + 0.52 * dx, cy + 0.3 * dy, cw, t / 3)} ${miniCardGroup('Gelb', '4', cx + 1.55 * dx, cy + 1.7 * dy, cw, t)} `; return { svg, fittedCardW: cw }; } function frontContent(w, h) { const cx = w / 2; // gewünschte Kartengröße aus CARD_SCALE; Auto-Fit hält sie im Rahmen const wantCardW = w * CARD_SCALE; const fan = cardFan(cx, h * FAN_CY, wantCardW, w); return ` ${fan.svg} TROX TACTICAL CARD GAME 2-6 Players | Ages 7+ | 80 Cards`; } function backContent(w, h) { const cx = w / 2; return ` HOW TO PLAY Predict exactly how many tricks you will win each round. Hit your bid exactly -> +10 bonus. Miss it -> no bonus at all. Red (Crown) is the permanent trump and beats every suit. 10 rounds. Cards rise then fall: 1-2-3-4-5-5-4-3-2-1 "Bid exactly. Win tricks. Rule the table."`; } function spineContent(w, h) { return ` TROX`; } function buildFront() { const w = PANEL_W + 2 * BLEED; const h = PANEL_H + 2 * BLEED; return ` ${DEFS} ${frontContent(w, h)} `; } function buildWrap() { const bandW = PANEL_W + SPINE + PANEL_W + SPINE; const bandH = PANEL_H; const totalW = bandW + 2 * BLEED; const totalH = bandH + 2 * FLAP + 2 * BLEED; const ox = BLEED, oy = BLEED + FLAP; return ` ${DEFS} ${DEFS}${frontContent(PANEL_W, PANEL_H)} ${DEFS}${spineContent(SPINE, PANEL_H)} ${DEFS}${backContent(PANEL_W, PANEL_H)} ${DEFS}${spineContent(SPINE, PANEL_H)} Faltlinien (gestrichelt) vor dem Upload entfernen - Maße an TGC-Tuckbox-Template angleichen `; } const outputDir = path.join(__dirname, 'box_export'); if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true }); console.log('TROX – Box wird generiert ...'); if (MODE === 'front' || MODE === 'both') { fs.writeFileSync(path.join(outputDir, 'trox_box_front.svg'), buildFront()); console.log(` Front-Panel -> box_export/trox_box_front.svg (${PANEL_W + 2 * BLEED}x${PANEL_H + 2 * BLEED})`); } if (MODE === 'wrap' || MODE === 'both') { fs.writeFileSync(path.join(outputDir, 'trox_box_wrap.svg'), buildWrap()); console.log(` Box-Netz -> box_export/trox_box_wrap.svg`); } console.log('Fertig.');