event-manual / chapter 18

セッションカード レイアウトエンジン

@event/session-card

@event/session-cardengine/ は React 非依存の純関数群である。3リポに4コピーあった balance engine(最多重複コード)をここに一本化した。移植は ivs-cms cardLayouts.tsx数式・分岐を同一に保つ関数化であり、JSDoc にも「数式・分岐は同一」と明記している — つまり ivs の実カードとの挙動差分ゼロが移植の受け入れ条件である(resolveTitleLines / resolveRoleLines は描画側と同一式を共有する切り出しそのもの)。

設計原則(1920×1080 master frame)

  1. 写真をぎちぎちまで大きく取る(photo-first balance)
  2. タイトルは1行が基本、相当長い場合のみ2行
  3. 名前は省略しない(overlap "name plate" でフレーム外まで許容)
  4. 役職は短ければ大きく1〜2行、長ければフォントを下げて最大3〜4行で全文表示(予約高は据え置きなので写真サイズは不変)

variant は "1A".."10A"(数字 = 並べる登壇者数)。保存された cardVariant が人数より少ないと写真が黙って欠けるため、effectiveVariant(cardVariant, speakerCount) が最低でも人数まで枠を自動補正する(明示的に多い枠は尊重して下げない)。

visualWidth — 幅の通貨

全ての fit 計算は px 実測ではなく概算幅 visualWidth(CJK = 1.0 / ASCII = 0.55 em)で行う。これにより server / client / satori のどこでも同じ答えが出る(決定論)。CHAR_WIDTH_MULT = 1.0 は「CJK は palt + letter-spacing 詰めでも1文字 ≒ 1em、ASCII は visualWidth 側で 0.55 に圧縮済み」という前提の係数である。

computeBalance — {titleSize, titleLines, photoSize, gap} を解く

上タイトル / 下スピーカー構成(1A–4A 系)の配分ソルバ。可用高 available = H - TICKER_H - METAROW_H - FOOTER_AREA - topPad の中で、写真を大きい方から 10px 刻みで下げながら探索する:

英語(Latin)タイトルは字面が大きく見えるため基準フォントを 0.8 倍に抑える補正が入る。computeGridBalance は同じ Phase 探索を rows × cols グリッド(5A–10A)に拡張し、カラム幅 colW = (contentWidth - (cols-1)*colGap) / cols を写真サイズの上限 cap にする。6A–10A は DENSE_DEFAULTS(maxTitleLines: 2、minPhotoSize: 240 等)を使う。

breakTitleLines — 決定論的改行

text-wrap: balance + word-break: auto-phrase 任せでは CJK が文節途中で割れて不安定だった。ここでは改行候補を品質ペナルティ付きで列挙し、DP で「行長の均し(raggedness)+ 境界ペナルティ」を最小化する:

境界penalty
スペース区切り0
句読点直後(、。・〜 等)2
閉じ括弧直後 / 開き括弧直前3
文節(任意の CJK 境界、最後の手段)9

禁則も守る: 行頭に閉じ括弧・句読点・小書き仮名・長音を置かない、行末を開き括弧で終えない、英単語の途中で割らない。PENALTY_CLEAN = 3(良い境界のみ)と PENALTY_PHRASE = 9(文節も許可)の2段があり、fitTitleLayout は「clean で割れる最大フォント → phrase 許可 → 面積 fit」の順に落ちる。

役職と名前の fit

これらは第19章のデザイナーが onResolved 経由でそのまま参照する。engine の数値(W=1920 / TICKER_H=80 / TITLE_LINE_RESERVE=1.04 等)は engine/constants.ts に集約されており、変更はレイアウト全体に波及するため原則触らない。