commit 81ba9a72c74e9f5e633d09dd027ef1d2b5d298e5
parent 8c1a7ea6166f7007afd087cfc1980b8ff431270a
Author: minerva-jupiter <ryouturn@gmail.com>
Date: Thu, 30 Oct 2025 07:59:31 +0900
refactor(r3f-types): Centralize custom material type declarations
Moved `OilArtMaterial` type definitions to `types/r3f-custom.d.ts` to use
@react-three/fiber module augmentation for better organization.
feat(chat): Implemented anti-repetition logic for AI responses; AI now
responds with 'は?' if its answer matches the user's last message.
fix(lightactivity): Corrected 'use client' pragma typo and re-formatted.
Diffstat:
5 files changed, 151 insertions(+), 131 deletions(-)
diff --git a/app/components/1_chat.tsx b/app/components/1_chat.tsx
@@ -261,7 +261,10 @@ const useAudioPlayback = (
sender: 'user',
};
setMessages((prev) => [...prev, newUserMessage]);
- const ans = dict == undefined ? 'もっとマシなことを言いなさい。' : chat(dict, text);
+ let ans = dict == undefined ? 'もっとマシなことを言いなさい。' : chat(dict, text);
+ if(messages[messages.length - 1].text === ans){
+ ans = 'は?';
+ };
const aiResponse: Message = {
id: Date.now() + 1,
text: ans,
diff --git a/app/components/8_lightactivity.tsx b/app/components/8_lightactivity.tsx
@@ -1,107 +1,110 @@
-'use clinet'
-
-import React, { useState, useEffect } from 'react';
-import styled, { keyframes, css } from 'styled-components';
-
-// 演出データ
-const flickerEvents = [
- { word: "TSCHERNOBYL", duration: 500, delay: 1000 },
- { word: "HARRISBURG", duration: 600, delay: 5000 },
- // ... その他の単語
-];
-
-type FlickerEvent = typeof flickerEvents[number];
-
-// --- 1. CSS-in-JSによるアニメーション定義 ---
-
-// 背景のチカチカアニメーション
-const backgroundFlicker = keyframes`
-0%, 100% { background-color: #000; }
-10%, 30%, 50%, 70%, 90% { background-color: #fff; }
-20%, 40%, 60%, 80% { background-color: #000; }
-`;
-
-// 文字のチカチカアニメーション
-const textFlicker = keyframes`
-0%, 100% { color: #fff; text-shadow: 0 0 5px rgba(255, 255, 255, 0.5); }
-50% { color: #f00; text-shadow: none; }
-`;
-
-// --- 2. スタイル付きコンポーネントの定義 ---
-
-// 背景を制御するコンテナ
-const VisualContainer = styled.div<{ $isFlickering: boolean }>`
-/* 基本スタイル */
- display: flex;
-justify-content: center;
-align-items: center;
-width: 100%;
-height: 100vh; /* 画面全体に広げるための例 */
- background-color: black;
-transition: background-color 0.1s;
-
-/* フリッカー適用 */
- ${props => props.$isFlickering && css`
- animation: ${backgroundFlicker} 0.1s infinite step-end;
- `}
- `;
-
- // テキストを制御するH1要素
- const FlickerText = styled.h1<{ $isFlickering: boolean }>`
- /* 基本スタイル */
- font-size: 8rem;
- font-weight: bold;
- color: white;
- margin: 0;
-
- /* フリッカー適用 */
- ${props => props.$isFlickering && css`
- animation: ${textFlicker} 0.9s infinite step-end;
- `}
- `;
-
-
-const FlickerVisualizer: React.FC = () => {
- const [currentWord, setCurrentWord] = useState<string>('');
- const [isFlickering, setIsFlickering] = useState<boolean>(false);
- const startTime = React.useRef(Date.now()); // 演出開始時間を保持
-
- useEffect(() => {
- // 演出の開始時刻を固定
- const startOffset = startTime.current;
-
- const tick = () => {
- const elapsedTime = Date.now() - startOffset;
-
- // 現在のイベント判定ロジック
- const activeEvent = flickerEvents.find(event =>
- elapsedTime >= event.delay && elapsedTime < (event.delay + event.duration)
- );
-
- if (activeEvent) {
- setCurrentWord(activeEvent.word);
- setIsFlickering(true);
- } else {
- setIsFlickering(false);
- // フリッカーが終わった後も単語を一定時間表示し続けるなどの調整はここで行う
- }
-
- // 次のフレームで再実行 (よりスムーズなアニメーション処理)
- // 今回はタイミングが重要なため、setIntervalの代わりに使用
- requestAnimationFrame(tick);
- };
-
- const animationFrameId = requestAnimationFrame(tick);
-
- return () => cancelAnimationFrame(animationFrameId);
- }, []); // 依存配列は空で、マウント時に一度だけ実行
-
- return (
- <VisualContainer $isFlickering={isFlickering}>
- <FlickerText $isFlickering={isFlickering}>
- {currentWord}
- </FlickerText>
- </VisualContainer>
- );
-};
-export default FlickerVisualizer
+"use clinet";
+
+import React, { useState, useEffect } from "react";
+import styled, { keyframes, css } from "styled-components";
+
+// 演出データ
+const flickerEvents = [
+ { word: "TSCHERNOBYL", duration: 500, delay: 1000 },
+ { word: "HARRISBURG", duration: 600, delay: 5000 },
+ // ... その他の単語
+];
+
+type FlickerEvent = (typeof flickerEvents)[number];
+
+// --- 1. CSS-in-JSによるアニメーション定義 ---
+
+// 背景のチカチカアニメーション
+const backgroundFlicker = keyframes`
+0%, 100% { background-color: #000; }
+10%, 30%, 50%, 70%, 90% { background-color: #fff; }
+20%, 40%, 60%, 80% { background-color: #000; }
+`;
+
+// 文字のチカチカアニメーション
+const textFlicker = keyframes`
+0%, 100% { color: #fff; text-shadow: 0 0 5px rgba(255, 255, 255, 0.5); }
+50% { color: #f00; text-shadow: none; }
+`;
+
+// --- 2. スタイル付きコンポーネントの定義 ---
+
+// 背景を制御するコンテナ
+const VisualContainer = styled.div<{ $isFlickering: boolean }>`
+ /* 基本スタイル */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100vh; /* 画面全体に広げるための例 */
+ background-color: black;
+ transition: background-color 0.1s;
+
+ /* フリッカー適用 */
+ ${(props) =>
+ props.$isFlickering &&
+ css`
+ animation: ${backgroundFlicker} 0.1s infinite step-end;
+ `}
+`;
+
+// テキストを制御するH1要素
+const FlickerText = styled.h1<{ $isFlickering: boolean }>`
+ /* 基本スタイル */
+ font-size: 8rem;
+ font-weight: bold;
+ color: white;
+ margin: 0;
+
+ /* フリッカー適用 */
+ ${(props) =>
+ props.$isFlickering &&
+ css`
+ animation: ${textFlicker} 0.9s infinite step-end;
+ `}
+`;
+
+const FlickerVisualizer: React.FC = () => {
+ const [currentWord, setCurrentWord] = useState<string>("");
+ const [isFlickering, setIsFlickering] = useState<boolean>(false);
+ const startTime = React.useRef(Date.now()); // 演出開始時間を保持
+
+ useEffect(() => {
+ // 演出の開始時刻を固定
+ const startOffset = startTime.current;
+
+ const tick = () => {
+ const elapsedTime = Date.now() - startOffset;
+
+ // 現在のイベント判定ロジック
+ const activeEvent = flickerEvents.find(
+ (event) =>
+ elapsedTime >= event.delay &&
+ elapsedTime < event.delay + event.duration,
+ );
+
+ if (activeEvent) {
+ setCurrentWord(activeEvent.word);
+ setIsFlickering(true);
+ } else {
+ setIsFlickering(false);
+ // フリッカーが終わった後も単語を一定時間表示し続けるなどの調整はここで行う
+ }
+
+ // 次のフレームで再実行 (よりスムーズなアニメーション処理)
+ // 今回はタイミングが重要なため、setIntervalの代わりに使用
+ requestAnimationFrame(tick);
+ };
+
+ const animationFrameId = requestAnimationFrame(tick);
+
+ return () => cancelAnimationFrame(animationFrameId);
+ }, []); // 依存配列は空で、マウント時に一度だけ実行
+
+ return (
+ <VisualContainer $isFlickering={isFlickering}>
+ <FlickerText $isFlickering={isFlickering}>{currentWord}</FlickerText>
+ </VisualContainer>
+ );
+};
+export default FlickerVisualizer;
diff --git a/app/components/OilArt/OilArtMaterial.d.ts b/app/components/OilArt/OilArtMaterial.d.ts
@@ -1,18 +0,0 @@
-// src/types/OilArtMaterial.d.ts
-import { Material } from 'three';
-
-declare global {
- namespace JSX {
- interface IntrinsicElements {
- oilArtMaterial: ThreeElements['shaderMaterial'] & {
- u_time?: number;
- u_resolution?: THREE.Vector2;
- u_dropPosition?: THREE.Vector2;
- u_dropColor?: THREE.Color;
- u_dropRadius?: number;
- u_tiltDirection?: THREE.Vector2;
- u_feedbackTexture?: THREE.Texture | null; // Ping-Pongバッファ用
- };
- }
- }
-}
diff --git a/app/components/OilArt/OilArtPlane.tsx b/app/components/OilArt/OilArtPlane.tsx
@@ -171,9 +171,21 @@ import * as THREE from 'three';
// -----------------------------------------------------------
// (上記2.の vertexShader と fragmentShader をここに定義またはインポート)
-// -----------------------------------------------------------
-// カスタムマテリアルの定義と拡張 (型定義は src/types/OilArtMaterial.d.ts に依存)
-// -----------------------------------------------------------
+// 1. OilArtMaterial クラスのカスタムユニフォームプロパティ
+interface OilArtUniforms {
+ u_time?: number;
+ u_resolution?: THREE.Vector2;
+ u_dropPosition?: THREE.Vector2;
+ u_dropColor?: THREE.Color;
+ u_dropRadius?: number;
+ u_tiltDirection?: THREE.Vector2;
+ u_feedbackTexture?: THREE.Texture | null;
+}
+
+// 3. OilArtMaterialRef のエクスポート型定義
+// THREE.ShaderMaterial を拡張した型を定義
+export type OilArtMaterialRef = THREE.ShaderMaterial & OilArtUniforms;
+
const OilArtMaterial = shaderMaterial(
{
u_time: 0,
@@ -201,8 +213,6 @@ interface OilArtPlaneProps {
onTriggerAPI: (api: OilArtAPI) => void;
}
-type OilArtMaterialRef = THREE.ShaderMaterial & typeof OilArtMaterial.prototype;
-
// -----------------------------------------------------------
// OilArtPlane 本体
// -----------------------------------------------------------
diff --git a/types/r3f-custom.d.ts b/types/r3f-custom.d.ts
@@ -0,0 +1,22 @@
+// THREEとR3Fの型をインポート
+import * as THREE from 'three';
+import { ThreeElements } from '@react-three/fiber';
+
+// OilArtMaterial のユニフォーム型を定義
+interface OilArtUniforms {
+ u_time?: number;
+ u_resolution?: THREE.Vector2;
+ u_dropPosition?: THREE.Vector2;
+ u_dropColor?: THREE.Color;
+ u_dropRadius?: number;
+ u_tiltDirection?: THREE.Vector2;
+ u_feedbackTexture?: THREE.Texture | null;
+}
+
+// @react-three/fiber のモジュールを拡張することで、カスタム要素の型を認識させる
+declare module '@react-three/fiber' {
+ // ThreeElements インターフェースに新しい要素を追加します
+ export interface ThreeElements {
+ oilArtMaterial: ThreeElements['shaderMaterial'] & OilArtUniforms;
+ }
+}