# Posimai 背景スタイル — コピペ用リファレンス 新規アプリで Station/Guard と同じ背景を使いたい時はここからコピーする。 AI への指示は「`docs/posimai-bg.md` の背景スタイルを適用して」だけで OK。 --- ## 何が入っているか | 要素 | 内容 | |------|------| | 背景色 | `#0C1221`(Termius 系ダークブルー) | | グリッド線 | 48px 格子、極薄白 | | 上部グロー | シアン+アクセントカラーのグラデーション | | バイナリオーロラ | 0/1 が降るキャンバスアニメーション(Station と同仕様) | | フォント | Inter(UI) + JetBrains Mono(コード・キャンバス) | アクセントカラーだけ各アプリで差し替える(Guard は `#F97316`、Station は `#22D3EE` など)。 --- ## 1. layout.tsx — フォント読み込み ```tsx import { Inter, JetBrains_Mono } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], weight: ['300', '400', '500', '600'], display: 'swap', variable: '--font-sans', }); const jetbrainsMono = JetBrains_Mono({ subsets: ['latin'], weight: ['400', '500'], display: 'swap', variable: '--font-mono', }); // body の className に両方渡す ``` --- ## 2. globals.css — 背景スタイル一式 ```css @theme inline { --font-sans: 'Inter', system-ui, -apple-system, sans-serif; --font-mono: 'JetBrains Mono', 'Fira Code', monospace; } :root { --bg: #0C1221; --surface: #111827; --surface2: #1A2332; --border: #1F2D40; /* アクセントカラーはアプリごとに変える */ --accent: #22D3EE; /* 例: Station はシアン */ --grid-line: rgba(255, 255, 255, 0.028); } body { background: var(--bg); font-family: var(--font-sans); -webkit-font-smoothing: antialiased; } /* グリッド線 */ body::before { content: ''; position: fixed; inset: 0; background-image: linear-gradient(var(--grid-line) 1px, transparent 1px), linear-gradient(90deg, var(--grid-line) 1px, transparent 1px); background-size: 48px 48px; pointer-events: none; z-index: 0; } /* 上部グロー(アクセントカラーを参照) */ body::after { content: ''; position: fixed; top: 0; left: 0; right: 0; height: 480px; background: radial-gradient( ellipse 70% 45% at 50% -5%, color-mix(in srgb, var(--accent) 8%, transparent) 0%, transparent 70% ); pointer-events: none; z-index: 0; } /* コンテンツをオーロラの上に */ body > * { position: relative; z-index: 1; } ``` --- ## 3. BinaryAurora.tsx — コピペ用完全版 色帯(BANDS)の hue を変えるとアプリのカラーに合わせられる。 現在値は Station と同じシアン(185) / パープル(265) / グリーン(150)。 ```tsx 'use client'; import { useEffect, useRef } from 'react'; export function BinaryAurora() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const FONT_SIZE = 14; // 色帯: hue を変えてアプリのカラーに合わせる // シアン/パープル/グリーン(Station・Guard 共通) const BANDS = [ { hue: 185, sat: 90, x: 0.15, speed: 0.00018, phase: 0 }, { hue: 265, sat: 80, x: 0.38, speed: 0.00013, phase: 1.5 }, { hue: 185, sat: 85, x: 0.62, speed: 0.00020, phase: 3.0 }, { hue: 150, sat: 70, x: 0.80, speed: 0.00015, phase: 4.2 }, ]; type Col = { y: number; speed: number; len: number; chars: string[]; opacity: number }; let cols: Col[] = []; let t = 0; let raf: number; const resize = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; const numCols = Math.ceil(canvas.width / FONT_SIZE); while (cols.length < numCols) { cols.push({ y: Math.random() * canvas.height, speed: 1.0 + Math.random() * 3.2, len: 8 + Math.floor(Math.random() * 22), chars: [], opacity: 0.28 + Math.random() * 0.50, }); } if (cols.length > numCols) cols.length = numCols; }; resize(); window.addEventListener('resize', resize); function getBandColor(x: number, now: number): { hue: number; sat: number; alpha: number } { const xf = x / (canvas?.width ?? 1); let best = BANDS[0]; let bestDist = Infinity; for (const b of BANDS) { const bx = b.x + Math.sin(now * b.speed + b.phase) * 0.12; const dist = Math.abs(xf - bx); if (dist < bestDist) { bestDist = dist; best = b; } } const bx = best.x + Math.sin(now * best.speed + best.phase) * 0.12; const alpha = Math.max(0, 1 - Math.abs(xf - bx) / 0.22); return { hue: best.hue, sat: best.sat, alpha }; } const draw = () => { t++; raf = requestAnimationFrame(draw); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.font = `${FONT_SIZE}px 'JetBrains Mono', monospace`; cols.forEach((col, i) => { const x = i * FONT_SIZE; const band = getBandColor(x, t); for (let j = 0; j < col.len; j++) { const cy = col.y - j * FONT_SIZE; if (cy < -FONT_SIZE || cy > canvas.height + FONT_SIZE) continue; if (!col.chars[j] || (t % 8 === 0 && Math.random() < 0.05)) { col.chars[j] = Math.random() < 0.5 ? '1' : '0'; } const ch = col.chars[j]; const trailAlpha = (1 - j / col.len) * col.opacity; const finalAlpha = trailAlpha * (band.alpha * 0.7 + 0.15); if (j === 0) { ctx.fillStyle = `hsla(${band.hue},${band.sat}%,94%,${Math.min(1, finalAlpha * 2.2)})`; } else if (ch === '1') { ctx.fillStyle = `hsla(${band.hue},${band.sat}%,65%,${finalAlpha})`; } else { ctx.fillStyle = `hsla(${(band.hue + 30) % 360},${Math.round(band.sat * 0.6)}%,45%,${finalAlpha * 0.55})`; } ctx.fillText(ch, x, cy); } col.y += col.speed; if (col.y - col.len * FONT_SIZE > canvas.height) { col.y = -FONT_SIZE * 2; col.speed = 1.0 + Math.random() * 3.2; col.len = 8 + Math.floor(Math.random() * 22); col.chars = []; col.opacity = 0.28 + Math.random() * 0.50; } }); }; raf = requestAnimationFrame(draw); return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', resize); }; }, []); return (