Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@
- イラスト花火:http://localhost:5173
- ジャンプ花火:http://localhost:5173/detector
- フィナーレ :http://localhost:5173/finale
- デモ    :http://localhost:5173/demo
- 管理者画面 :http://localhost:3000
- Swagger UI:http://localhost:8081(APIの動作確認などができます)
Binary file added user/public/demo/45th_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions user/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Detector from './pages/Detector';
import Finale from './pages/Finale';
import Demo from './pages/Demo';


// ===== Appコンポーネント =====
Expand All @@ -14,6 +15,7 @@ export default function App() {
<Route path="/" element={<Home />} /> {/* ホーム画面 */}
<Route path="/detector" element={<Detector />} /> {/* ジャンプ検出画面 */}
<Route path="/finale" element={<Finale />} /> {/* フィナーレ花火大会画面 */}
<Route path="/demo" element={<Demo />} /> {/* デモ画面 */}
</Routes>
</Router>
)
Expand Down
110 changes: 110 additions & 0 deletions user/src/pages/Demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useEffect, useRef, useState } from 'react';
import { imageUrlToParticles } from '../utils/imageToParticles';
import type { ColorParticleData } from '../types/illustrationFireworksType';
import HomeCanvas from '../canvas/HomeCanvas';
import type { HomeCanvasHandle } from '../canvas/HomeCanvas';
import {
overlayContainerStyle,
panelStyle,
primaryButtonStyle,
secondaryButtonStyle,
statusPillStyle,
errorPillStyle,
} from './homeStyles';
import {
guideContainerStyle,
guideCardStyle,
guideTitleStyle,
guideTextStyle,
} from './demoStyles';

// デモで打ち上げる固定画像(public/ 配下のパス)。差し替えるときはここだけ変更する
const DEMO_IMAGE_URL = '/demo/45th_logo.png';

// AR花火の場所へ誘導する固定文言。文言変更はここだけ
const DEMO_GUIDE_TITLE = '🎆 AR花火誘導文';
const DEMO_GUIDE_TEXT = 'AR花火の宣伝とか\nどこでやってるか書くスペース';

// 画質(Home の「中」相当。デモでは固定)
const DEMO_RESOLUTION = 64;

// ===== Demoのページ =====
// API・QRコードを使わず、同梱の固定画像を花火として打ち上げるデモ用画面。
// user画面(Home)と同じ打ち上げ体験に加え、AR花火の場所へ誘導するテキストを表示する。
export default function Demo() {
const [particleData, setParticleData] = useState<ColorParticleData | null>(null);
const [isConverting, setIsConverting] = useState(true);
const [error, setError] = useState<string | null>(null);

const homeCanvasRef = useRef<HomeCanvasHandle>(null);

// マウント時に固定画像をパーティクルへ変換する
useEffect(() => {
setIsConverting(true);
imageUrlToParticles(DEMO_IMAGE_URL, {
resolution: DEMO_RESOLUTION,
whiteThreshold: 200,
saturationThreshold: 30,
includeWhite: false,
})
.then((pd) => {
setParticleData(pd);
console.log(`パーティクル変換完了: ${pd.particles.length} 粒子`);
})
.catch((err) => {
console.error('パーティクル変換失敗:', err);
setError('花火が読み込めませんでした');
})
.finally(() => {
setIsConverting(false);
});
}, []);

const handleLaunch = () => {
homeCanvasRef.current?.handleLaunch();
resetCameraRotation();
};

const resetCameraRotation = () => {
homeCanvasRef.current?.resetCameraRotation();
};

const isReady = !!particleData && !isConverting;

return (
<div style={{ width: '100vw', height: '100vh', overflow: 'hidden' }}>
<HomeCanvas
illustrationFireworks={null}
particleData={particleData}
ref={homeCanvasRef}
/>

{/* AR花火の場所へ誘導するテキスト(Demo画面のみの要素) */}
<div style={guideContainerStyle}>
<div style={guideCardStyle}>
<div style={guideTitleStyle}>{DEMO_GUIDE_TITLE}</div>
<div style={guideTextStyle}>{DEMO_GUIDE_TEXT}</div>
</div>
</div>

<div style={overlayContainerStyle}>
<div style={panelStyle}>
{isConverting ? (
<div style={statusPillStyle}>画像を変換中...</div>
) : isReady ? (
<>
<button onClick={handleLaunch} style={primaryButtonStyle}>
🎆 花火を打ち上げる
</button>
<button onClick={resetCameraRotation} style={secondaryButtonStyle}>
カメラのリセット
</button>
</>
) : (
<div style={errorPillStyle}>{error ?? '花火が読み込めませんでした'}</div>
)}
</div>
</div>
</div>
);
}
46 changes: 46 additions & 0 deletions user/src/pages/demoStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { CSSProperties } from 'react';
import { ACCENT_COLOR } from './homeStyles';

// ===== Demo ページの見た目に関する定数・スタイル定義 =====
// homeStyles.ts と同様に、インラインstyleオブジェクトをここへ集約する。
// コントロールパネル系のスタイルは homeStyles.ts のものを再利用し、
// Demo画面固有の「AR花火への誘導テキスト」部分のみここで定義する。

/** 画面上部中央に浮かぶ誘導テキストの位置(ノッチ等のセーフエリアを避ける) */
export const guideContainerStyle: CSSProperties = {
position: 'absolute',
top: 'calc(env(safe-area-inset-top, 0px) + 16px)',
left: '50%',
transform: 'translateX(-50%)',
width: 'min(340px, 92vw)',
zIndex: 1000,
// Canvas側のタッチ操作(カメラ操作等)を妨げないようにする
pointerEvents: 'none',
};

/** 誘導テキストの背景カード(コントロールパネルと同じ質感に揃える) */
export const guideCardStyle: CSSProperties = {
boxSizing: 'border-box',
padding: '14px 16px',
borderRadius: '16px',
backgroundColor: 'rgba(20, 20, 24, 0.6)',
backdropFilter: 'blur(6px)',
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.25)',
textAlign: 'center',
};

/** 誘導テキストのタイトル */
export const guideTitleStyle: CSSProperties = {
color: ACCENT_COLOR,
fontSize: '16px',
fontWeight: 700,
marginBottom: '6px',
};

/** 誘導テキストの本文(定数内の \n で改行させるため pre-line) */
export const guideTextStyle: CSSProperties = {
color: '#fff',
fontSize: '14px',
lineHeight: 1.6,
whiteSpace: 'pre-line',
};