diff --git a/README.md b/README.md index f4e9eb4..a04df86 100644 --- a/README.md +++ b/README.md @@ -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の動作確認などができます) diff --git a/user/public/demo/45th_logo.png b/user/public/demo/45th_logo.png new file mode 100644 index 0000000..d9cde79 Binary files /dev/null and b/user/public/demo/45th_logo.png differ diff --git a/user/src/App.tsx b/user/src/App.tsx index 866e63c..48e0006 100644 --- a/user/src/App.tsx +++ b/user/src/App.tsx @@ -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コンポーネント ===== @@ -14,6 +15,7 @@ export default function App() { } /> {/* ホーム画面 */} } /> {/* ジャンプ検出画面 */} } /> {/* フィナーレ花火大会画面 */} + } /> {/* デモ画面 */} ) diff --git a/user/src/pages/Demo.tsx b/user/src/pages/Demo.tsx new file mode 100644 index 0000000..bf2ccc5 --- /dev/null +++ b/user/src/pages/Demo.tsx @@ -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(null); + const [isConverting, setIsConverting] = useState(true); + const [error, setError] = useState(null); + + const homeCanvasRef = useRef(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 ( +
+ + + {/* AR花火の場所へ誘導するテキスト(Demo画面のみの要素) */} +
+
+
{DEMO_GUIDE_TITLE}
+
{DEMO_GUIDE_TEXT}
+
+
+ +
+
+ {isConverting ? ( +
画像を変換中...
+ ) : isReady ? ( + <> + + + + ) : ( +
{error ?? '花火が読み込めませんでした'}
+ )} +
+
+
+ ); +} diff --git a/user/src/pages/demoStyles.ts b/user/src/pages/demoStyles.ts new file mode 100644 index 0000000..ca8d1e7 --- /dev/null +++ b/user/src/pages/demoStyles.ts @@ -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', +};