Files
TinyTorch/site/extra/community/index.html

1072 lines
40 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI History Landscape</title>
<link rel="icon" href="assets/flame.ico" type="image/x-icon">
<style>
body {
margin: 0;
overflow-x: hidden;
background-color: #f0f0f0;
font-family: 'Courier New', Courier, monospace;
}
#canvas-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
z-index: 1;
}
#scroll-track {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 25000vh;
z-index: 2;
pointer-events: none;
}
/* --- Hero Title Section --- */
#hero-title {
position: fixed;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 20;
text-align: center;
pointer-events: none;
transition: opacity 0.2s ease-out, transform 0.2s ease-out;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo-container {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 10px;
}
.fire-icon {
width: 90px;
height: 120px;
filter: drop-shadow(0 4px 6px rgba(255, 102, 0, 0.3));
}
.logo-text {
display: flex;
flex-direction: column;
align-items: flex-start;
line-height: 1;
}
.logo-tiny {
font-family: 'Verdana', sans-serif;
font-size: 2.5rem;
color: #ff8c00;
font-weight: bold;
letter-spacing: -1px;
}
.logo-torch {
font-family: 'Arial Black', 'Helvetica Inserat', sans-serif;
font-size: 5.5rem;
color: #003366;
text-transform: uppercase;
letter-spacing: -3px;
margin-top: -8px;
}
.subtitle {
font-family: 'Courier New', Courier, monospace;
font-size: 1.5rem;
color: #555;
letter-spacing: 2px;
margin-top: 15px;
background: rgba(255, 255, 255, 0.6);
padding: 8px 20px;
border-radius: 30px;
backdrop-filter: blur(2px);
}
/* --- Hamburger Menu, Create Account, Sidebar Styles moved to layout.js --- */
/* --- Markers --- */
#markers-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 5;
overflow: hidden;
}
.marker {
position: absolute;
transform: translate(-50%, -100%);
text-align: center;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: auto;
}
.marker-content {
background: rgba(255, 255, 255, 0.95);
padding: 8px 12px;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
border: 1px solid rgba(255, 102, 0, 0.2);
display: flex;
flex-direction: column;
align-items: center;
}
.marker-year {
font-size: 0.8rem;
font-weight: bold;
color: #ff6600;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 2px;
}
.marker-label {
font-size: 1.1rem;
font-weight: 800;
color: #222;
white-space: nowrap;
margin-bottom: 2px;
}
.marker-author {
font-size: 0.75rem;
color: #666;
font-style: italic;
max-width: 200px;
white-space: normal;
line-height: 1.1;
}
.marker.past .marker-content {
background: rgba(240, 240, 240, 0.85);
border-color: #ddd;
}
.marker.past .marker-year {
color: #999;
}
.marker.past .marker-label {
color: #aaa;
}
.marker.past .marker-author {
color: #bbb;
}
/* --- Generic Card Styles --- */
.feed-card {
position: fixed;
width: 300px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border: 1px solid rgba(0,0,0,0.1);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
z-index: 100;
transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Content Styling */
.card-header {
padding: 12px 15px;
font-family: 'Verdana', sans-serif;
font-weight: bold;
font-size: 0.9rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.feed-list {
padding: 0;
margin: 0;
list-style: none;
max-height: 300px;
overflow-y: auto;
}
.feed-item {
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
font-family: 'Verdana', sans-serif;
font-size: 0.8rem;
display: flex;
justify-content: space-between;
align-items: center;
background: white;
line-height: 1.4;
}
.feed-item:last-child {
border-bottom: none;
}
/* --- 1. The Journey Card (Right) --- */
#process-card {
top: 45vh;
right: 30px;
left: auto; /* Ensure no conflict */
transform: translateX(150%);
}
#process-card.visible {
transform: translateX(0);
}
#process-card .card-header {
background: transparent;
color: #333;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
#process-card .feed-item {
justify-content: flex-start;
gap: 15px;
min-height: 50px;
opacity: 1;
transform: translateX(0);
transition: all 0.3s ease;
border-left: 4px solid transparent;
}
#process-card .feed-item.active-step {
background: #f0f4f8;
box-shadow: 0 4px 15px rgba(0,0,0,0.15);
transform: scale(1.03) translateX(5px);
border-left: 4px solid #2a323a;
color: #2a323a;
font-weight: bold;
z-index: 10;
}
#process-card .feed-item::before {
content: '';
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid #ccc;
flex-shrink: 0;
box-sizing: border-box;
transition: all 0.3s ease;
}
#process-card .feed-item.active-step::before {
border-color: #ff6600;
border-top-color: transparent;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* --- 2. News Feed Card (Left) --- */
#news-card {
top: 30vh;
left: 30px;
right: auto;
transform: translateX(-150%);
}
#news-card.visible {
transform: translateX(0);
}
#news-card .card-header {
background: #dcedc8;
color: #33691e;
border-bottom: 1px solid #c5e1a5;
}
/* --- 3. Submission Feed Card (Left - Appears after News) --- */
#submission-card {
display: flex;
top: 30vh; /* Same vertical position as News for continuity */
left: 30px;
right: auto;
transform: translateX(-150%);
}
#submission-card.visible {
transform: translateX(0);
}
#submission-card .card-header {
background: #dcedc8; /* Same Green as News */
color: #33691e;
border-bottom: 1px solid #c5e1a5;
}
/* Submission Specifics */
.sub-user { font-weight: bold; color: #003366; }
.sub-module { color: #666; font-style: italic; text-align: right; max-width: 120px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* News Specifics */
.news-link {
text-decoration: none;
color: #333;
display: flex;
flex-direction: column;
width: 100%;
}
.news-link:hover .news-title { color: #33691e; text-decoration: underline; }
.news-date { font-size: 0.7em; color: #999; margin-bottom: 2px; text-transform: uppercase; }
.news-title { font-weight: bold; font-size: 0.85rem; line-height: 1.2; }
.ui-overlay {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
z-index: 10;
color: #333;
text-align: center;
background: rgba(255, 255, 255, 0.85);
padding: 10px 25px;
border-radius: 30px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
pointer-events: none;
backdrop-filter: blur(5px);
font-weight: bold;
border: 1px solid rgba(0,0,0,0.05);
}
</style>
<!-- Load Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>
<!-- Layout elements (Menu, Sidebar, Login) are injected by layout.js -->
<div id="hero-title">
<div class="logo-container">
<svg class="fire-icon" viewBox="0 0 100 130" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="fireGrad" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" style="stop-color:#b30000;stop-opacity:1" />
<stop offset="40%" style="stop-color:#ff4500;stop-opacity:1" />
<stop offset="100%" style="stop-color:#ffcc00;stop-opacity:1" />
</linearGradient>
<mask id="flameHole">
<rect x="0" y="0" width="100" height="130" fill="white" />
<ellipse cx="50" cy="80" rx="15" ry="20" fill="black" />
</mask>
</defs>
<path d="M50 125 C20 125 5 95 5 65 C5 35 30 5 50 0 C70 5 95 35 95 65 C95 95 80 125 50 125 Z"
fill="url(#fireGrad)"
mask="url(#flameHole)" />
</svg>
<div class="logo-text">
<span class="logo-tiny">tiny</span>
<span class="logo-torch">TORCH</span>
</div>
</div>
<div class="subtitle">Builder Community</div>
</div>
<!-- 1. The Journey/Process Card -->
<div id="process-card" class="feed-card">
<div class="card-header">
<span>The Journey</span>
<span style="font-size:0.7em; opacity:0.8;">GUIDE</span>
</div>
<ul class="feed-list process-list">
<li class="feed-item">Create Account & Track Progress</li>
<li class="feed-item">Clone & Run TinyTorch Repo</li>
<li class="feed-item">Build AI Infrastructure (Mimic PyTorch)</li>
<li class="feed-item">Connect with Global Builders</li>
<li class="feed-item">Join Hackathons & Earn Badges</li>
<li class="feed-item">Innovate in the AI Space</li>
</ul>
</div>
<!-- 2. News Feed Card (Left) -->
<div id="news-card" class="feed-card">
<div class="card-header">
<span>Recent News</span>
<span style="font-size:0.7em; opacity:0.8;">UPDATES</span>
</div>
<ul class="feed-list" id="news-list">
<!-- Fake News Items -->
</ul>
</div>
<!-- 3. Submission Feed Card (Left - After News) -->
<div id="submission-card" class="feed-card">
<div class="card-header">
<span>Recent Submissions</span>
<span style="font-size:0.7em; opacity:0.8;">LIVE</span>
</div>
<ul class="feed-list" id="submission-list">
<!-- Items injected by JS -->
</ul>
</div>
<div id="canvas-container"></div>
<div id="scroll-track"></div>
<div id="markers-container"></div>
<div class="ui-overlay">
<span>Scroll to traverse the History of AI</span>
</div>
<!-- Sky Shader Vertex -->
<script id="sky-vs" type="x-shader/x-vertex">
varying vec2 vUv;
varying vec3 vWorldPosition;
void main() {
vUv = uv;
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
vWorldPosition = worldPosition.xyz;
gl_Position = projectionMatrix * viewMatrix * worldPosition;
}
</script>
<!-- Sky Shader Fragment -->
<script id="sky-fs" type="x-shader/x-fragment">
uniform float uTime;
uniform vec3 uColor;
uniform float uCloudDramatic;
varying vec2 vUv;
varying vec3 vWorldPosition;
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; }
float snoise(vec3 v){
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - D.yyy;
i = mod289(i);
vec4 p = permute( permute( permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
float n_ = 0.142857142857;
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ );
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
vec3 p0 = vec3(a0.xy,h.x);
vec3 p1 = vec3(a0.zw,h.y);
vec3 p2 = vec3(a1.xy,h.z);
vec3 p3 = vec3(a1.zw,h.w);
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
vec4 m = max(0.5 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 105.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
dot(p2,x2), dot(p3,x3) ) );
}
float fbm(vec3 p) {
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < 5; i++) {
value += amplitude * snoise(p);
p *= 2.0;
amplitude *= 0.5;
}
return value;
}
void main() {
vec3 pos = normalize(vWorldPosition);
vec3 motion = vec3(uTime * 0.02, 0.0, 0.0);
vec3 warp = vec3(
fbm(pos * 2.0 + vec3(uTime * 0.05)),
fbm(pos * 2.0 + vec3(uTime * 0.05 + 10.0)),
fbm(pos * 2.0 + vec3(uTime * 0.05 + 20.0))
);
float n = fbm(pos * 3.0 + motion + warp * 0.5);
float cloudVal = (n + 1.0) * 0.5;
float dramFactor = (1.0 - uCloudDramatic) * 0.2;
cloudVal = smoothstep(0.45 - dramFactor, 0.65 + dramFactor, cloudVal);
vec3 baseColor = uColor;
float intensity = uCloudDramatic * uCloudDramatic;
vec3 cloudHighlight = baseColor + vec3(0.2, 0.2, 0.25) * intensity;
cloudHighlight = min(cloudHighlight, vec3(1.0));
vec3 finalColor = mix(baseColor, cloudHighlight, cloudVal);
gl_FragColor = vec4(finalColor, 1.0);
}
</script>
<script src="layout.js"></script>
<script>
// --- Configuration & Data ---
const MILESTONES = [
{ year: 1958, name: "Perceptron", authors: "Frank Rosenblatt", z: -20 },
{ year: 1969, name: "XOR Problem", authors: "Minsky & Papert", z: -50 },
{ year: 1986, name: "Backprop / MLP", authors: "Rumelhart, Hinton, Williams", z: -90 },
{ year: 1997, name: "Deep Blue", authors: "Feng-hsiung Hsu, Campbell", z: -130 },
{ year: 1997, name: "LSTM", authors: "Hochreiter & Schmidhuber", z: -140 },
{ year: 1998, name: "LeNet (CNN)", authors: "Yann LeCun et al.", z: -160 },
{ year: 2012, name: "AlexNet", authors: "Krizhevsky, Sutskever, Hinton", z: -200 },
{ year: 2013, name: "DQN (Deep RL)", authors: "Mnih et al. (DeepMind)", z: -215 },
{ year: 2014, name: "GANs", authors: "Ian Goodfellow et al.", z: -230 },
{ year: 2015, name: "ResNet", authors: "Kaiming He et al.", z: -245 },
{ year: 2016, name: "AlphaGo", authors: "Silver, Hassabis (DeepMind)", z: -260 },
{ year: 2017, name: "Transformer", authors: "Vaswani, Shazeer et al.", z: -280 },
{ year: 2018, name: "BERT", authors: "Devlin et al. (Google)", z: -295 },
{ year: 2018, name: "GPT-1", authors: "Radford et al. (OpenAI)", z: -310 },
{ year: 2020, name: "GPT-3", authors: "Brown et al. (OpenAI)", z: -330 },
{ year: 2022, name: "ChatGPT", authors: "OpenAI", z: -360 },
{ year: 2022, name: "Stable Diffusion", authors: "Rombach et al. (CompVis)", z: -375 },
{ year: 2023, name: "Mistral 7B", authors: "Mistral AI", z: -400 },
{ year: 2023, name: "I-JEPA (World Model)", authors: "LeCun et al. (Meta)", z: -420 },
{ year: 2023, name: "Phi-2", authors: "Microsoft Research", z: -440 },
{ year: 2024, name: "Llama 3", authors: "Meta AI", z: -460 },
{ year: 2024, name: "DeepSeek-V2", authors: "DeepSeek AI", z: -480 }
];
const Noise = (function() {
var p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,
68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,
186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,
178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,
214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,
195,78,66,215,61,156,180];
for (var i=0; i < 256 ; i++) p[256+i] = p[i];
function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
function lerp(t, a, b) { return a + t * (b - a); }
function grad(hash, x, y, z) {
var h = hash & 15;
var u = h<8 ? x : y, v = h<4 ? y : h==12||h==14 ? x : z;
return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
}
return {
perlin2: function(x, y) {
var X = Math.floor(x) & 255, Y = Math.floor(y) & 255;
x -= Math.floor(x); y -= Math.floor(y);
var u = fade(x), v = fade(y);
var A = p[X]+Y, AA = p[A], AB = p[A+1], B = p[X+1]+Y, BA = p[B], BB = p[B+1];
return lerp(v, lerp(u, grad(p[AA], x, y, 0), grad(p[BA], x-1, y, 0)),
lerp(u, grad(p[AB], x, y-1, 0), grad(p[BB], x-1, y-1, 0)));
}
};
})();
// --- Scene Setup ---
const container = document.getElementById('canvas-container');
const scene = new THREE.Scene();
// Updated background color (Brighter)
const initialBgColor = new THREE.Color(0xf0f4f8);
// FOG Adjustment
scene.fog = new THREE.Fog(initialBgColor, 20, 90);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 150);
camera.position.set(0, 6, 12);
camera.rotation.x = -Math.PI / 6;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
container.appendChild(renderer.domElement);
// --- Sky Mesh Setup ---
const skyGeo = new THREE.SphereGeometry(100, 64, 64);
const skyMat = new THREE.ShaderMaterial({
vertexShader: document.getElementById('sky-vs').textContent,
fragmentShader: document.getElementById('sky-fs').textContent,
uniforms: {
uTime: { value: 0 },
// Updated background color (Brighter)
uColor: { value: new THREE.Color(0xf0f4f8) },
uCloudDramatic: { value: 1.0 } // Default to dramatic
},
side: THREE.BackSide,
fog: false
});
const skyMesh = new THREE.Mesh(skyGeo, skyMat);
scene.add(skyMesh);
// --- Terrain ---
const width = 60;
const depth = 80;
const widthSegments = 80;
const depthSegments = 100;
const geometry = new THREE.PlaneGeometry(width, depth, widthSegments, depthSegments);
geometry.rotateX(-Math.PI / 2);
const count = geometry.attributes.position.count;
geometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3));
const material = new THREE.MeshBasicMaterial({
vertexColors: true,
wireframe: true,
transparent: true,
opacity: 0.4
});
const terrain = new THREE.Mesh(geometry, material);
scene.add(terrain);
// --- Solid Terrain Background (The Fix) ---
// Instead of a flat backdrop plane (which creates a horizon line),
// we create a solid mesh that shares the SAME geometry as the wireframe.
// This makes the hills opaque, blocking the sky behind them naturally.
const solidMat = new THREE.MeshBasicMaterial({
color: 0xf5f5f0, // Initial color
polygonOffset: true,
polygonOffsetFactor: 1, // Push it back slightly so wireframe shows on top
polygonOffsetUnits: 1
});
const terrainSolid = new THREE.Mesh(geometry, solidMat);
scene.add(terrainSolid);
// --- Timeline Path ---
const historyLineGeo = new THREE.BufferGeometry();
const futureLineGeo = new THREE.BufferGeometry();
const linePoints = 400;
historyLineGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(linePoints * 3), 3));
historyLineGeo.setAttribute('color', new THREE.BufferAttribute(new Float32Array(linePoints * 3), 3));
futureLineGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(linePoints * 3), 3));
const historyMat = new THREE.LineBasicMaterial({ vertexColors: true, linewidth: 4 });
const futureMat = new THREE.LineDashedMaterial({
color: 0xff4500, dashSize: 0.5, gapSize: 0.3, scale: 1, linewidth: 2, opacity: 0.6, transparent: true
});
const historyLine = new THREE.Line(historyLineGeo, historyMat);
const futureLine = new THREE.Line(futureLineGeo, futureMat);
futureLine.computeLineDistances();
const glowGeo = new THREE.BufferGeometry();
glowGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(linePoints * 3), 3));
const glowMat = new THREE.PointsMaterial({
color: 0xff6600, size: 0.15, transparent: true, opacity: 0.8
});
const glowPoints = new THREE.Points(glowGeo, glowMat);
scene.add(glowPoints);
scene.add(historyLine);
scene.add(futureLine);
// --- Markers ---
const markersContainer = document.getElementById('markers-container');
const markerElements = [];
MILESTONES.forEach(m => {
const div = document.createElement('div');
div.className = 'marker';
div.innerHTML = `
<div class="marker-content">
<div class="marker-year">${m.year}</div>
<div class="marker-label">${m.name}</div>
<div class="marker-author">${m.authors}</div>
</div>
`;
markersContainer.appendChild(div);
markerElements.push({ data: m, element: div });
});
// --- Logic & Animation ---
const noiseScale = 0.15;
const heightScale = 4.0;
const positionAttribute = geometry.attributes.position;
const colorAttribute = geometry.attributes.color;
const originalPositions = positionAttribute.array.slice();
const colorWater = new THREE.Color(0x3498db);
const colorSand = new THREE.Color(0xf1c40f);
const colorGrass = new THREE.Color(0x2ecc71);
const colorRock = new THREE.Color(0xe74c3c);
const colorTimelineOrange = new THREE.Color(0xff4500);
const colorTimelineGray = new THREE.Color(0x888888);
let flyOverY = 0;
let targetFlyOverY = 0;
const heroTitle = document.getElementById('hero-title');
// Sky Cycle Colors (Updated for brighter start)
const skyColors = [
{ pos: 0, color: new THREE.Color(0xf0f4f8), dramatic: 1.0 }, // Morning (Bright, dramatic)
{ pos: 150, color: new THREE.Color(0x8a929a), dramatic: 0.8 }, // Noon
{ pos: 300, color: new THREE.Color(0x5a626a), dramatic: 0.3 }, // Dusk
{ pos: 450, color: new THREE.Color(0x2a323a), dramatic: 0.1 }, // Night (Calm)
{ pos: 550, color: new THREE.Color(0x5a626a), dramatic: 0.3 }, // Dawn
{ pos: 700, color: new THREE.Color(0xf0f4f8), dramatic: 1.0 } // Day (Bright, dramatic)
];
function getSkyState(y) {
const cycle = 700;
const modY = y % cycle;
for(let i=0; i<skyColors.length-1; i++) {
const curr = skyColors[i];
const next = skyColors[i+1];
if (modY >= curr.pos && modY < next.pos) {
const t = (modY - curr.pos) / (next.pos - curr.pos);
return {
color: curr.color.clone().lerp(next.color, t),
dramatic: lerp(t, curr.dramatic, next.dramatic)
};
}
}
return { color: skyColors[0].color, dramatic: skyColors[0].dramatic };
}
function lerp(t, a, b) { return a + t * (b - a); }
function getPathX(z) {
return Math.sin(z * 0.1) * 8 + Math.cos(z * 0.03) * 12;
}
function getColorForHeight(h) {
let finalColor = new THREE.Color();
if (h < -0.2) finalColor.copy(colorWater);
else if (h < 0.1) finalColor.lerpColors(colorWater, colorSand, (h + 0.2) / 0.3);
else if (h < 0.6) finalColor.lerpColors(colorSand, colorGrass, (h - 0.1) / 0.5);
else finalColor.lerpColors(colorGrass, colorRock, Math.min((h - 0.6) / 0.9, 1));
return finalColor;
}
function updateScene() {
flyOverY += (targetFlyOverY - flyOverY) * 0.08;
const skyState = getSkyState(flyOverY);
// Sync Fog, Sky Mesh
scene.fog.color.copy(skyState.color);
skyMesh.material.uniforms.uColor.value.copy(skyState.color);
skyMesh.material.uniforms.uCloudDramatic.value = skyState.dramatic;
// Slower time for cloud movement
skyMesh.material.uniforms.uTime.value += 0.01;
// Sync solid terrain color so the ground darkens with the sky cycle
terrainSolid.material.color.copy(skyState.color);
for (let i = 0; i < count; i++) {
const ix = i * 3;
const iz = i * 3 + 2;
const x = originalPositions[ix];
const z = originalPositions[iz];
const vZ = z - flyOverY;
const noiseVal = Noise.perlin2(x * noiseScale, vZ * noiseScale);
let height = noiseVal * heightScale;
if (height < -1) height = -1;
positionAttribute.setY(i, height);
const c = getColorForHeight(noiseVal);
c.multiplyScalar(0.9);
colorAttribute.setXYZ(i, c.r, c.g, c.b);
}
positionAttribute.needsUpdate = true;
colorAttribute.needsUpdate = true;
const lineStartZ = 10;
const lineEndZ = -70;
const step = (lineStartZ - lineEndZ) / linePoints;
const histPos = historyLine.geometry.attributes.position;
const histCol = historyLine.geometry.attributes.color;
const futPos = futureLine.geometry.attributes.position;
const glowPos = glowPoints.geometry.attributes.position;
const presentZ = MILESTONES[MILESTONES.length - 1].z;
for (let i = 0; i < linePoints; i++) {
const worldZ = lineStartZ - (i * step);
const virtualZ = worldZ - flyOverY;
const pX = getPathX(virtualZ);
const noiseVal = Noise.perlin2(pX * noiseScale, virtualZ * noiseScale);
const pY = Math.max(noiseVal * heightScale, -1) + 0.5;
glowPos.setXYZ(i, pX, pY, worldZ);
if (virtualZ > presentZ) {
histPos.setXYZ(i, pX, pY, worldZ);
futPos.setXYZ(i, 0, 0, 99999);
if (worldZ > 4) {
histCol.setXYZ(i, colorTimelineGray.r, colorTimelineGray.g, colorTimelineGray.b);
} else {
histCol.setXYZ(i, colorTimelineOrange.r, colorTimelineOrange.g, colorTimelineOrange.b);
}
} else {
histPos.setXYZ(i, 0, 0, 99999);
futPos.setXYZ(i, pX, pY, worldZ);
}
}
histPos.needsUpdate = true;
histCol.needsUpdate = true;
futPos.needsUpdate = true;
glowPos.needsUpdate = true;
futureLine.computeLineDistances();
const tempV = new THREE.Vector3();
markerElements.forEach(item => {
const m = item.data;
const el = item.element;
const wZ = m.z + flyOverY;
const vZ = m.z;
if (wZ > -60 && wZ < 20) {
const wX = getPathX(vZ);
const noiseVal = Noise.perlin2(wX * noiseScale, vZ * noiseScale);
const wY = Math.max(noiseVal * heightScale, -1) + 2.5;
tempV.set(wX, wY, wZ);
tempV.project(camera);
const x = (tempV.x * .5 + .5) * window.innerWidth;
const y = (-(tempV.y * .5) + .5) * window.innerHeight;
if (wZ > 5) el.classList.add('past');
else el.classList.remove('past');
const dist = Math.abs(wZ);
let opacity = 1;
if (wZ > 12) opacity = 0;
else if (wZ < -40) opacity = Math.max(0, 1 - (Math.abs(wZ) - 40) / 10);
el.style.opacity = opacity;
el.style.left = `${x}px`;
el.style.top = `${y}px`;
} else {
el.style.opacity = 0;
}
});
}
function animate() {
requestAnimationFrame(animate);
updateScene();
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
window.addEventListener('scroll', () => {
const scrollY = window.scrollY;
targetFlyOverY = scrollY * 0.25;
const startFade = 100;
const endFade = 600;
let opacity = 1;
if (scrollY > startFade) {
opacity = 1 - (scrollY - startFade) / (endFade - startFade);
if (opacity < 0) opacity = 0;
}
heroTitle.style.opacity = opacity;
if (opacity > 0) {
const lift = Math.min(scrollY * 0.2, 100);
heroTitle.style.transform = `translate(-50%, calc(-50% - ${lift}px))`;
}
// Show card logic
const processCard = document.getElementById('process-card');
const submissionCard = document.getElementById('submission-card');
const newsCard = document.getElementById('news-card');
// Process: Show 50px - 700px
if (scrollY > 50 && scrollY < 700) {
processCard.classList.add('visible');
// Highlight Logic
const start = 50;
const end = 700;
const range = end - start;
const progress = (scrollY - start) / range;
const items = processCard.querySelectorAll('.feed-item');
const activeIndex = Math.floor(progress * items.length);
items.forEach((item, index) => {
if (index === activeIndex) {
item.classList.add('active-step');
} else {
item.classList.remove('active-step');
}
});
} else {
processCard.classList.remove('visible');
}
// News: Show 700px - 1500px (Immediate after Process, Shortened Duration)
if (scrollY >= 700 && scrollY < 1500) {
newsCard.classList.add('visible');
} else {
newsCard.classList.remove('visible');
}
// Submissions: Show 1500px - 3500px (Immediate after News)
if (scrollY >= 1500 && scrollY < 3500) {
submissionCard.classList.add('visible');
} else {
submissionCard.classList.remove('visible');
}
});
animate();
// --- Feed Logic ---
const cardList = document.getElementById('submission-list');
const newsList = document.getElementById('news-list');
let lastSubmissionData = null;
function addSubmissionRow(user, mod) {
const li = document.createElement('li');
li.className = 'feed-item';
li.innerHTML = `
<span class="sub-user">${user}</span>
<span class="sub-module">${mod}</span>
`;
cardList.appendChild(li);
}
// --- News Logic (Tiny Torch Specific) ---
const NEWS_ITEMS = [
{ date: "Dec 15", title: "New Module: Diffusion Models", link: "#" },
{ date: "Nov 30", title: "Tiny Torch Community Meetup", link: "#" },
{ date: "Nov 20", title: "Edge AI Foundation Course Live", link: "#" },
{ date: "Nov 10", title: "Guest Lecture: Andrej Karpathy", link: "#" }
];
function populateNews() {
newsList.innerHTML = '';
NEWS_ITEMS.forEach(item => {
const li = document.createElement('li');
li.className = 'feed-item';
li.innerHTML = `
<a href="${item.link}" target="_blank" class="news-link">
<span class="news-date">${item.date}</span>
<span class="news-title">${item.title}</span>
</a>
`;
newsList.appendChild(li);
});
}
populateNews();
// --- Real Data Integration ---
const API_URL = "https://zrvmjrxhokwwmjacyhpq.supabase.co/functions/v1/get_recent_submitted";
// Map DB IDs to UI Strings
const MODULE_MAP = {
"01": "01_tensor",
"02": "02_activations",
"03": "03_layers",
"04": "04_losses",
"05": "05_autograd",
"06": "06_optimizers",
"07": "07_training",
"08": "08_spatial",
"09": "09_dataloader",
"10": "10_tokenization",
"11": "11_embeddings",
"12": "12_attention",
"13": "13_transformers",
"14": "14_profiling",
"15": "15_acceleration",
"16": "16_quantization",
"17": "17_compression",
"18": "18_caching",
"19": "19_benchmarking",
"20": "20_capstone",
"21": "21_mlops"
};
async function fetchRealSubmissions() {
try {
// Fetch top 5 recent submissions
const response = await fetch(`${API_URL}?limit=5`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
processRealData(data);
} catch (error) {
console.error("Error fetching submissions:", error);
}
}
function processRealData(data) {
// Check if data changed to avoid redraws
const currentHash = JSON.stringify(data);
if (currentHash === lastSubmissionData) return;
lastSubmissionData = currentHash;
// Clear the existing list
cardList.innerHTML = '';
data.forEach(row => {
const username = row.username || "Anonymous";
// Get the last completed module ID (most recent)
const lastId = row.completed_ids && row.completed_ids.length > 0
? row.completed_ids[row.completed_ids.length - 1]
: 1;
// Format ID to match map keys (e.g. 1 -> "01")
const idKey = String(lastId).padStart(2, '0');
// Lookup or fallback
const modName = MODULE_MAP[idKey] || `Module ${lastId}`;
addSubmissionRow(username, modName);
});
}
// Start Polling
fetchRealSubmissions(); // Initial fetch
setInterval(fetchRealSubmissions, 10000); // Poll every 10 seconds
</script>
</body>
</html>