@cardenelabs/cdl 0.75.0 → 0.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.75.0",
3
+ "version": "0.76.0",
4
4
  "description": "CDL (Chainome Diagram Language). Mermaid-like declarative DSL that compiles to animated SVG diagrams. Built for blockchain / Solidity flows, generic enough for sequence / flow / state / ER / topology diagrams.",
5
5
  "license": "MIT",
6
6
  "author": "cardene777",
@@ -39,7 +39,7 @@ const BLOCK_W = 240;
39
39
  const BLOCK_H = 220;
40
40
  /** `shape-blockchain-block` の字の指定 (#871 で打ち消しの計算に使うため定数へ出した) */
41
41
  const 見出しの字 = 13;
42
- const 名の字 = 10.5;
42
+ const 名の字 = 11;
43
43
  const 値の字 = 11;
44
44
  /** 5 行を並べる間隔と、見出しが乗る帯の高さ。 打ち消しの上限をここから出す (#871) */
45
45
  const 行の間隔 = 22;
@@ -63,12 +63,12 @@ const CHAIN_TEXT_W = CHAIN_BLOCK_W - 8;
63
63
  /** 箱の中の 2 行 (中央の記号と下の字) の baseline の間隔 */
64
64
  const CHAIN_ROW_GAP = 14;
65
65
  /** 連鎖の箱の中の字の指定 (#887 で打ち消しの計算に使うため定数へ出した) */
66
- const CHAIN_NO_SIZE = 10;
66
+ const CHAIN_NO_SIZE = 11;
67
67
  const CONTRACT_CALL_SIZE = 11;
68
68
  const NFT_ID_SIZE = 15;
69
- const CHAIN_HASH_SIZE = 7;
70
- const BTC_NO_SIZE = 9;
71
- const ETH_NO_SIZE = 9;
69
+ const CHAIN_HASH_SIZE = 11;
70
+ const BTC_NO_SIZE = 11;
71
+ const ETH_NO_SIZE = 11;
72
72
 
73
73
  const NODE_HEX_R = 44;
74
74
  const NODE_PEER_R = 12;
@@ -583,15 +583,20 @@ export function ShapeEthereumChainNode({ node, active }: ShapeProps): JSX.Elemen
583
583
  const totalW = blockCount * blockW + (blockCount - 1) * linkW;
584
584
  const startX = cx - totalW / 2;
585
585
  /*
586
- * **番号は打ち消さない** (#887)。 6 桁の番号は指定 9 でも幅の見積りが 46 になり、
587
- * 箱の中で字が使える幅 42 (`CHAIN_TEXT_W`) を縮む前から超えている = 打ち消す余地が無い
588
- * (`textWidth` は等幅の指定を無視して比例の書体で測る。 dragon の `cdl-theme.css` が
589
- * `--d-body` を `!important` で当てるため、描かれるのは比例の書体になる)。
586
+ * 打ち消しの上限を出す帯 (#888)。 番号は箱の下半分に置き、ロゴとの間隔 20 に収まる。
590
587
  *
591
- * 4 桁の `shape-bitcoin-chain` は同じ帯に収まるので打ち消しに乗っている。
592
- * 縮む前から帯を超えていること自体は別の課題 (cardene777/cdl#885 と同じ形)。
588
+ * **ロゴは打ち消さない** (絵で、字ではない)。
589
+ *
590
+ * #887 では 6 桁 (`#21400`) の幅 56.8 が帯 42 を縮む前から超えていたため打ち消しに
591
+ * 乗せられなかった。 番号を 3 桁に揃えて 37.8 に収めたので、他の 2 つの連鎖と同じ
592
+ * 扱いに戻した。
593
593
  */
594
- const art = (
594
+ const 打ち消す字ら: 打ち消す字[] = [
595
+ { text: "#214", size: ETH_NO_SIZE, 帯: { 幅: CHAIN_TEXT_W, 高さ: 20 } },
596
+ ];
597
+ const art = (倍率: number): JSX.Element => {
598
+ const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
599
+ return (
595
600
  <g>
596
601
  {Array.from({ length: blockCount }, (_, i) => i).map((i) => {
597
602
  const bx = startX + i * (blockW + linkW);
@@ -616,14 +621,15 @@ export function ShapeEthereumChainNode({ node, active }: ShapeProps): JSX.Elemen
616
621
  />
617
622
  {/* Ethereum Diamond ロゴ */}
618
623
  <EthereumDiamond cx={bx + blockW / 2} cy={cy - 6} h={16} colors={iconColors} />
619
- <text data-cdl-counter-scaled="0" x={bx + blockW / 2} y={cy + 22} fontSize={ETH_NO_SIZE} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"}>
620
- #{21_400 + i}
624
+ <text data-cdl-counter-scaled="1" x={bx + blockW / 2} y={cy + 22} fontSize={ETH_NO_SIZE * 倍} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"}>
625
+ #{214 + i}
621
626
  </text>
622
627
  </g>
623
628
  );
624
629
  })}
625
630
  </g>
626
- );
631
+ );
632
+ };
627
633
  return (
628
634
  <g>
629
635
  {fitArt(r, chainExtent(), art)}
@@ -215,6 +215,8 @@ export function ShapeStorefrontNode({ node, active, lang }: ShapeProps): JSX.Ele
215
215
  const doorH = facadeH - 30;
216
216
  const doorX = x + bw - doorW - 12;
217
217
  const doorY = facadeY + 20;
218
+ // 営業中の札。 扉より広げない = 扉の外へ貼り出すと店の絵が壊れる
219
+ const 札幅 = Math.min(doorW - 6, 50);
218
220
  return (
219
221
  <g>
220
222
  {/* drop shadow */}
@@ -239,9 +241,9 @@ export function ShapeStorefrontNode({ node, active, lang }: ShapeProps): JSX.Ele
239
241
  {/* door の水平 line 装飾 */}
240
242
  <line x1={doorX + 4} y1={doorY + doorH * 0.38} x2={doorX + doorW - 4} y2={doorY + doorH * 0.38} stroke={accentDarker} strokeWidth={1} />
241
243
  <line x1={doorX + 4} y1={doorY + doorH * 0.72} x2={doorX + doorW - 4} y2={doorY + doorH * 0.72} stroke={accentDarker} strokeWidth={1} />
242
- {/* OPEN sign (door 下部に貼付) */}
243
- <rect x={doorX + doorW / 2 - 20} y={doorY + doorH * 0.85} width={40} height={12} fill="#e5c07b" stroke="#a67c00" strokeWidth={1.5} />
244
- <text x={doorX + doorW / 2} y={doorY + doorH * 0.85 + 9} fontSize={8} fontWeight={700} letterSpacing={1} textAnchor="middle" fill="#5a3a10">{字引("storefront.open")}</text>
244
+ {/* OPEN sign (door 下部に貼付)。 札は字に合わせる = 指定 11 の `OPEN` は字間 1 で幅 41.8 (#888) */}
245
+ <rect x={doorX + doorW / 2 - 札幅 / 2} y={doorY + doorH * 0.85} width={札幅} height={16} fill="#e5c07b" stroke="#a67c00" strokeWidth={1.5} />
246
+ <text x={doorX + doorW / 2} y={doorY + doorH * 0.85 + 11} fontSize={11} fontWeight={700} letterSpacing={1} textAnchor="middle" fill="#5a3a10">{字引("storefront.open")}</text>
245
247
  {/* door handle (小さな dot) */}
246
248
  <circle cx={doorX + 6} cy={doorY + doorH * 0.55} r={1.5} fill="#c15a4a" />
247
249
  {/* base 床 (前 storefront 直下のみ) */}
@@ -344,7 +344,7 @@ export function ShapePaymentProviderNode({ node, active, lang }: ShapeProps): JS
344
344
  <text x={cx} y={cy + 9} fontSize={13} fontWeight={700} textAnchor="middle" fill={accentDarker} fontFamily="Georgia, serif">$</text>
345
345
  </g>
346
346
  {/* PROCESSING label */}
347
- <text x={cx} y={y + boxH - 16} fontSize={9} fontWeight={700} letterSpacing={2} textAnchor="middle" fill="#a4b5f2">{字引("paymentProvider.status")}</text>
347
+ <text x={cx} y={y + boxH - 16} fontSize={11} fontWeight={700} letterSpacing={2} textAnchor="middle" fill="#a4b5f2">{字引("paymentProvider.status")}</text>
348
348
  {bottomLabel(node, r.cx, r.label)}
349
349
  </g>
350
350
  );
@@ -398,15 +398,20 @@ export function ShapeCreditCardNode({ node, active }: ShapeProps): JSX.Element {
398
398
  *
399
399
  * 影と床帯はビルの外へ出る。 出る量は箱の高さに依らないので、 描ける帯を先に狭める。
400
400
  */
401
- const BROKERAGE_TICK_H = 12;
401
+ const BROKERAGE_TICK_H = 18;
402
402
  const BROKERAGE_LOGO_H = 26;
403
- /** 電光掲示板の内側の帯と、そこに流れる相場の字の指定。 */
404
- const BROKERAGE_TICK_INNER_H = 8;
405
- const BROKERAGE_TICKER_SIZE = 5.5;
403
+ /**
404
+ * 電光掲示板の内側の帯と、そこに流れる相場の字の指定。
405
+ *
406
+ * 帯の高さは **字の指定から決める** (#888)。 打ち消しの上限は `帯の高さ x 0.8 / 指定` なので、
407
+ * 帯が `指定 x 0.8` を下回ると上限が 1 を切り、打ち消しが 1 度も効かなくなる。
408
+ */
409
+ const BROKERAGE_TICKER_SIZE = 11;
410
+ const BROKERAGE_TICK_INNER_H = 14;
406
411
  /** ロゴ帯の看板の字の指定。 */
407
412
  const BROKERAGE_SIGN_SIZE = 12;
408
413
  /** 窓の格子。 上端はロゴ帯の下、 下端は基壇の上。 */
409
- const BROKERAGE_GRID_TOP = 62;
414
+ const BROKERAGE_GRID_TOP = 68;
410
415
  const BROKERAGE_CELL_W = 14;
411
416
  const BROKERAGE_CELL_H = 18;
412
417
  const BROKERAGE_GAP_X = 8;
@@ -490,13 +495,13 @@ export function ShapeBrokerageNode({ node, active, lang }: ShapeProps): JSX.Elem
490
495
  <rect x={x - BROKERAGE_SHADE_OUT} y={y} width={BROKERAGE_SHADE_OUT * 2} height={h} fill={accentDarker} />
491
496
  {/* 上部電光掲示板 tick */}
492
497
  <rect x={x} y={y} width={w} height={BROKERAGE_TICK_H} fill="#0f1830" stroke="none" />
493
- <rect x={x + 2} y={y + 2} width={w - 4} height={8} fill="#1a2340" stroke="none" />
494
- <text data-cdl-counter-scaled="1" x={cx} y={y + 9} fontSize={BROKERAGE_TICKER_SIZE * 倍} fontWeight={700} textAnchor="middle" fill="#4ea36a" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">
498
+ <rect x={x + 2} y={y + 2} width={w - 4} height={BROKERAGE_TICK_INNER_H} fill="#1a2340" stroke="none" />
499
+ <text data-cdl-counter-scaled="1" x={cx} y={y + 12} fontSize={BROKERAGE_TICKER_SIZE * 倍} fontWeight={700} textAnchor="middle" fill="#4ea36a" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">
495
500
  {字引("brokerage.ticker")}
496
501
  </text>
497
502
  {/* $ SECURITIES ロゴ帯 */}
498
503
  <rect x={x} y={y + BROKERAGE_TICK_H} width={w} height={BROKERAGE_LOGO_H} fill="#e5c07b" stroke="none" />
499
- <text data-cdl-counter-scaled="0" x={cx} y={y + 30} fontSize={BROKERAGE_SIGN_SIZE} fontWeight={700} letterSpacing={2.5} textAnchor="middle" fill="#0f1830">
504
+ <text data-cdl-counter-scaled="0" x={cx} y={y + BROKERAGE_TICK_H + 18} fontSize={BROKERAGE_SIGN_SIZE} fontWeight={700} letterSpacing={2.5} textAnchor="middle" fill="#0f1830">
500
505
  {字引("brokerage.sign")}
501
506
  </text>
502
507
  {/* window grid (段数は高さ次第、 列は 6 固定) */}
@@ -529,8 +534,10 @@ export function ShapeBrokerageNode({ node, active, lang }: ShapeProps): JSX.Elem
529
534
  }
530
535
 
531
536
  /** shape-exchange = E2-A 中央 clearing house (hexagon EXCHANGE + 放射 8 node + 双方向 tx dots) */
532
- /** 六角形の下に置く清算の字の指定。 */
533
- const EXCHANGE_SUB_SIZE = 8;
537
+ /** 六角形の中に置く清算の字の指定。 */
538
+ const EXCHANGE_SUB_SIZE = 11;
539
+ /** 中央の六角形の外接半径。 看板の字 (指定 11、 字間 1 で幅 83.7) が入る大きさから決めた (#888)。 */
540
+ const EXCHANGE_HEX_R = 52;
534
541
 
535
542
  export function ShapeExchangeNode({ node, active, lang }: ShapeProps): JSX.Element {
536
543
  const 字引 = 字引きを作る(lang);
@@ -553,7 +560,15 @@ export function ShapeExchangeNode({ node, active, lang }: ShapeProps): JSX.Eleme
553
560
  };
554
561
  });
555
562
  // 中央 hexagon = 縦向き正六角形 (上下に頂点、 30° 回転)
556
- const hexR = 45;
563
+ const hexR = EXCHANGE_HEX_R;
564
+ /**
565
+ * 六角形の中で、中心から縦に `d` 離れた高さに引ける横幅。
566
+ *
567
+ * 上下の頂点へ向かって細るので、字の帯を `hexR * 2` で見積もると入らない字を入ると
568
+ * 判定する。 **字の下端の高さで測る** = そこが字の占める範囲でいちばん狭い (#888)。
569
+ */
570
+ const 六角の横幅 = (d: number): number =>
571
+ hexR * Math.sqrt(3) * Math.min(1, Math.max(0, (hexR - Math.abs(d)) / (hexR / 2)));
557
572
  const hexPoints = [-90, -30, 30, 90, 150, 210].map((deg) => {
558
573
  const rad = (deg * Math.PI) / 180;
559
574
  return { x: cx + Math.cos(rad) * hexR, y: cy + Math.sin(rad) * hexR };
@@ -570,7 +585,7 @@ export function ShapeExchangeNode({ node, active, lang }: ShapeProps): JSX.Eleme
570
585
  */
571
586
  const 節点 = Math.max(0, nodeR) + 8 + 1.5 / 2;
572
587
  const 六角 = hexR + frame.strokeWidth / 2;
573
- const 字下 = 26 + EXCHANGE_SUB_SIZE * 0.25;
588
+ const 字下 = 28 + EXCHANGE_SUB_SIZE * 0.25;
574
589
  const 外 = Math.max(節点, 六角);
575
590
  const ずれ = cy - r.cy;
576
591
  const 広がり = {
@@ -580,14 +595,19 @@ export function ShapeExchangeNode({ node, active, lang }: ShapeProps): JSX.Eleme
580
595
  右: 外,
581
596
  };
582
597
  /*
583
- * 打ち消しの上限を出す帯 (#887)。 清算の字は六角形の下の空いた所に置くので、幅は絵の横幅を
584
- * そのまま使える。 高さは字の下の余り (`字下`) に合わせて 14 を採る。
598
+ * 打ち消しの上限を出す帯 (#887)。 清算の字は六角形の下寄りに置くので、幅はその高さで
599
+ * 引ける横幅を採る (#888 で `外 * 2` から改めた。 絵の横幅は六角形の外まで含むため、
600
+ * 入らない字を入ると判定していた)。 高さは記号との間隔に合わせて 14 を採る。
585
601
  *
586
- * **看板 `EXCHANGE` と記号 `⇄` は打ち消さない**。 前者は字間 2 を持ち幅が字数に比例し、
602
+ * **看板 `EXCHANGE` と記号 `⇄` は打ち消さない**。 前者は字間 1 を持ち幅が字数に比例し、
587
603
  * 後者は六角形の中に余白を持たない。
588
604
  */
589
605
  const 打ち消す字ら: 打ち消す字[] = [
590
- { text: 字引("exchange.sub"), size: EXCHANGE_SUB_SIZE, 帯: { 幅: 外 * 2, 高さ: 14 } },
606
+ {
607
+ text: 字引("exchange.sub"),
608
+ size: EXCHANGE_SUB_SIZE,
609
+ 帯: { 幅: 六角の横幅(字下), 高さ: 14 },
610
+ },
591
611
  ];
592
612
  const 絵 = (倍率: number): JSX.Element => {
593
613
  const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
@@ -609,11 +629,11 @@ export function ShapeExchangeNode({ node, active, lang }: ShapeProps): JSX.Eleme
609
629
  })}
610
630
  {/* 中央 hexagon EXCHANGE (単色 #255a7a、 縦向き正六角形) */}
611
631
  <path d={hexPath} fill="#255a7a" stroke={accentDarker} strokeWidth={frame.strokeWidth} strokeLinejoin="round" />
612
- <text data-cdl-counter-scaled="0" x={cx} y={cy - 8} fontSize={10} fontWeight={700} letterSpacing={2} textAnchor="middle" fill="#e5c07b">{字引("exchange.sign")}</text>
632
+ <text data-cdl-counter-scaled="0" x={cx} y={cy - 10} fontSize={11} fontWeight={700} letterSpacing={1} textAnchor="middle" fill="#e5c07b">{字引("exchange.sign")}</text>
613
633
  {/* swap 記号 */}
614
634
  <text data-cdl-counter-scaled="0" data-cdl-role="node-body" x={cx} y={cy + 12} fontSize={22} fontWeight={700} textAnchor="middle" fill="#e5c07b">⇄</text>
615
635
  {/* clearing label */}
616
- <text data-cdl-counter-scaled="1" x={cx} y={cy + 26} fontSize={EXCHANGE_SUB_SIZE * 倍} fontWeight={700} textAnchor="middle" fill="#a4b5f2">{字引("exchange.sub")}</text>
636
+ <text data-cdl-counter-scaled="1" x={cx} y={cy + 28} fontSize={EXCHANGE_SUB_SIZE * 倍} fontWeight={700} textAnchor="middle" fill="#a4b5f2">{字引("exchange.sub")}</text>
617
637
  {/* 8 外周 node (buyer/seller) */}
618
638
  {nodes.map((n, i) => (
619
639
  <circle key={`n${i}`} cx={n.cx} cy={n.cy} r={8} fill={n.fill} stroke={accentDarker} strokeWidth={1.5} />
@@ -650,12 +670,22 @@ const ATM_SHADOW_RX_RATIO = 0.6;
650
670
  const ATM_FLOOR_UP = 6;
651
671
  const ATM_FLOOR_H = 10;
652
672
  const ATM_FLOOR_RESERVE = 12;
653
- /** 操作面の自然な大きさ。 これより本体が小さければ丸ごと縮める。 */
654
- const ATM_PANEL_W = 140;
673
+ /**
674
+ * 操作面の自然な大きさ。 これより本体が小さければ丸ごと縮める。
675
+ *
676
+ * 幅は **画面の字から決める** (#888)。 画面でいちばん長い `SELECT AMOUNT` は指定 11 で
677
+ * 幅 119.6 になり、画面の内側 (`ATM_PANEL_W - 16`) から左右 4 ずつ空ける分を足すと 136 要る。
678
+ */
679
+ const ATM_PANEL_W = 152;
655
680
  const ATM_PANEL_H = 146;
656
- /** 全体の幅。 上限は今までと同じ 140。 */
681
+ /**
682
+ * 全体の幅。
683
+ *
684
+ * 上限を操作面と同じにする = 上限が操作面より狭いと `面` が 1 を切り、操作面の字が
685
+ * どんな箱でも指定より小さく描かれる (#888)。
686
+ */
657
687
  const ATM_W_MIN = 100;
658
- const ATM_W_MAX = 140;
688
+ const ATM_W_MAX = ATM_PANEL_W;
659
689
  /** 全体の高さの下限。 看板 + 間 + 本体 30 + 床帯。 */
660
690
  const ATM_H_MIN = ATM_SIGN_H + ATM_SIGN_GAP + 30 + ATM_FLOOR_RESERVE;
661
691
 
@@ -698,15 +728,13 @@ export function ShapeAtmNode({ node, active, lang }: ShapeProps): JSX.Element {
698
728
  const 面 = Math.min(1, atmW / ATM_PANEL_W, atmH / ATM_PANEL_H);
699
729
  const 面左 = -ATM_PANEL_W / 2;
700
730
  /*
701
- * **操作面の字は打ち消さない** (#887)。 画面は高さ 52 に 3 行 (指定 10 / 8 / 10) を詰めており、
702
- * 行の間隔が 12 から 14 しか無い = 指定 10 の字に対して行の間隔の 8 割を割る
703
- * (12 * 0.8 / 10 = 0.96)。 打ち消すと上下の行か画面の枠に触れる。
704
- *
705
- * 1 行だけ除いて残りを戻す形も採らない = 画面の 3 行は 1 つのまとまりなので、`¥ 20,000` だけ
706
- * 戻さないと金額が見出しより小さくなる。 鍵盤の数字 (18x10 の鍵に入る絵) と天井看板
707
- * (字間 2 を持ち幅が字数に比例する) も据え置く。
731
+ * **操作面の字は打ち消さない** (#887 / #888)。 打ち消しの倍率は絵に 1 つで、いちばん余白の
732
+ * 無い字が全体の上限を決める。 鍵盤は 18x10 の鍵に数字を載せるため余白が無く
733
+ * (10 * 0.8 / 7 = 1.14 に対し幅は 18 / 字の幅)、鍵を含む操作面では上限が 1 前後に張り付く。
734
+ * 乗せても倍率が動かないので、印だけ付けて空回りさせない。
708
735
  *
709
- * 縮む前から画面の字が読める大きさに届かないこと自体は別の課題 (cardene777/cdl#888)。
736
+ * 画面の 3 行とカード投入口の札は #888 で指定 11 に上げた。 行の間隔は 14 で、
737
+ * 指定 11 の字 (上 7.9 / 下 2.8) が上下の行にも画面の枠にも触れない。
710
738
  */
711
739
  const 絵 = (
712
740
  <g>
@@ -720,9 +748,9 @@ export function ShapeAtmNode({ node, active, lang }: ShapeProps): JSX.Element {
720
748
  <g transform={`translate(${centerX} ${atmY}) scale(${面})`}>
721
749
  {/* screen */}
722
750
  <rect x={面左 + 8} y={10} width={ATM_PANEL_W - 16} height={52} rx={3} fill="#0f1830" stroke="#000" strokeWidth={1.5} />
723
- <text data-cdl-counter-scaled="0" x={0} y={30} fontSize={10} fontWeight={700} letterSpacing={1} textAnchor="middle" fill="#e5c07b">お引出し</text>
724
- <text data-cdl-counter-scaled="0" x={0} y={44} fontSize={8} fontWeight={700} textAnchor="middle" fill="#a4b5f2">{字引("atm.screen")}</text>
725
- <text data-cdl-counter-scaled="0" x={0} y={56} fontSize={10} fontWeight={700} textAnchor="middle" fill="#4ea36a">¥ 20,000</text>
751
+ <text data-cdl-counter-scaled="0" x={0} y={28} fontSize={11} fontWeight={700} letterSpacing={1} textAnchor="middle" fill="#e5c07b">お引出し</text>
752
+ <text data-cdl-counter-scaled="0" x={0} y={42} fontSize={11} fontWeight={700} textAnchor="middle" fill="#a4b5f2">{字引("atm.screen")}</text>
753
+ <text data-cdl-counter-scaled="0" x={0} y={56} fontSize={11} fontWeight={700} textAnchor="middle" fill="#4ea36a">¥ 20,000</text>
726
754
  {/* keypad 5x2 */}
727
755
  <rect x={面左 + 8} y={68} width={ATM_PANEL_W - 16} height={42} rx={2} fill="#8b949e" stroke="#5a6270" strokeWidth={1} />
728
756
  {(() => {
@@ -733,7 +761,7 @@ export function ShapeAtmNode({ node, active, lang }: ShapeProps): JSX.Element {
733
761
  const keyW = 18;
734
762
  const keyH = 10;
735
763
  const keyGap = 4;
736
- const startX = 面左 + 14;
764
+ const startX = 面左 + (ATM_PANEL_W - (5 * keyW + 4 * keyGap)) / 2;
737
765
  return keys.map((k, i) => {
738
766
  const row = Math.floor(i / 5);
739
767
  const col = i % 5;
@@ -750,7 +778,7 @@ export function ShapeAtmNode({ node, active, lang }: ShapeProps): JSX.Element {
750
778
  {/* card slot */}
751
779
  <rect x={面左 + 12} y={116} width={ATM_PANEL_W - 24} height={8} rx={1.5} fill="#0f1830" stroke={accentDarker} strokeWidth={1} />
752
780
  <rect x={面左 + 15} y={118} width={ATM_PANEL_W - 30} height={4} rx={0.5} fill="#e5c07b" />
753
- <text data-cdl-counter-scaled="0" x={0} y={132} fontSize={6} fontWeight={700} letterSpacing={1} textAnchor="middle" fill="#e5c07b">{字引("atm.cardSlot")}</text>
781
+ <text data-cdl-counter-scaled="0" x={0} y={132} fontSize={11} fontWeight={700} letterSpacing={1} textAnchor="middle" fill="#e5c07b">{字引("atm.cardSlot")}</text>
754
782
  {/* cash slot */}
755
783
  <rect x={面左 + 12} y={136} width={ATM_PANEL_W - 24} height={4} rx={1} fill="#0f1830" />
756
784
  </g>
@@ -520,9 +520,10 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
520
520
  const ID字 = 11;
521
521
  const ID字間 = 1;
522
522
  const PILL上 = 20;
523
- const PILL幅 = 54;
523
+ // 札の幅は状態の字から決める = 指定 11 の `IN PROG` は字間 1 で幅 62.4、丸い端に触れない余白を足す (#888)
524
+ const PILL幅 = 70;
524
525
  const PILL高 = 16;
525
- const PILL字 = 9;
526
+ const PILL字 = 11;
526
527
  const FOOTER下 = 22;
527
528
  const AVATAR中心Y = 4;
528
529
  const AVATAR半径 = 9;
package/src/lang.ts CHANGED
@@ -68,8 +68,14 @@ const en = {
68
68
  "paymentProvider.sign": "PAY",
69
69
  /** `shape-payment-provider` の状態の札 */
70
70
  "paymentProvider.status": "PROCESSING",
71
- /** `shape-brokerage` の株価の帯 */
72
- "brokerage.ticker": "▲NKY 38,240 ▲AAPL 194",
71
+ /**
72
+ * `shape-brokerage` の株価の帯。
73
+ *
74
+ * **1 銘柄に絞る** (#888)。 電光掲示板の内側は幅 136 しか無く、2 銘柄を指定 11 で描くと
75
+ * 幅 181 になってビルの外へ出る。 板が流れている雰囲気は帯そのもの (黒地に緑の等幅) が
76
+ * 担うので、銘柄を減らしても絵は変わらない。
77
+ */
78
+ "brokerage.ticker": "▲NKY 38,240",
73
79
  /** `shape-brokerage` の看板 */
74
80
  "brokerage.sign": "$ SECURITIES",
75
81
  /** `shape-exchange` の看板 */
@@ -284,7 +290,7 @@ const ja: Record<CdlTextKey, string> = {
284
290
  "trustBank.sign": "信託",
285
291
  "paymentProvider.sign": "決済",
286
292
  "paymentProvider.status": "処理中",
287
- "brokerage.ticker": "▲日経 38,240 ▲東証 2,710",
293
+ "brokerage.ticker": "▲日経 38,240",
288
294
  "brokerage.sign": "¥ 証券",
289
295
  "exchange.sign": "取引所",
290
296
  "exchange.sub": "清算",
@@ -500,7 +500,6 @@ export interface VisualValidationReport {
500
500
 
501
501
  const MIN_NODE_W = 80;
502
502
  const MIN_NODE_H = 40;
503
- const MIN_FONT_SIZE = 12;
504
503
  const ROW_FORMAT = /^[^:]+:\s*.+$/;
505
504
 
506
505
  function emptyCounts(): Record<VisualAxis, number> {
@@ -2007,10 +2006,6 @@ function runAxes(
2007
2006
  );
2008
2007
  }
2009
2008
  }
2010
- // edge label の font size は collisions.ts で BBox 計算済、 ここでは MIN_FONT_SIZE 違反のみ check
2011
- // (実装では cdl が 12px 以下を render することはないので no-op、 将来拡張用)
2012
- void MIN_FONT_SIZE;
2013
-
2014
2009
  // ───────────────────────────────────────────────────────────
2015
2010
  // Axis 4: row-format (rows[] format + placeholder 解決)
2016
2011
  //