mirror of
https://github.com/MLSysBook/TinyTorch.git
synced 2026-03-12 00:13:33 -05:00
1072 lines
40 KiB
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>
|