fix: fractal animation on webgl, targeted builds
Fractal animation (Newton's fractal) now animates smoothly. Rebuilds now can be targeted based on which module was changed.
This commit is contained in:
parent
4200ca77df
commit
d10f539e06
7 changed files with 574 additions and 273 deletions
|
|
@ -9,6 +9,15 @@ use std::{
|
||||||
|
|
||||||
const WEB_BUILD_DIR: &str = "web-build";
|
const WEB_BUILD_DIR: &str = "web-build";
|
||||||
|
|
||||||
|
#[derive(clap::ValueEnum, Clone, Debug, PartialEq)]
|
||||||
|
enum BuildTarget {
|
||||||
|
Messages,
|
||||||
|
Wasm,
|
||||||
|
Server,
|
||||||
|
Routes,
|
||||||
|
Keys,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
|
@ -19,7 +28,11 @@ struct Cli {
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
/// Builds the website and server, including wasm files
|
/// 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
|
/// Runs the server locally
|
||||||
Start {
|
Start {
|
||||||
|
|
@ -64,16 +77,20 @@ fn main() {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match &cli.command {
|
match &cli.command {
|
||||||
Commands::Build => {
|
Commands::Build { only } => {
|
||||||
println!("Executing build command...");
|
println!("Executing build command...");
|
||||||
build();
|
if only.is_empty() {
|
||||||
|
build_full();
|
||||||
|
} else {
|
||||||
|
build_targeted(only);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::Start { rebuild } => {
|
Commands::Start { rebuild } => {
|
||||||
println!("Starting server...");
|
println!("Starting server...");
|
||||||
if *rebuild {
|
if *rebuild {
|
||||||
println!("Rebuilding web pages...");
|
println!("Rebuilding web pages...");
|
||||||
build();
|
build_full();
|
||||||
} // Change the current working directory of the running Rust program
|
} // Change the current working directory of the running Rust program
|
||||||
println!("Changing directory to {WEB_BUILD_DIR}...");
|
println!("Changing directory to {WEB_BUILD_DIR}...");
|
||||||
let cd_result = std::env::set_current_dir(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)...");
|
println!("Building wasm package (message-tools)...");
|
||||||
// build the wasm package
|
|
||||||
let status = Command::new("wasm-pack")
|
let status = Command::new("wasm-pack")
|
||||||
.arg("build")
|
.arg("build")
|
||||||
.arg("--out-dir")
|
.arg("--out-dir")
|
||||||
|
|
@ -266,20 +289,10 @@ fn build() {
|
||||||
.arg("no-modules")
|
.arg("no-modules")
|
||||||
.status();
|
.status();
|
||||||
handle_command_status(status, "wasm-pack build message-tools");
|
handle_command_status(status, "wasm-pack build message-tools");
|
||||||
|
}
|
||||||
|
|
||||||
println!("Building wasm package (fractal-engine)...");
|
fn build_server() {
|
||||||
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");
|
|
||||||
|
|
||||||
println!("Building server executable...");
|
println!("Building server executable...");
|
||||||
// build the server executable
|
|
||||||
let status = Command::new("cargo")
|
let status = Command::new("cargo")
|
||||||
.arg("build")
|
.arg("build")
|
||||||
.arg("--release")
|
.arg("--release")
|
||||||
|
|
@ -287,28 +300,34 @@ fn build() {
|
||||||
.args(["--target", "x86_64-unknown-linux-musl"])
|
.args(["--target", "x86_64-unknown-linux-musl"])
|
||||||
.status();
|
.status();
|
||||||
handle_command_status(status, "cargo build server");
|
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...");
|
println!("Copying routes...");
|
||||||
// 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)
|
|
||||||
let status = Command::new("cp")
|
let status = Command::new("cp")
|
||||||
.args(["-R", "-f", "routes"])
|
.args(["-R", "-f", "routes"])
|
||||||
.arg(WEB_BUILD_DIR)
|
.arg(WEB_BUILD_DIR)
|
||||||
.status();
|
.status();
|
||||||
handle_command_status(status, "cp routes");
|
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")
|
let status = Command::new("cp")
|
||||||
.arg("-f")
|
.arg("-f")
|
||||||
.arg(server_bin)
|
.arg(server_bin)
|
||||||
.arg(format!("{WEB_BUILD_DIR}"))
|
.arg(format!("{WEB_BUILD_DIR}"))
|
||||||
.status();
|
.status();
|
||||||
handle_command_status(status, "cp server bin");
|
handle_command_status(status, "cp server bin");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_keys_and_copy() {
|
||||||
|
ensure_web_build_dir();
|
||||||
|
|
||||||
let pubkey_file = "target/public_keys.json";
|
let pubkey_file = "target/public_keys.json";
|
||||||
|
|
||||||
|
|
@ -333,7 +352,7 @@ fn build() {
|
||||||
let status = Command::new("mkdir")
|
let status = Command::new("mkdir")
|
||||||
.args(["-p", &format!("{WEB_BUILD_DIR}/pubkeys")])
|
.args(["-p", &format!("{WEB_BUILD_DIR}/pubkeys")])
|
||||||
.status();
|
.status();
|
||||||
handle_command_status(status, "mkdir web-build");
|
handle_command_status(status, "mkdir pubkeys");
|
||||||
|
|
||||||
let status = Command::new("cp")
|
let status = Command::new("cp")
|
||||||
.arg("-f")
|
.arg("-f")
|
||||||
|
|
@ -341,6 +360,49 @@ fn build() {
|
||||||
.arg(format!("{WEB_BUILD_DIR}/pubkeys/"))
|
.arg(format!("{WEB_BUILD_DIR}/pubkeys/"))
|
||||||
.status();
|
.status();
|
||||||
handle_command_status(status, "cp pubkeys");
|
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!");
|
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>()?;
|
.dyn_into::<CanvasRenderingContext2d>()?;
|
||||||
temp_ctx.put_image_data(&image_data, 0.0, 0.0)?;
|
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(
|
context.draw_image_with_html_canvas_element_and_dw_and_dh(
|
||||||
&temp_canvas,
|
&temp_canvas,
|
||||||
0.0, 0.0,
|
0.0, 0.0,
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,18 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Contact</title>
|
<title>Contact</title>
|
||||||
<style>
|
<style>
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, sans-serif;
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: #060a10;
|
||||||
}
|
}
|
||||||
|
|
||||||
#fractal-canvas {
|
#fractal-canvas {
|
||||||
|
|
@ -29,22 +36,27 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
animation: fadeInUp 800ms ease 300ms both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-box {
|
.glass-card {
|
||||||
background: rgba(0, 0, 0, 0.65);
|
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;
|
padding: 2.5rem 3rem;
|
||||||
border-radius: 10px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-box h2 {
|
.glass-card h2 {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin: 0 0 1.5rem 0;
|
margin: 0 0 1.5rem 0;
|
||||||
font-weight: normal;
|
font-weight: 300;
|
||||||
font-family: "Gill Sans", sans-serif;
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
font-size: 1.6rem;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-list {
|
.contact-list {
|
||||||
|
|
@ -54,42 +66,55 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-list li {
|
.contact-list li {
|
||||||
margin: 1rem 0;
|
margin: 0.75rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-list li a {
|
.contact-list li a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #fff;
|
color: rgba(255, 255, 255, 0.75);
|
||||||
padding: 0.6rem 1.5rem;
|
padding: 0.6rem 1.5rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
font-size: 0.85rem;
|
||||||
transition: background 0.2s;
|
font-weight: 400;
|
||||||
font-size: 1.1rem;
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: all 250ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-list li a:hover {
|
.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;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.back-link a {
|
.back-link a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #fff;
|
color: rgba(255, 255, 255, 0.4);
|
||||||
padding: 0.5rem 1rem;
|
font-size: 0.8rem;
|
||||||
display: inline-block;
|
letter-spacing: 0.04em;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
transition: color 250ms ease;
|
||||||
border-radius: 4px;
|
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.back-link a:hover {
|
.back-link a:hover {
|
||||||
background: rgba(0, 0, 0, 0.5);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(12px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -97,7 +122,7 @@
|
||||||
<canvas id="fractal-canvas"></canvas>
|
<canvas id="fractal-canvas"></canvas>
|
||||||
|
|
||||||
<div class="content-overlay">
|
<div class="content-overlay">
|
||||||
<div class="contact-box">
|
<div class="glass-card">
|
||||||
<h2>Contact</h2>
|
<h2>Contact</h2>
|
||||||
<ul class="contact-list">
|
<ul class="contact-list">
|
||||||
<li><a href="mailto:">Email me</a></li>
|
<li><a href="mailto:">Email me</a></li>
|
||||||
|
|
@ -110,40 +135,7 @@
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/routes/root/pkg/fractal_engine.js"></script>
|
<script src="/routes/root/fractal-gl.js"></script>
|
||||||
<script>
|
<script>initFractal('fractal-canvas');</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();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,12 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Direct Message</title>
|
<title>Direct Message</title>
|
||||||
<style>
|
<style>
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -12,8 +18,8 @@
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
min-height: 120vh;
|
min-height: 120vh;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: Arial, sans-serif;
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
background-color: #111;
|
background: #060a10;
|
||||||
}
|
}
|
||||||
|
|
||||||
#fractal-canvas {
|
#fractal-canvas {
|
||||||
|
|
@ -28,40 +34,37 @@
|
||||||
.form-container {
|
.form-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
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;
|
color: #fff;
|
||||||
padding: 30px;
|
padding: 2.5rem 3rem;
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 800px;
|
max-width: 560px;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
max-height: 1000px;
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
animation: fadeInUp 800ms ease 300ms both;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory {
|
.back-link {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 90%;
|
margin-top: 0.5rem;
|
||||||
max-width: 800px;
|
animation: fadeInUp 800ms ease 500ms both;
|
||||||
margin: 0 auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory dt a {
|
.back-link a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #fff;
|
color: rgba(255, 255, 255, 0.4);
|
||||||
padding: 0.5rem 1rem;
|
font-size: 0.8rem;
|
||||||
display: inline-block;
|
letter-spacing: 0.04em;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
transition: color 250ms ease;
|
||||||
border-radius: 4px;
|
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory dt a:hover {
|
.back-link a:hover {
|
||||||
background: rgba(0, 0, 0, 0.5);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
.input-group {
|
||||||
|
|
@ -71,46 +74,115 @@
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 5px;
|
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="text"],
|
||||||
input[type="password"] {
|
input[type="password"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px;
|
padding: 10px 12px;
|
||||||
border: 1px solid #555;
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
box-sizing: border-box;
|
background: rgba(255, 255, 255, 0.06);
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
color: #fff;
|
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 {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
padding: 8px;
|
padding: 10px 12px;
|
||||||
border: 1px solid #555;
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
box-sizing: border-box;
|
background: rgba(255, 255, 255, 0.06);
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
color: #fff;
|
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 {
|
.expandable-section {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expandable-header {
|
.expandable-header {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.06);
|
||||||
border-radius: 4px;
|
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 {
|
.expandable-content {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.03);
|
||||||
border-radius: 0 0 4px 4px;
|
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>
|
</style>
|
||||||
<script src="/routes/contact/pkg/message_tools.js"></script>
|
<script src="/routes/contact/pkg/message_tools.js"></script>
|
||||||
|
|
@ -230,20 +302,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<button
|
<button type="submit">Submit</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>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
@ -261,44 +320,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="directory">
|
<nav class="back-link">
|
||||||
<dt><a href="/contact">back to contact</a></dt>
|
<a href="/contact">back to contact</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<script src="/routes/root/pkg/fractal_engine.js"></script>
|
<script src="/routes/root/fractal-gl.js"></script>
|
||||||
<script>
|
<script>initFractal('fractal-canvas');</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();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
152
routes/root/fractal-gl.js
Normal file
152
routes/root/fractal-gl.js
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
// WebGL Newton fractal renderer (z^3 - 1, ocean palette)
|
||||||
|
// Ported from crates/fractal-engine/src/{newton.rs, color.rs}
|
||||||
|
|
||||||
|
function initFractal(canvasId) {
|
||||||
|
var canvas = document.getElementById(canvasId);
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
||||||
|
if (!gl) {
|
||||||
|
// Fallback: ocean-themed CSS gradient
|
||||||
|
canvas.style.background =
|
||||||
|
'radial-gradient(ellipse at center, #0d3842 0%, #0c2e58 40%, #060a10 100%)';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Shaders ---
|
||||||
|
|
||||||
|
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;',
|
||||||
|
'',
|
||||||
|
'void main() {',
|
||||||
|
' vec2 uv = gl_FragCoord.xy / u_resolution;',
|
||||||
|
' float aspect = u_resolution.x / u_resolution.y;',
|
||||||
|
'',
|
||||||
|
' // Map to complex plane (view_size=3.0, centered)',
|
||||||
|
' float zr = (uv.x - 0.5) * 3.0 * aspect;',
|
||||||
|
' float zi = (uv.y - 0.5) * 3.0;',
|
||||||
|
'',
|
||||||
|
' // Rotate by time (animation)',
|
||||||
|
' float ct = cos(u_time);',
|
||||||
|
' float st = sin(u_time);',
|
||||||
|
' float tmp = zr * ct - zi * st;',
|
||||||
|
' zi = zr * st + zi * ct;',
|
||||||
|
' zr = tmp;',
|
||||||
|
'',
|
||||||
|
' // Newton iteration for z^3 - 1',
|
||||||
|
' int rootIndex = 0;',
|
||||||
|
' int iters = 0;',
|
||||||
|
' for (int i = 0; i < 32; i++) {',
|
||||||
|
' iters = i;',
|
||||||
|
' float zr2 = zr*zr - zi*zi;',
|
||||||
|
' float zi2 = 2.0*zr*zi;',
|
||||||
|
' float zr3 = zr*zr2 - zi*zi2;',
|
||||||
|
' float zi3 = zr*zi2 + zi*zr2;',
|
||||||
|
'',
|
||||||
|
' float d0 = (zr-1.0)*(zr-1.0) + zi*zi;',
|
||||||
|
' float d1 = (zr+0.5)*(zr+0.5) + (zi-0.866025)*(zi-0.866025);',
|
||||||
|
' float d2 = (zr+0.5)*(zr+0.5) + (zi+0.866025)*(zi+0.866025);',
|
||||||
|
'',
|
||||||
|
' if (d0 < 1e-6) { rootIndex = 0; break; }',
|
||||||
|
' if (d1 < 1e-6) { rootIndex = 1; break; }',
|
||||||
|
' if (d2 < 1e-6) { rootIndex = 2; break; }',
|
||||||
|
'',
|
||||||
|
' float nr = zr3 - 1.0;',
|
||||||
|
' float ni = zi3;',
|
||||||
|
' float dr = 3.0*zr2;',
|
||||||
|
' float di = 3.0*zi2;',
|
||||||
|
' float denom = dr*dr + di*di;',
|
||||||
|
' if (denom < 1e-12) break;',
|
||||||
|
' zr -= (nr*dr + ni*di) / denom;',
|
||||||
|
' zi -= (ni*dr - nr*di) / denom;',
|
||||||
|
' }',
|
||||||
|
'',
|
||||||
|
' // Fallback: closest root',
|
||||||
|
' float d0 = (zr-1.0)*(zr-1.0) + zi*zi;',
|
||||||
|
' float d1 = (zr+0.5)*(zr+0.5) + (zi-0.866025)*(zi-0.866025);',
|
||||||
|
' float d2 = (zr+0.5)*(zr+0.5) + (zi+0.866025)*(zi+0.866025);',
|
||||||
|
' if (d1 < d0 && d1 < d2) rootIndex = 1;',
|
||||||
|
' else if (d2 < d0) rootIndex = 2;',
|
||||||
|
'',
|
||||||
|
' // Ocean palette',
|
||||||
|
' float brightness = max(1.0 - float(iters)/32.0 * 0.7, 0.3);',
|
||||||
|
' vec3 color;',
|
||||||
|
' if (rootIndex == 0) color = vec3(0.05, 0.25, 0.55);',
|
||||||
|
' else if (rootIndex == 1) color = vec3(0.0, 0.45, 0.50);',
|
||||||
|
' else color = vec3(0.15, 0.55, 0.65);',
|
||||||
|
'',
|
||||||
|
' gl_FragColor = vec4(color * brightness, 1.0);',
|
||||||
|
'}'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
// --- Compile helpers ---
|
||||||
|
|
||||||
|
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('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;
|
||||||
|
|
||||||
|
var prog = gl.createProgram();
|
||||||
|
gl.attachShader(prog, vs);
|
||||||
|
gl.attachShader(prog, fs);
|
||||||
|
gl.linkProgram(prog);
|
||||||
|
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
||||||
|
console.error('Program link error:', gl.getProgramInfoLog(prog));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gl.useProgram(prog);
|
||||||
|
|
||||||
|
// --- Full-screen triangle ---
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
// --- Resize ---
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
resize();
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
// --- Animation loop ---
|
||||||
|
|
||||||
|
var startTime = performance.now();
|
||||||
|
var animationSpeed = 0.0001;
|
||||||
|
|
||||||
|
function frame() {
|
||||||
|
var t = (performance.now() - startTime) * animationSpeed;
|
||||||
|
gl.uniform1f(uTime, t);
|
||||||
|
gl.uniform2f(uRes, canvas.width, canvas.height);
|
||||||
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
|
@ -2,13 +2,21 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Zach's page</title>
|
<title>Zach's page</title>
|
||||||
<style>
|
<style>
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, sans-serif;
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: #060a10;
|
||||||
}
|
}
|
||||||
|
|
||||||
#fractal-canvas {
|
#fractal-canvas {
|
||||||
|
|
@ -28,42 +36,68 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
animation: fadeInUp 800ms ease 300ms both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
header h2 {
|
||||||
font-weight: normal;
|
font-weight: 300;
|
||||||
font-family: "Gill Sans", sans-serif;
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
margin: 0 0 1.5rem 0;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
font-size: 1.6rem;
|
||||||
padding: 0.5rem 1.5rem;
|
letter-spacing: 0.01em;
|
||||||
border-radius: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory {
|
.nav-links {
|
||||||
text-align: center;
|
display: flex;
|
||||||
padding: 1rem 0;
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory dt {
|
.nav-links a {
|
||||||
margin: 1.2rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.directory dt a {
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #fff;
|
color: rgba(255, 255, 255, 0.75);
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1.5rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
font-size: 0.85rem;
|
||||||
transition: background 0.2s;
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: all 250ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory dt a:hover {
|
.nav-links a:hover {
|
||||||
background: rgba(0, 0, 0, 0.5);
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -71,54 +105,21 @@
|
||||||
<canvas id="fractal-canvas"></canvas>
|
<canvas id="fractal-canvas"></canvas>
|
||||||
|
|
||||||
<div class="content-overlay">
|
<div class="content-overlay">
|
||||||
<header>
|
<div class="glass-card">
|
||||||
<h2>Zachery Aaron Shores-Chmielewski</h2>
|
<header>
|
||||||
</header>
|
<h2>Zachery Aaron Shores-Chmielewski</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
<nav class="directory">
|
|
||||||
<nav>
|
<nav>
|
||||||
<dl>
|
<div class="nav-links">
|
||||||
<dt><a href="./who">Who</a></dt>
|
<a href="./who">Who</a>
|
||||||
<dt><a href="./contact">Contact</a></dt>
|
<a href="./contact">Contact</a>
|
||||||
</dl>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</nav>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/routes/root/pkg/fractal_engine.js"></script>
|
<script src="/routes/root/fractal-gl.js"></script>
|
||||||
<script>
|
<script>initFractal('fractal-canvas');</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();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -2,50 +2,113 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Zach's page</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Who</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
*,
|
||||||
font-family: system-ui, sans-serif;
|
*::before,
|
||||||
background-color: Cornsilk;
|
*::after {
|
||||||
}
|
box-sizing: border-box;
|
||||||
header h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
font-family: "Gill Sans", sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #060a10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fractal-canvas {
|
||||||
|
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;
|
||||||
|
animation: fadeInUp 800ms ease 300ms both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
text-align: center;
|
||||||
|
min-width: 250px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory {
|
.glass-card h2 {
|
||||||
text-align: center;
|
color: #fff;
|
||||||
padding: 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory dt {
|
.glass-card p {
|
||||||
margin: 1.2rem 0;
|
margin: 0.5rem 0;
|
||||||
font-size: 1.1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.directory dt a {
|
.back-link {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #333;
|
color: rgba(255, 255, 255, 0.4);
|
||||||
padding: 0.5rem 1rem;
|
font-size: 0.8rem;
|
||||||
display: inline-block;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<canvas id="fractal-canvas"></canvas>
|
||||||
<p>No one in particular :)</p>
|
|
||||||
<p>I'll write more later...</p>
|
<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>
|
</div>
|
||||||
|
|
||||||
<nav class="directory">
|
<script src="/routes/root/fractal-gl.js"></script>
|
||||||
<dt><a href="/">back to home</a></dt>
|
<script>initFractal('fractal-canvas');</script>
|
||||||
</nav>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue