mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-04-30 01:29:07 -05:00
index.html comm site with arxiv
This commit is contained in:
86
tinytorch/site/extra/community/arxiv_service.js
Normal file
86
tinytorch/site/extra/community/arxiv_service.js
Normal file
@@ -0,0 +1,86 @@
|
||||
export class ArxivService {
|
||||
constructor() {
|
||||
this.baseUrl = "https://export.arxiv.org/api/query";
|
||||
this.category = "cat:cs.AI";
|
||||
this.maxResults = 10;
|
||||
this.papers = [];
|
||||
this.fetching = false;
|
||||
this.nextStart = 0;
|
||||
}
|
||||
|
||||
async fetchPapers() {
|
||||
if (this.fetching) return;
|
||||
this.fetching = true;
|
||||
|
||||
try {
|
||||
// Sort by submittedDate descending (newest first)
|
||||
// Use corsproxy.io to bypass CORS in browser environment
|
||||
const query = `https://corsproxy.io/?${encodeURIComponent(this.baseUrl + `?search_query=${this.category}&sortBy=submittedDate&sortOrder=descending&start=${this.nextStart}&max_results=${this.maxResults}`)}`;
|
||||
|
||||
const response = await fetch(query);
|
||||
const str = await response.text();
|
||||
|
||||
// Parse XML
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(str, "text/xml");
|
||||
const entries = xmlDoc.getElementsByTagName("entry");
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
const titleNode = entry.getElementsByTagName("title")[0];
|
||||
const publishedNode = entry.getElementsByTagName("published")[0];
|
||||
|
||||
if (!titleNode || !publishedNode) continue;
|
||||
|
||||
const title = titleNode.textContent.replace(/\n/g, ' ').trim();
|
||||
|
||||
// Authors
|
||||
const authorNodes = Array.from(entry.getElementsByTagName("author"));
|
||||
let authors = "Unknown";
|
||||
if (authorNodes.length > 0) {
|
||||
const names = authorNodes.map(a => {
|
||||
const n = a.getElementsByTagName("name")[0];
|
||||
return n ? n.textContent : "";
|
||||
}).filter(n => n);
|
||||
|
||||
if (names.length > 2) {
|
||||
authors = names.slice(0, 2).join(', ') + " et al.";
|
||||
} else {
|
||||
authors = names.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
const published = publishedNode.textContent;
|
||||
const year = new Date(published).getFullYear();
|
||||
|
||||
this.papers.push({
|
||||
year: year,
|
||||
name: title,
|
||||
authors: authors,
|
||||
isPaper: true,
|
||||
// Use a unique ID to avoid dupes if api acts up?
|
||||
id: entry.getElementsByTagName("id")[0]?.textContent
|
||||
});
|
||||
}
|
||||
|
||||
this.nextStart += this.maxResults;
|
||||
|
||||
} catch (e) {
|
||||
console.error("Arxiv fetch failed", e);
|
||||
} finally {
|
||||
this.fetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
async getNextPaper() {
|
||||
// Buffer management
|
||||
if (this.papers.length < 3) {
|
||||
this.fetchPapers();
|
||||
}
|
||||
|
||||
// Return null if empty (async wait loop handled by caller or just try next frame)
|
||||
if (this.papers.length === 0) return null;
|
||||
|
||||
return this.papers.shift();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
background-color: #f0f0f0;
|
||||
background-color: #eeeee6;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
@@ -323,14 +323,14 @@
|
||||
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);
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
pointer-events: none;
|
||||
backdrop-filter: blur(5px);
|
||||
backdrop-filter: none;
|
||||
font-weight: bold;
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
<!-- Load Three.js -->
|
||||
@@ -347,6 +347,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 1. The Journey/Process Card -->
|
||||
<!--
|
||||
<div id="process-card" class="feed-card">
|
||||
<div class="card-header">
|
||||
<span>The Journey</span>
|
||||
@@ -362,19 +363,22 @@
|
||||
<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) -->
|
||||
<!-- 3. Submission Feed Card (Left - Appears after News) -->
|
||||
<div id="submission-card" class="feed-card">
|
||||
<div class="card-header">
|
||||
<span>Recent Submissions</span>
|
||||
@@ -389,8 +393,25 @@
|
||||
<div id="scroll-track"></div>
|
||||
<div id="markers-container"></div>
|
||||
<div class="ui-overlay">
|
||||
<span>Scroll to traverse the History of AI</span>
|
||||
<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>
|
||||
|
||||
<!-- Sky Shader Vertex -->
|
||||
<script id="sky-vs" type="x-shader/x-vertex">
|
||||
@@ -406,99 +427,19 @@
|
||||
|
||||
<!-- 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);
|
||||
gl_FragColor = vec4(uColor, 1.0);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module" src="app.js"></script>
|
||||
<script>
|
||||
<script type="module">
|
||||
import { ArxivService } from './arxiv_service.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 },
|
||||
@@ -558,7 +499,7 @@
|
||||
const container = document.getElementById('canvas-container');
|
||||
const scene = new THREE.Scene();
|
||||
// Updated background color (Brighter)
|
||||
const initialBgColor = new THREE.Color(0xf0f4f8);
|
||||
const initialBgColor = new THREE.Color(0xeeeee6);
|
||||
|
||||
// FOG Adjustment
|
||||
scene.fog = new THREE.Fog(initialBgColor, 20, 90);
|
||||
@@ -580,7 +521,7 @@
|
||||
uniforms: {
|
||||
uTime: { value: 0 },
|
||||
// Updated background color (Brighter)
|
||||
uColor: { value: new THREE.Color(0xf0f4f8) },
|
||||
uColor: { value: new THREE.Color(0xeeeee6) },
|
||||
uCloudDramatic: { value: 1.0 } // Default to dramatic
|
||||
},
|
||||
side: THREE.BackSide,
|
||||
@@ -590,10 +531,10 @@
|
||||
scene.add(skyMesh);
|
||||
|
||||
// --- Terrain ---
|
||||
const width = 60;
|
||||
const width = 50;
|
||||
const depth = 80;
|
||||
const widthSegments = 80;
|
||||
const depthSegments = 100;
|
||||
const depthSegments = 120; // Increased density slightly to help with flickering
|
||||
|
||||
const geometry = new THREE.PlaneGeometry(width, depth, widthSegments, depthSegments);
|
||||
geometry.rotateX(-Math.PI / 2);
|
||||
@@ -632,9 +573,10 @@
|
||||
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 });
|
||||
// Start invisible (opacity 0) for scroll-triggered fade-in
|
||||
const historyMat = new THREE.LineBasicMaterial({ vertexColors: true, linewidth: 4, transparent: true, opacity: 0 });
|
||||
const futureMat = new THREE.LineDashedMaterial({
|
||||
color: 0xff4500, dashSize: 0.5, gapSize: 0.3, scale: 1, linewidth: 2, opacity: 0.6, transparent: true
|
||||
color: 0xff4500, dashSize: 0.5, gapSize: 0.3, scale: 1, linewidth: 2, opacity: 0, transparent: true
|
||||
});
|
||||
|
||||
const historyLine = new THREE.Line(historyLineGeo, historyMat);
|
||||
@@ -644,12 +586,12 @@
|
||||
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
|
||||
color: 0xff6600, size: 0.15, transparent: true, opacity: 0
|
||||
});
|
||||
const glowPoints = new THREE.Points(glowGeo, glowMat);
|
||||
scene.add(glowPoints);
|
||||
scene.add(historyLine);
|
||||
scene.add(futureLine);
|
||||
// scene.add(historyLine);
|
||||
// scene.add(futureLine);
|
||||
|
||||
// --- Markers ---
|
||||
const markersContainer = document.getElementById('markers-container');
|
||||
@@ -690,12 +632,12 @@
|
||||
|
||||
// Sky Cycle Colors (Updated for brighter start)
|
||||
const skyColors = [
|
||||
{ pos: 0, color: new THREE.Color(0xf0f4f8), dramatic: 1.0 }, // Morning (Bright, dramatic)
|
||||
{ pos: 0, color: new THREE.Color(0xeeeee6), 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)
|
||||
{ pos: 700, color: new THREE.Color(0xeeeee6), dramatic: 1.0 } // Day (Bright, dramatic)
|
||||
];
|
||||
|
||||
function getSkyState(y) {
|
||||
@@ -730,9 +672,62 @@
|
||||
return finalColor;
|
||||
}
|
||||
|
||||
// Initialize ArXiv
|
||||
let lastZ = MILESTONES[MILESTONES.length - 1].z;
|
||||
const Z_INTERVAL = 20; // Distance between papers
|
||||
let nextPaperZ = lastZ - Z_INTERVAL;
|
||||
let spawningPapers = true;
|
||||
|
||||
function updateScene() {
|
||||
flyOverY += (targetFlyOverY - flyOverY) * 0.08;
|
||||
|
||||
// --- ArXiv Paper Spawning Logic ---
|
||||
// As we scroll down (flyOverY increases), negative Z milestones come closer to 0.
|
||||
// When we get close to the "end" of the current list, spawn more.
|
||||
// Check the last milestone's world Z position.
|
||||
// wZ = lastZ + flyOverY. If wZ > -300 (it's getting closer), spawn next.
|
||||
|
||||
const lastMilestoneWorldZ = lastZ + flyOverY;
|
||||
if (spawningPapers && lastMilestoneWorldZ > -500) {
|
||||
// We are within range, try to get a paper
|
||||
// We can't await in animation loop easily, so we poll
|
||||
const paper = arxiv.papers.shift(); // Synchronous grab from buffer
|
||||
|
||||
if (paper) {
|
||||
// Create marker data
|
||||
const newM = {
|
||||
year: paper.year,
|
||||
name: paper.name,
|
||||
authors: paper.authors,
|
||||
z: nextPaperZ
|
||||
};
|
||||
|
||||
// Add to data structures
|
||||
MILESTONES.push(newM);
|
||||
|
||||
// Create DOM Element
|
||||
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 });
|
||||
|
||||
// Update indices
|
||||
lastZ = nextPaperZ;
|
||||
nextPaperZ -= Z_INTERVAL;
|
||||
} else {
|
||||
// Trigger fetch if empty (service handles dedup/busy state)
|
||||
arxiv.getNextPaper();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const skyState = getSkyState(flyOverY);
|
||||
|
||||
// Sync Fog, Sky Mesh
|
||||
@@ -744,6 +739,23 @@
|
||||
|
||||
// Sync solid terrain color so the ground darkens with the sky cycle
|
||||
terrainSolid.material.color.copy(skyState.color);
|
||||
|
||||
const Y_OFFSET = -12.0;
|
||||
|
||||
// Palette for patches (Vibrant, Brighter Variety)
|
||||
if (!window.randomPalette) {
|
||||
const hueBase = Math.random();
|
||||
window.randomPalette = [];
|
||||
for(let i=0; i<8; i++) {
|
||||
const color = new THREE.Color();
|
||||
const h = (hueBase + (Math.random() * 0.5 - 0.25) + i*0.1) % 1.0;
|
||||
const s = 0.8 + Math.random() * 0.2;
|
||||
const l = 0.5 + Math.random() * 0.3;
|
||||
color.setHSL(h, s, l);
|
||||
window.randomPalette.push(color);
|
||||
}
|
||||
}
|
||||
const palette = window.randomPalette;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const ix = i * 3;
|
||||
@@ -751,12 +763,80 @@
|
||||
const x = originalPositions[ix];
|
||||
const z = originalPositions[iz];
|
||||
const vZ = z - flyOverY;
|
||||
|
||||
// --- Geometry ---
|
||||
const noiseVal = Noise.perlin2(x * noiseScale, vZ * noiseScale);
|
||||
let height = noiseVal * heightScale;
|
||||
if (height < -1) height = -1;
|
||||
const jaggedNoise = Noise.perlin2(x * noiseScale * 4.5, vZ * noiseScale * 4.5);
|
||||
|
||||
// Mountain Layer: Compound noise for variety
|
||||
// Base low-freq shape
|
||||
const mNoise1 = Noise.perlin2(x * 0.03, vZ * 0.03);
|
||||
// Secondary shape to break symmetry
|
||||
const mNoise2 = Noise.perlin2(x * 0.1 + 123.4, vZ * 0.1 + 567.8);
|
||||
|
||||
// Combine and threshold
|
||||
let mountainShape = Math.max(0, mNoise1 + mNoise2 * 0.4 - 0.45);
|
||||
|
||||
// Variable sharpness/height per peak (using low freq noise to map regions)
|
||||
const regionVar = Noise.perlin2(x * 0.01, vZ * 0.01);
|
||||
const peakHeight = 35.0 + regionVar * 15.0; // 20 to 50 height
|
||||
const peakSharpness = 2.0 + (mNoise2 * 0.5); // Vary exponent
|
||||
|
||||
// Reduced exponent from 2.5 to 1.7 to avoid "icicle" needles
|
||||
mountainShape = Math.pow(mountainShape, 1.7) * peakHeight;
|
||||
|
||||
// Add jaggedness SPECIFICALLY to mountains (rocky texture)
|
||||
if (mountainShape > 0.5) {
|
||||
mountainShape += jaggedNoise * 1.5;
|
||||
}
|
||||
|
||||
// Jagged peaks only on higher ground
|
||||
const jaggedFactor = Math.max(0, noiseVal - 0.2);
|
||||
const finalHeightVal = noiseVal + (jaggedFactor * jaggedNoise * 0.8) + mountainShape;
|
||||
|
||||
let height = (finalHeightVal * heightScale) + Y_OFFSET;
|
||||
// Clamp bottom
|
||||
if (height < -1 + Y_OFFSET) height = -1 + Y_OFFSET;
|
||||
|
||||
positionAttribute.setY(i, height);
|
||||
const c = getColorForHeight(noiseVal);
|
||||
c.multiplyScalar(0.9);
|
||||
|
||||
// --- Color ---
|
||||
// 1. Patch Base Color (Randomized patches)
|
||||
const patchNoise = Noise.perlin2(x * 0.15 + 100, vZ * 0.15 + 100);
|
||||
const normPatch = (patchNoise + 1) * 0.5; // 0..1
|
||||
|
||||
// Interpolate palette
|
||||
const scaledPatch = normPatch * (palette.length - 1);
|
||||
const idx0 = Math.floor(scaledPatch);
|
||||
const idx1 = Math.min(idx0 + 1, palette.length - 1);
|
||||
const tColor = scaledPatch - idx0;
|
||||
|
||||
const c = new THREE.Color().lerpColors(palette[idx0], palette[idx1], tColor);
|
||||
|
||||
// 2. Linear Chunks / Streaks (Visibility Noise)
|
||||
// "Shorter linear patches": Higher Frequency in Z (0.2)
|
||||
const visNoise = Noise.perlin2(x * 0.5 + 200, vZ * 0.2 + 200);
|
||||
|
||||
if (visNoise < -0.15) {
|
||||
// Faint linear chunks
|
||||
c.lerp(skyState.color, 0.85);
|
||||
} else {
|
||||
// Bright areas
|
||||
c.multiplyScalar(0.95);
|
||||
}
|
||||
|
||||
// 3. Snow Caps (Light on top)
|
||||
// Check relative height of mountain part
|
||||
if (mountainShape > 6.0) {
|
||||
// Blend to white based on height
|
||||
const snowFactor = Math.min(1, (mountainShape - 6.0) / 4.0);
|
||||
c.lerp(new THREE.Color(0xffffff), snowFactor);
|
||||
}
|
||||
// 4. Black/Dark Peaks (Contour outlines) - only for lower rocky parts or edges
|
||||
else if (finalHeightVal > 0.45 && mountainShape < 1.0) {
|
||||
c.setHex(0x111111);
|
||||
}
|
||||
|
||||
colorAttribute.setXYZ(i, c.r, c.g, c.b);
|
||||
}
|
||||
positionAttribute.needsUpdate = true;
|
||||
@@ -769,16 +849,39 @@
|
||||
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;
|
||||
// Update presentZ dynamically as we add papers?
|
||||
// Actually, keep presentZ at the "Current" date (2024ish) or let it move?
|
||||
// Let's keep the timeline line strictly for the "History" part (up to DeepSeek).
|
||||
// Future papers appear beyond the timeline line.
|
||||
const presentZ = -480;
|
||||
|
||||
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);
|
||||
|
||||
// Adjust line height matches terrain logic (simplified)
|
||||
const mNoise1 = Noise.perlin2(pX * 0.03, virtualZ * 0.03);
|
||||
const mNoise2 = Noise.perlin2(pX * 0.1 + 123.4, virtualZ * 0.1 + 567.8);
|
||||
let mountainShape = Math.max(0, mNoise1 + mNoise2 * 0.4 - 0.45);
|
||||
const regionVar = Noise.perlin2(pX * 0.01, virtualZ * 0.01);
|
||||
const peakHeight = 35.0 + regionVar * 15.0;
|
||||
// Match reduced exponent
|
||||
mountainShape = Math.pow(mountainShape, 1.7) * peakHeight;
|
||||
if (mountainShape > 0.5) mountainShape += (Noise.perlin2(pX * noiseScale * 3.0, virtualZ * noiseScale * 3.0) * 1.5);
|
||||
|
||||
const jaggedNoise = Noise.perlin2(pX * noiseScale * 3.0, virtualZ * noiseScale * 3.0);
|
||||
const jaggedFactor = Math.max(0, noiseVal - 0.2);
|
||||
const roughness = Noise.perlin2(pX * 0.4, virtualZ * 0.4) * 0.15;
|
||||
const finalHeightVal = noiseVal + (jaggedFactor * jaggedNoise * 0.8) + roughness + mountainShape;
|
||||
|
||||
const pY = Math.max(finalHeightVal * heightScale, -1) + 0.5 + Y_OFFSET;
|
||||
|
||||
glowPos.setXYZ(i, pX, pY, worldZ);
|
||||
|
||||
// Hide lines for now per user request, but logic remains
|
||||
/*
|
||||
if (virtualZ > presentZ) {
|
||||
histPos.setXYZ(i, pX, pY, worldZ);
|
||||
futPos.setXYZ(i, 0, 0, 99999);
|
||||
@@ -791,10 +894,11 @@
|
||||
histPos.setXYZ(i, 0, 0, 99999);
|
||||
futPos.setXYZ(i, pX, pY, worldZ);
|
||||
}
|
||||
*/
|
||||
}
|
||||
histPos.needsUpdate = true;
|
||||
histCol.needsUpdate = true;
|
||||
futPos.needsUpdate = true;
|
||||
// histPos.needsUpdate = true;
|
||||
// histCol.needsUpdate = true;
|
||||
// futPos.needsUpdate = true;
|
||||
glowPos.needsUpdate = true;
|
||||
futureLine.computeLineDistances();
|
||||
|
||||
@@ -808,7 +912,24 @@
|
||||
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;
|
||||
|
||||
// Adjust marker height logic
|
||||
const mNoise1 = Noise.perlin2(wX * 0.03, vZ * 0.03);
|
||||
const mNoise2 = Noise.perlin2(wX * 0.1 + 123.4, vZ * 0.1 + 567.8);
|
||||
let mountainShape = Math.max(0, mNoise1 + mNoise2 * 0.4 - 0.45);
|
||||
const regionVar = Noise.perlin2(wX * 0.01, vZ * 0.01);
|
||||
const peakHeight = 35.0 + regionVar * 15.0;
|
||||
// Match reduced exponent
|
||||
mountainShape = Math.pow(mountainShape, 1.7) * peakHeight;
|
||||
if (mountainShape > 0.5) mountainShape += (Noise.perlin2(wX * noiseScale * 3.0, vZ * noiseScale * 3.0) * 1.5);
|
||||
|
||||
const jaggedNoise = Noise.perlin2(wX * noiseScale * 3.0, vZ * noiseScale * 3.0);
|
||||
const jaggedFactor = Math.max(0, noiseVal - 0.2);
|
||||
const roughness = Noise.perlin2(wX * 0.4, vZ * 0.4) * 0.15;
|
||||
const finalHeightVal = noiseVal + (jaggedFactor * jaggedNoise * 0.8) + roughness + mountainShape;
|
||||
|
||||
const wY = Math.max(finalHeightVal * heightScale, -1) + 2.5 + Y_OFFSET;
|
||||
|
||||
tempV.set(wX, wY, wZ);
|
||||
tempV.project(camera);
|
||||
const x = (tempV.x * .5 + .5) * window.innerWidth;
|
||||
@@ -821,6 +942,16 @@
|
||||
let opacity = 1;
|
||||
if (wZ > 12) opacity = 0;
|
||||
else if (wZ < -40) opacity = Math.max(0, 1 - (Math.abs(wZ) - 40) / 10);
|
||||
|
||||
// --- NEW: Global Fade In for Markers (Post-Header) ---
|
||||
// Header fades out by scrollY=600. targetFlyOverY = scrollY * 0.25 = 150.
|
||||
// smooth step from 150 to 200
|
||||
let globalFade = (flyOverY - 150) / 50;
|
||||
if (globalFade < 0) globalFade = 0;
|
||||
if (globalFade > 1) globalFade = 1;
|
||||
|
||||
opacity *= globalFade;
|
||||
|
||||
el.style.opacity = opacity;
|
||||
el.style.left = `${x}px`;
|
||||
el.style.top = `${y}px`;
|
||||
@@ -846,6 +977,7 @@
|
||||
const scrollY = window.scrollY;
|
||||
targetFlyOverY = scrollY * 0.25;
|
||||
|
||||
// Hero Fade Out
|
||||
const startFade = 100;
|
||||
const endFade = 600;
|
||||
let opacity = 1;
|
||||
@@ -859,41 +991,58 @@
|
||||
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
|
||||
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 (scrollY > 50 && scrollY < 700) {
|
||||
processCard.classList.add('visible');
|
||||
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;
|
||||
// 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);
|
||||
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');
|
||||
}
|
||||
});
|
||||
items.forEach((item, index) => {
|
||||
if (index === activeIndex) {
|
||||
item.classList.add('active-step');
|
||||
} else {
|
||||
item.classList.remove('active-step');
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
processCard.classList.remove('visible');
|
||||
} 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');
|
||||
if (newsCard) {
|
||||
if (scrollY >= 700 && scrollY < 1500) {
|
||||
newsCard.classList.add('visible');
|
||||
} else {
|
||||
newsCard.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
// Submissions: Show 1500px - 3500px (Immediate after News)
|
||||
|
||||
Reference in New Issue
Block a user