11491149 ctx . lineTo ( targetWidthPx , targetHeightPx + 0.5 ) ;
11501150 ctx . stroke ( ) ;
11511151 // テキスト(左右中央揃え・プリセット名対応)
1152+ const grainLabel = ( grain === 'T' ) ? 'T目' : 'Y目' ; // 全判での目(flip適用前)
11521153 const paperLabel = ( ! customMode && currentBase && currentCut )
1153- ? `${ currentBase . name_label } ${ currentCut . cut_label } (${ paperH . toFixed ( 1 ) } ×${ paperW . toFixed ( 1 ) } ミリ)`
1154+ ? `${ currentBase . name_label } ${ currentCut . cut_label } (${ grainLabel } ${ paperH . toFixed ( 1 ) } ×${ paperW . toFixed ( 1 ) } ミリ)`
11541155 : `${ paperH . toFixed ( 1 ) } ×${ paperW . toFixed ( 1 ) } ミリ` ;
11551156 const paperText = `紙サイズ: ${ paperLabel } ` ;
11561157 const trimText = `製品仕上がりサイズ: ${ layout . trimW . toFixed ( 1 ) } ×${ layout . trimH . toFixed ( 1 ) } ミリ` ;
11571158 const countText = `面付け数: ${ layout . positions . length } 面` ;
1158- const infoLine = `${ paperText } ${ trimText } ${ countText } ` ; // 全角スペースで区切り
1159+ // マージン情報(面付け数の後に:咥え, 咥え尻 → 次に 左右アキ → 次に 中央アキ(有効時のみ))
1160+ const gripText = `咥え: ${ gripMargin . toFixed ( 1 ) } mm` ;
1161+ const tailText = `咥え尻: ${ tailMargin . toFixed ( 1 ) } mm` ;
1162+ const gripTailText = `${ gripText } 、${ tailText } ` ;
1163+ const lrText = `左右アキ: ${ leftRightMargin . toFixed ( 1 ) } mm` ;
1164+ const centerText = centerMarginEnabled ? `中央アキ: ${ centerMargin . toFixed ( 1 ) } mm` : null ;
1165+ const parts = [ paperText , trimText , countText , gripTailText , lrText ] ;
1166+ if ( centerText ) parts . push ( centerText ) ;
1167+ const infoLine = parts . join ( ' ' ) ; // 全角スペース区切り
11591168 ctx . fillStyle = '#111827' ;
11601169 ctx . font = '14px system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif' ;
11611170 ctx . textBaseline = 'middle' ;
12551264 const sizeChanged = Math . abs ( cssW - lastSize . cssW ) >= 0.5 || Math . abs ( cssH - lastSize . cssH ) >= 0.5 ;
12561265 lastSize . cssW = cssW ; lastSize . cssH = cssH ;
12571266
1258- const dpr = window . devicePixelRatio || 1 ;
1267+ const qualityFactor = 2 ; // 1.0 = 標準, 2.0 = 高精細
1268+ const dpr = ( window . devicePixelRatio || 1 ) * qualityFactor ;
1269+ // 先にアスペクト比を確定し、幅→高さの順で一度だけ反映(maxWidthで親幅超過も防止)
1270+ canvas . style . aspectRatio = `${ contentW } / ${ contentH } ` ;
12591271 canvas . style . width = `${ cssW } px` ;
1272+ canvas . style . maxWidth = '100%' ;
12601273 canvas . style . height = `${ cssH } px` ;
1261- canvas . style . aspectRatio = `${ contentW } / ${ contentH } ` ;
12621274 const bufferW = Math . ceil ( cssW * dpr ) ;
12631275 const bufferH = Math . ceil ( cssH * dpr ) ;
12641276 canvas . width = bufferW ; canvas . height = bufferH ;
13431355 } ) ;
13441356 } ;
13451357
1346- const resizeObserver = new ResizeObserver ( ( ) => {
1358+ // ResizeObserver: wrapper の「幅」変化にのみ反応(高さだけの変化は無視してループ防止)
1359+ let lastObservedWidth = - 1 ;
1360+ const resizeObserver = new ResizeObserver ( ( entries ) => {
1361+ const entry = entries && entries [ 0 ] ;
1362+ const cr = entry && entry . contentRect ;
1363+ const w = Math . round ( ( cr && cr . width ) || ( wrapper ? wrapper . clientWidth : 0 ) || 0 ) ;
1364+ if ( w <= 0 ) return ;
1365+ if ( w === lastObservedWidth ) return ; // 高さのみの変化は無視
1366+ lastObservedWidth = w ;
13471367 computeAndSetScale ( ) ;
13481368 scheduleRedraw ( ) ;
13491369 } ) ;
13501370 if ( ! isFullscreen && wrapper ) {
1351- // 通常時は wrapper のサイズ変化だけを見る
13521371 resizeObserver . observe ( wrapper ) ;
13531372 }
13541373 // FS時のみウインドウリサイズを監視(通常時はROに任せる)
13841403 < div className = "mx-auto max-w-7xl p-3 space-y-6" >
13851404
13861405 < div className = "space-y-4" >
1406+ < div className = "grid gap-4 min-[1000px]:grid-cols-2" >
13871407 { /* プリセット選択と用紙サイズ */ }
13881408 < section aria-labelledby = "paper-size-title" className = "group relative rounded-xl ring-1 ring-slate-200 bg-slate-50/40 p-3 pt-5 hover:ring-slate-300 focus-within:ring-blue-300 transition-colors" >
13891409 < h2 id = "paper-size-title" className = "absolute -top-3 left-3 bg-slate-50/40 px-2 text-sm font-medium text-gray-700 rounded" > 用紙サイズ(mm)</ h2 >
@@ -1453,14 +1473,16 @@ <h2 id="trim-bleed-title" className="absolute -top-3 left-3 bg-slate-50/40 px-2
14531473 < div className = "grid grid-cols-3 gap-4" >
14541474 < NumberInput label = "タテ" value = { prodTrimW } onChange = { setProdTrimW } min = { 1 } step = { 0.5 } />
14551475 < NumberInput label = "ヨコ" value = { prodTrimH } onChange = { setProdTrimH } min = { 1 } step = { 0.5 } />
1456- < NumberInput label = "裁ち落とし" value = { bleedEachSide } onChange = { setBleedEachSide } min = { 0 } step = { 0. 1} />
1476+ < NumberInput label = "裁ち落とし" value = { bleedEachSide } onChange = { setBleedEachSide } min = { 0 } step = { 1 } />
14571477 </ div >
14581478 </ section >
1479+ </ div >
1480+ < div className = "grid gap-4 min-[1000px]:grid-cols-2" >
14591481
14601482 { /* マージン設定 */ }
14611483 < section aria-labelledby = "margin-title" className = "group relative rounded-xl ring-1 ring-slate-200 bg-slate-50/40 p-3 pt-5 hover:ring-slate-300 focus-within:ring-blue-300 transition-colors" >
14621484 < h2 id = "margin-title" className = "absolute -top-3 left-3 bg-slate-50/40 px-2 text-sm font-medium text-gray-700 rounded" > マージン設定(mm)</ h2 >
1463- < div className = "grid grid-cols-2 gap -4" >
1485+ < div className = "grid gap-4 grid-cols-2 min-[500px]:grid-cols -4" >
14641486 < div className = "min-w-0" >
14651487 < NumberInput label = "咥え" value = { gripMargin } onChange = { setGripMargin } min = { 0 } step = { 0.5 } />
14661488 </ div >
@@ -1519,57 +1541,58 @@ <h2 id="margin-title" className="absolute -top-3 left-3 bg-slate-50/40 px-2 text
15191541 </ div >
15201542 </ div > </ section >
15211543
1522- { /* 配置方向 / 紙の目 */ }
1523- < section aria-labelledby = "orientation-title" className = "group relative rounded-xl ring-1 ring-slate-200 bg-slate-50/40 p-3 pt-5 hover:ring-slate-300 focus-within:ring-blue-300 transition-colors" >
1524- < h2 id = "orientation-title" className = "absolute -top-3 left-3 bg-slate-50/40 px-2 text-sm font-medium text-gray-700 rounded" > 配置方向 / 紙の目(断裁後の目:{ effectiveGrain === "T" ? "T目" : "Y目" } )</ h2 >
1525- < div className = "flex flex-nowrap items-center gap-2" >
1526- < div className = "flex flex-nowrap gap-2" >
1527- < button
1528- className = { `px-2 sm:px-3 py-2 rounded-lg border font-medium text-sm transition-colors ${
1529- placeOrientation === "portrait"
1530- ? "bg-blue-600 text-white border-blue-600"
1531- : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
1532- } `}
1533- onClick = { ( ) => setPlaceOrientation ( "portrait" ) }
1534- >
1535- 縦置き
1536- </ button >
1537- < button
1538- className = { `px-2 sm:px-3 py-2 rounded-lg border font-medium text-sm transition-colors ${
1539- placeOrientation === "landscape"
1540- ? "bg-blue-600 text-white border-blue-600"
1541- : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
1542- } `}
1543- onClick = { ( ) => setPlaceOrientation ( "landscape" ) }
1544- >
1545- 横置き
1546- </ button >
1547- </ div >
1548- < div className = "w-px h-6 bg-gray-300" aria-hidden = "true" > </ div >
1549- < div className = "flex flex-nowrap gap-2" >
1550- < button
1551- className = { `px-1.5 sm:px-2 py-2 rounded-lg border font-medium text-sm transition-colors ${
1552- grain === "T"
1553- ? "bg-sky-600 text-white border-sky-600"
1554- : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
1555- } `}
1556- onClick = { ( ) => setGrain ( "T" ) }
1557- >
1558- 全判T目
1559- </ button >
1560- < button
1561- className = { `px-1.5 sm:px-2 py-2 rounded-lg border font-medium text-sm transition-colors ${
1562- grain === "Y"
1563- ? "bg-sky-600 text-white border-sky-600"
1564- : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
1565- } `}
1566- onClick = { ( ) => setGrain ( "Y" ) }
1567- >
1568- 全判Y目
1569- </ button >
1570- </ div >
1571- </ div >
1572- </ section >
1544+ { /* 配置方向 / 紙の目 */ }
1545+ < section aria-labelledby = "orientation-title" className = "group relative rounded-xl ring-1 ring-slate-200 bg-slate-50/40 p-3 pt-5 hover:ring-slate-300 focus-within:ring-blue-300 transition-colors" >
1546+ < h2 id = "orientation-title" className = "absolute -top-3 left-3 bg-slate-50/40 px-2 text-sm font-medium text-gray-700 rounded" > 配置方向 / 紙の目(断裁後の目:{ effectiveGrain === "T" ? "T目" : "Y目" } )</ h2 >
1547+ < div className = "flex flex-nowrap items-center gap-2" >
1548+ < div className = "flex flex-nowrap gap-2" >
1549+ < button
1550+ className = { `px-2 sm:px-3 py-2 rounded-lg border font-medium text-sm transition-colors ${
1551+ placeOrientation === "portrait"
1552+ ? "bg-blue-600 text-white border-blue-600"
1553+ : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
1554+ } `}
1555+ onClick = { ( ) => setPlaceOrientation ( "portrait" ) }
1556+ >
1557+ 縦置き
1558+ </ button >
1559+ < button
1560+ className = { `px-2 sm:px-3 py-2 rounded-lg border font-medium text-sm transition-colors ${
1561+ placeOrientation === "landscape"
1562+ ? "bg-blue-600 text-white border-blue-600"
1563+ : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
1564+ } `}
1565+ onClick = { ( ) => setPlaceOrientation ( "landscape" ) }
1566+ >
1567+ 横置き
1568+ </ button >
1569+ </ div >
1570+ < div className = "w-px h-6 bg-gray-300" aria-hidden = "true" > </ div >
1571+ < div className = "flex flex-nowrap gap-2" >
1572+ < button
1573+ className = { `px-1.5 sm:px-2 py-2 rounded-lg border font-medium text-sm transition-colors ${
1574+ grain === "T"
1575+ ? "bg-sky-600 text-white border-sky-600"
1576+ : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
1577+ } `}
1578+ onClick = { ( ) => setGrain ( "T" ) }
1579+ >
1580+ 全判T目
1581+ </ button >
1582+ < button
1583+ className = { `px-1.5 sm:px-2 py-2 rounded-lg border font-medium text-sm transition-colors ${
1584+ grain === "Y"
1585+ ? "bg-sky-600 text-white border-sky-600"
1586+ : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
1587+ } `}
1588+ onClick = { ( ) => setGrain ( "Y" ) }
1589+ >
1590+ 全判Y目
1591+ </ button >
1592+ </ div >
1593+ </ div >
1594+ </ section >
1595+ </ div >
15731596
15741597 </ div >
15751598
@@ -1580,11 +1603,11 @@ <h2 id="orientation-title" className="absolute -top-3 left-3 bg-slate-50/40 px-2
15801603 { /* Fullscreen overlay */ }
15811604 { isFullscreen && (
15821605 < div
1583- className = "fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center aspect-square "
1606+ className = "fixed inset-0 z-50 flex items-center justify-center"
15841607 onClick = { ( ) => setIsFullscreen ( false ) }
1585- style = { { cursor : 'zoom-out' } }
1608+ style = { { cursor : 'zoom-out' , background : 'transparent' } }
15861609 >
1587- < canvas ref = { canvasRef } />
1610+ < canvas ref = { canvasRef } className = "block" />
15881611 </ div >
15891612 ) }
15901613 { /* Normal preview area, clickable for fullscreen */ }
0 commit comments