diff --git a/web_site/src/AppRoutes.jsx b/web_site/src/AppRoutes.jsx
index 8c676e3..796c444 100644
--- a/web_site/src/AppRoutes.jsx
+++ b/web_site/src/AppRoutes.jsx
@@ -6,7 +6,7 @@ import {
import About from './about/About.jsx';
import Docs from "./docs/Docs.jsx";
import Editor from './Editor.jsx';
-import Examples from './Examples.jsx';
+import Examples from './examples/Examples.jsx';
const AppRoutes = () => {
return (
diff --git a/web_site/src/Examples.jsx b/web_site/src/Examples.jsx
deleted file mode 100644
index 238f42b..0000000
--- a/web_site/src/Examples.jsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React, { useEffect } from 'react';
-
-const useIframe = true;
-const examplesPageURL = "https://wesunwin.github.io/three-game-engine/examples"
-
-const Examples = () => {
- useEffect(() => {
- if (!useIframe) {
- window.location = examplesPageURL;
- }
- }, [useIframe]);
-
- return useIframe ? (
-
- ) :
- Redirecting to examples page...
-
-};
-
-export default Examples;
\ No newline at end of file
diff --git a/web_site/src/examples/CurrentExample.jsx b/web_site/src/examples/CurrentExample.jsx
new file mode 100644
index 0000000..f1232f7
--- /dev/null
+++ b/web_site/src/examples/CurrentExample.jsx
@@ -0,0 +1,69 @@
+import React, { useEffect, useRef, useState } from 'react'
+import CodeMirror from '@uiw/react-codemirror';
+import { javascript } from '@codemirror/lang-javascript';
+import { vscodeDark } from '@uiw/codemirror-theme-vscode';
+
+const CurrentExample = ({ currentExample }) => {
+ const iframeRef= useRef();
+
+ const [showCode, setShowCode] = useState(false);
+ const [code, setCode] = useState('');
+
+ const iframeSrc = `https://wesunwin.github.io/three-game-engine/examples/${currentExample.name}.html`;
+
+ const reloadIframe = () => {
+ iframeRef.current.src = iframeRef.current.src + '';
+ };
+
+ useEffect(() => {
+ if (currentExample?.code) {
+ setCode('Loading...')
+ window.fetch(currentExample.code)
+ .then(response => response.text())
+ .then(code => setCode(code))
+ }
+ }, [currentExample?.code])
+
+ return (
+
+
{currentExample.label}
+
+
+
+
+ {currentExample.description}
+
+
+
+
+
+ {currentExample.footer}
+
+
+
+
+
+ {showCode &&
+
+ {}}
+ />
+
+ }
+
+
+ );
+};
+
+export default CurrentExample
\ No newline at end of file
diff --git a/web_site/src/examples/Examples.jsx b/web_site/src/examples/Examples.jsx
new file mode 100644
index 0000000..963fc1c
--- /dev/null
+++ b/web_site/src/examples/Examples.jsx
@@ -0,0 +1,50 @@
+import React, { useEffect, useState } from 'react';
+import ExamplesSidebar from './ExamplesSidebar.jsx';
+import CurrentExample from './CurrentExample.jsx';
+
+const Examples = () => {
+ const [currentExample, setCurrentExample] = useState(null)
+
+ const examples = [
+ {
+ thumbnail: 'https://raw.githubusercontent.com/WesUnwin/three-game-engine/main/docs/images/first_person_kinematic_character_controller_example_thumbnail.png',
+ name: 'first_person_kinematic_character_controller_example',
+ label: 'First-Person Kinematic Character Controller',
+ description: 'Demonstrates using the KinematicCharacterController class to create a first-person controlled character game object.',
+ code: 'https://raw.githubusercontent.com/WesUnwin/three-game-engine/main/examples/first_person_kinematic_character_controller/index.js',
+ footer: (
+
+
Controls:
+
+ - Click on the iframe to allow the game to capture your mouse (ESC to escape).
+ - Move your mouse to look around.
+ - Use WSAD (or arrow keys) to move relative to the direction your facing.
+ - Hold shift to run.
+
+
+ )
+ }
+ ];
+
+ useEffect(() => {
+ if (!currentExample) {
+ setCurrentExample(examples[0])
+ }
+ }, [currentExample?.name])
+
+ return (
+
+
+
+ {currentExample &&
+
+ }
+
+ );
+};
+
+export default Examples;
\ No newline at end of file
diff --git a/web_site/src/examples/ExamplesSidebar.jsx b/web_site/src/examples/ExamplesSidebar.jsx
new file mode 100644
index 0000000..95bfe21
--- /dev/null
+++ b/web_site/src/examples/ExamplesSidebar.jsx
@@ -0,0 +1,22 @@
+import React from 'react'
+
+const ExamplesSidebar = ({ examples, currentExample, setCurrentExample }) => {
+ return (
+
+ {examples.map(example => (
+
setCurrentExample(example)}
+ >
+
+
+ {example.label}
+
+
+ ))}
+
+ )
+}
+
+export default ExamplesSidebar
\ No newline at end of file
diff --git a/web_site/src/styles.css b/web_site/src/styles.css
index de26096..dbeb732 100644
--- a/web_site/src/styles.css
+++ b/web_site/src/styles.css
@@ -100,6 +100,70 @@ body {
margin: 0 auto;
}
+.examples {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+}
+
+.examples-sidebar {
+ border-right: 1px solid lightslategray;
+ flex-grow: 1;
+
+ display: flex;
+ flex-direction: column;
+}
+
+.examples-sidebar__example {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ cursor: pointer;
+}
+
+.examples-sidebar__example-label {
+ text-align: center;
+ font-size: small;
+}
+
+.examples-sidebar__example.active .examples-sidebar__example-label {
+ font-weight: 600;
+}
+
+.examples-sidebar__example img {
+ object-fit: cover;
+ height: 180px;
+ margin: 10px auto;
+ max-width: 300px;
+ box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
+}
+
+.examples-sidebar__example.active img {
+ box-shadow: rgb(80 115 63) 0px 3px 8px;
+}
+
+.current-example {
+ text-align: center;
+ flex-grow: 5;
+}
+
+.current-example-iframe {
+ height: 55vh;
+ width: 50vw;
+}
+
+.current-example-footer {
+ text-align: left;
+ width: 50%;
+ margin: 5px auto;
+}
+
+.current-example-code {
+ margin: 1em;
+ text-align: left;
+ font-size: small;
+}
+
.markdown pre {
padding: 10px;
background-color: rgba(230, 230, 230, 0.699);