VisualChrome(@event/visuals)は xdeal の XdealChrome を 1 つの汎用プリミティブに一般化した背景レイヤ群である。全レイヤが var(--vis-*)(brandCssVars の出力)を読むインラインスタイルなので、ブランドを差し替えるだけで色・書体・グレイン・透かしが一括で変わる。
レイヤ構成
1 枚の inset: 0 / pointer-events: none コンテナに、下から順に:
- faint grid — 2 本の
linear-gradient(var(--vis-hair-soft) 1px, ...)を64*uセルで敷く(opacity 0.5) - 巨大 italic 透かしグリフ —
brand.watermark。サイズmin(w,h)0.62scale。glyphが空文字なら描かない - grain —
var(--vis-grain)を 160×160 タイル、opacity 0.05 - inner frame —
inset: 26*uに1px solid var(--vis-hair-soft)の枠 - 4 隅コーナー —
46uの L 字ブラケット(var(--vis-hair)2px)を36uオフセットに
全寸法は unit(w,h)(短辺 1080 = 1.0)から出すため、square でも banner でも比率非依存で崩れない。
brandCssVars を container 自身に注入する意味
<div style={{ ...brandCssVars(brand), position: "absolute", inset: 0, pointerEvents: "none" }}>
{/* grid / watermark / grain / frame / corners は全て var(--vis-*) で描く */}
</div>
chrome コンテナが --vis-* を宣言するので、同じ root にコンテンツを重ねれば本文も同じトークンを継承する。カード側の使い方は「position: relative な root の先頭に <VisualChrome> を置き、その上にコンテンツを z-index で重ねる」。
props(レイヤの on/off と透かし位置)
type VisualChromeProps = {
w: number; h: number; brand: EventBrand;
grid?: boolean; // 既定 true
grain?: boolean; // 既定 true
corners?: boolean; // inner frame + 4 隅(既定 true)
watermark?: boolean; // 既定 true(ただし glyph 空なら自動で描かない)
watermarkScale?: number; // 短辺比の倍率(会場パネルで大きく見せる時に上げる)
watermarkRight?: number; // 右オフセット(透かしサイズ比、正で内側へ)
watermarkBottom?: number;
};
scanlines / rail は「意図的に非移植」
目次見出しは grid/grain/scanlines/corner/rail/watermark と superset で書いてあるが、出荷プリミティブに scanlines と rail は含まない:
- VerticalRail — xdeal 固有の縦組みラベル(イベント名を縦に流す装飾)。イベント依存が強いので汎用 chrome には移植せず、必要なイベントが独自レイヤとして足す
- scanlines — CRT 風の走査線。追加したいイベントは
VisualChromeの上(またはgrainと同格)にrepeating-linear-gradientを 1 枚重ねるだけで足せる。基底プリミティブを薄く保つため既定では入れていない
「基底 chrome は最小・装飾はイベント側で追加」が方針。追加レイヤも インラインスタイルのみにすれば capture(modern-screenshot / foreignObject = ブラウザ自身のレンダラ)で画面と完全一致で焼ける。CSS Module を使うと裏レンダの capture 時にスタイルが当たらず崩れるため、chrome は一貫してインラインで書いている。