mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-05-04 16:48:48 -05:00
892 lines
32 KiB
HTML
892 lines
32 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.svg" type="image/svg+xml">
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow-x: hidden;
|
|
background-color: #eeeee6;
|
|
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;
|
|
}
|
|
|
|
.hero-logo {
|
|
height: 180px;
|
|
filter: drop-shadow(0 4px 6px rgba(255, 102, 0, 0.2));
|
|
}
|
|
|
|
.subtitle {
|
|
font-family: 'Courier New', Courier, monospace;
|
|
font-size: 1.75rem;
|
|
color: #555;
|
|
letter-spacing: 3px;
|
|
margin-top: 12px;
|
|
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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.feed-item a {
|
|
text-decoration: none;
|
|
color: inherit;
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
/* --- 1. The Journey Card (Right) --- */
|
|
#process-card {
|
|
top: 45vh;
|
|
right: 30px;
|
|
left: auto; /* Ensure no conflict */
|
|
transform: translateX(150%);
|
|
max-height: 50vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
#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 {
|
|
background-color: #ff6600; /* Orange core for the flame */
|
|
border-color: #ff9900;
|
|
animation: glow 1.5s ease-in-out infinite alternate;
|
|
}
|
|
|
|
@keyframes glow {
|
|
from {
|
|
box-shadow: 0 0 4px #ff9900, 0 0 8px #ff6600;
|
|
}
|
|
to {
|
|
box-shadow: 0 0 8px #ff9900, 0 0 16px #ff6600;
|
|
}
|
|
}
|
|
|
|
/* --- 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: transparent;
|
|
padding: 0;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
pointer-events: none;
|
|
backdrop-filter: none;
|
|
font-weight: bold;
|
|
border: none;
|
|
}
|
|
</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">
|
|
<img src="../_static/logos/logo-tinytorch-transparent.png" alt="TinyTorch" class="hero-logo">
|
|
<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"><a href="?action=join">Create Account & Track Progress</a></li>
|
|
<li class="feed-item"><a href="dashboard.html">View and interact with others during your TinyTorch journey</a></li>
|
|
<li class="feed-item"><a href="community.html">View the global TinyTorch community</a></li>
|
|
<li class="feed-item"><a href="?action=profile">Update Your Profile</a></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">
|
|
|
|
</ul>
|
|
</div>
|
|
-->
|
|
|
|
<!-- 3. Submission Feed Card (Left - Appears 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">
|
|
<div class="caret-up"></div>
|
|
<div style="font-size: 10px; letter-spacing: 2px; margin-top: 8px;">SCROLL</div>
|
|
</div>
|
|
<style>
|
|
.caret-up {
|
|
width: 24px;
|
|
height: 24px;
|
|
border-left: 4px solid #333;
|
|
border-top: 4px solid #333;
|
|
transform: rotate(45deg);
|
|
margin: 0 auto;
|
|
animation: bounce 2s infinite;
|
|
}
|
|
@keyframes bounce {
|
|
0%, 20%, 50%, 80%, 100% {transform: rotate(45deg) translate(0, 0);}
|
|
40% {transform: rotate(45deg) translate(-5px, -5px);}
|
|
60% {transform: rotate(45deg) translate(-3px, -3px);}
|
|
}
|
|
</style>
|
|
|
|
<script type="module" src="app.js"></script>
|
|
<script type="module">
|
|
import { ArxivService } from './arxiv_service.js';
|
|
import { DotismScene } from './modules/dotism.js';
|
|
|
|
// --- Configuration & Data ---
|
|
const arxiv = new ArxivService();
|
|
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 }
|
|
];
|
|
|
|
let flyOverY = 0;
|
|
let targetFlyOverY = 0;
|
|
const container = document.getElementById('canvas-container');
|
|
const scene = new THREE.Scene();
|
|
const initialBgColor = new THREE.Color(0xeeeee6);
|
|
scene.background = initialBgColor;
|
|
scene.fog = new THREE.Fog(initialBgColor, 20, 140);
|
|
|
|
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
camera.position.set(0, 5, 20);
|
|
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
container.appendChild(renderer.domElement);
|
|
|
|
// --- 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 });
|
|
});
|
|
|
|
// Initialize Dotism Scene
|
|
DotismScene.init({
|
|
scene,
|
|
camera,
|
|
renderer
|
|
});
|
|
|
|
const heroTitle = document.getElementById('hero-title');
|
|
|
|
// Sky Cycle Colors
|
|
const skyColors = [
|
|
{ pos: 0, color: new THREE.Color(0xeeeee6), dramatic: 1.0 },
|
|
{ pos: 150, color: new THREE.Color(0x8a929a), dramatic: 0.8 },
|
|
{ pos: 300, color: new THREE.Color(0x5a626a), dramatic: 0.3 },
|
|
{ pos: 450, color: new THREE.Color(0x2a323a), dramatic: 0.1 },
|
|
{ pos: 550, color: new THREE.Color(0x5a626a), dramatic: 0.3 },
|
|
{ pos: 700, color: new THREE.Color(0xeeeee6), dramatic: 1.0 }
|
|
];
|
|
|
|
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); }
|
|
|
|
// Initialize ArXiv
|
|
const MARKER_SCALE = 8;
|
|
let lastZ = MILESTONES[MILESTONES.length - 1].z;
|
|
const Z_INTERVAL = 30; // Spacing for new papers
|
|
let nextPaperZ = lastZ - Z_INTERVAL;
|
|
let spawningPapers = true;
|
|
|
|
function updateScene() {
|
|
flyOverY += (targetFlyOverY - flyOverY) * 0.08;
|
|
|
|
// Move camera through the dot landscape (matching dotism.html behavior)
|
|
camera.position.z = 20 - (flyOverY * 4);
|
|
camera.position.x = Math.sin(flyOverY * 0.2) * 2;
|
|
camera.position.y = 5 + Math.cos(flyOverY * 0.12) * 1;
|
|
camera.lookAt(0, 0, camera.position.z - 50);
|
|
|
|
// Ensure camera matrices are updated before projection in updateMarkers
|
|
camera.updateMatrixWorld();
|
|
|
|
// --- ArXiv Paper Spawning Logic ---
|
|
const cameraZ = camera.position.z;
|
|
const lastMilestoneWorldZ = lastZ * MARKER_SCALE;
|
|
|
|
if (spawningPapers && lastMilestoneWorldZ > cameraZ - 300) {
|
|
const paper = arxiv.papers.shift();
|
|
if (paper) {
|
|
const newM = {
|
|
year: paper.year,
|
|
name: paper.name,
|
|
authors: paper.authors,
|
|
z: nextPaperZ
|
|
};
|
|
MILESTONES.push(newM);
|
|
const div = document.createElement('div');
|
|
div.className = 'marker';
|
|
div.innerHTML = `
|
|
<div class="marker-content" style="border-color: #00ccff;">
|
|
<div class="marker-year" style="color: #00ccff;">LATEST RESEARCH</div>
|
|
<div class="marker-label" style="font-size: 0.9rem; white-space: normal; max-width: 250px;">${newM.name}</div>
|
|
<div class="marker-author">${newM.authors}</div>
|
|
</div>
|
|
`;
|
|
markersContainer.appendChild(div);
|
|
markerElements.push({ data: newM, element: div });
|
|
lastZ = nextPaperZ;
|
|
nextPaperZ -= Z_INTERVAL;
|
|
} else {
|
|
arxiv.getNextPaper();
|
|
}
|
|
}
|
|
|
|
const skyState = getSkyState(flyOverY);
|
|
scene.background.copy(skyState.color);
|
|
|
|
// Update Dotism Scene
|
|
DotismScene.update(skyState);
|
|
|
|
// Update marker positions for the dotism camera movement
|
|
updateMarkers();
|
|
}
|
|
|
|
function getPathX(z) {
|
|
return Math.sin(z * 0.1) * 8 + Math.cos(z * 0.03) * 12;
|
|
}
|
|
|
|
function updateMarkers() {
|
|
const tempV = new THREE.Vector3();
|
|
markerElements.forEach(item => {
|
|
const m = item.data;
|
|
const el = item.element;
|
|
|
|
// Markers are stationary in world space, scaled to match scroll
|
|
const worldZ = m.z * MARKER_SCALE;
|
|
|
|
const wX = getPathX(worldZ * 0.005); // Use lower frequency for path scaling
|
|
const simplex = DotismScene.getSimplex();
|
|
const h1 = simplex.noise2D(wX * 0.01, worldZ * 0.01) * 25;
|
|
const h2 = simplex.noise2D(wX * 0.03, worldZ * 0.03) * 8;
|
|
const baseHeight = h1 + h2 - 10;
|
|
const wY = baseHeight + 2.5;
|
|
|
|
tempV.set(wX, wY, worldZ);
|
|
tempV.project(camera);
|
|
|
|
const screenX = (tempV.x * 0.5 + 0.5) * window.innerWidth;
|
|
const screenY = (-tempV.y * 0.5 + 0.5) * window.innerHeight;
|
|
|
|
// Past check based on camera position
|
|
if (worldZ > camera.position.z) el.classList.add('past');
|
|
else el.classList.remove('past');
|
|
|
|
// Fade in as marker approaches, fade out as it passes behind
|
|
const viewDistance = 150;
|
|
const fadeInStart = camera.position.z - viewDistance;
|
|
const fadeInEnd = camera.position.z - 60;
|
|
const fadeOutStart = camera.position.z + 10;
|
|
const fadeOutEnd = camera.position.z + 40;
|
|
|
|
let opacity = 1;
|
|
if (worldZ > fadeOutStart) {
|
|
opacity = Math.max(0, 1 - (worldZ - fadeOutStart) / (fadeOutEnd - fadeOutStart));
|
|
} else if (worldZ < fadeInStart) {
|
|
opacity = Math.max(0, (worldZ - fadeInStart) / (fadeInEnd - fadeInStart));
|
|
}
|
|
|
|
// Global fade for hero section
|
|
let globalFade = (flyOverY * 4 - 150) / 50;
|
|
if (globalFade < 0) globalFade = 0;
|
|
if (globalFade > 1) globalFade = 1;
|
|
opacity *= globalFade;
|
|
|
|
el.style.opacity = opacity;
|
|
el.style.left = `${screenX}px`;
|
|
el.style.top = `${screenY}px`;
|
|
|
|
// Hide if too far behind or ahead in projection
|
|
if (tempV.z < -1 || tempV.z > 1) 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);
|
|
DotismScene.resize();
|
|
});
|
|
|
|
window.addEventListener('scroll', () => {
|
|
const scrollY = window.scrollY;
|
|
targetFlyOverY = scrollY * 0.25;
|
|
|
|
// Hero Fade Out
|
|
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))`;
|
|
}
|
|
|
|
// Timeline Fade In (Start after Hero starts fading, fully visible after Hero is gone)
|
|
const tlStart = 400;
|
|
const tlEnd = 900;
|
|
let tlOpacity = 0;
|
|
if (scrollY > tlStart) {
|
|
tlOpacity = (scrollY - tlStart) / (tlEnd - tlStart);
|
|
if (tlOpacity > 1) tlOpacity = 1;
|
|
}
|
|
// Update materials (timeline lines removed)
|
|
// historyMat.opacity = tlOpacity;
|
|
// futureMat.opacity = 0.6 * tlOpacity;
|
|
// glowMat.opacity = 0.8 * tlOpacity;
|
|
|
|
// 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 (processCard) {
|
|
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 (newsCard) {
|
|
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 17", title: "MLSYSBOOK 10k GitHub Stars Celebration via Edge AI Foundation", link: "#" },
|
|
{ date: "Dec 9", title: "Tiny Torch official launch", link: "#" },
|
|
{ date: "Dec 8", title: "Tiny Torch merged with MLSYSBOOK.ai", link: "#" },
|
|
{ date: "Dec 1", title: "Tiny Torch Community is born", link: "#" }
|
|
];
|
|
|
|
function populateNews() {
|
|
if (!newsList) return;
|
|
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 (must match src/ folder names)
|
|
const MODULE_MAP = {
|
|
"01": "01_tensor",
|
|
"02": "02_activations",
|
|
"03": "03_layers",
|
|
"04": "04_losses",
|
|
"05": "05_dataloader",
|
|
"06": "06_autograd",
|
|
"07": "07_optimizers",
|
|
"08": "08_training",
|
|
"09": "09_convolutions",
|
|
"10": "10_tokenization",
|
|
"11": "11_embeddings",
|
|
"12": "12_attention",
|
|
"13": "13_transformers",
|
|
"14": "14_profiling",
|
|
"15": "15_quantization",
|
|
"16": "16_compression",
|
|
"17": "17_acceleration",
|
|
"18": "18_memoization",
|
|
"19": "19_benchmarking",
|
|
"20": "20_capstone"
|
|
};
|
|
|
|
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>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log("Welcome to v1.2, Kai says HI!")
|
|
const processList = document.querySelector('#process-card .process-list');
|
|
if (processList) {
|
|
processList.addEventListener('click', (e) => {
|
|
const link = e.target.closest('a');
|
|
if (!link) return;
|
|
|
|
e.preventDefault();
|
|
const href = link.getAttribute('href');
|
|
if (!href || href === '#') return;
|
|
|
|
// Re-implement getBasePath logic from modules/config.js
|
|
let basePath = '';
|
|
const hostname = window.location.hostname;
|
|
if (hostname === 'mlsysbook.ai') {
|
|
basePath = '/tinytorch/community';
|
|
} else if (hostname === 'tinytorch.ai' || (hostname === 'localhost' && window.location.port === '8000')) {
|
|
basePath = '/community';
|
|
} else if (hostname === 'harvard-edge.github.io') {
|
|
basePath = '/cs249r_book_dev/tinytorch/community';
|
|
}
|
|
|
|
if (href.startsWith('?')) {
|
|
// It's a modal action, reload the page with the param
|
|
window.location.href = window.location.pathname + href;
|
|
} else {
|
|
// It's a page navigation
|
|
window.location.href = basePath ? `${basePath}/${href}` : href;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|