Skip to content
Open
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
30 changes: 30 additions & 0 deletions threejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Three.js Playable sample

This sample uses Three.js and Vite to demonstrate a lightweight 3D YouTube Playables integration. It keeps the visuals procedural so the bundle stays small: geometry, materials, particles, trails, and UI are generated in code without external art assets.

## Features

- Fullscreen WebGL scene with responsive camera sizing.
- Lightweight VFX using emissive materials, additive particles, and motion trails.
- Basic lane-switching runner interaction for mouse, touch, and keyboard.
- YouTube Playables lifecycle calls for `firstFrameReady`, `gameReady`, pause, resume, save data, score submission, and rewarded ads.

## Install

```shell
npm install
```

## Development

```shell
npm run start
```

## Build

```shell
npm run build
```

Built files are written to the `dist` directory.
144 changes: 144 additions & 0 deletions threejs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="minimal-ui, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
<title>Neon Orbit Three.js Playable</title>
<script src="https://www.youtube.com/game_api/v1"></script>
<style>
:root {
color-scheme: dark;
font-family:
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
background: #06070d;
}

* {
box-sizing: border-box;
}

body {
margin: 0;
min-height: 100vh;
overflow: hidden;
touch-action: none;
background:
radial-gradient(circle at 50% 20%, rgba(31, 170, 255, 0.25), transparent 42%),
linear-gradient(180deg, #090d1b 0%, #05060b 100%);
}

canvas {
display: block;
}

.hud {
position: fixed;
inset: 0;
z-index: 2;
display: grid;
grid-template-rows: auto 1fr auto;
gap: 16px;
padding: max(18px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-right))
max(18px, env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left));
pointer-events: none;
}

.top-bar,
.bottom-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}

.stat {
min-width: 88px;
padding: 10px 12px;
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 10px;
background: rgba(4, 8, 20, 0.58);
box-shadow: 0 12px 34px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(10px);
}

.label {
display: block;
color: #91a4bd;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}

.value {
display: block;
color: #ffffff;
font-size: 24px;
font-weight: 800;
line-height: 1;
}

.title {
color: white;
font-size: clamp(20px, 4vw, 42px);
font-weight: 900;
letter-spacing: 0;
text-shadow: 0 0 28px rgba(80, 225, 255, 0.45);
}

.bottom-bar {
justify-content: center;
}

.control {
width: min(420px, 100%);
padding: 11px 14px;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 10px;
color: #dfe9ff;
background: rgba(4, 8, 20, 0.58);
text-align: center;
font-size: 13px;
font-weight: 700;
backdrop-filter: blur(10px);
}

.reward {
pointer-events: auto;
border: 0;
border-radius: 999px;
padding: 12px 18px;
color: #07111c;
background: linear-gradient(135deg, #72ffca, #48d2ff);
box-shadow: 0 0 30px rgba(72, 210, 255, 0.35);
font-weight: 900;
cursor: pointer;
}
</style>
</head>
<body>
<main class="hud" aria-label="Neon Orbit game HUD">
<section class="top-bar">
<div class="stat">
<span class="label">Score</span>
<span class="value" id="score">0</span>
</div>
<div class="title">Neon Orbit</div>
<div class="stat">
<span class="label">Shield</span>
<span class="value" id="shield">0</span>
</div>
</section>
<div></div>
<section class="bottom-bar">
<div class="control">Tap left or right to switch lanes. Collect energy and avoid blocks.</div>
<button class="reward" id="reward" type="button">Rewarded boost</button>
</section>
</main>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Loading
Loading