Compare commits
2 commits
4200ca77df
...
40f2541cfb
| Author | SHA1 | Date | |
|---|---|---|---|
| 40f2541cfb | |||
| d10f539e06 |
8 changed files with 778 additions and 267 deletions
|
|
@ -9,6 +9,15 @@ use std::{
|
|||
|
||||
const WEB_BUILD_DIR: &str = "web-build";
|
||||
|
||||
#[derive(clap::ValueEnum, Clone, Debug, PartialEq)]
|
||||
enum BuildTarget {
|
||||
Messages,
|
||||
Wasm,
|
||||
Server,
|
||||
Routes,
|
||||
Keys,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
|
|
@ -19,7 +28,11 @@ struct Cli {
|
|||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
/// Builds the website and server, including wasm files
|
||||
Build,
|
||||
Build {
|
||||
/// Build only specific targets (comma-separated). Omit for full build.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
only: Vec<BuildTarget>,
|
||||
},
|
||||
|
||||
/// Runs the server locally
|
||||
Start {
|
||||
|
|
@ -64,16 +77,20 @@ fn main() {
|
|||
let cli = Cli::parse();
|
||||
|
||||
match &cli.command {
|
||||
Commands::Build => {
|
||||
Commands::Build { only } => {
|
||||
println!("Executing build command...");
|
||||
build();
|
||||
if only.is_empty() {
|
||||
build_full();
|
||||
} else {
|
||||
build_targeted(only);
|
||||
}
|
||||
}
|
||||
|
||||
Commands::Start { rebuild } => {
|
||||
println!("Starting server...");
|
||||
if *rebuild {
|
||||
println!("Rebuilding web pages...");
|
||||
build();
|
||||
build_full();
|
||||
} // Change the current working directory of the running Rust program
|
||||
println!("Changing directory to {WEB_BUILD_DIR}...");
|
||||
let cd_result = std::env::set_current_dir(WEB_BUILD_DIR);
|
||||
|
|
@ -254,9 +271,15 @@ fn handle_command_status(status_result: std::io::Result<ExitStatus>, command_nam
|
|||
}
|
||||
}
|
||||
|
||||
fn build() {
|
||||
// --- Build steps ---
|
||||
|
||||
fn ensure_web_build_dir() {
|
||||
let status = Command::new("mkdir").args(["-p", WEB_BUILD_DIR]).status();
|
||||
handle_command_status(status, "mkdir web-build");
|
||||
}
|
||||
|
||||
fn build_message_tools_wasm() {
|
||||
println!("Building wasm package (message-tools)...");
|
||||
// build the wasm package
|
||||
let status = Command::new("wasm-pack")
|
||||
.arg("build")
|
||||
.arg("--out-dir")
|
||||
|
|
@ -266,20 +289,10 @@ fn build() {
|
|||
.arg("no-modules")
|
||||
.status();
|
||||
handle_command_status(status, "wasm-pack build message-tools");
|
||||
}
|
||||
|
||||
println!("Building wasm package (fractal-engine)...");
|
||||
let status = Command::new("wasm-pack")
|
||||
.arg("build")
|
||||
.arg("--out-dir")
|
||||
.arg("../../routes/root/pkg")
|
||||
.arg("crates/fractal-engine")
|
||||
.arg("--target")
|
||||
.arg("no-modules")
|
||||
.status();
|
||||
handle_command_status(status, "wasm-pack build fractal-engine");
|
||||
|
||||
fn build_server() {
|
||||
println!("Building server executable...");
|
||||
// build the server executable
|
||||
let status = Command::new("cargo")
|
||||
.arg("build")
|
||||
.arg("--release")
|
||||
|
|
@ -287,28 +300,34 @@ fn build() {
|
|||
.args(["--target", "x86_64-unknown-linux-musl"])
|
||||
.status();
|
||||
handle_command_status(status, "cargo build server");
|
||||
}
|
||||
|
||||
let server_bin = "target/x86_64-unknown-linux-musl/release/server";
|
||||
fn copy_routes() {
|
||||
ensure_web_build_dir();
|
||||
|
||||
println!("Creating build directory...");
|
||||
// make webpage build directory if it doesn't already exist
|
||||
let status = Command::new("mkdir").args(["-p", WEB_BUILD_DIR]).status();
|
||||
handle_command_status(status, "mkdir web-build");
|
||||
|
||||
println!("Copying files...");
|
||||
// copy relevant files (use trailing slash to merge into existing directory)
|
||||
println!("Copying routes...");
|
||||
let status = Command::new("cp")
|
||||
.args(["-R", "-f", "routes"])
|
||||
.arg(WEB_BUILD_DIR)
|
||||
.status();
|
||||
handle_command_status(status, "cp routes");
|
||||
}
|
||||
|
||||
fn copy_server_binary() {
|
||||
ensure_web_build_dir();
|
||||
|
||||
let server_bin = "target/x86_64-unknown-linux-musl/release/server";
|
||||
println!("Copying server binary...");
|
||||
let status = Command::new("cp")
|
||||
.arg("-f")
|
||||
.arg(server_bin)
|
||||
.arg(format!("{WEB_BUILD_DIR}"))
|
||||
.status();
|
||||
handle_command_status(status, "cp server bin");
|
||||
}
|
||||
|
||||
fn ensure_keys_and_copy() {
|
||||
ensure_web_build_dir();
|
||||
|
||||
let pubkey_file = "target/public_keys.json";
|
||||
|
||||
|
|
@ -333,7 +352,7 @@ fn build() {
|
|||
let status = Command::new("mkdir")
|
||||
.args(["-p", &format!("{WEB_BUILD_DIR}/pubkeys")])
|
||||
.status();
|
||||
handle_command_status(status, "mkdir web-build");
|
||||
handle_command_status(status, "mkdir pubkeys");
|
||||
|
||||
let status = Command::new("cp")
|
||||
.arg("-f")
|
||||
|
|
@ -341,6 +360,49 @@ fn build() {
|
|||
.arg(format!("{WEB_BUILD_DIR}/pubkeys/"))
|
||||
.status();
|
||||
handle_command_status(status, "cp pubkeys");
|
||||
}
|
||||
|
||||
// --- Build orchestration ---
|
||||
|
||||
fn build_full() {
|
||||
build_message_tools_wasm();
|
||||
build_server();
|
||||
copy_routes();
|
||||
copy_server_binary();
|
||||
ensure_keys_and_copy();
|
||||
println!("\nBuild successful!");
|
||||
}
|
||||
|
||||
fn build_targeted(targets: &[BuildTarget]) {
|
||||
let mut did_wasm = false;
|
||||
|
||||
for target in targets {
|
||||
match target {
|
||||
BuildTarget::Messages => {
|
||||
build_message_tools_wasm();
|
||||
did_wasm = true;
|
||||
}
|
||||
BuildTarget::Wasm => {
|
||||
build_message_tools_wasm();
|
||||
did_wasm = true;
|
||||
}
|
||||
BuildTarget::Server => {
|
||||
build_server();
|
||||
copy_server_binary();
|
||||
}
|
||||
BuildTarget::Routes => {
|
||||
copy_routes();
|
||||
}
|
||||
BuildTarget::Keys => {
|
||||
ensure_keys_and_copy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WASM output lands inside routes/, so always copy routes after any WASM build
|
||||
if did_wasm {
|
||||
copy_routes();
|
||||
}
|
||||
|
||||
println!("\nTargeted build successful!");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,12 @@ pub fn render_fractal_animated(canvas_id: &str, time: f64, scale: u32) -> Result
|
|||
.dyn_into::<CanvasRenderingContext2d>()?;
|
||||
temp_ctx.put_image_data(&image_data, 0.0, 0.0)?;
|
||||
|
||||
context.set_image_smoothing_enabled(false);
|
||||
context.set_image_smoothing_enabled(true);
|
||||
js_sys::Reflect::set(
|
||||
context.as_ref(),
|
||||
&JsValue::from_str("imageSmoothingQuality"),
|
||||
&JsValue::from_str("high"),
|
||||
)?;
|
||||
context.draw_image_with_html_canvas_element_and_dw_and_dh(
|
||||
&temp_canvas,
|
||||
0.0, 0.0,
|
||||
|
|
|
|||
|
|
@ -5,14 +5,22 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Contact</title>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
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;
|
||||
|
|
@ -31,20 +39,24 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.contact-box {
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
.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;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.contact-box h2 {
|
||||
.glass-card h2 {
|
||||
color: #fff;
|
||||
margin: 0 0 1.5rem 0;
|
||||
font-weight: normal;
|
||||
font-family: "Gill Sans", sans-serif;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
font-weight: 300;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
font-size: 1.6rem;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.contact-list {
|
||||
|
|
@ -54,42 +66,55 @@
|
|||
}
|
||||
|
||||
.contact-list li {
|
||||
margin: 1rem 0;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.contact-list li a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
padding: 0.6rem 1.5rem;
|
||||
display: inline-block;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
transition: background 0.2s;
|
||||
font-size: 1.1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
transition: all 250ms ease;
|
||||
}
|
||||
|
||||
.contact-list li a:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
nav.back-link {
|
||||
.back-link {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
nav.back-link a {
|
||||
.back-link a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
padding: 0.5rem 1rem;
|
||||
display: inline-block;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
transition: background 0.2s;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 250ms ease;
|
||||
}
|
||||
|
||||
nav.back-link a:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
.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>
|
||||
|
|
@ -97,7 +122,7 @@
|
|||
<canvas id="fractal-canvas"></canvas>
|
||||
|
||||
<div class="content-overlay">
|
||||
<div class="contact-box">
|
||||
<div class="glass-card">
|
||||
<h2>Contact</h2>
|
||||
<ul class="contact-list">
|
||||
<li><a href="mailto:">Email me</a></li>
|
||||
|
|
@ -110,40 +135,15 @@
|
|||
</nav>
|
||||
</div>
|
||||
|
||||
<script src="/routes/root/pkg/fractal_engine.js"></script>
|
||||
<script>
|
||||
(async function() {
|
||||
await wasm_bindgen('/routes/root/pkg/fractal_engine_bg.wasm');
|
||||
|
||||
const canvas = document.getElementById('fractal-canvas');
|
||||
|
||||
function resizeCanvas() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
let startTime = performance.now();
|
||||
const animationSpeed = 0.0001;
|
||||
const scale = 4;
|
||||
|
||||
function animate() {
|
||||
const elapsed = performance.now() - startTime;
|
||||
const time = elapsed * animationSpeed;
|
||||
|
||||
try {
|
||||
wasm_bindgen.render_fractal_animated('fractal-canvas', time, scale);
|
||||
} catch (e) {
|
||||
console.error('Fractal render error:', e);
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
animate();
|
||||
})();
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Direct Message</title>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -12,11 +18,12 @@
|
|||
padding-top: 50px;
|
||||
min-height: 120vh;
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #111;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
background: #060a10;
|
||||
}
|
||||
|
||||
#fractal-canvas {
|
||||
image-rendering: auto;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
|
@ -28,40 +35,35 @@
|
|||
.form-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
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;
|
||||
color: #fff;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
padding: 2.5rem 3rem;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
max-width: 560px;
|
||||
min-height: 400px;
|
||||
max-height: 1000px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
nav.directory {
|
||||
.back-link {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
nav.directory dt a {
|
||||
.back-link a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
padding: 0.5rem 1rem;
|
||||
display: inline-block;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
transition: background 0.2s;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 250ms ease;
|
||||
}
|
||||
|
||||
nav.directory dt a:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
.back-link a:hover {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
|
|
@ -71,46 +73,115 @@
|
|||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
font-weight: 300;
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: #fff;
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 250ms ease, box-shadow 250ms ease;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: rgba(120, 200, 220, 0.3);
|
||||
box-shadow: 0 0 0 3px rgba(120, 200, 220, 0.1);
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
padding: 8px;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: #fff;
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 250ms ease, box-shadow 250ms ease;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: rgba(120, 200, 220, 0.3);
|
||||
box-shadow: 0 0 0 3px rgba(120, 200, 220, 0.1);
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
transition: all 250ms ease;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
color: #fff;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.expandable-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.expandable-header {
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 0.85rem;
|
||||
transition: all 250ms ease;
|
||||
}
|
||||
|
||||
.expandable-header:hover {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.expandable-content {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 0 0 4px 4px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 0 0 8px 8px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script src="/routes/contact/pkg/message_tools.js"></script>
|
||||
|
|
@ -230,20 +301,7 @@
|
|||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<button
|
||||
type="submit"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
@ -261,44 +319,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="directory">
|
||||
<dt><a href="/contact">back to contact</a></dt>
|
||||
<nav class="back-link">
|
||||
<a href="/contact">back to contact</a>
|
||||
</nav>
|
||||
|
||||
<script src="/routes/root/pkg/fractal_engine.js"></script>
|
||||
<script>
|
||||
(async function() {
|
||||
await wasm_bindgen('/routes/root/pkg/fractal_engine_bg.wasm');
|
||||
|
||||
const canvas = document.getElementById('fractal-canvas');
|
||||
|
||||
function resizeCanvas() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
let startTime = performance.now();
|
||||
const animationSpeed = 0.0001;
|
||||
const scale = 4;
|
||||
|
||||
function animate() {
|
||||
const elapsed = performance.now() - startTime;
|
||||
const time = elapsed * animationSpeed;
|
||||
|
||||
try {
|
||||
wasm_bindgen.render_fractal_animated('fractal-canvas', time, scale);
|
||||
} catch (e) {
|
||||
console.error('Fractal render error:', e);
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
animate();
|
||||
})();
|
||||
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>
|
||||
|
|
|
|||
BIN
routes/root/dist.png
Normal file
BIN
routes/root/dist.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
330
routes/root/fractal-gl.js
Normal file
330
routes/root/fractal-gl.js
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
// Julia distance-isolines background. WebGL preferred, Canvas2D fallback.
|
||||
// Palette: BSOD-blue, smooth cosine bands, 1024-entry LUT for finer transitions.
|
||||
|
||||
function initFractal(canvasId) {
|
||||
console.log('[fractal-gl] initFractal:', canvasId);
|
||||
|
||||
var canvas = document.getElementById(canvasId);
|
||||
if (!canvas) {
|
||||
console.warn('[fractal-gl] canvas not found:', 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%)';
|
||||
|
||||
var gl = null;
|
||||
try {
|
||||
gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
||||
} catch (e) {
|
||||
console.warn('[fractal-gl] WebGL exception:', e);
|
||||
}
|
||||
|
||||
if (gl) {
|
||||
console.log('[fractal-gl] using WebGL path');
|
||||
initWebGL(canvas, gl);
|
||||
} else {
|
||||
console.warn('[fractal-gl] WebGL unavailable; using Canvas2D fallback');
|
||||
initCanvas2D(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
function _hslToRgb(h, s, l) {
|
||||
var a = s * Math.min(l, 1 - l);
|
||||
function f(n) {
|
||||
var k = (n + h * 12) % 12;
|
||||
return Math.round((l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))) * 255);
|
||||
}
|
||||
return [f(0), f(8), f(4)];
|
||||
}
|
||||
|
||||
// 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);
|
||||
for (var i = 0; i < PALETTE_SIZE; i++) {
|
||||
var f = i / 4; // logical index in [0, 256)
|
||||
var band = 0.5 + 0.5 * Math.cos(f * 2 * Math.PI / 56);
|
||||
var h = 0.665 + 0.015 * Math.sin(f * Math.PI / 128);
|
||||
var s = 0.80;
|
||||
var l = 0.18 + 0.28 * band;
|
||||
var rgb = _hslToRgb(h, s, l);
|
||||
p32[i] = (255 << 24) | (rgb[2] << 16) | (rgb[1] << 8) | rgb[0];
|
||||
}
|
||||
return p32;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// WebGL path
|
||||
// ----------------------------------------------------------------
|
||||
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);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
||||
console.error('[fractal-gl] shader compile error:', gl.getShaderInfoLog(s));
|
||||
return null;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
var vs = compile(gl.VERTEX_SHADER, VERT_SRC);
|
||||
var fs = compile(gl.FRAGMENT_SHADER, FRAG_SRC);
|
||||
if (!vs || !fs) return initCanvas2D(canvas);
|
||||
|
||||
var prog = gl.createProgram();
|
||||
gl.attachShader(prog, vs);
|
||||
gl.attachShader(prog, fs);
|
||||
gl.linkProgram(prog);
|
||||
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
||||
console.error('[fractal-gl] link error:', gl.getProgramInfoLog(prog));
|
||||
return initCanvas2D(canvas);
|
||||
}
|
||||
gl.useProgram(prog);
|
||||
|
||||
var buf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
||||
var aPos = gl.getAttribLocation(prog, 'a_pos');
|
||||
gl.enableVertexAttribArray(aPos);
|
||||
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
var uTime = gl.getUniformLocation(prog, 'u_time');
|
||||
var uRes = gl.getUniformLocation(prog, 'u_resolution');
|
||||
var uDist = gl.getUniformLocation(prog, 'u_dist');
|
||||
|
||||
var tex = gl.createTexture();
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
|
||||
gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255]));
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
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';
|
||||
|
||||
function resize() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
var startTime = performance.now();
|
||||
function frame() {
|
||||
var t = (performance.now() - startTime) * 0.001;
|
||||
gl.uniform1f(uTime, t);
|
||||
gl.uniform2f(uRes, canvas.width, canvas.height);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
requestAnimationFrame(frame);
|
||||
console.log('[fractal-gl] WebGL loop started');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Canvas2D fallback
|
||||
// - 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)
|
||||
// ----------------------------------------------------------------
|
||||
function initCanvas2D(canvas) {
|
||||
var ctx2d = canvas.getContext('2d');
|
||||
if (!ctx2d) {
|
||||
console.error('[fractal-gl] Canvas2D also unavailable; body gradient remains');
|
||||
return;
|
||||
}
|
||||
var palette32 = _buildPalette32();
|
||||
|
||||
var img = new Image();
|
||||
img.onerror = function (e) { console.error('[fractal-gl] dist.png load failed (canvas2d)', e); };
|
||||
img.onload = function () {
|
||||
var SW = img.width, SH = img.height;
|
||||
console.log('[fractal-gl] PNG decoded (canvas2d):', SW, 'x', SH);
|
||||
var off = document.createElement('canvas');
|
||||
off.width = SW; off.height = SH;
|
||||
var octx = off.getContext('2d');
|
||||
octx.drawImage(img, 0, 0);
|
||||
var data = octx.getImageData(0, 0, SW, SH).data;
|
||||
var gray = new Uint8Array(SW * SH);
|
||||
for (var i = 0, j = 0; i < gray.length; i++, j += 4) gray[i] = data[j];
|
||||
|
||||
// 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)));
|
||||
var aspect = window.innerWidth / window.innerHeight;
|
||||
return { w: Math.max(2, Math.floor(h * aspect)), h: h };
|
||||
}
|
||||
|
||||
function resize() {
|
||||
var s = targetSize();
|
||||
canvas.width = s.w; canvas.height = s.h;
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
var imgOut = ctx2d.createImageData(canvas.width, canvas.height);
|
||||
var out32 = new Uint32Array(imgOut.data.buffer);
|
||||
var lastW = canvas.width, lastH = canvas.height;
|
||||
var startTime = performance.now();
|
||||
|
||||
// 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.
|
||||
var BAYER = new Float32Array([0,8,2,10, 12,4,14,6, 3,11,1,9, 15,7,13,5]);
|
||||
for (var bi = 0; bi < 16; bi++) BAYER[bi] = (BAYER[bi] - 7.5) * 0.6 / 7.5;
|
||||
|
||||
function frame() {
|
||||
var W = canvas.width, H = canvas.height;
|
||||
if (W !== lastW || H !== lastH) {
|
||||
imgOut = ctx2d.createImageData(W, H);
|
||||
out32 = new Uint32Array(imgOut.data.buffer);
|
||||
lastW = W; lastH = H;
|
||||
}
|
||||
var elapsed = (performance.now() - startTime) * 0.001;
|
||||
var t = elapsed * 0.12;
|
||||
var aspect = W / H;
|
||||
var maxH = 1.0 / Math.max(aspect, 1.0);
|
||||
var zoomFrac = 0.65 + 0.15 * Math.sin(t * 0.4);
|
||||
var halfH = maxH * zoomFrac * 0.5;
|
||||
var halfW = halfH * aspect;
|
||||
var fxRem = 0.5 - halfW, fyRem = 0.5 - halfH;
|
||||
var cx = 0.5 + fxRem * 0.5 * Math.cos(t * 0.55);
|
||||
var cy = 0.5 + fyRem * 0.5 * Math.sin(t * 0.45);
|
||||
var x0 = (cx - halfW) * SW;
|
||||
var y0 = (cy - halfH) * SH;
|
||||
var dxs = (2 * halfW) / W * SW;
|
||||
var dys = (2 * halfH) / H * SH;
|
||||
// offset in 1024-entry space (4x granularity) — same cycle period (~24 s) as before
|
||||
var offsetHD = (elapsed * 10.8 * 4) | 0;
|
||||
|
||||
var SW1 = SW - 1, SH1 = SH - 1;
|
||||
var idx = 0;
|
||||
var sy = y0;
|
||||
for (var y = 0; y < H; y++) {
|
||||
var sy0i = sy | 0;
|
||||
if (sy0i < 0) sy0i = 0; else if (sy0i > SH1) sy0i = SH1;
|
||||
var sy1i = sy0i + 1;
|
||||
if (sy1i > SH1) sy1i = SH1;
|
||||
var fy = sy - (sy | 0);
|
||||
var ify = 1 - fy;
|
||||
var row0 = sy0i * SW;
|
||||
var row1 = sy1i * SW;
|
||||
|
||||
var sx = x0;
|
||||
for (var x = 0; x < W; x++) {
|
||||
var sx0i = sx | 0;
|
||||
if (sx0i < 0) sx0i = 0; else if (sx0i > SW1) sx0i = SW1;
|
||||
var sx1i = sx0i + 1;
|
||||
if (sx1i > SW1) sx1i = SW1;
|
||||
var fx = sx - (sx | 0);
|
||||
var ifx = 1 - fx;
|
||||
|
||||
var a = gray[row0 + sx0i];
|
||||
var b = gray[row0 + sx1i];
|
||||
var c = gray[row1 + sx0i];
|
||||
var d = gray[row1 + sx1i];
|
||||
var v = (a * ifx + b * fx) * ify + (c * ifx + d * fx) * fy;
|
||||
// Selective dither: only the outer smooth region (v 180→240 ramps in).
|
||||
if (v > 180) {
|
||||
var mix = v >= 240 ? 1 : (v - 180) / 60;
|
||||
v += BAYER[(y & 3) * 4 + (x & 3)] * 1.6 * mix;
|
||||
}
|
||||
// Multiply by 4 to index into 1024-entry palette, then wrap.
|
||||
out32[idx++] = palette32[(((v * 4) | 0) - offsetHD) & 1023];
|
||||
|
||||
sx += dxs;
|
||||
}
|
||||
sy += dys;
|
||||
}
|
||||
ctx2d.putImageData(imgOut, 0, 0);
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
requestAnimationFrame(frame);
|
||||
console.log('[fractal-gl] Canvas2D loop started; internal',
|
||||
canvas.width, 'x', canvas.height);
|
||||
};
|
||||
img.src = '/routes/root/dist.png';
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function () { initFractal('fractal-canvas'); });
|
||||
} else {
|
||||
initFractal('fractal-canvas');
|
||||
}
|
||||
|
|
@ -2,16 +2,25 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Zach's page</title>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
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;
|
||||
|
|
@ -30,40 +39,65 @@
|
|||
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;
|
||||
}
|
||||
|
||||
header h2 {
|
||||
font-weight: normal;
|
||||
font-family: "Gill Sans", sans-serif;
|
||||
font-weight: 300;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 0.5rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
margin: 0 0 1.5rem 0;
|
||||
font-size: 1.6rem;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
nav.directory {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
nav.directory dt {
|
||||
margin: 1.2rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
nav.directory dt a {
|
||||
.nav-links a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
padding: 0.5rem 1rem;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
padding: 0.5rem 1.5rem;
|
||||
display: inline-block;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
transition: background 0.2s;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
transition: all 250ms ease;
|
||||
}
|
||||
|
||||
nav.directory dt a:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
.nav-links a:hover {
|
||||
color: #fff;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
@ -71,54 +105,30 @@
|
|||
<canvas id="fractal-canvas"></canvas>
|
||||
|
||||
<div class="content-overlay">
|
||||
<div class="glass-card">
|
||||
<header>
|
||||
<h2>Zachery Aaron Shores-Chmielewski</h2>
|
||||
</header>
|
||||
|
||||
<nav class="directory">
|
||||
<nav>
|
||||
<dl>
|
||||
<dt><a href="./who">Who</a></dt>
|
||||
<dt><a href="./contact">Contact</a></dt>
|
||||
</dl>
|
||||
</nav>
|
||||
<div class="nav-links">
|
||||
<a href="./who">Who</a>
|
||||
<a href="./contact">Contact</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/routes/root/pkg/fractal_engine.js"></script>
|
||||
<script>
|
||||
(async function() {
|
||||
await wasm_bindgen('/routes/root/pkg/fractal_engine_bg.wasm');
|
||||
|
||||
const canvas = document.getElementById('fractal-canvas');
|
||||
|
||||
function resizeCanvas() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
let startTime = performance.now();
|
||||
const animationSpeed = 0.0001;
|
||||
const scale = 4; // Render at 1/4 resolution for performance
|
||||
|
||||
function animate() {
|
||||
const elapsed = performance.now() - startTime;
|
||||
const time = elapsed * animationSpeed;
|
||||
|
||||
try {
|
||||
wasm_bindgen.render_fractal_animated('fractal-canvas', time, scale);
|
||||
} catch (e) {
|
||||
console.error('Fractal render error:', e);
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
animate();
|
||||
})();
|
||||
// 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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,50 +2,121 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Zach's page</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Who</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
background-color: Cornsilk;
|
||||
}
|
||||
header h2 {
|
||||
font-weight: normal;
|
||||
font-family: "Gill Sans", sans-serif;
|
||||
text-align: center;
|
||||
*,
|
||||
*::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;
|
||||
}
|
||||
|
||||
nav.directory {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
.glass-card h2 {
|
||||
color: #fff;
|
||||
margin: 0 0 1rem 0;
|
||||
font-weight: 300;
|
||||
font-size: 1.6rem;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
nav.directory dt {
|
||||
margin: 1.2rem 0;
|
||||
font-size: 1.1rem;
|
||||
.glass-card p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
nav.directory dt a {
|
||||
.back-link {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.back-link a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
padding: 0.5rem 1rem;
|
||||
display: inline-block;
|
||||
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>
|
||||
<div>
|
||||
<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="directory">
|
||||
<dt><a href="/">back to home</a></dt>
|
||||
<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