Skip to content

Commit 1f2d19e

Browse files
committed
add: ランディング
1 parent eda9516 commit 1f2d19e

File tree

7 files changed

+9348
-7175
lines changed

7 files changed

+9348
-7175
lines changed

frontend/app/components/globe.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { Canvas, useFrame, useThree } from "@react-three/fiber";
2+
import { useEffect, useMemo, useRef } from "react";
3+
import * as THREE from "three";
4+
import { LineMaterial } from "three/examples/jsm/Addons.js";
5+
import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
6+
7+
export default function Globe() {
8+
return (
9+
<Canvas camera={{ fov: 50, near: 1, far: 100 }}>
10+
<Scene />
11+
</Canvas>
12+
);
13+
}
14+
15+
function Scene() {
16+
const { camera } = useThree();
17+
const meshReference = useRef<THREE.Mesh>(null);
18+
const geometryReference = useRef<THREE.BufferGeometry>(null);
19+
const wireframeReference = useRef<LineSegments2>(null);
20+
const material = new LineMaterial({ color: 0x88_91_F3, linewidth: 3 });
21+
const sphereGeometry = useSphereMesh();
22+
23+
useEffect(() => {
24+
geometryReference.current = new THREE.BufferGeometry().copy(new THREE.EdgesGeometry());
25+
26+
if (meshReference.current) {
27+
meshReference.current.geometry = geometryReference.current;
28+
meshReference.current.material = material;
29+
}
30+
31+
camera.position.set(0, 13, 4);
32+
camera.lookAt(0, 8.5, 0);
33+
}, []);
34+
35+
useFrame(() => {
36+
if (wireframeReference.current) {
37+
wireframeReference.current.rotation.y += 0.0025;
38+
}
39+
});
40+
41+
return (
42+
<>
43+
<mesh>
44+
<sphereGeometry args={[9.95, 32, 32]} />
45+
<meshBasicMaterial color="black" />
46+
</mesh>
47+
<lineSegments geometry={sphereGeometry} ref={wireframeReference}>
48+
<lineBasicMaterial color="slateblue" />
49+
</lineSegments>
50+
</>
51+
);
52+
}
53+
54+
function useSphereMesh() {
55+
return useMemo(() => {
56+
const geometry = new THREE.SphereGeometry(10, 32, 32);
57+
58+
const p = (geometry as unknown).parameters;
59+
if (!p) return;
60+
61+
const segmentsX = p.widthSegments;
62+
const segmentsY = p.heightSegments - 2;
63+
const mainShift = segmentsX + 1;
64+
const indices: number[] = [];
65+
66+
for (let index = 0; index < segmentsY + 1; index++) {
67+
let index11 = 0;
68+
let index12 = 0;
69+
for (let index_ = 0; index_ < segmentsX; index_++) {
70+
index11 = (segmentsX + 1) * index + index_;
71+
index12 = index11 + 1;
72+
const index21 = index11;
73+
const index22 = index11 + (segmentsX + 1);
74+
indices.push(index11 + mainShift, index12 + mainShift);
75+
if (index22 < (segmentsX + 1) * (segmentsY + 1) - 1) {
76+
indices.push(index21 + mainShift, index22 + mainShift);
77+
}
78+
}
79+
if (index12 + segmentsX + 1 <= (segmentsX + 1) * (segmentsY + 1) - 1) {
80+
indices.push(index12 + mainShift, index12 + segmentsX + 1 + mainShift);
81+
}
82+
}
83+
84+
const lastIndex = indices.at(-1) + 2;
85+
86+
for (let index = 0; index < segmentsX; index++) {
87+
indices.push(index, index + mainShift, index, index + mainShift + 1);
88+
89+
const index_ = lastIndex + index;
90+
const backShift = mainShift + 1;
91+
indices.push(index_, index_ - backShift, index_, index_ - backShift + 1);
92+
}
93+
94+
geometry.setIndex(indices);
95+
96+
return geometry;
97+
}, []);
98+
}

frontend/app/components/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default function Layout({
55
children,
66
}: {
77
children: ReactNode;
8-
status: "error" | "recording" | "success";
8+
status: "error" | "landing" | "recording" | "success";
99
}) {
1010
return (
1111
<div className="bg-[#13141b] relative min-h-[100dvh] overflow-y-scroll">

frontend/app/pages/landing.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
3+
import Globe from "~/components/globe";
4+
import Layout from "~/components/layout";
5+
import Oracle from "~/components/oracle";
6+
7+
export default function Landing({onClickBtn} : {onClickBtn: () => void}) {
8+
return (
9+
<Layout status={"landing"}>
10+
<div className="flex flex-1 flex-col items-center self-stretch lg:min-h-[700px] min-h-[522px]">
11+
<div className="z-20 flex flex-col gap-6">
12+
<div className="rounded-[30px] relative pt-5 flex items-center justify-center">
13+
<div className="w-[87px] h-[87px] lg:w-[97px] lg:h-[97px] z-10">
14+
<div>
15+
<div className="mt-4 w-[60px] h-[60px]">
16+
<Oracle />
17+
</div>
18+
</div>
19+
</div>
20+
<div className="absolute w-[768px] md:w-[980px] lg:w-[1082px] lg:bottom-[-17px]">
21+
<img alt="OracleWaveAudioFuzzy" height="100%" src="/OracleWaveAudioFuzzy.png" width="100%" />
22+
</div>
23+
</div>
24+
<div className="flex flex-col gap-6 lg:max-w-[610px] lg:w-[610px] items-center justify-center">
25+
<div className="flex flex-col w-[100%] items-center gap-5">
26+
<div className="font-normal font-sofia leading-[80%] lg:text-[65px] text-[60px] text-center uppercase opacity-100 text-white relative top-[9px] mb-[12px]">
27+
BoldVoice <span className="text-indigo-500">Accent</span> Oracle
28+
</div>
29+
<div className="font-medium spacing-[-0.48px] lg:text-2xl text-xl text-center text-indigo-500">
30+
Do you have an accent when speaking Japanese? I bet I can guess it in 30 seconds or less.
31+
</div>
32+
</div>
33+
<form className="w-full justify-center flex flex-col items-center gap-[16px]">
34+
<button
35+
className="flex max-w-[300px] w-[100%] pt-4 pb-4 pl-8 pr-8 rounded-[40px] gap-2 z-20 items-center justify-center cursor-pointer bg-[#fff]"
36+
onClick={onClickBtn}
37+
>
38+
<div className="text-text-contrast-dark text-center font-bold lg:text-xl text-base leading-[120%] spacing-[-0.2px]">
39+
Try Me
40+
</div>
41+
</button>
42+
<div className="text-center font-normal text-sm text-indigo-500">
43+
{/* 元ネタは &quot; なし */}
44+
Powered by OBACHAN: &quot;Kansaibenn&quot; Accent Training App
45+
</div>
46+
</form>
47+
</div>
48+
</div>
49+
<div className="flex flex-col items-center justify-end mt-10 relative h-[280px] w-[100%]">
50+
<div className="absolute bottom-0 mb-[-32px] h-[307px] w-[825px]">
51+
<Globe />
52+
</div>
53+
</div>
54+
</div>
55+
</Layout>
56+
);
57+
}

frontend/app/routes/_index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { FaMicrophone, FaRegSquare } from "react-icons/fa";
99
import Layout from "~/components/layout";
1010
import Oracle from "~/components/oracle";
1111
import Wave from "~/components/wave";
12+
import Landing from "~/pages/landing";
1213

1314
export const meta: MetaFunction = () => {
1415
return [{ title: "Accent Guesser" }, { content: "発音から地域を推定するAI", name: "description" }];
@@ -69,6 +70,8 @@ export async function action({ request }: ActionFunctionArgs): Promise<Response>
6970
}
7071

7172
export default function Index() {
73+
const [isLandingPage, setIsLandingPage] = useState(true)
74+
7275
const mediaRecorder = useRef<MediaRecorder | null>(null);
7376
const actionData = useActionData<typeof action>() as { error?: string; result?: ApiResponse };
7477
const navigation = useNavigation();
@@ -115,6 +118,8 @@ export default function Index() {
115118
setRecordingState("idle");
116119
}, [actionData]);
117120

121+
if (isLandingPage) return <Landing onClickBtn={() => setIsLandingPage(false)} />
122+
118123
const isProcessing = navigation.state === "submitting" || recordingState === "processing";
119124

120125
const startRecording = async () => {

0 commit comments

Comments
 (0)