269 lines
7.7 KiB
Markdown
269 lines
7.7 KiB
Markdown
|
|
# 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 に両方渡す
|
|||
|
|
<body className={`${inter.variable} ${jetbrainsMono.variable}`}>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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<HTMLCanvasElement>(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 (
|
|||
|
|
<canvas
|
|||
|
|
ref={canvasRef}
|
|||
|
|
aria-hidden="true"
|
|||
|
|
style={{
|
|||
|
|
position: 'fixed',
|
|||
|
|
inset: 0,
|
|||
|
|
opacity: 1,
|
|||
|
|
pointerEvents: 'none',
|
|||
|
|
zIndex: 0,
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 使い方
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// layout.tsx または page.tsx に追加するだけ
|
|||
|
|
import { BinaryAurora } from '@/components/BinaryAurora';
|
|||
|
|
|
|||
|
|
export default function Layout({ children }) {
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<BinaryAurora />
|
|||
|
|
{children}
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. カスタマイズチートシート
|
|||
|
|
|
|||
|
|
| 変えたいもの | 場所 | 変更内容 |
|
|||
|
|
|-------------|------|---------|
|
|||
|
|
| 雨の色 | `BANDS[*].hue` | オレンジ系は 18〜42、シアンは 185、パープルは 265 |
|
|||
|
|
| 雨の速さ | `speed: 1.0 + Math.random() * 3.2` | 数値を大きくすると速い |
|
|||
|
|
| 雨の長さ | `len: 8 + Math.floor(Math.random() * 22)` | 数値を大きくすると長いトレイル |
|
|||
|
|
| 背景色 | `--bg` CSS 変数 | `#0C1221` 標準。より暗くしたければ `#080E1A` など |
|
|||
|
|
| 上部グロー | `body::after` の `--accent` | アクセントカラーに追従する |
|
|||
|
|
| キャンバス全体の明るさ | `canvas opacity` | `0.5〜1.0` の範囲で調整 |
|