Flesh out about and projects pages
Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
40f2541cfb
commit
219bb6552e
12 changed files with 770 additions and 230 deletions
|
|
@ -10,7 +10,7 @@ path = "bin/main.rs"
|
|||
[dependencies]
|
||||
axum = "0.8.7"
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
tower-http = { version = "0.6.6", features = ["fs", "cors", "limit"] }
|
||||
tower-http = { version = "0.6.6", features = ["fs", "cors", "limit", "set-header"] }
|
||||
serde_json = "1.0.145"
|
||||
|
||||
message-tools = { path = "../message-tools" }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use axum::{
|
||||
Json, Router,
|
||||
http::{Method, StatusCode},
|
||||
http::{HeaderValue, Method, StatusCode, header},
|
||||
response::Html,
|
||||
routing::{get, post},
|
||||
};
|
||||
|
|
@ -13,7 +13,10 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tower_http::{cors::CorsLayer, limit::RequestBodyLimitLayer, services::ServeDir};
|
||||
use tower_http::{
|
||||
cors::CorsLayer, limit::RequestBodyLimitLayer, services::ServeDir,
|
||||
set_header::SetResponseHeaderLayer,
|
||||
};
|
||||
|
||||
const PUBKEY_PATH: &str = "./pubkeys/public_keys.json";
|
||||
const MESSAGE_STORAGE: &str = "./messages";
|
||||
|
|
@ -76,18 +79,51 @@ async fn main() -> Result<()> {
|
|||
.allow_methods([Method::GET, Method::POST])
|
||||
.allow_headers([axum::http::header::CONTENT_TYPE]);
|
||||
|
||||
// Cache static assets so navigating between pages reuses the streamed field
|
||||
// instead of re-downloading it. Scoped to /routes only; HTML routes stay fresh.
|
||||
let routes_static = Router::new()
|
||||
.fallback_service(ServeDir::new("./routes"))
|
||||
.layer(SetResponseHeaderLayer::overriding(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("public, max-age=86400"),
|
||||
));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(serve_path("./routes/root/index.html")?))
|
||||
.route("/who", get(serve_path("./routes/who/index.html")?))
|
||||
.route("/about", get(serve_path("./routes/about/index.html")?))
|
||||
.route(
|
||||
"/projects",
|
||||
get(serve_path("./routes/projects/index.html")?),
|
||||
)
|
||||
.route(
|
||||
"/projects/swactor",
|
||||
get(serve_path("./routes/projects/swactor/index.html")?),
|
||||
)
|
||||
.route(
|
||||
"/projects/airfrans-neural-cfd-surrogate",
|
||||
get(serve_path(
|
||||
"./routes/projects/airfrans-neural-cfd-surrogate/index.html",
|
||||
)?),
|
||||
)
|
||||
.route(
|
||||
"/projects/cstat-yoke",
|
||||
get(serve_path("./routes/projects/cstat-yoke/index.html")?),
|
||||
)
|
||||
.route("/api/pubkey", get(async || select_key(&PUBLIC_KEYS)))
|
||||
.route("/contact", get(serve_path("./routes/contact/index.html")?))
|
||||
.route("/contact/message", get(serve_path("./routes/contact/message/index.html")?))
|
||||
.route(
|
||||
"/contact/message",
|
||||
get(serve_path("./routes/contact/message/index.html")?),
|
||||
)
|
||||
.route(
|
||||
"/api/publish",
|
||||
post(publish_message).layer(RequestBodyLimitLayer::new(64 * 1024)),
|
||||
)
|
||||
.nest_service("/gossip-dashboard", ServeDir::new("./routes/gossip-dashboard"))
|
||||
.nest_service("/routes", ServeDir::new("./routes"))
|
||||
.nest_service(
|
||||
"/gossip-dashboard",
|
||||
ServeDir::new("./routes/gossip-dashboard"),
|
||||
)
|
||||
.nest("/routes", routes_static)
|
||||
.layer(cors);
|
||||
|
||||
let addr = "0.0.0.0:3000";
|
||||
|
|
@ -123,9 +159,7 @@ async fn publish_message(Json(msg): Json<BoxedMessage>) -> (StatusCode, Json<Use
|
|||
if limiter.1 >= MAX_PUBLISHES_PER_MINUTE {
|
||||
return (
|
||||
StatusCode::TOO_MANY_REQUESTS,
|
||||
Json(UserCreated {
|
||||
tag: String::new(),
|
||||
}),
|
||||
Json(UserCreated { tag: String::new() }),
|
||||
);
|
||||
}
|
||||
limiter.1 += 1;
|
||||
|
|
@ -141,11 +175,7 @@ async fn publish_message(Json(msg): Json<BoxedMessage>) -> (StatusCode, Json<Use
|
|||
|
||||
// 3. Create all necessary parent directories recursively (async operation)
|
||||
if let Err(e) = tokio::fs::create_dir_all(&MESSAGE_STORAGE).await {
|
||||
eprintln!(
|
||||
"Failed to create directory {}: {}",
|
||||
MESSAGE_STORAGE,
|
||||
e
|
||||
);
|
||||
eprintln!("Failed to create directory {}: {}", MESSAGE_STORAGE, e);
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, Json(UserCreated { tag }));
|
||||
}
|
||||
|
||||
|
|
|
|||
17
routes/about/about-section-skeleton.md
Normal file
17
routes/about/about-section-skeleton.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Interest line]
|
||||
Software engineer cum neural network wrangler.
|
||||
My technical interests include systems engineering, distributed compute, physical simulation, and applied machine learning infrastructure.
|
||||
|
||||
|
||||
[Projects]
|
||||
swactor:
|
||||
Toolkit for orchestrating somewhat-unreliable heterogeneous GPU nodes over WAN links
|
||||
|
||||
AirfRANS / neural CFD surrogate:
|
||||
Machine learning as simulation: neural CFD surrogates, model scaling, and solver-grounded validation.
|
||||
|
||||
cstat and yoke:
|
||||
Code as data: statistical structure, LLM coding behavior, and agent harnesses.
|
||||
|
||||
[Closing line]
|
||||
If any of these interest you, please contact me `zachery.lol/contact. I love to talk shop.
|
||||
152
routes/about/index.html
Normal file
152
routes/about/index.html
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>About</title>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
background: #060a10;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
#fractal-canvas {
|
||||
image-rendering: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content-overlay {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(10, 14, 20, 0.55);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem 3rem;
|
||||
width: min(960px, calc(100vw - 2rem));
|
||||
line-height: 1.6;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.glass-card h2 {
|
||||
color: #fff;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.glass-card h2 {
|
||||
margin: 2rem 0 0.75rem 0;
|
||||
font-size: 1.375rem;
|
||||
}
|
||||
|
||||
.glass-card p {
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.project-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 1rem 0 0;
|
||||
}
|
||||
|
||||
.project-list li {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.project-list strong a {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.glass-card a {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-decoration-color: rgba(255, 255, 255, 0.25);
|
||||
text-underline-offset: 0.18em;
|
||||
transition: color 250ms ease, text-decoration-color 250ms ease;
|
||||
}
|
||||
|
||||
.glass-card a:hover {
|
||||
color: #fff;
|
||||
text-decoration-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.back-link {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.back-link a {
|
||||
text-decoration: none;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 250ms ease;
|
||||
}
|
||||
|
||||
.back-link a:hover {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
</style>
|
||||
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
|
||||
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fractal-canvas"></canvas>
|
||||
|
||||
<main class="content-overlay">
|
||||
<article class="glass-card">
|
||||
<p>Software engineer cum neural network wrangler.</p>
|
||||
<p>Technical interests include systems engineering, distributed compute, physical simulation, and applied machine learning infrastructure.</p>
|
||||
|
||||
<h2>Projects</h2>
|
||||
<ul class="project-list">
|
||||
<li>
|
||||
<strong><a href="/projects/swactor">swactor</a></strong>
|
||||
Toolkit for orchestrating somewhat-unreliable heterogeneous GPU nodes over WAN links.
|
||||
</li>
|
||||
<li>
|
||||
<strong><a href="/projects/airfrans-neural-cfd-surrogate">AirfRANS / neural CFD surrogate</a></strong>
|
||||
Machine learning as simulation: neural CFD surrogates, model scaling, and solver-grounded validation.
|
||||
</li>
|
||||
<li>
|
||||
<strong><a href="/projects/cstat-yoke">cstat and yoke</a></strong>
|
||||
Code as data: statistical structure, LLM coding behavior, and agent harnesses.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>If any of these interest you, please <a href="/contact">contact me</a>. I love to talk shop.</p>
|
||||
</article>
|
||||
|
||||
<nav class="back-link">
|
||||
<a href="/">back to home</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- fractal background loaded via deferred <script> in <head> -->
|
||||
</body>
|
||||
</html>
|
||||
42
routes/projects/airfrans-neural-cfd-surrogate/index.html
Normal file
42
routes/projects/airfrans-neural-cfd-surrogate/index.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AirfRANS / neural CFD surrogate</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: #060a10;
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
#fractal-canvas { image-rendering: auto; position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; }
|
||||
.content-overlay { position: relative; z-index: 1; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem 1rem; }
|
||||
.glass-card { background: rgba(10, 14, 20, 0.55); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; padding: 2.5rem 3rem; width: min(960px, calc(100vw - 2rem)); line-height: 1.65; font-size: 1.1rem; }
|
||||
h1 { color: #fff; margin: 0 0 1rem; font-weight: 300; letter-spacing: 0.01em; }
|
||||
p { margin: 0.85rem 0; }
|
||||
a { color: rgba(255, 255, 255, 0.85); text-decoration-color: rgba(255, 255, 255, 0.25); text-underline-offset: 0.18em; }
|
||||
a:hover { color: #fff; text-decoration-color: rgba(255, 255, 255, 0.7); }
|
||||
.back-link { margin-top: 2rem; }
|
||||
.back-link a { text-decoration: none; color: rgba(255, 255, 255, 0.4); font-size: 0.8rem; letter-spacing: 0.04em; }
|
||||
.back-link a:hover { color: rgba(255, 255, 255, 0.7); }
|
||||
</style>
|
||||
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
|
||||
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fractal-canvas"></canvas>
|
||||
<main class="content-overlay">
|
||||
<article class="glass-card">
|
||||
<h1>AirfRANS / neural CFD surrogate</h1>
|
||||
<p>This fake writeup pretends the goal is simple: make a neural surrogate that behaves enough like a CFD solver to be useful, but not so much like a CFD solver that it takes all afternoon to answer.</p>
|
||||
<p>The model gets geometry, flow conditions, and a stern lecture from validation metrics. It returns fields, uncertainty hints, and the occasional reminder that conservation laws are not optional.</p>
|
||||
<p>The interesting part is the boundary between learned approximation and solver-grounded truth: where the network is fast, where it is wrong, and how to know before a wing falls off in a slide deck.</p>
|
||||
</article>
|
||||
<nav class="back-link"><a href="/projects">back to projects</a></nav>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
36
routes/projects/cstat-yoke/index.html
Normal file
36
routes/projects/cstat-yoke/index.html
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>cstat and yoke</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body { font-family: system-ui, -apple-system, sans-serif; margin: 0; min-height: 100vh; background: #060a10; color: rgba(255, 255, 255, 0.76); }
|
||||
#fractal-canvas { image-rendering: auto; position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; }
|
||||
.content-overlay { position: relative; z-index: 1; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem 1rem; }
|
||||
.glass-card { background: rgba(10, 14, 20, 0.55); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; padding: 2.5rem 3rem; width: min(960px, calc(100vw - 2rem)); line-height: 1.65; font-size: 1.1rem; }
|
||||
h1 { color: #fff; margin: 0 0 1rem; font-weight: 300; letter-spacing: 0.01em; }
|
||||
p { margin: 0.85rem 0; }
|
||||
a { color: rgba(255, 255, 255, 0.85); text-decoration-color: rgba(255, 255, 255, 0.25); text-underline-offset: 0.18em; }
|
||||
a:hover { color: #fff; text-decoration-color: rgba(255, 255, 255, 0.7); }
|
||||
.back-link { margin-top: 2rem; }
|
||||
.back-link a { text-decoration: none; color: rgba(255, 255, 255, 0.4); font-size: 0.8rem; letter-spacing: 0.04em; }
|
||||
.back-link a:hover { color: rgba(255, 255, 255, 0.7); }
|
||||
</style>
|
||||
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
|
||||
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fractal-canvas"></canvas>
|
||||
<main class="content-overlay">
|
||||
<article class="glass-card">
|
||||
<h1>cstat and yoke</h1>
|
||||
<p>cstat is the fake microscope; yoke is the fake harness. Together they treat code as data and ask what shape a codebase has before anyone starts arguing about taste.</p>
|
||||
<p>The writeup claims cstat measures local structure, repetition, churn-shaped scars, and the weird fingerprints left by agents that are too confident near edge cases.</p>
|
||||
<p>yoke then turns those observations into experiments: give an agent a task, constrain the harness, watch the trace, and learn whether the model fixed the problem or merely rearranged the furniture.</p>
|
||||
</article>
|
||||
<nav class="back-link"><a href="/projects">back to projects</a></nav>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
156
routes/projects/index.html
Normal file
156
routes/projects/index.html
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Projects</title>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
background: #060a10;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
#fractal-canvas {
|
||||
image-rendering: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content-overlay {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(10, 14, 20, 0.55);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem 3rem;
|
||||
max-width: 760px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.glass-card h1 {
|
||||
color: #fff;
|
||||
margin: 0 0 1.5rem 0;
|
||||
font-weight: 300;
|
||||
font-size: 1.8rem;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.project-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.project-card {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
padding: 1.25rem 0;
|
||||
}
|
||||
|
||||
.project-card:first-child {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.project-card:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.project-card h2 {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
margin: 0 0 0.4rem 0;
|
||||
font-weight: 500;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.project-card h2 a {
|
||||
color: inherit;
|
||||
text-decoration-color: rgba(255, 255, 255, 0.25);
|
||||
text-underline-offset: 0.18em;
|
||||
transition: color 250ms ease, text-decoration-color 250ms ease;
|
||||
}
|
||||
|
||||
.project-card h2 a:hover {
|
||||
color: #fff;
|
||||
text-decoration-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.project-card p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.back-link a {
|
||||
text-decoration: none;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 250ms ease;
|
||||
}
|
||||
|
||||
.back-link a:hover {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
</style>
|
||||
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
|
||||
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fractal-canvas"></canvas>
|
||||
|
||||
<main class="content-overlay">
|
||||
<article class="glass-card">
|
||||
<h1>Projects</h1>
|
||||
<section class="project-list" aria-label="Project list">
|
||||
<article class="project-card">
|
||||
<h2><a href="/projects/swactor">swactor</a></h2>
|
||||
<p>Toolkit for orchestrating somewhat-unreliable heterogeneous GPU nodes over WAN links.</p>
|
||||
</article>
|
||||
|
||||
<article class="project-card">
|
||||
<h2><a href="/projects/airfrans-neural-cfd-surrogate">AirfRANS / neural CFD surrogate</a></h2>
|
||||
<p>Machine learning as simulation: neural CFD surrogates, model scaling, and solver-grounded validation.</p>
|
||||
</article>
|
||||
|
||||
<article class="project-card">
|
||||
<h2><a href="/projects/cstat-yoke">cstat and yoke</a></h2>
|
||||
<p>Code as data: statistical structure, LLM coding behavior, and agent harnesses.</p>
|
||||
</article>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<nav class="back-link">
|
||||
<a href="/">back to home</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- fractal background loaded via deferred <script> in <head> -->
|
||||
</body>
|
||||
</html>
|
||||
68
routes/projects/swactor/index.html
Normal file
68
routes/projects/swactor/index.html
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>swactor</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: #060a10;
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
#fractal-canvas {
|
||||
image-rendering: auto;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
.content-overlay {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
.glass-card {
|
||||
background: rgba(10, 14, 20, 0.55);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem 3rem;
|
||||
width: min(960px, calc(100vw - 2rem));
|
||||
line-height: 1.65;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
h1 { color: #fff; margin: 0 0 1rem; font-weight: 300; letter-spacing: 0.01em; }
|
||||
p { margin: 0.85rem 0; }
|
||||
a { color: rgba(255, 255, 255, 0.85); text-decoration-color: rgba(255, 255, 255, 0.25); text-underline-offset: 0.18em; }
|
||||
a:hover { color: #fff; text-decoration-color: rgba(255, 255, 255, 0.7); }
|
||||
.back-link { margin-top: 2rem; }
|
||||
.back-link a { text-decoration: none; color: rgba(255, 255, 255, 0.4); font-size: 0.8rem; letter-spacing: 0.04em; }
|
||||
.back-link a:hover { color: rgba(255, 255, 255, 0.7); }
|
||||
</style>
|
||||
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
|
||||
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fractal-canvas"></canvas>
|
||||
<main class="content-overlay">
|
||||
<article class="glass-card">
|
||||
<h1>swactor</h1>
|
||||
<p>swactor is the imaginary control plane I would build for a room full of moody GPUs scattered across apartments, closets, and spare cloud instances.</p>
|
||||
<p>The fake writeup version starts with a tiny actor runtime, gives each node a heartbeat, then treats dropped packets and flaky thermal envelopes as normal weather instead of exceptional tragedy.</p>
|
||||
<p>The punchline: jobs migrate toward available memory, checkpoints move over boring protocols, and the scheduler learns which machines are liars before the humans do.</p>
|
||||
</article>
|
||||
<nav class="back-link"><a href="/projects">back to projects</a></nav>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
BIN
routes/root/dist-strips.bin
Normal file
BIN
routes/root/dist-strips.bin
Normal file
Binary file not shown.
|
|
@ -1,5 +1,20 @@
|
|||
// Julia distance-isolines background. WebGL preferred, Canvas2D fallback.
|
||||
// Palette: BSOD-blue, smooth cosine bands, 1024-entry LUT for finer transitions.
|
||||
// Julia distance-isolines background.
|
||||
// Renderer: WebGL (default) -> Canvas2D -> flat dark backdrop.
|
||||
//
|
||||
// The 512x512 distance field is precomputed (live per-pixel iteration is too slow).
|
||||
// It ships as `dist-strips.bin`: full-resolution horizontal bands, each WebP-compressed,
|
||||
// concatenated in CENTER-OUT order. We stream the file and upload each band to its texture
|
||||
// sub-rectangle as its bytes arrive.
|
||||
//
|
||||
// The DISPLAY FOLLOWS THE STREAM: instead of drawing the whole screen over a half-loaded
|
||||
// texture (which looked like a "glow"), the shader only reveals the field rows that have
|
||||
// actually streamed in, blooming outward from the center over a flat dark backdrop. The
|
||||
// revealed extent eases smoothly, so it reads as an intentional reveal, not jank.
|
||||
// Palette: BSOD-blue, smooth cosine bands, 1024-entry LUT (Canvas2D path only).
|
||||
|
||||
var FIELD_URL = '/routes/root/dist-strips.bin?v=1'; // streamed WebP bands (bump ?v on regen)
|
||||
var DIST_URL = '/routes/root/dist.png'; // monolithic source (Canvas2D fallback)
|
||||
var DARK_HEX = '#060a10'; // backdrop / unloaded tone
|
||||
|
||||
function initFractal(canvasId) {
|
||||
console.log('[fractal-gl] initFractal:', canvasId);
|
||||
|
|
@ -10,9 +25,8 @@ function initFractal(canvasId) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Visible base color even if both render paths fail.
|
||||
document.body.style.background =
|
||||
'radial-gradient(ellipse at center, #142566 0%, #0a103a 60%, #060a10 100%)';
|
||||
// Flat dark backdrop: clean start state, and the tone the reveal blooms over.
|
||||
document.body.style.background = DARK_HEX;
|
||||
|
||||
var gl = null;
|
||||
try {
|
||||
|
|
@ -30,6 +44,91 @@ function initFractal(canvasId) {
|
|||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Shared streamed-field loader
|
||||
// Emits each band as a decoded ImageBitmap the moment its bytes land.
|
||||
// cb = { onHeader(header), onStrip(bitmap, y, h), onError(err) }
|
||||
// File: u32(LE) headerLen | JSON header | concatenated band WebPs.
|
||||
// JSON: { fullW, fullH, bands:[{y,h,off,len}] } in stream (center-out) order.
|
||||
// ----------------------------------------------------------------
|
||||
function streamFieldBands(cb) {
|
||||
var TD = new TextDecoder();
|
||||
|
||||
function emitBand(bytes, b) {
|
||||
// bytes is an independent copy, safe to keep past the next read.
|
||||
// Disable color management so the grayscale field bytes survive intact.
|
||||
createImageBitmap(new Blob([bytes], { type: 'image/webp' }),
|
||||
{ colorSpaceConversion: 'none', premultiplyAlpha: 'none' })
|
||||
.then(function (bmp) {
|
||||
try { cb.onStrip(bmp, b.y, b.h); }
|
||||
catch (e) { console.warn('[fractal-gl] band upload failed', e); }
|
||||
})
|
||||
.catch(function (e) { console.warn('[fractal-gl] band decode failed', e); });
|
||||
}
|
||||
|
||||
function emitAll(buf) {
|
||||
var hlen = new DataView(buf.buffer, buf.byteOffset, 4).getUint32(0, true);
|
||||
var header = JSON.parse(TD.decode(buf.subarray(4, 4 + hlen)));
|
||||
var ps = 4 + hlen;
|
||||
cb.onHeader(header);
|
||||
header.bands.forEach(function (b) {
|
||||
emitBand(buf.subarray(ps + b.off, ps + b.off + b.len).slice(), b);
|
||||
});
|
||||
}
|
||||
|
||||
fetch(FIELD_URL).then(function (res) {
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
if (!res.body || !res.body.getReader) {
|
||||
return res.arrayBuffer().then(function (ab) { emitAll(new Uint8Array(ab)); });
|
||||
}
|
||||
|
||||
var reader = res.body.getReader();
|
||||
var chunks = [];
|
||||
var total = 0;
|
||||
var header = null;
|
||||
var ps = 0;
|
||||
var next = 0;
|
||||
|
||||
function assemble() {
|
||||
var out = new Uint8Array(total);
|
||||
var o = 0;
|
||||
for (var i = 0; i < chunks.length; i++) { out.set(chunks[i], o); o += chunks[i].length; }
|
||||
return out;
|
||||
}
|
||||
|
||||
function step() {
|
||||
return reader.read().then(function (r) {
|
||||
if (r.value) { chunks.push(r.value); total += r.value.length; }
|
||||
var buf = assemble();
|
||||
|
||||
if (!header && total >= 4) {
|
||||
var hlen = new DataView(buf.buffer, 0, 4).getUint32(0, true);
|
||||
if (total >= 4 + hlen) {
|
||||
header = JSON.parse(TD.decode(buf.subarray(4, 4 + hlen)));
|
||||
ps = 4 + hlen;
|
||||
cb.onHeader(header);
|
||||
}
|
||||
}
|
||||
if (header) {
|
||||
while (next < header.bands.length) {
|
||||
var b = header.bands[next];
|
||||
var end = ps + b.off + b.len;
|
||||
if (total < end) break;
|
||||
emitBand(buf.subarray(ps + b.off, end).slice(), b);
|
||||
next++;
|
||||
}
|
||||
}
|
||||
if (r.done) return;
|
||||
return step();
|
||||
});
|
||||
}
|
||||
return step();
|
||||
}).catch(function (e) {
|
||||
console.error('[fractal-gl] field stream failed', e);
|
||||
if (cb.onError) cb.onError(e);
|
||||
});
|
||||
}
|
||||
|
||||
function _hslToRgb(h, s, l) {
|
||||
var a = s * Math.min(l, 1 - l);
|
||||
function f(n) {
|
||||
|
|
@ -41,7 +140,6 @@ function _hslToRgb(h, s, l) {
|
|||
|
||||
// 1024-entry palette: 4x finer than the iteration-count space (0..255),
|
||||
// which kills visible quantization banding in the band peaks/troughs.
|
||||
// Cosine band (no abs/pow) → smooth bands; hue pinned ~true blue.
|
||||
var PALETTE_SIZE = 1024;
|
||||
function _buildPalette32() {
|
||||
var p32 = new Uint32Array(PALETTE_SIZE);
|
||||
|
|
@ -58,59 +156,68 @@ function _buildPalette32() {
|
|||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// WebGL path
|
||||
// WebGL path (default) — streams bands in, reveals only what has loaded
|
||||
// ----------------------------------------------------------------
|
||||
var FIELD_VERT_GLSL = [
|
||||
'attribute vec2 a_pos;',
|
||||
'void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }'
|
||||
].join('\n');
|
||||
|
||||
// Same field-sampling math as before, plus a feathered reveal mask gated on the
|
||||
// loaded field-row extent [u_loadV0, u_loadV1]. Unloaded rows show the dark backdrop.
|
||||
var FIELD_FRAG_GLSL = [
|
||||
'precision highp float;',
|
||||
'uniform float u_time;',
|
||||
'uniform vec2 u_resolution;',
|
||||
'uniform sampler2D u_dist;',
|
||||
'uniform float u_loadV0;',
|
||||
'uniform float u_loadV1;',
|
||||
'const vec3 DARK = vec3(0.024, 0.039, 0.063);',
|
||||
'vec3 hslToRgb(float h, float s, float l) {',
|
||||
' float a = s * min(l, 1.0 - l);',
|
||||
' vec3 k = mod(vec3(0.0, 8.0, 4.0) + h * 12.0, 12.0);',
|
||||
' vec3 v = max(min(min(k - 3.0, 9.0 - k), vec3(1.0)), vec3(-1.0));',
|
||||
' return vec3(l) - a * v;',
|
||||
'}',
|
||||
'vec3 calmBand(float idx) {',
|
||||
' float f = mod(idx, 256.0);',
|
||||
' float band = 0.5 + 0.5 * cos(f * 6.28318530 / 56.0);',
|
||||
' float h = 0.665 + 0.015 * sin(f * 3.14159265 / 128.0);',
|
||||
' float s = 0.80;',
|
||||
' float l = 0.18 + 0.28 * band;',
|
||||
' return hslToRgb(h, s, l);',
|
||||
'}',
|
||||
'void main() {',
|
||||
' vec2 uv = gl_FragCoord.xy / u_resolution;',
|
||||
' float aspect = u_resolution.x / u_resolution.y;',
|
||||
' float t = u_time * 0.12;',
|
||||
' float maxH = 1.0 / max(aspect, 1.0);',
|
||||
' float zoomFrac = 0.65 + 0.15 * sin(t * 0.4);',
|
||||
' float halfH = maxH * zoomFrac * 0.5;',
|
||||
' float halfW = halfH * aspect;',
|
||||
' float fx = 0.5 - halfW;',
|
||||
' float fy = 0.5 - halfH;',
|
||||
' float cx = 0.5 + fx * 0.5 * cos(t * 0.55);',
|
||||
' float cy = 0.5 + fy * 0.5 * sin(t * 0.45);',
|
||||
' float sx = cx + (uv.x - 0.5) * 2.0 * halfW;',
|
||||
' float sy = cy + (uv.y - 0.5) * 2.0 * halfH;',
|
||||
' float fv = 1.0 - sy;', // field v-coordinate (texture uploaded natural orientation)
|
||||
' // Reveal only loaded field rows; nothing loaded yet => all dark.',
|
||||
' if (u_loadV1 - u_loadV0 < 0.001) { gl_FragColor = vec4(DARK, 1.0); return; }',
|
||||
' float E = 0.03;',
|
||||
' float m = smoothstep(u_loadV0 - E, u_loadV0 + E, fv) *',
|
||||
' (1.0 - smoothstep(u_loadV1 - E, u_loadV1 + E, fv));',
|
||||
' float v = texture2D(u_dist, vec2(sx, fv)).r * 255.0;',
|
||||
' float ditherMix = clamp((v - 180.0) / 60.0, 0.0, 1.0);',
|
||||
' float noise = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453) - 0.5;',
|
||||
' v += noise * 1.2 * ditherMix;',
|
||||
' float offset = u_time * 10.8;',
|
||||
' vec3 color = calmBand(v - offset);',
|
||||
' gl_FragColor = vec4(mix(DARK, color, m), 1.0);',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
function initWebGL(canvas, gl) {
|
||||
var VERT_SRC = [
|
||||
'attribute vec2 a_pos;',
|
||||
'void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }'
|
||||
].join('\n');
|
||||
|
||||
var FRAG_SRC = [
|
||||
'precision highp float;',
|
||||
'uniform float u_time;',
|
||||
'uniform vec2 u_resolution;',
|
||||
'uniform sampler2D u_dist;',
|
||||
'vec3 hslToRgb(float h, float s, float l) {',
|
||||
' float a = s * min(l, 1.0 - l);',
|
||||
' vec3 k = mod(vec3(0.0, 8.0, 4.0) + h * 12.0, 12.0);',
|
||||
' vec3 v = max(min(min(k - 3.0, 9.0 - k), vec3(1.0)), vec3(-1.0));',
|
||||
' return vec3(l) - a * v;',
|
||||
'}',
|
||||
'vec3 calmBand(float idx) {',
|
||||
' float f = mod(idx, 256.0);',
|
||||
' float band = 0.5 + 0.5 * cos(f * 6.28318530 / 56.0);',
|
||||
' float h = 0.665 + 0.015 * sin(f * 3.14159265 / 128.0);',
|
||||
' float s = 0.80;',
|
||||
' float l = 0.18 + 0.28 * band;',
|
||||
' return hslToRgb(h, s, l);',
|
||||
'}',
|
||||
'void main() {',
|
||||
' vec2 uv = gl_FragCoord.xy / u_resolution;',
|
||||
' float aspect = u_resolution.x / u_resolution.y;',
|
||||
' float t = u_time * 0.12;',
|
||||
' float maxH = 1.0 / max(aspect, 1.0);',
|
||||
' float zoomFrac = 0.65 + 0.15 * sin(t * 0.4);',
|
||||
' float halfH = maxH * zoomFrac * 0.5;',
|
||||
' float halfW = halfH * aspect;',
|
||||
' float fx = 0.5 - halfW;',
|
||||
' float fy = 0.5 - halfH;',
|
||||
' float cx = 0.5 + fx * 0.5 * cos(t * 0.55);',
|
||||
' float cy = 0.5 + fy * 0.5 * sin(t * 0.45);',
|
||||
' float sx = cx + (uv.x - 0.5) * 2.0 * halfW;',
|
||||
' float sy = cy + (uv.y - 0.5) * 2.0 * halfH;',
|
||||
' float v = texture2D(u_dist, vec2(sx, sy)).r * 255.0;',
|
||||
' // Dither only in the outer smooth region (v in [180, 240] ramps in;',
|
||||
' // inner fractal detail stays crisp).',
|
||||
' float ditherMix = clamp((v - 180.0) / 60.0, 0.0, 1.0);',
|
||||
' float noise = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453) - 0.5;',
|
||||
' v += noise * 1.2 * ditherMix;',
|
||||
' float offset = u_time * 10.8;',
|
||||
' vec3 color = calmBand(v - offset);',
|
||||
' gl_FragColor = vec4(color, 1.0);',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
function compile(type, src) {
|
||||
var s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
|
|
@ -122,8 +229,8 @@ function initWebGL(canvas, gl) {
|
|||
return s;
|
||||
}
|
||||
|
||||
var vs = compile(gl.VERTEX_SHADER, VERT_SRC);
|
||||
var fs = compile(gl.FRAGMENT_SHADER, FRAG_SRC);
|
||||
var vs = compile(gl.VERTEX_SHADER, FIELD_VERT_GLSL);
|
||||
var fs = compile(gl.FRAGMENT_SHADER, FIELD_FRAG_GLSL);
|
||||
if (!vs || !fs) return initCanvas2D(canvas);
|
||||
|
||||
var prog = gl.createProgram();
|
||||
|
|
@ -146,6 +253,8 @@ function initWebGL(canvas, gl) {
|
|||
var uTime = gl.getUniformLocation(prog, 'u_time');
|
||||
var uRes = gl.getUniformLocation(prog, 'u_resolution');
|
||||
var uDist = gl.getUniformLocation(prog, 'u_dist');
|
||||
var uV0 = gl.getUniformLocation(prog, 'u_loadV0');
|
||||
var uV1 = gl.getUniformLocation(prog, 'u_loadV1');
|
||||
|
||||
var tex = gl.createTexture();
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
|
|
@ -159,25 +268,69 @@ function initWebGL(canvas, gl) {
|
|||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.uniform1i(uDist, 0);
|
||||
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
var w = img.width, h = img.height;
|
||||
console.log('[fractal-gl] PNG decoded:', w, 'x', h);
|
||||
var off = document.createElement('canvas');
|
||||
off.width = w; off.height = h;
|
||||
var octx = off.getContext('2d');
|
||||
octx.drawImage(img, 0, 0);
|
||||
var data = octx.getImageData(0, 0, w, h).data;
|
||||
var bytes = new Uint8Array(data.length);
|
||||
bytes.set(data);
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0,
|
||||
gl.RGBA, gl.UNSIGNED_BYTE, bytes);
|
||||
};
|
||||
img.onerror = function (e) { console.error('[fractal-gl] dist.png load failed', e); };
|
||||
img.src = '/routes/root/dist.png';
|
||||
// Drawing the band bitmap to a 2D canvas yields a universally-supported
|
||||
// TexImageSource (works on WebGL1 and WebGL2 alike).
|
||||
var scratch = document.createElement('canvas');
|
||||
var sctx = scratch.getContext('2d');
|
||||
|
||||
// ---- reveal state: which field rows have streamed in ----
|
||||
var totalH = 1;
|
||||
var spatial = []; // bands sorted top->bottom: {y, h}
|
||||
var loadedSlots = []; // parallel booleans
|
||||
var centerSlot = 0, lastSlot = 0;
|
||||
var targetV0 = 0.5, targetV1 = 0.5; // loaded extent (v-fraction), grows from center
|
||||
var dispV0 = 0.5, dispV1 = 0.5; // eased toward target for a smooth bloom
|
||||
|
||||
// Bloom only on the first page of a tab session; on later navigations the field
|
||||
// is cached, so snap straight to it instead of replaying the ~1s intro.
|
||||
var revealed = false;
|
||||
try { revealed = sessionStorage.getItem('fractalRevealed') === '1'; } catch (e) {}
|
||||
|
||||
streamFieldBands({
|
||||
onHeader: function (header) {
|
||||
var w = header.fullW, h = header.fullH;
|
||||
totalH = h;
|
||||
spatial = header.bands.slice().sort(function (a, b) { return a.y - b.y; });
|
||||
loadedSlots = spatial.map(function () { return false; });
|
||||
lastSlot = spatial.length - 1;
|
||||
centerSlot = 0;
|
||||
for (var i = 0; i < spatial.length; i++) {
|
||||
if (h / 2 >= spatial[i].y && h / 2 < spatial[i].y + spatial[i].h) { centerSlot = i; break; }
|
||||
}
|
||||
// Allocate the full texture (black; masked until rows load).
|
||||
var fill = new Uint8Array(w * h * 4);
|
||||
for (var k = 3; k < fill.length; k += 4) fill[k] = 255;
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, fill);
|
||||
},
|
||||
onStrip: function (bitmap, y, h) {
|
||||
scratch.width = bitmap.width;
|
||||
scratch.height = bitmap.height;
|
||||
sctx.drawImage(bitmap, 0, 0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, y, gl.RGBA, gl.UNSIGNED_BYTE, scratch);
|
||||
|
||||
var s = -1;
|
||||
for (var i = 0; i < spatial.length; i++) { if (spatial[i].y === y) { s = i; break; } }
|
||||
if (s < 0) return;
|
||||
loadedSlots[s] = true;
|
||||
// Grow the contiguous loaded run around the center (guards against a late
|
||||
// middle band briefly revealing an unloaded gap).
|
||||
if (loadedSlots[centerSlot]) {
|
||||
var lo = centerSlot; while (lo - 1 >= 0 && loadedSlots[lo - 1]) lo--;
|
||||
var hi = centerSlot; while (hi + 1 < spatial.length && loadedSlots[hi + 1]) hi++;
|
||||
// Push the extent past the field edge once the outermost band is in, so the
|
||||
// feather never darkens the true field edge in the steady state.
|
||||
targetV0 = (lo === 0) ? -1.0 : spatial[lo].y / totalH;
|
||||
targetV1 = (hi === lastSlot) ? 2.0 : (spatial[hi].y + spatial[hi].h) / totalH;
|
||||
if (lo === 0 && hi === lastSlot) {
|
||||
try { sessionStorage.setItem('fractalRevealed', '1'); } catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function resize() {
|
||||
canvas.width = window.innerWidth;
|
||||
|
|
@ -190,8 +343,15 @@ function initWebGL(canvas, gl) {
|
|||
var startTime = performance.now();
|
||||
function frame() {
|
||||
var t = (performance.now() - startTime) * 0.001;
|
||||
// Ease the displayed extent toward the loaded extent for a smooth bloom.
|
||||
// On a repeat visit (cached field) snap instead of replaying the intro.
|
||||
var ease = revealed ? 1.0 : 0.12;
|
||||
dispV0 += (targetV0 - dispV0) * ease;
|
||||
dispV1 += (targetV1 - dispV1) * ease;
|
||||
gl.uniform1f(uTime, t);
|
||||
gl.uniform2f(uRes, canvas.width, canvas.height);
|
||||
gl.uniform1f(uV0, dispV0);
|
||||
gl.uniform1f(uV1, dispV1);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
|
@ -200,15 +360,16 @@ function initWebGL(canvas, gl) {
|
|||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Canvas2D fallback
|
||||
// Canvas2D fallback (rare; ancient / no-GPU browsers)
|
||||
// - 1024-entry palette (4x finer than the byte-quantized iteration value)
|
||||
// - bilinear sample of source buffer (no nearest-neighbor jaggies)
|
||||
// - Uint32 writes (one packed write per pixel instead of four byte writes)
|
||||
// Loads the monolithic dist.png and renders once decoded (fades in).
|
||||
// ----------------------------------------------------------------
|
||||
function initCanvas2D(canvas) {
|
||||
var ctx2d = canvas.getContext('2d');
|
||||
if (!ctx2d) {
|
||||
console.error('[fractal-gl] Canvas2D also unavailable; body gradient remains');
|
||||
console.error('[fractal-gl] Canvas2D also unavailable; body backdrop remains');
|
||||
return;
|
||||
}
|
||||
var palette32 = _buildPalette32();
|
||||
|
|
@ -226,6 +387,10 @@ function initCanvas2D(canvas) {
|
|||
var gray = new Uint8Array(SW * SH);
|
||||
for (var i = 0, j = 0; i < gray.length; i++, j += 4) gray[i] = data[j];
|
||||
|
||||
// Fade the canvas in once the first frame is ready.
|
||||
canvas.style.transition = 'opacity 400ms ease';
|
||||
canvas.style.opacity = '0';
|
||||
|
||||
// Higher internal resolution = sharper. Capped to keep frame budget reasonable.
|
||||
function targetSize() {
|
||||
var h = Math.min(800, Math.max(360, Math.floor(window.innerHeight * 0.7)));
|
||||
|
|
@ -244,6 +409,7 @@ function initCanvas2D(canvas) {
|
|||
var out32 = new Uint32Array(imgOut.data.buffer);
|
||||
var lastW = canvas.width, lastH = canvas.height;
|
||||
var startTime = performance.now();
|
||||
var faded = false;
|
||||
|
||||
// 4x4 Bayer matrix, recentered to ±0.6 source units. Only applied where
|
||||
// v is in the outer-smooth band (ramp 180→240) so inner detail stays crisp.
|
||||
|
|
@ -314,13 +480,14 @@ function initCanvas2D(canvas) {
|
|||
sy += dys;
|
||||
}
|
||||
ctx2d.putImageData(imgOut, 0, 0);
|
||||
if (!faded) { faded = true; canvas.style.opacity = '1'; }
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
requestAnimationFrame(frame);
|
||||
console.log('[fractal-gl] Canvas2D loop started; internal',
|
||||
canvas.width, 'x', canvas.height);
|
||||
};
|
||||
img.src = '/routes/root/dist.png';
|
||||
img.src = DIST_URL;
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
|
||||
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fractal-canvas"></canvas>
|
||||
|
|
@ -112,23 +114,15 @@
|
|||
|
||||
<nav>
|
||||
<div class="nav-links">
|
||||
<a href="./who">Who</a>
|
||||
<a href="./about">About</a>
|
||||
<a href="./projects">Projects</a>
|
||||
<a href="./contact">Contact</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Lazy-load the fractal background after first paint so the page feels instant.
|
||||
requestAnimationFrame(function () {
|
||||
requestAnimationFrame(function () {
|
||||
var s = document.createElement('script');
|
||||
s.src = '/routes/root/fractal-gl.js?v=9';
|
||||
s.async = true;
|
||||
document.body.appendChild(s);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- fractal background loaded via deferred <script> in <head> -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Who</title>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background: #060a10;
|
||||
}
|
||||
|
||||
#fractal-canvas {
|
||||
image-rendering: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content-overlay {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(10, 14, 20, 0.55);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem 3rem;
|
||||
text-align: center;
|
||||
min-width: 250px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.glass-card h2 {
|
||||
color: #fff;
|
||||
margin: 0 0 1rem 0;
|
||||
font-weight: 300;
|
||||
font-size: 1.6rem;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.glass-card p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.back-link a {
|
||||
text-decoration: none;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 250ms ease;
|
||||
}
|
||||
|
||||
.back-link a:hover {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fractal-canvas"></canvas>
|
||||
|
||||
<div class="content-overlay">
|
||||
<div class="glass-card">
|
||||
<h2>Who</h2>
|
||||
<p>No one in particular :)</p>
|
||||
<p>I'll write more later...</p>
|
||||
</div>
|
||||
|
||||
<nav class="back-link">
|
||||
<a href="/">back to home</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
requestAnimationFrame(function () {
|
||||
requestAnimationFrame(function () {
|
||||
var s = document.createElement('script');
|
||||
s.src = '/routes/root/fractal-gl.js?v=9';
|
||||
s.async = true;
|
||||
document.body.appendChild(s);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue