From 251e5316eb0134e5ae6c62a141b81852066863a5 Mon Sep 17 00:00:00 2001 From: posimai Date: Sun, 12 Apr 2026 22:20:37 +0900 Subject: [PATCH] docs: add posimai-bg.md aurora/background reusable reference, link from DESIGN.md --- DESIGN.md | 10 ++ docs/posimai-bg.md | 268 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 docs/posimai-bg.md diff --git a/DESIGN.md b/DESIGN.md index 77601fcc..22205baf 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -14,6 +14,14 @@ | `ponshu-room` / `ponshu_room_lite` | 独自テーマ(和紙 x 墨 x 琥珀)。Posimai デザインシステム適用外 | | `posimai-analytics` | BtoB ダッシュボード。TailwindCSS + React + ライトテーマで構築 | +## 背景スタイル(Station / Guard 系) + +Station・Guard・その他サイバー系アプリで使うダークブルー背景 + バイナリオーロラのコードは **`docs/posimai-bg.md`** にコピペ用で完備。 + +新規アプリに適用する場合は AI に「`docs/posimai-bg.md` の背景スタイルを適用して」と指示するだけでよい。 + +--- + ## `docs/design-system.md` との関係 `docs/design-system.md` は **このファイルの補足・詳細版**として残存しているが、**AI が参照する正はこの DESIGN.md のみ**。二重参照した場合はこのファイルを優先すること。`docs/design-system.md` は将来的に廃止または統合予定。 @@ -69,6 +77,7 @@ [data-app-id="posimai-atlas"] { --accent: #22D3EE; } /* Cyan */ [data-app-id="posimai-dev"] { --accent: #A78BFA; } /* Violet */ [data-app-id="posimai-journal"]{ --accent: #80CAEE; } /* Sky-Blue */ +[data-app-id="posimai-guard"] { --accent: #F97316; } /* Amber-Orange */ /* NG: ルートへの直書き上書き */ :root { --accent: #22D3EE; } @@ -80,6 +89,7 @@ | posimai-journal / posimai-site | `#80CAEE` Sky-Blue | 静かで知的な印象 | | posimai-atlas | `#22D3EE` Cyan | サイバー・ターミナル感。背景も `#0C1221` navy | | posimai-dev | `#A78BFA` Violet | コード・AI・ターミナルの融合 | +| posimai-guard | `#F97316` Amber-Orange | セキュリティ・警告の視覚的コンテキスト | --- diff --git a/docs/posimai-bg.md b/docs/posimai-bg.md new file mode 100644 index 00000000..becb34d3 --- /dev/null +++ b/docs/posimai-bg.md @@ -0,0 +1,268 @@ +# 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 ( +